Мышь уровня 8 + холодная половина главного цикла в банк 8
Спецсобытие уровня 8 (SDLPoP seg003:0545/0ABA, seg002:07EB): когда дверь уровня открыта, а Кид остаётся в комнате 16, через 150 кадров приходит мышь, пробегает справа налево по верхнему ряду, наступает на кнопку (0,7) — решётка (0,3) открывается — и убегает. Ассетов не потребовалось: кадры 186..188 идут по таблице Кида, их спрайты (images 130..132) уже лежат в kid16.atl, отрисовка подхватывает мышь сама. - guards.c: pop_check_mouse, autocontrol_mouse, ветки мыши в autocontrol_opponent / play_guard / check_can_guard_see_kid; - pop_leveldoor_open стал word, как в оригинале (в него же считается задержка — байт переполнился бы через 255 кадров); - leave_guard больше не записывает тень и мышь в данные уровня (seg002:02F5); - полосы HP у мыши нет (seg000:1159); - tests-host/t_mouse: 17 проверок, единственный набор с guards.c. Разгрузка резидента (куча в huge упала до 879 Б): pop_start_level, find_start_level_door, чит-навигация по комнатам и лейбл номера уехали в roomtest_cold.c (--bank 8, n_banks = 8). _CODE 25653 -> 24825, куча -> 1707 Б; кадровый путь не тронут. Проверено: tests-host 6/6, make size-check OK, живьём в MAME (мышь появилась, нажала кнопку, решётка поехала вверх, мышь ушла). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,7 @@ MEMORY ?= huge # small-раскладка + банки кода в W
|
|||||||
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
||||||
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c $(PROF_FLAGS)
|
EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c --bank 8=roomtest_cold.c $(PROF_FLAGS)
|
||||||
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
||||||
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
||||||
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||||
@@ -112,4 +112,4 @@ $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py
|
|||||||
kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
|
kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
|
||||||
cd $(TC) && python3 pop_extract_kid_data.py
|
cd $(TC) && python3 pop_extract_kid_data.py
|
||||||
|
|
||||||
$(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_cdraw.c pop_cdraw.h pop_draw.c _pop_draw.h pop_tile.c pop_tile.h pop_state.c pop_state.h pop_cheat.h pop_bg.c pop_bg.h pop_room.c _pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA)
|
$(EXAMPLE).exe: roomtest_cold.c roomtest_cold.h guards.c pop_guard.c pop_guard.h pop_cdraw.c pop_cdraw.h pop_draw.c _pop_draw.h pop_tile.c pop_tile.h pop_state.c pop_state.h pop_cheat.h pop_bg.c pop_bg.h pop_room.c _pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Точка входа для следующей сессии (обновлено 2026-08-12)
|
# Точка входа для следующей сессии (обновлено 2026-08-12, вечер)
|
||||||
|
|
||||||
Файл для старта с чистого контекста: где всё стоит, что делать первым, какие
|
Файл для старта с чистого контекста: где всё стоит, что делать первым, какие
|
||||||
грабли уже собраны. Читать целиком — он короткий. Дальше по ссылкам:
|
грабли уже собраны. Читать целиком — он короткий. Дальше по ссылкам:
|
||||||
@@ -6,39 +6,37 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. СЛЕДУЮЩАЯ ЗАДАЧА: живая проверка зелий в MAME
|
## 1. СЛЕДУЮЩАЯ ЗАДАЧА: smoke-прогон уровня 8
|
||||||
|
|
||||||
Код зелья медленного падения НАПИСАН (все шесть шагов плана
|
Зелья приняты пользователем (уровень 2 — «−HP», уровень 7 — перо), уровень 7
|
||||||
[`TASKS_OPEN.md#l7-feather`](TASKS_OPEN.md#l7-feather)) и закрыт хост-тестом,
|
считается прошедшим smoke. Спецсобытие уровня 8 —
|
||||||
но в MAME ещё не смотрелся. Проверить две сцены:
|
[L8-MOUSE](TASKS_CLOSED.md#l8-mouse) — **закрыто 2026-08-12**: мышь
|
||||||
|
портирована, покрыта хост-набором `t_mouse` (17 проверок) и проверена живьём в
|
||||||
|
MAME (появилась, нажала кнопку (0,7) комнаты 16, решётка (0,3) поехала вверх,
|
||||||
|
ушла и погасла). Новых тайлов уровень 8 не приносит вовсе.
|
||||||
|
|
||||||
- **уровень 7, комната 1** — зелье пера: зелёные пузырьки, зелёная вспышка
|
Осталось пройти сам уровень: он длинный, с возвратом через два чомпера
|
||||||
при питье, спуск в шахту заметно медленнее обычного и без урона;
|
комнаты 4, и стражи там сильнее прежних — комната 5 держит **skill 7**
|
||||||
- **уровень 2, комната 13, тайл (1,3)** — зелье «−HP»: пузырьки СИНИЕ (были
|
(максимум из встречавшихся), комнаты 12/22/24 — skill 2/3/3.
|
||||||
красные — цвет по типу зелья не был портирован вовсе), HP убавляется на
|
|
||||||
единицу, экран краснеет ровно один раз.
|
|
||||||
|
|
||||||
Заодно попадают под проверку `pop_flash_color` (жёлтый/красный/зелёный) и
|
Правило появления мыши (сверено с оригиналом, детали в
|
||||||
атлас `pop_pot` (23 → 37 спрайтов: добавлены зелёный и синий наборы кадров
|
[TASKS_CLOSED](TASKS_CLOSED.md#l8-mouse)): счётчик — сам `leveldoor_open`,
|
||||||
пузырька).
|
растёт только пока Кид в комнате 16, срабатывание ровно на 150, **второго
|
||||||
|
раза нет**; сбрасывает только рестарт уровня. Тупика это не создаёт — низом
|
||||||
|
через провал есть путь к двери уровня ценой 1 HP.
|
||||||
|
|
||||||
**Зацеп в падении — закрыт 2026-08-12**
|
Резидент разгружен ([MEM-COLD1](TASKS_CLOSED.md#mem-cold1)): холодная половина
|
||||||
([GRAB-BELOW-ROOM](BUGS_CLOSED.md#grab-below-room)). Оказалось, что зацеп
|
главного цикла уехала в **банк 8** (`roomtest_cold.c`), куча 879 → **1707 Б**.
|
||||||
из падения был жив, а сломан был спуск через НИЖНЮЮ кромку комнаты (уровень 7,
|
Следующие кандидаты — [MEM-COLD2](TASKS_OPEN.md#mem-cold2).
|
||||||
комната 14): `do_fall` не пускал ряд персонажа до 3, и `check_grab` на этом
|
|
||||||
отрезке не звался ни разу. Плюс восстановлена ветка `ACT_MIDAIR` (кадры
|
|
||||||
102..105 — начало падения). Проверено хост-тестом `t_grab` и живьём в MAME.
|
|
||||||
|
|
||||||
Осталась ощущенческая часть — [GRAB-KBD-TIMING](BUGS_OPEN.md#grab-kbd-timing):
|
Ощущенческий хвост прежний — [GRAB-KBD-TIMING](BUGS_OPEN.md#grab-kbd-timing),
|
||||||
зацеп при входе на уровень 7 срабатывает менее стабильно, чем в оригинале
|
разбирается после готовности всех уровней (решение пользователя).
|
||||||
(подозрение на клавиатурный модуль и на общий темп игры, `L1-SPEED`).
|
|
||||||
**Решение пользователя: разбирать после того, как будут готовы все уровни.**
|
|
||||||
|
|
||||||
## 2. Состояние репозитория
|
## 2. Состояние репозитория
|
||||||
|
|
||||||
Всё закоммичено, `main` чист. `tests-host`: 5 наборов проходят
|
Всё закоммичено, `main` чист. `tests-host`: **6 наборов** проходят
|
||||||
(`[char] 65`, `[geom] 3144`, `[grab] 55`, `[phys] 1727`, `[wall] 1`).
|
(`[char] 65`, `[geom] 3144`, `[grab] 55`, `[mouse] 17`, `[phys] 1727`,
|
||||||
`make size-check`: OK (65 программ, роста нет).
|
`[wall] 1`). `make size-check`: OK (65 программ, роста нет).
|
||||||
|
|
||||||
Коммиты 2026-08-12:
|
Коммиты 2026-08-12:
|
||||||
|
|
||||||
@@ -55,10 +53,17 @@
|
|||||||
## 3. Состояние окружения
|
## 3. Состояние окружения
|
||||||
|
|
||||||
- **MAME запущена с мостом**, на HDD — roomtest, собранный с
|
- **MAME запущена с мостом**, на HDD — roomtest, собранный с
|
||||||
`-DFIRST_LEVEL=7`. Пересобрать образ: `make hdd` (или
|
`-DFIRST_LEVEL=8`. **ПРАВИЛО ПОЛЬЗОВАТЕЛЯ: перед сборкой образа —
|
||||||
`make PROF_FLAGS="-DPROF_BORDER=1 -DFIRST_LEVEL=N" hdd` — при смене
|
`make clean`, потом `make`, потом `make hdd`** (memory
|
||||||
`FIRST_LEVEL` нужен `touch roomtest.c`, иначе не переедет), после чего
|
`mame_build_clean_rule`). Без этого `make hdd` кладёт на диск ПРОШЛУЮ
|
||||||
MAME **обязана** пройти полный рестарт (memory `mame_hdd_rebuild_restart`).
|
сборку: `FIRST_LEVEL` живёт внутри `roomtest.c`, и без изменения исходника
|
||||||
|
make считает `.exe` актуальным — 2026-08-12 на этом уехал уровень 1 вместо
|
||||||
|
8, заметил пользователь. После пересборки образа MAME **обязана** пройти
|
||||||
|
полный рестарт (memory `mame_hdd_rebuild_restart`).
|
||||||
|
- Адреса состояния сместились после MEM-COLD1: брать из
|
||||||
|
`.sprinter-cc-roomtest/roomtest.map` заново (`cur_room` 0xA33D,
|
||||||
|
`pop_leveldoor_open` 0xA34C, `Kid` 0xA3B1, `Guard` 0xA759 — на сборку
|
||||||
|
2026-08-12). Мост: `setmem ADDR HEXBYTES` (не `write`), `mem ADDR N`.
|
||||||
- **Остановка MAME — `mame/v306/bridge_cmd.sh 'cmd exit'`**, НЕ `kill`
|
- **Остановка MAME — `mame/v306/bridge_cmd.sh 'cmd exit'`**, НЕ `kill`
|
||||||
(правило пользователя). `pkill -f` бывает не срабатывает, и остаются ДВА
|
(правило пользователя). `pkill -f` бывает не срабатывает, и остаются ДВА
|
||||||
инстанса, оба отвечающих на файловый IPC — тогда скриншоты приходят от
|
инстанса, оба отвечающих на файловый IPC — тогда скриншоты приходят от
|
||||||
|
|||||||
@@ -11,6 +11,103 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Уровни: спецсобытия
|
||||||
|
|
||||||
|
<a id="l8-mouse"></a>
|
||||||
|
### L8-MOUSE. Мышь уровня 8 — ЗАКРЫТА 2026-08-12
|
||||||
|
|
||||||
|
Спецсобытие комнаты 16: кнопка (0,7) открывает решётку (0,3), но с левой
|
||||||
|
площадки верхнего ряда до кнопки не дойти — между ними провал в три колонки.
|
||||||
|
Когда дверь уровня уже открыта, а Кид всё ещё в комнате 16, оригинал через
|
||||||
|
150 кадров присылает мышь: она пробегает справа налево, наступает на кнопку и
|
||||||
|
убегает. Порт по SDLPoP: `do_timers` seg003:0545, `do_mouse` seg003:0ABA,
|
||||||
|
`autocontrol_mouse` seg002:07EB.
|
||||||
|
|
||||||
|
**Ассетов и отрисовки не потребовалось вовсе.** Кадры 186..188 лежат в
|
||||||
|
таблице КИДА (`load_frame` seg006:0293 — общий case), их images 130..132 уже
|
||||||
|
упакованы в `kid16.atl` (idx 2/3/4: 17×4, 19×4, 12×10) — `pop_pack_kid.py`
|
||||||
|
берёт все `res(401+N).png`, что есть в `data/KID`. Отрисовка подхватывает
|
||||||
|
мышь сама: `pop_frame_tbl_is_guard(24, …)` отдаёт 0, значит и таблица кадров,
|
||||||
|
и набор атласов берутся кидовские, и палитра (0x70) тоже.
|
||||||
|
|
||||||
|
Что написано:
|
||||||
|
|
||||||
|
| где | что |
|
||||||
|
|---|---|
|
||||||
|
| `guards.c` | `pop_check_mouse` (условие + рождение), `autocontrol_mouse`, ветки мыши в `autocontrol_opponent` / `play_guard` / `pop_check_can_guard_see_kid` |
|
||||||
|
| `pop_state.c/h` | `pop_leveldoor_open` стал **word**, как в оригинале (data.h:361): в него же считается задержка, байт переполнился бы через 255 кадров и обнулил признак «дверь открыта» |
|
||||||
|
| `pop_guard.c` | `leave_guard` не записывает в данные уровня тень и мышь (seg002:02F5) — раньше эта проверка отсутствовала вовсе |
|
||||||
|
| `pop_cdraw.c` | полосы HP у мыши нет (`draw_guard_hp`, seg000:1159) |
|
||||||
|
| `pop_tune.h` | числа события (уровень/комната/задержка/пороги X) |
|
||||||
|
|
||||||
|
**Правило появления — мышь ОДНОРАЗОВАЯ (сверено с оригиналом).** Счётчик
|
||||||
|
живёт в `leveldoor_open`: 0 на старте уровня (seg003:97), 1 — когда створка
|
||||||
|
двери уровня доехала доверху (seg007:456, то есть Кид нажал кнопку в комнате
|
||||||
|
3), дальше `++` каждый кадр, пока Кид в комнате 16, и вызов ровно при
|
||||||
|
`== 150`. Отсюда три следствия:
|
||||||
|
|
||||||
|
- второго раза НЕТ: счётчик растёт дальше, а сравнение строгое. Не успел
|
||||||
|
пройти решётку, пока она открыта — мышь больше не придёт;
|
||||||
|
- выход из комнаты 16 счётчик не сбрасывает, а ЗАМОРАЖИВАЕТ: ушёл на сотом
|
||||||
|
кадре, вернулся — ждать осталось пятьдесят;
|
||||||
|
- обнуляет только `start_level`, то есть смерть/рестарт уровня — но он же
|
||||||
|
возвращает тайлы и модификаторы, так что дверь придётся открывать заново.
|
||||||
|
|
||||||
|
Значения `0x4D` (музыка уровней 4 и 6) и `2` (победа над Джаффаром, уровень
|
||||||
|
13) в ту же переменную к уровню 8 отношения не имеют — там счёт идёт
|
||||||
|
монотонно 1 → 150.
|
||||||
|
|
||||||
|
**Чего в условии НЕТ (проверено грепом по всем упоминаниям `mouse_level` /
|
||||||
|
`mouse_room` / `do_mouse` — единственная точка запуска это seg003:545):**
|
||||||
|
оригинал не смотрит ни на состояние решётки (0,3), ни на то, с какой стороны
|
||||||
|
провала стоит Кид, ни на то, прошёл ли он её уже. «Кид заперт за закрытой
|
||||||
|
решёткой» получается САМО: находиться в комнате 16 двенадцать с половиной
|
||||||
|
секунд можно только топчась там, а стоит уйти в комнату 4 — счётчик
|
||||||
|
замирает. Обратная сторона той же простоты: вернувшись в комнату 16 с ЛЮБОЙ
|
||||||
|
стороны, игрок доигрывает накопленное ожидание и может получить мышь, даже
|
||||||
|
если решётка в этот момент открыта.
|
||||||
|
|
||||||
|
Тупика при этом нет: провал в колонках 4..6 ведёт на нижний ряд комнаты
|
||||||
|
(`debris` между колоннами, а колонна — проходимый пол, seg006:0628), оттуда
|
||||||
|
низом через комнату 4 с шипами есть путь к двери уровня в комнате 3. Цена —
|
||||||
|
падение на два ряда, то есть −1 HP. Мышь экономит эту единицу, а не спасает
|
||||||
|
от тупика.
|
||||||
|
|
||||||
|
**Тонкость, на которой легко сломать уход:** `seq_107` прыгает на метку
|
||||||
|
`Mscurry1`, стоящую ПОСЛЕ опкода `act(actions_1_run_jump)` в `seq_105` — на
|
||||||
|
обратном пути кадры бега проигрываются, а `action` остаётся стоечным. Именно
|
||||||
|
этим оригинал отличает «бежит к кнопке» от «убегает»: условие исчезновения
|
||||||
|
висит на `action == 0`.
|
||||||
|
|
||||||
|
**Проверено.** Хост-набор `t_mouse` (17 проверок, единственный, куда
|
||||||
|
линкуется `guards.c`) — рождение по таймеру, замороженный счётчик вне комнаты,
|
||||||
|
требование открытой двери, нажатие кнопки на пробеге, разворот и исчезновение,
|
||||||
|
невидимость для Кида, необращение в «стража комнаты». Регресс проверен
|
||||||
|
откатом: без ветки в `autocontrol_opponent` падают две проверки ухода.
|
||||||
|
Живьём в MAME (комната 16 уровня 8, `leveldoor_open` взведён через мост):
|
||||||
|
мышь появилась (`charid = 0x18`), добежала до колонки 7 (кадр 188, x = 164),
|
||||||
|
решётка (0,3) поехала вверх, мышь ушла вправо и погасла (`direction = 0x56`).
|
||||||
|
|
||||||
|
<a id="mem-cold1"></a>
|
||||||
|
### MEM-COLD1. Холодная половина главного цикла — в банк 8 (2026-08-12)
|
||||||
|
|
||||||
|
К уровню 8 куча в режиме huge упала до **879 Б**. Убирать из `roomtest.c`
|
||||||
|
оказалось нечего — отладочное там всё живое (обход комнат, стоп-кадр, читы,
|
||||||
|
профиль), — поэтому вынесли в банк то, что отрабатывает раз на уровень или
|
||||||
|
только по клавише: `pop_start_level` + `find_start_level_door`, чит-навигацию
|
||||||
|
по комнатам и лейбл номера комнаты (`roomtest_cold.c`, `--bank 8`, `n_banks`
|
||||||
|
поднят до 8).
|
||||||
|
|
||||||
|
Результат: `_CODE` 25653 → 24825, **куча 879 → 1707 Б**, банк 8 занят на 5 %.
|
||||||
|
Кадровый путь не тронут: за обычный кадр в банк 8 уходит ноль вызовов, при
|
||||||
|
смене комнаты — один трамплин.
|
||||||
|
|
||||||
|
Что осталось в резиденте намеренно: `enter_room_side` и рабочие массивы
|
||||||
|
комнаты (их читает и главный цикл, и холодная половина), `guard_over_kid` с
|
||||||
|
хелперами (зовётся каждый кадр). Следующий шаг — [MEM-COLD2](TASKS_OPEN.md#mem-cold2).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Приёмка уровней
|
## Приёмка уровней
|
||||||
|
|
||||||
<a id="pass-policy"></a>
|
<a id="pass-policy"></a>
|
||||||
|
|||||||
@@ -35,7 +35,38 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ТЕКУЩАЯ ЦЕЛЬ: уровень 5
|
## ТЕКУЩАЯ ЦЕЛЬ: уровень 8
|
||||||
|
|
||||||
|
Уровни 1-7 играются (smoke; зелья уровней 2 и 7 приняты пользователем
|
||||||
|
2026-08-12). Уровень 8 **новых тайлов не приносит вовсе** (инвентарь
|
||||||
|
`res2008.bin` целиком покрыт уровнями 1-7), тайлсет — подземелье.
|
||||||
|
Спецсобытие уровня — мышь, [L8-MOUSE](TASKS_CLOSED.md#l8-mouse) закрыта
|
||||||
|
2026-08-12 (хост-набор `t_mouse` + живая проверка в MAME).
|
||||||
|
|
||||||
|
Остаётся smoke-прогон самого уровня: он длинный и с возвратом через два
|
||||||
|
чомпера комнаты 4, а стражи там сильнее прежних (комната 5 — skill **7**,
|
||||||
|
это максимум из встречавшихся; 12/22/24 — skill 2/3/3).
|
||||||
|
|
||||||
|
<a id="mem-cold2"></a>
|
||||||
|
### MEM-COLD2. Второй шаг разгрузки резидента
|
||||||
|
|
||||||
|
После [MEM-COLD1](TASKS_CLOSED.md#mem-cold1) куча 1707 Б. Когда упрётся
|
||||||
|
снова, в банк 8 переезжают следующие кандидаты (в порядке выгоды):
|
||||||
|
|
||||||
|
1. `enter_room_side` — самая большая функция `roomtest.c`, зовётся раз на
|
||||||
|
комнату. Требует вынести наружу рабочие массивы комнаты
|
||||||
|
(`room_bg`/`lcol_*`/`rcol_*`/`below_fg`/`above_*`) — сами данные останутся
|
||||||
|
в `_DATA`, уедет только код;
|
||||||
|
2. `guard_over_kid` + `objtile_at_char`/`char_x_left_of`/`tile_div_mod` —
|
||||||
|
зовётся КАЖДЫЙ кадр, но ровно один раз: цена выноса — один трамплин;
|
||||||
|
3. блок редроя левого шва в `main` (`seam_row_sig` + разбор сигнатуры).
|
||||||
|
|
||||||
|
Если и этого не хватит — следующим уходит сам кадровый порядок отрисовки,
|
||||||
|
но тогда уже стоит мерить, а не выносить наугад.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ПРЕДЫДУЩАЯ ЦЕЛЬ: уровень 5
|
||||||
|
|
||||||
Уровни 1-4 играются (smoke). Дальше идём по порядку уровней; уровень 5 —
|
Уровни 1-4 играются (smoke). Дальше идём по порядку уровней; уровень 5 —
|
||||||
следующий.
|
следующий.
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ void pop_check_can_guard_see_kid(void) __banked
|
|||||||
int16_t left_pos, right_pos;
|
int16_t left_pos, right_pos;
|
||||||
uint8_t tile;
|
uint8_t tile;
|
||||||
|
|
||||||
|
/* МЫШЬ Кида «не видит» (seg003:0965): иначе он выхватит меч на зверька,
|
||||||
|
* пробегающего мимо. */
|
||||||
|
if (Guard.charid == CHARID_24_MOUSE) { can_guard_see_kid = 0; return; }
|
||||||
|
|
||||||
/* Первый множитель — порт seg003:702. ТЕНЬ боевая только на 12-м
|
/* Первый множитель — порт seg003:702. ТЕНЬ боевая только на 12-м
|
||||||
* уровне; на 4/5/6 она Кида «не видит», и это не косметика: увидев её,
|
* уровне; на 4/5/6 она Кида «не видит», и это не косметика: увидев её,
|
||||||
* Кид достаёт меч (control_standing, seg005:352), а сама тень идёт той
|
* Кид достаёт меч (control_standing, seg005:352), а сама тень идёт той
|
||||||
@@ -198,6 +202,50 @@ void pop_check_skel(void) __banked
|
|||||||
is_guard_notice = guard_refrac = 0;
|
is_guard_notice = guard_refrac = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Мышь: спецсобытие уровня 8 (do_timers seg003:0545 + do_mouse) --- *
|
||||||
|
*
|
||||||
|
* Кнопка (0,7) комнаты 16 открывает решётку (0,3), но с левой площадки
|
||||||
|
* верхнего ряда до кнопки не дойти — между ними провал в три колонки.
|
||||||
|
* Поэтому, когда дверь уровня уже открыта, а Кид всё ещё в комнате 16,
|
||||||
|
* оригинал через POP_MOUSE_DELAY кадров присылает мышь: она пробегает
|
||||||
|
* справа налево, наступает на кнопку (кнопку жмёт ОБЩИЙ check_press из
|
||||||
|
* физики соперника, как и любой персонаж) и убегает обратно.
|
||||||
|
*
|
||||||
|
* Счётчик — сам pop_leveldoor_open, как в оригинале: он растёт ТОЛЬКО пока
|
||||||
|
* Кид в комнате 16, поэтому «12,5 секунды» отсчитываются с момента, когда
|
||||||
|
* игрок туда вернулся, а не с открытия двери. Ради этого он и word
|
||||||
|
* (см. pop_state.c).
|
||||||
|
*
|
||||||
|
* Слот соперника занимаем БЕЗ проверки «свободен ли» — так в оригинале, и
|
||||||
|
* на уровне 8 конфликта нет: в данных комнаты 16 стража нет
|
||||||
|
* (guards_tile[15] = 30 = «пусто»). */
|
||||||
|
#define SEQ_105_MOUSE_FORWARD 105
|
||||||
|
#define SEQ_107_MOUSE_STAND_UP_GO 107
|
||||||
|
|
||||||
|
void pop_check_mouse(void) __banked
|
||||||
|
{
|
||||||
|
if (pop_current_level != POP_MOUSE_LEVEL) return;
|
||||||
|
if (Kid.room != POP_MOUSE_ROOM) return;
|
||||||
|
if (!pop_leveldoor_open) return;
|
||||||
|
if (++pop_leveldoor_open != POP_MOUSE_DELAY) return;
|
||||||
|
|
||||||
|
/* do_mouse (seg003:0ABA) поднимает мышь В ОКНЕ Char, начиная с КИДА
|
||||||
|
* (loadkid): поля, которых нет в списке ниже — frame/action/room/
|
||||||
|
* fall_x/fall_y — наследуются от него. Комната как раз оттуда и
|
||||||
|
* берётся, отдельного присваивания в оригинале нет. */
|
||||||
|
pop_loadkid();
|
||||||
|
Char.charid = CHARID_24_MOUSE;
|
||||||
|
Char.x = POP_MOUSE_START_X;
|
||||||
|
Char.curr_row = 0;
|
||||||
|
Char.y = (uint8_t)pop_y_land[1];
|
||||||
|
Char.alive = -1;
|
||||||
|
Char.direction = -1; /* dir_FF_left */
|
||||||
|
guardhp_curr = 1;
|
||||||
|
pop_char_set_seq(SEQ_105_MOUSE_FORWARD);
|
||||||
|
play_seq();
|
||||||
|
pop_saveshad();
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- Тень: спецсобытие входа в комнату (check_shadow, seg002:0064) -- *
|
/* ---- Тень: спецсобытие входа в комнату (check_shadow, seg002:0064) -- *
|
||||||
*
|
*
|
||||||
* На «шадоу»-уровнях слот соперника занимает ТЕНЬ, и занимает его ВМЕСТО
|
* На «шадоу»-уровнях слот соперника занимает ТЕНЬ, и занимает его ВМЕСТО
|
||||||
@@ -638,6 +686,26 @@ static void autocontrol_shadow_level6(void)
|
|||||||
move_1_forward();
|
move_1_forward();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* autocontrol_mouse (seg002:07EB). Весь «ИИ» мыши — два порога по X:
|
||||||
|
* добежала влево до POP_MOUSE_TURN_X → seq_107 (встать, поднять голову,
|
||||||
|
* развернуться и уйти), вернулась вправо до POP_MOUSE_LEAVE_X → исчезнуть.
|
||||||
|
*
|
||||||
|
* Почему проверка на исчезновение висит на action == 0 (стойка), хотя мышь
|
||||||
|
* при этом БЕЖИТ: seq_107 заканчивается прыжком на метку `Mscurry1`, которая
|
||||||
|
* стоит ПОСЛЕ опкода act(actions_1_run_jump) в seq_105 — то есть на обратном
|
||||||
|
* пути кадры бега проигрываются, а action остаётся стоечным. Именно этим
|
||||||
|
* оригинал и различает «бежит туда» и «бежит обратно». */
|
||||||
|
static void autocontrol_mouse(void)
|
||||||
|
{
|
||||||
|
if (Char.direction == DIR_56_NONE) return;
|
||||||
|
if (Char.action == 0) {
|
||||||
|
if (Char.x >= POP_MOUSE_LEAVE_X) clear_char();
|
||||||
|
} else if (Char.x < POP_MOUSE_TURN_X) {
|
||||||
|
pop_char_set_seq(SEQ_107_MOUSE_STAND_UP_GO);
|
||||||
|
play_seq();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* autocontrol_shadow (seg002:081D) — по уровням. Ветка 12 появится со своим
|
/* autocontrol_shadow (seg002:081D) — по уровням. Ветка 12 появится со своим
|
||||||
* уровнем. */
|
* уровнем. */
|
||||||
static void autocontrol_shadow(void)
|
static void autocontrol_shadow(void)
|
||||||
@@ -658,6 +726,8 @@ static void autocontrol_opponent(void)
|
|||||||
/* autocontrol_skeleton (seg002:685): у скелета меч вынут ВСЕГДА — он не
|
/* autocontrol_skeleton (seg002:685): у скелета меч вынут ВСЕГДА — он не
|
||||||
* бывает «неактивным», поэтому ветка inactive ему недоступна. */
|
* бывает «неактивным», поэтому ветка inactive ему недоступна. */
|
||||||
if (Char.charid == CHARID_4_SKELETON) Char.sword = SWORD_2_DRAWN;
|
if (Char.charid == CHARID_4_SKELETON) Char.sword = SWORD_2_DRAWN;
|
||||||
|
/* МЫШЬ — своей веткой (seg002:0643), первой в цепочке charid'ов. */
|
||||||
|
if (Char.charid == CHARID_24_MOUSE) { autocontrol_mouse(); return; }
|
||||||
/* ТЕНЬ идёт своей веткой ЦЕЛИКОМ (seg002:0647): к стражьему ИИ она не
|
/* ТЕНЬ идёт своей веткой ЦЕЛИКОМ (seg002:0647): к стражьему ИИ она не
|
||||||
* сводится — на уровне 4 не дерётся вовсе. */
|
* сводится — на уровне 4 не дерётся вовсе. */
|
||||||
if (Char.charid == CHARID_1_SHADOW) { autocontrol_shadow(); return; }
|
if (Char.charid == CHARID_1_SHADOW) { autocontrol_shadow(); return; }
|
||||||
@@ -780,6 +850,11 @@ void pop_check_sword_hurt(void) __banked
|
|||||||
* доигрывает последовательность. */
|
* доигрывает последовательность. */
|
||||||
static void play_guard(void)
|
static void play_guard(void)
|
||||||
{
|
{
|
||||||
|
/* МЫШЬ (seg006:0D85, первая ветка): ей положен ТОЛЬКО автоконтроль.
|
||||||
|
* Диспетчер control() ей не нужен и вреден — он бы гонял её по
|
||||||
|
* стоечным веткам управления, а вся её «жизнь» уже описана
|
||||||
|
* последовательностями seq_105/107. */
|
||||||
|
if (Char.charid == CHARID_24_MOUSE) { autocontrol_opponent(); return; }
|
||||||
if (Char.alive < 0 && guardhp_curr == 0) {
|
if (Char.alive < 0 && guardhp_curr == 0) {
|
||||||
Char.alive = 0;
|
Char.alive = 0;
|
||||||
/* on_guard_killed (seg006:1927) — у нас пока только звук победы,
|
/* on_guard_killed (seg006:1927) — у нас пока только звук победы,
|
||||||
|
|||||||
@@ -807,8 +807,10 @@ void pop_hp_draw(void) __banked
|
|||||||
for (i = 0; i < hitp_curr && i < HP_MAXDRAW; i++)
|
for (i = 0; i < hitp_curr && i < HP_MAXDRAW; i++)
|
||||||
pop_kid_img_blit(HP_KID_FULL, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
pop_kid_img_blit(HP_KID_FULL, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
||||||
|
|
||||||
/* Страж: только полные деления, справа налево. Скелет полосы не имеет. */
|
/* Страж: только полные деления, справа налево. Скелет полосы не имеет,
|
||||||
if (g_ok && Guard.charid != 0 && Guard.charid != CHARID_4_SKELETON && gd) {
|
* мышь (спецсобытие уровня 8) — тоже: draw_guard_hp, seg000:1159. */
|
||||||
|
if (g_ok && Guard.charid != 0 && Guard.charid != CHARID_4_SKELETON &&
|
||||||
|
Guard.charid != CHARID_24_MOUSE && gd) {
|
||||||
const uint8_t *img = (const uint8_t *)atlas_image(&gp[0], 0);
|
const uint8_t *img = (const uint8_t *)atlas_image(&gp[0], 0);
|
||||||
uint16_t w, h;
|
uint16_t w, h;
|
||||||
gfx_w0_map(gp[0].page);
|
gfx_w0_map(gp[0].page);
|
||||||
|
|||||||
@@ -185,6 +185,10 @@ void pop_guard_load_frame(void)
|
|||||||
void pop_guard_leave(void)
|
void pop_guard_leave(void)
|
||||||
{
|
{
|
||||||
if (Guard.charid == 0) return; /* стража не было */
|
if (Guard.charid == 0) return; /* стража не было */
|
||||||
|
/* Тень и МЫШЬ в данные уровня не записываются (seg002:02F5): они не
|
||||||
|
* «стражи комнаты», а спецсобытия — вернувшись, их надо получить заново
|
||||||
|
* от check_shadow / check_mouse, а не поднять из guards_tile. */
|
||||||
|
if (Guard.charid == CHARID_1_SHADOW || Guard.charid == CHARID_24_MOUSE) return;
|
||||||
pop_guard_state_save(Guard.room,
|
pop_guard_state_save(Guard.room,
|
||||||
(uint8_t)(Guard.curr_row * 10), /* get_tilepos(0,row) */
|
(uint8_t)(Guard.curr_row * 10), /* get_tilepos(0,row) */
|
||||||
Guard.direction, Guard.x, guard_skill,
|
Guard.direction, Guard.x, guard_skill,
|
||||||
|
|||||||
@@ -68,6 +68,12 @@
|
|||||||
#define CHARID_2_GUARD 2
|
#define CHARID_2_GUARD 2
|
||||||
#define CHARID_4_SKELETON 4
|
#define CHARID_4_SKELETON 4
|
||||||
#define CHARID_5_PRINCESS 5
|
#define CHARID_5_PRINCESS 5
|
||||||
|
/* МЫШЬ (спецсобытие уровня 8) — тоже персонаж слота Guard: своей таблицы
|
||||||
|
* кадров у неё нет, кадры 186..188 лежат в таблице КИДА (load_frame,
|
||||||
|
* seg006:0293 — общий case с charid_0_kid), а спрайты (images 130..132) —
|
||||||
|
* в атласах Кида, страница kid16.atl. Поэтому отрисовка её подхватывает
|
||||||
|
* без единой правки: pop_frame_tbl_is_guard(24,…) отдаёт 0. */
|
||||||
|
#define CHARID_24_MOUSE 24
|
||||||
|
|
||||||
/* dir_56_none (types.h:402): «направления нет» — им clear_char убирает
|
/* dir_56_none (types.h:402): «направления нет» — им clear_char убирает
|
||||||
* персонажа со сцены. Значение 0x56 в int8_t = 86, то есть заведомо не
|
* персонажа со сцены. Значение 0x56 в int8_t = 86, то есть заведомо не
|
||||||
@@ -191,6 +197,12 @@ void pop_check_can_guard_see_kid(void) __banked;
|
|||||||
* в кадр, ДО логики персонажей (порядок play_frame, seg000:868). */
|
* в кадр, ДО логики персонажей (порядок play_frame, seg000:868). */
|
||||||
void pop_check_skel(void) __banked;
|
void pop_check_skel(void) __banked;
|
||||||
|
|
||||||
|
/* Спецсобытие уровня 8 — МЫШЬ (do_timers seg003:0545 + do_mouse seg003:0ABA).
|
||||||
|
* Условие запуска: дверь уровня открыта И Кид в комнате 16; тогда счётчик
|
||||||
|
* pop_leveldoor_open растёт кадрами, и на 150-м мышь появляется у правого
|
||||||
|
* края верхнего ряда. Звать раз в кадр рядом с pop_check_skel. */
|
||||||
|
void pop_check_mouse(void) __banked;
|
||||||
|
|
||||||
/* check_shadow (seg002:0064) — СПЕЦСОБЫТИЕ входа в комнату: на «шадоу»-
|
/* check_shadow (seg002:0064) — СПЕЦСОБЫТИЕ входа в комнату: на «шадоу»-
|
||||||
* уровнях слот соперника занимает ТЕНЬ, а не страж из данных. Звать ВМЕСТО
|
* уровнях слот соперника занимает ТЕНЬ, а не страж из данных. Звать ВМЕСТО
|
||||||
* pop_guard_enter при входе в комнату; вернула 1 — событие обработано и
|
* pop_guard_enter при входе в комнату; вернула 1 — событие обработано и
|
||||||
|
|||||||
@@ -27,8 +27,13 @@ uint8_t pop_droppedout;
|
|||||||
/* leveldoor_open (seg007:456): дверь уровня доехала до верха. Ставит
|
/* leveldoor_open (seg007:456): дверь уровня доехала до верха. Ставит
|
||||||
* анимация двери (pop_trob, банк 6), читает спецсобытие подъёма скелета
|
* анимация двери (pop_trob, банк 6), читает спецсобытие подъёма скелета
|
||||||
* (guards.c, банк 1) — снова случай «данные через границу банков».
|
* (guards.c, банк 1) — снова случай «данные через границу банков».
|
||||||
* Сбрасывается стартом уровня (seg003:97). */
|
* Сбрасывается стартом уровня (seg003:97).
|
||||||
uint8_t pop_leveldoor_open;
|
*
|
||||||
|
* ШИРИНА — word, как в оригинале (data.h:361), а не byte: на уровне 8
|
||||||
|
* do_timers СЧИТАЕТ этой же переменной время до появления мыши
|
||||||
|
* (`++leveldoor_open` каждый кадр, пока Кид в комнате 16), и байт
|
||||||
|
* переполнился бы через 255 кадров, обнулив признак «дверь открыта». */
|
||||||
|
uint16_t pop_leveldoor_open;
|
||||||
|
|
||||||
/* ---- Шина управления (seg006 globals) ------------------------------ *
|
/* ---- Шина управления (seg006 globals) ------------------------------ *
|
||||||
* Объявлены в pop_ctrl.h, а ЛЕЖАТ здесь: к ним обращаются ТРИ банка —
|
* Объявлены в pop_ctrl.h, а ЛЕЖАТ здесь: к ним обращаются ТРИ банка —
|
||||||
|
|||||||
@@ -43,8 +43,10 @@ extern int pop_leveldoor_ybottom;
|
|||||||
extern uint8_t pop_droppedout;
|
extern uint8_t pop_droppedout;
|
||||||
|
|
||||||
/* leveldoor_open (seg007:456): 1 — дверь уровня открыта полностью. Пишет
|
/* leveldoor_open (seg007:456): 1 — дверь уровня открыта полностью. Пишет
|
||||||
* анимация двери (банк 6), читает подъём скелета (банк 1). */
|
* анимация двери (банк 6), читает подъём скелета (банк 1). Word, как в
|
||||||
extern uint8_t pop_leveldoor_open;
|
* оригинале: на уровне 8 в него же СЧИТАЕТСЯ задержка до мыши (см.
|
||||||
|
* pop_state.c и pop_check_mouse в guards.c). */
|
||||||
|
extern uint16_t pop_leveldoor_open;
|
||||||
|
|
||||||
/* Отладочная «пустышка» для брейкпоинтов из банков — см. pop_state.c.
|
/* Отладочная «пустышка» для брейкпоинтов из банков — см. pop_state.c.
|
||||||
* Звать из отлаживаемого места под нужным `if`, брейкпоинт ставить на
|
* Звать из отлаживаемого места под нужным `if`, брейкпоинт ставить на
|
||||||
|
|||||||
@@ -90,6 +90,25 @@
|
|||||||
* (TASKS_OPEN.md, L1-SPEED), а не свойство зелья. */
|
* (TASKS_OPEN.md, L1-SPEED), а не свойство зелья. */
|
||||||
#define POP_FEATHER_FRAMES 225
|
#define POP_FEATHER_FRAMES 225
|
||||||
|
|
||||||
|
/* ---- Спецсобытие «МЫШЬ» (уровень 8, комната 16) ---------------------- *
|
||||||
|
* Кнопка (0,7) комнаты 16 открывает решётку (0,3) — ту, за которой лежит
|
||||||
|
* путь через комнату 4 (два чомпера) к кнопке выхода в комнате 3. Сам Кид
|
||||||
|
* до кнопки с левой площадки не дотягивается (провал в колонках 4..6),
|
||||||
|
* поэтому, когда дверь уровня уже открыта, а он всё ещё в комнате 16,
|
||||||
|
* оригинал через 150 тиков присылает мышь: она пробегает справа налево по
|
||||||
|
* верхнему ряду, наступает на кнопку и убегает обратно (do_timers
|
||||||
|
* seg003:0545, do_mouse seg003:0ABA, autocontrol_mouse seg002:07EB).
|
||||||
|
*
|
||||||
|
* Числа — custom-константы SDLPoP (data.h:821): уровень/комната/задержка/
|
||||||
|
* стартовая X. Пороги разворота (166) и исчезновения (200) — литералы
|
||||||
|
* autocontrol_mouse. Задержка в ИГРОВЫХ КАДРАХ, как POP_FEATHER_FRAMES. */
|
||||||
|
#define POP_MOUSE_LEVEL 8
|
||||||
|
#define POP_MOUSE_ROOM 16
|
||||||
|
#define POP_MOUSE_DELAY 150
|
||||||
|
#define POP_MOUSE_START_X 200
|
||||||
|
#define POP_MOUSE_TURN_X 166 /* x < этого — развернуться и уйти */
|
||||||
|
#define POP_MOUSE_LEAVE_X 200 /* x >= этого в стойке — исчезнуть */
|
||||||
|
|
||||||
/* ---- Уровень зелий (SDLPoP: «potions level», seg006:1894) ------------ *
|
/* ---- Уровень зелий (SDLPoP: «potions level», seg006:1894) ------------ *
|
||||||
* На нём синие склянки «вреда» забирают ПОЛОВИНУ запаса HP, а не единицу.
|
* На нём синие склянки «вреда» забирают ПОЛОВИНУ запаса HP, а не единицу.
|
||||||
* В оригинале это условие `current_level == 15` прямо в proc_get_object. */
|
* В оригинале это условие `current_level == 15` прямо в proc_get_object. */
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "pop_cdraw.h" /* общая отрисовка персонажей (слоты Кид/соперник) */
|
#include "pop_cdraw.h" /* общая отрисовка персонажей (слоты Кид/соперник) */
|
||||||
#include "pop_cheat.h"
|
#include "pop_cheat.h"
|
||||||
#include "pop_tune.h"
|
#include "pop_tune.h"
|
||||||
|
#include "roomtest_cold.h" /* холодная половина цикла — в банке */
|
||||||
|
|
||||||
__sfr __at (0xFE) io_border;
|
__sfr __at (0xFE) io_border;
|
||||||
|
|
||||||
@@ -54,7 +55,9 @@ __sfr __at (0xFE) io_border;
|
|||||||
* оверлеи, кладка), 3 = pop_map (коллизия/физика), 4 = pop_cdraw
|
* оверлеи, кладка), 3 = pop_map (коллизия/физика), 4 = pop_cdraw
|
||||||
* (отрисовка персонажей + полоса HP), 5 = pop_ctrl (диспетчер управления),
|
* (отрисовка персонажей + полоса HP), 5 = pop_ctrl (диспетчер управления),
|
||||||
* 6 = pop_trob (ловушки/анимация тайлов), 7 = pop_room (ХОЛОДНАЯ половина
|
* 6 = pop_trob (ловушки/анимация тайлов), 7 = pop_room (ХОЛОДНАЯ половина
|
||||||
* фона: полная отрисовка комнаты, точечные перерисовки, падающие плиты).
|
* фона: полная отрисовка комнаты, точечные перерисовки, падающие плиты),
|
||||||
|
* 8 = roomtest_cold (ХОЛОДНАЯ половина главного цикла: старт уровня и
|
||||||
|
* отладочная навигация — см. roomtest_cold.h).
|
||||||
* Отрисовка персонажей и холодная половина фона вынесены из банка 2
|
* Отрисовка персонажей и холодная половина фона вынесены из банка 2
|
||||||
* отдельно: тот дважды подходил к 16 КБ вплотную, а расти ему ещё есть
|
* отдельно: тот дважды подходил к 16 КБ вплотную, а расти ему ещё есть
|
||||||
* куда — тайлы поздних уровней, чомперы, зеркало.
|
* куда — тайлы поздних уровней, чомперы, зеркало.
|
||||||
@@ -64,7 +67,7 @@ __sfr __at (0xFE) io_border;
|
|||||||
* останется нулём, и трамплин прыгнет по 0xC000 в мусор (см. шапку
|
* останется нулём, и трамплин прыгнет по 0xC000 в мусор (см. шапку
|
||||||
* runtime/bank.s). Проверено 2026-08-05: с n_banks=4 и пятым банком игра
|
* runtime/bank.s). Проверено 2026-08-05: с n_banks=4 и пятым банком игра
|
||||||
* доходила до конца загрузки ресурсов и вставала намертво в DSS. */
|
* доходила до конца загрузки ресурсов и вставала намертво в DSS. */
|
||||||
const uint8_t n_banks = 7;
|
const uint8_t n_banks = 8;
|
||||||
|
|
||||||
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
||||||
uint8_t pop_cheats;
|
uint8_t pop_cheats;
|
||||||
@@ -119,13 +122,14 @@ static void kbd_idle(void)
|
|||||||
/* Рабочая копия ТЕКУЩЕЙ комнаты (W2, мутабельная: loose→empty) + срезы
|
/* Рабочая копия ТЕКУЩЕЙ комнаты (W2, мутабельная: loose→empty) + срезы
|
||||||
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
||||||
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
||||||
static uint8_t room_fg[30], room_bg[30];
|
uint8_t room_fg[30];
|
||||||
|
static uint8_t room_bg[30];
|
||||||
/* lcol_fg/rcol_fg — ШЕСТЬ записей: [0..2] кромочная колонка соседа (col9/col0),
|
/* lcol_fg/rcol_fg — ШЕСТЬ записей: [0..2] кромочная колонка соседа (col9/col0),
|
||||||
* [3..5] следующая за ней (col8/col1) для коллизии, когда Kid стоит В шве и
|
* [3..5] следующая за ней (col8/col1) для коллизии, когда Kid стоит В шве и
|
||||||
* смотрит наружу (колонка −2 / 11). См. pop_level.h. */
|
* смотрит наружу (колонка −2 / 11). См. pop_level.h. */
|
||||||
static uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[11];
|
static uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[11];
|
||||||
static uint8_t above_fg[10], above_mod[10]; /* ряд 2 верхнего соседа (полоса кладки у потолка) */
|
static uint8_t above_fg[10], above_mod[10]; /* ряд 2 верхнего соседа (полоса кладки у потолка) */
|
||||||
static uint8_t cur_room; /* номер ОТРИСОВАННОЙ комнаты (drawn_room) */
|
uint8_t cur_room; /* номер ОТРИСОВАННОЙ комнаты (drawn_room) */
|
||||||
static uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (S1: == cur_room; расходятся в S3 straddle) */
|
static uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (S1: == cur_room; расходятся в S3 straddle) */
|
||||||
|
|
||||||
/* Рендер-смещение Kid по логической X (порт xpos_in_drawn_room): комната Kid
|
/* Рендер-смещение Kid по логической X (порт xpos_in_drawn_room): комната Kid
|
||||||
@@ -258,10 +262,11 @@ static uint8_t seam_sig[3]; /* последняя нарисованная ope
|
|||||||
static uint8_t seam_rows; /* маска рядов, ждущих редроя (переживает 2 кадра) */
|
static uint8_t seam_rows; /* маска рядов, ждущих редроя (переживает 2 кадра) */
|
||||||
static uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */
|
static uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */
|
||||||
|
|
||||||
/* Отладочный лейбл номера комнаты — рисуем только при смене (outtextxy тяжёлая):
|
/* checkpoint (seg000:270): взводится, когда Кид уходит ВЛЕВО в комнату 7
|
||||||
* sig = последняя показанная комната, redraw = остаток кадров (2 = обе страницы). */
|
* уровня 3 (level3_set_chkp, seg002:0665); по нему do_startpos подменяет
|
||||||
static uint8_t room_label_sig = 0xFF;
|
* стартовую позицию. Сбрасывается началом игры и прохождением уровня —
|
||||||
static uint8_t room_label_redraw;
|
* ровно как в оригинале (seg003:37 и seg003:413). */
|
||||||
|
uint8_t pop_checkpoint;
|
||||||
|
|
||||||
/* Изменения тайлов (упавшая плита → пусто, щебень на месте приземления
|
/* Изменения тайлов (упавшая плита → пусто, щебень на месте приземления
|
||||||
* куска, поднятый предмет → пол) пишутся ПРЯМО В УРОВЕНЬ —
|
* куска, поднятый предмет → пол) пишутся ПРЯМО В УРОВЕНЬ —
|
||||||
@@ -387,7 +392,7 @@ static void enter_room_side(uint8_t room, uint8_t side)
|
|||||||
|
|
||||||
/* Вход в комнату БЕЗ перехода за Кидом: старт уровня, респавн, чит-обход.
|
/* Вход в комнату БЕЗ перехода за Кидом: старт уровня, респавн, чит-обход.
|
||||||
* Страж такой смены не «видит» — он остаётся в своей комнате. */
|
* Страж такой смены не «видит» — он остаётся в своей комнате. */
|
||||||
static void enter_room(uint8_t room) { enter_room_side(room, 0xFF); }
|
void enter_room(uint8_t room) { enter_room_side(room, 0xFF); }
|
||||||
|
|
||||||
/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos
|
/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos
|
||||||
* (seg003:028A) — поставить Кида в стартовую позицию УРОВНЯ. Один вход и
|
* (seg003:028A) — поставить Кида в стартовую позицию УРОВНЯ. Один вход и
|
||||||
@@ -414,109 +419,6 @@ static void enter_room(uint8_t room) { enter_room_side(room, 0xFF); }
|
|||||||
#define ENTRY_POSE_RUN 2 /* tbl_entry_pose[13] */
|
#define ENTRY_POSE_RUN 2 /* tbl_entry_pose[13] */
|
||||||
#define TILE_LEVELDOOR_LEFT 16 /* tiles_16_level_door_left */
|
#define TILE_LEVELDOOR_LEFT 16 /* tiles_16_level_door_left */
|
||||||
|
|
||||||
/* find_start_level_door (seg003:02E6): дверь уровня В СТАРТОВОЙ КОМНАТЕ —
|
|
||||||
* это дверь, через которую Кид ВОШЁЛ, поэтому она открыта полностью
|
|
||||||
* (modif = 43) и заводится trob'ом типа 3 (start_level_door, seg007:0D72).
|
|
||||||
* Без этого на уровне 2 Кид материализуется внутри глухой закрытой створки:
|
|
||||||
* его стартовый тайл (комната 5, ряд 1, колонка 3) — правая половина
|
|
||||||
* той самой двери. */
|
|
||||||
static void find_start_level_door(uint8_t room)
|
|
||||||
{
|
|
||||||
uint8_t *m = pop_trob_modif(room);
|
|
||||||
uint8_t tp;
|
|
||||||
if (!m) return;
|
|
||||||
/* Батч-доступ к странице уровня тут не нужен: функция отрабатывает раз
|
|
||||||
* за уровень, а pop_add_trob сам лезет в W0. */
|
|
||||||
for (tp = 0; tp < 30; tp++) {
|
|
||||||
if (pop_level_tile(room, tp) != TILE_LEVELDOOR_LEFT) continue;
|
|
||||||
m[tp] = 43; /* створка поднята полностью */
|
|
||||||
pop_add_trob(room, tp, 3); /* ...и тут же поедет ВНИЗ (type>=3) */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* checkpoint (seg000:270): взводится, когда Кид уходит ВЛЕВО в комнату 7
|
|
||||||
* уровня 3 (level3_set_chkp, seg002:0665); по нему do_startpos подменяет
|
|
||||||
* стартовую позицию. Сбрасывается началом игры и прохождением уровня —
|
|
||||||
* ровно как в оригинале (seg003:37 и seg003:413). */
|
|
||||||
static uint8_t pop_checkpoint;
|
|
||||||
|
|
||||||
static void pop_start_level(void)
|
|
||||||
{
|
|
||||||
pop_leveldoor_open = 0; /* seg003:97 — дверь прошлого уровня не в счёт */
|
|
||||||
pop_feather = 0; /* seg003:189 — перо не переживает старт уровня */
|
|
||||||
uint8_t entry_pose = pop_level_entry_pose(pop_current_level);
|
|
||||||
uint8_t start_room = pop_level_start_room();
|
|
||||||
uint8_t pos = pop_level_start_pos();
|
|
||||||
int8_t dir = (int8_t)~pop_level_start_dir();
|
|
||||||
uint8_t seq = SEQ_TURN;
|
|
||||||
/* Особое событие: старт с чекпойнта (do_startpos, seg003:0141).
|
|
||||||
* Числа — в pop_tune.h. Плиту снимаем ПОСЛЕ pop_level_reset_tiles()
|
|
||||||
* ниже, иначе рестарт её вернёт. */
|
|
||||||
uint8_t chkp = (uint8_t)(pop_current_level == POP_CHKP_LEVEL && pop_checkpoint);
|
|
||||||
if (chkp) {
|
|
||||||
start_room = POP_CHKP_RESPAWN_ROOM;
|
|
||||||
pos = POP_CHKP_RESPAWN_TILEPOS;
|
|
||||||
dir = (int8_t)~POP_CHKP_RESPAWN_DIR; /* стартует в ПРОТИВОположную
|
|
||||||
* и разворачивается seq_5 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Рестарт уровня = load_level() заново (play_level, seg003:57): в
|
|
||||||
* исходное возвращаются И модификаторы (пики/ворота), И сами тайлы —
|
|
||||||
* разбитые плиты целы, выпитые зелья на месте (BUG-RESPAWN-1), и
|
|
||||||
* убитые стражи снова живы на своих тайлах (BUG-RESPAWN-2). */
|
|
||||||
pop_trob_reset();
|
|
||||||
pop_level_reset_tiles();
|
|
||||||
pop_level_reset_guards();
|
|
||||||
pop_guard_reset(); /* start_level: Guard.direction = none */
|
|
||||||
pop_loose_mob_reset(); /* start_level: mobs_count = 0 (seg003:88) */
|
|
||||||
/* «Особое событие: убрать loose-плиту» (do_startpos): без этого путь в
|
|
||||||
* комнату-чекпойнт после респавна остался бы непройденным. */
|
|
||||||
if (chkp)
|
|
||||||
pop_level_set_tile(POP_CHKP_CLEAR_ROOM,
|
|
||||||
(uint8_t)(POP_CHKP_CLEAR_ROW * 10 + POP_CHKP_CLEAR_COL),
|
|
||||||
TILE_EMPTY);
|
|
||||||
|
|
||||||
/* Кнопку жмём ДО отрисовки комнаты — как в оригинале (do_startpos
|
|
||||||
* отрабатывает раньше первого draw_room), иначе шов кадр показывал бы
|
|
||||||
* ещё не тронутое состояние решётки. */
|
|
||||||
if (entry_pose == ENTRY_POSE_FALL) {
|
|
||||||
/* get_tile(5,2,0) + trigger_button(0,0,-1) — «нажать» кнопку room5,
|
|
||||||
* col2, row0 её же типом и модификатором (в оригинале и то и другое
|
|
||||||
* берётся из curr_* после get_tile). */
|
|
||||||
uint8_t tp = 2; /* row0*10 + col2 */
|
|
||||||
pop_trigger_button(5, tp, pop_level_tile(5, tp), pop_trob_modif(5)[tp]);
|
|
||||||
seq = SEQ_FALL;
|
|
||||||
} else if (entry_pose == ENTRY_POSE_RUN) {
|
|
||||||
seq = SEQ_RUN;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Тоже ДО отрисовки комнаты: дверь, через которую вошли, обязана быть
|
|
||||||
* открыта уже в первом кадре (в оригинале find_start_level_door стоит
|
|
||||||
* между do_startpos и draw_level_first). */
|
|
||||||
find_start_level_door(start_room);
|
|
||||||
|
|
||||||
enter_room(start_room);
|
|
||||||
kid_init(seq, (int8_t)(pos % 10), (int8_t)(pos / 10), dir);
|
|
||||||
/* Спецсобытие «вход падением» (set_start_pos, seg003:0196): на 7-м
|
|
||||||
* уровне Кид ставится в комнату 17, а экран тут же переводится на
|
|
||||||
* комнату ПОД ней (`goto_other_room(3)`: y −= 189, ряд пересчитать) —
|
|
||||||
* он влетает в неё сверху, продолжая падение с шестого уровня. */
|
|
||||||
if (pop_current_level == FALLING_ENTRY_LEVEL &&
|
|
||||||
start_room == FALLING_ENTRY_ROOM) {
|
|
||||||
uint8_t below = pop_room_link(start_room, LINK_DOWN);
|
|
||||||
if (below) {
|
|
||||||
Kid.y = (uint8_t)(Kid.y - 189);
|
|
||||||
Kid.curr_row = pop_y_to_row((int16_t)Kid.y);
|
|
||||||
enter_room(below);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Kid.sword = 0; /* set_start_pos: sword_0_sheathed */
|
|
||||||
/* play_level (seg003:106): have_sword = level >= have_sword_from_level
|
|
||||||
* (=2) — начиная со второго уровня меч у Кида уже есть. */
|
|
||||||
pop_have_sword = (uint8_t)(pop_current_level >= 2);
|
|
||||||
pop_kid_hp_reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
/* Дабл-буфер: front — видимая страница, back — куда рисуем. dbuf=1
|
/* Дабл-буфер: front — видимая страница, back — куда рисуем. dbuf=1
|
||||||
@@ -663,89 +565,13 @@ int main(void)
|
|||||||
#ifdef ROOMNAV
|
#ifdef ROOMNAV
|
||||||
/* Обход комнат по номеру: '+' (0x79 numpad / 0x55 '=') — вперёд,
|
/* Обход комнат по номеру: '+' (0x79 numpad / 0x55 '=') — вперёд,
|
||||||
* '-' (0x7B / 0x4E) — назад. Edge по nav_prev: одно нажатие = одна
|
* '-' (0x7B / 0x4E) — назад. Edge по nav_prev: одно нажатие = одна
|
||||||
* комната.
|
* комната. Сама навигация — в банке (roomtest_cold.c): она нужна
|
||||||
*
|
* только по клавише, а место в резиденте дефицитно. */
|
||||||
* Здесь БЫЛ pop_trob_reset() перед enter_room — «чтобы каждая комната
|
|
||||||
* смотрелась в исходном состоянии». Убран: reset обнуляет room_seen,
|
|
||||||
* и модификаторы перечитываются из уровня заново, то есть чит
|
|
||||||
* ОТМАТЫВАЕТ МИР — открытые/закрытые ворота, выдвинутые пики, нажатые
|
|
||||||
* кнопки. Пока ворота с bg=1 ошибочно стартовали закрытыми, откат был
|
|
||||||
* не виден; после порта load_alter_mod он выглядит как баг «ворота
|
|
||||||
* снова открылись» (уровень 1, решётка комнаты 5 (0,9), которую
|
|
||||||
* закрывает стартовый триггер do_startpos). Навигация обязана только
|
|
||||||
* телепортировать; исходное состояние даёт перезапуск уровня. */
|
|
||||||
{
|
{
|
||||||
uint8_t nav = (uint8_t)((kbd_raw_down(0x79) || kbd_raw_down(0x55)) |
|
uint8_t nav = (uint8_t)((kbd_raw_down(0x79) || kbd_raw_down(0x55)) |
|
||||||
((kbd_raw_down(0x7B) || kbd_raw_down(0x4E)) << 1));
|
((kbd_raw_down(0x7B) || kbd_raw_down(0x4E)) << 1));
|
||||||
if (nav && !nav_prev) {
|
if (nav && !nav_prev && pop_dbg_roomnav((uint8_t)(nav & 1 ? 1 : 2)))
|
||||||
uint8_t r = cur_room, i, c, pos = 20, any = 0xFF;
|
|
||||||
/* Кид МЁРТВ: сначала честный рестарт уровня, потом телепорт.
|
|
||||||
* Навигация только переставляет Кида (kid_init + сброс HP),
|
|
||||||
* то есть «оживляет» его мимо start_level — а тот обязан
|
|
||||||
* вернуть тайлы, модификаторы и ОЧИСТИТЬ слот соперника
|
|
||||||
* (seg003:100: Guard.direction = dir_56_none). Без этого
|
|
||||||
* после смерти оставался живой скелет рядом с вернувшейся
|
|
||||||
* кучей костей. */
|
|
||||||
if (pop_kid_dead) { pop_start_level(); dead_frames = 0; }
|
|
||||||
r = (nav & 1) ? (uint8_t)(r == 24 ? 1 : r + 1)
|
|
||||||
: (uint8_t)(r == 1 ? 24 : r - 1);
|
|
||||||
enter_room(r);
|
|
||||||
/* Куда поставить Кида в новой комнате: пол С МАКСИМАЛЬНЫМ Y —
|
|
||||||
* сначала НИЖНИЙ ряд (2), потом 1, и только потом 0
|
|
||||||
* (пользователь 2026-08-11). Раньше брался первый пол
|
|
||||||
* подряд, то есть почти всегда (0,0) — верхний левый угол:
|
|
||||||
* это и «прилёт» в проём/ворота, и, на уровне 5, посадка
|
|
||||||
* вплотную к тени спецсобытия, которая ждёт в колонке −1
|
|
||||||
* ряда 0. Вместо сцены кражи зелья начиналась схватка,
|
|
||||||
* которой в этой комнате быть не должно. Снизу вверх нужно
|
|
||||||
* и самой навигации: в верхнем ряду пола чаще нет, и Кид
|
|
||||||
* проваливается из комнаты сразу после телепорта.
|
|
||||||
*
|
|
||||||
* ИСКЛЮЧЕНИЕ — комната 14 уровня 7 (пользователь 2026-08-12):
|
|
||||||
* её проходят СВЕРХУ ВНИЗ (с ряда 0 на ряд 2 через зацеп за
|
|
||||||
* кромки), и с нижнего ряда этот спуск не проверить вовсе.
|
|
||||||
* Пол в ряду 0 там есть, так что Кид не проваливается.
|
|
||||||
*
|
|
||||||
* В каждом ряду сначала ищем ОБЫЧНЫЙ пол, и только если его
|
|
||||||
* там нет — любой проходимый tile_is_floor (seg001: всё,
|
|
||||||
* кроме пустоты, верха колонны, верха двери, стены и
|
|
||||||
* «половинок» 26..29). */
|
|
||||||
{
|
|
||||||
int8_t row, step = -1, first = 2;
|
|
||||||
if (pop_current_level == GRAB_DOWN_LEVEL &&
|
|
||||||
r == GRAB_DOWN_ROOM) { first = 0; step = 1; }
|
|
||||||
for (row = first; row >= 0 && row <= 2 && any == 0xFF;
|
|
||||||
row = (int8_t)(row + step)) {
|
|
||||||
uint8_t fallback = 0xFF;
|
|
||||||
for (i = (uint8_t)(row * 10); i < (uint8_t)(row * 10 + 10); i++) {
|
|
||||||
c = (uint8_t)(room_fg[i] & 0x1F);
|
|
||||||
if (c == 1) { any = i; break; }
|
|
||||||
if (fallback == 0xFF && c && c != 9 && c != 12 &&
|
|
||||||
c != 20 && !(c >= 26 && c <= 29)) fallback = i;
|
|
||||||
}
|
|
||||||
if (any == 0xFF) any = fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (any != 0xFF) pos = any;
|
|
||||||
kid_init(SEQ_STAND, (int8_t)(pos % 10), (int8_t)(pos / 10), 0);
|
|
||||||
pop_kid_hp_reset();
|
|
||||||
dead_frames = 0;
|
dead_frames = 0;
|
||||||
/* ВЫХОД ИЗ БОЯ (BUG-CHEAT-FIGHT-1). Телепорт с вынутым мечом
|
|
||||||
* заклинивал управление намертво: pop_control при
|
|
||||||
* Kid.sword == SWORD_2_DRAWN уходит в control_with_sword, а
|
|
||||||
* там единственный выход — ветка «убрать меч» на кадре
|
|
||||||
* FRAME_171_STAND_WITH_SWORD. После kid_init(SEQ_STAND) кадр
|
|
||||||
* обычный, стража в новой комнате нет, swordfight не
|
|
||||||
* запускается — и диспетчер каждый кадр не делает НИЧЕГО.
|
|
||||||
* Это болезнь ЧИТА: в оригинале телепорта между комнатами
|
|
||||||
* нет, попасть в режим боя без соперника нечем. Меч в
|
|
||||||
* инвентаре (pop_have_sword) НЕ теряем — только состояние
|
|
||||||
* схватки. */
|
|
||||||
Kid.sword = SWORD_0_SHEATHED;
|
|
||||||
holding_sword = 0;
|
|
||||||
offguard = 0;
|
|
||||||
guard_refrac = 0;
|
|
||||||
}
|
|
||||||
nav_prev = nav;
|
nav_prev = nav;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -755,6 +581,7 @@ int main(void)
|
|||||||
gfx_set_draw_page(back);
|
gfx_set_draw_page(back);
|
||||||
|
|
||||||
pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */
|
pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */
|
||||||
|
pop_check_mouse(); /* спецсобытие ур.8: мышь жмёт кнопку */
|
||||||
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
|
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
|
||||||
pop_ctrl_tick(); /* ввод -> control(): смена seq */
|
pop_ctrl_tick(); /* ввод -> control(): смена seq */
|
||||||
/* Кто в этом кадре не изменился с прошлой отрисовки ЭТОЙ страницы —
|
/* Кто в этом кадре не изменился с прошлой отрисовки ЭТОЙ страницы —
|
||||||
@@ -1045,26 +872,7 @@ int main(void)
|
|||||||
* при её смене (change-driven, счётчик 2 = обе страницы дабл-буфера),
|
* при её смене (change-driven, счётчик 2 = обе страницы дабл-буфера),
|
||||||
* а не каждый кадр (обход sdcc_z80_cmp_store_a_bug: сначала сохранить). */
|
* а не каждый кадр (обход sdcc_z80_cmp_store_a_bug: сначала сохранить). */
|
||||||
#ifdef ROOMNAV
|
#ifdef ROOMNAV
|
||||||
{
|
pop_dbg_room_label(cur_room); /* номер комнаты палочками — в банке */
|
||||||
uint8_t r = cur_room, prev = room_label_sig;
|
|
||||||
room_label_sig = r; /* СНАЧАЛА сохранить (sdcc_z80_cmp_store_a_bug) */
|
|
||||||
if (r != prev) room_label_redraw = 2;
|
|
||||||
if (room_label_redraw) {
|
|
||||||
/* Номер ПАЛОЧКАМИ, а не outtextxy: текст тянет системный
|
|
||||||
* знакогенератор (_gfx_font_buf, 2 КБ статики в W2) и сажает
|
|
||||||
* кучу с 3867 до 576 Б. Слева десятки, справа единицы:
|
|
||||||
* || |||| = 24. bar/setfillstyle уже используются. */
|
|
||||||
uint8_t i;
|
|
||||||
setfillstyle(SOLID_FILL, BLACK);
|
|
||||||
bar(100, 0, 175, 5);
|
|
||||||
/* 0x57, а НЕ WHITE: палитра игровая, запись 15 в ней чёрная
|
|
||||||
* (kid.pal: 0x0F = 0,0,0; чистый белый = 0x57). */
|
|
||||||
setfillstyle(SOLID_FILL, 0x57);
|
|
||||||
for (i = 0; i < (uint8_t)(r / 10); i++) bar(100 + i * 5, 0, 101 + i * 5, 5);
|
|
||||||
for (i = 0; i < (uint8_t)(r % 10); i++) bar(130 + i * 5, 0, 131 + i * 5, 5);
|
|
||||||
room_label_redraw--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
gfx_wait_vsync();
|
gfx_wait_vsync();
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* roomtest_cold.c — ХОЛОДНАЯ половина главного цикла, собирается в БАНК
|
||||||
|
* (--bank 8=roomtest_cold.c, окно W3). Здесь всё, что отрабатывает раз на
|
||||||
|
* уровень или только по отладочной клавише; кадровый путь остался в
|
||||||
|
* roomtest.c. Зачем разделили и что осталось снаружи — см. roomtest_cold.h.
|
||||||
|
*
|
||||||
|
* Правила банка те же, что у guards.c: состояние — в W1/W2 (данные этого
|
||||||
|
* модуля тоже, --bank-data не включён), звать можно резидент W1/W2 и другие
|
||||||
|
* __banked-модули через трамплины.
|
||||||
|
*/
|
||||||
|
#include <graphics.h>
|
||||||
|
#include <gfx.h>
|
||||||
|
#include "roomtest_cold.h"
|
||||||
|
#include "pop_bg.h" /* pop_loose_mob_reset — банк 7, зовём через трамплин */
|
||||||
|
#include "pop_kid.h"
|
||||||
|
#include "pop_map.h"
|
||||||
|
#include "pop_level.h"
|
||||||
|
#include "pop_trob.h"
|
||||||
|
#include "pop_guard.h"
|
||||||
|
#include "pop_state.h"
|
||||||
|
#include "pop_geom.h"
|
||||||
|
#include "pop_tune.h"
|
||||||
|
|
||||||
|
#define SEQ_STAND 2
|
||||||
|
#define SEQ_TURN 5
|
||||||
|
#define SEQ_FALL 7
|
||||||
|
#define SEQ_RUN 84
|
||||||
|
#define ENTRY_POSE_FALL 1 /* tbl_entry_pose[1] */
|
||||||
|
#define ENTRY_POSE_RUN 2 /* tbl_entry_pose[13] */
|
||||||
|
#define TILE_LEVELDOOR_LEFT 16 /* tiles_16_level_door_left */
|
||||||
|
#define TILE_EMPTY 0
|
||||||
|
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
|
||||||
|
|
||||||
|
/* find_start_level_door (seg003:02E6): дверь уровня В СТАРТОВОЙ КОМНАТЕ —
|
||||||
|
* это дверь, через которую Кид ВОШЁЛ, поэтому она открыта полностью
|
||||||
|
* (modif = 43) и заводится trob'ом типа 3 (start_level_door, seg007:0D72).
|
||||||
|
* Без этого на уровне 2 Кид материализуется внутри глухой закрытой створки:
|
||||||
|
* его стартовый тайл (комната 5, ряд 1, колонка 3) — правая половина
|
||||||
|
* той самой двери. */
|
||||||
|
static void find_start_level_door(uint8_t room)
|
||||||
|
{
|
||||||
|
uint8_t *m = pop_trob_modif(room);
|
||||||
|
uint8_t tp;
|
||||||
|
if (!m) return;
|
||||||
|
/* Батч-доступ к странице уровня тут не нужен: функция отрабатывает раз
|
||||||
|
* за уровень, а pop_add_trob сам лезет в W0. */
|
||||||
|
for (tp = 0; tp < 30; tp++) {
|
||||||
|
if (pop_level_tile(room, tp) != TILE_LEVELDOOR_LEFT) continue;
|
||||||
|
m[tp] = 43; /* створка поднята полностью */
|
||||||
|
pop_add_trob(room, tp, 3); /* ...и тут же поедет ВНИЗ (type>=3) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos
|
||||||
|
* (seg003:028A).
|
||||||
|
*
|
||||||
|
* Позиция берётся ИЗ ДАННЫХ уровня (pop_level_start_*), а не из #define:
|
||||||
|
* start_pos — тайл 0..29 (row*10+col), start_dir — направление, в котором
|
||||||
|
* персонаж должен ОКАЗАТЬСЯ.
|
||||||
|
*
|
||||||
|
* Направление ИНВЕРТИРУЕТСЯ (`Char.direction = ~level.start_dir`): персонаж
|
||||||
|
* ставится спиной и доворачивается кадрами позы входа. Сама поза — из
|
||||||
|
* tbl_entry_pose (data.h:848 = {0,1,0,…,2,…}):
|
||||||
|
* 1 (уровень 1) — падение внутрь (seq_7_fall) + нажатие кнопки room5(0,2);
|
||||||
|
* это оно захлопывает решётку за спиной в первой комнате;
|
||||||
|
* 2 (уровень 13) — вбегание (seq_84_run);
|
||||||
|
* иначе — разворот на месте (seq_5_turn).
|
||||||
|
* Таблица индексируется НОМЕРОМ УРОВНЯ — pop_level_entry_pose(). */
|
||||||
|
void pop_start_level(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t entry_pose, start_room, pos, seq = SEQ_TURN, chkp;
|
||||||
|
int8_t dir;
|
||||||
|
|
||||||
|
pop_leveldoor_open = 0; /* seg003:97 — дверь прошлого уровня не в счёт */
|
||||||
|
pop_feather = 0; /* seg003:189 — перо не переживает старт уровня */
|
||||||
|
entry_pose = pop_level_entry_pose(pop_current_level);
|
||||||
|
start_room = pop_level_start_room();
|
||||||
|
pos = pop_level_start_pos();
|
||||||
|
dir = (int8_t)~pop_level_start_dir();
|
||||||
|
/* Особое событие: старт с чекпойнта (do_startpos, seg003:0141).
|
||||||
|
* Числа — в pop_tune.h. Плиту снимаем ПОСЛЕ pop_level_reset_tiles()
|
||||||
|
* ниже, иначе рестарт её вернёт. */
|
||||||
|
chkp = (uint8_t)(pop_current_level == POP_CHKP_LEVEL && pop_checkpoint);
|
||||||
|
if (chkp) {
|
||||||
|
start_room = POP_CHKP_RESPAWN_ROOM;
|
||||||
|
pos = POP_CHKP_RESPAWN_TILEPOS;
|
||||||
|
dir = (int8_t)~POP_CHKP_RESPAWN_DIR; /* стартует в ПРОТИВОположную
|
||||||
|
* и разворачивается seq_5 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Рестарт уровня = load_level() заново (play_level, seg003:57): в
|
||||||
|
* исходное возвращаются И модификаторы (пики/ворота), И сами тайлы —
|
||||||
|
* разбитые плиты целы, выпитые зелья на месте (BUG-RESPAWN-1), и
|
||||||
|
* убитые стражи снова живы на своих тайлах (BUG-RESPAWN-2). */
|
||||||
|
pop_trob_reset();
|
||||||
|
pop_level_reset_tiles();
|
||||||
|
pop_level_reset_guards();
|
||||||
|
pop_guard_reset(); /* start_level: Guard.direction = none */
|
||||||
|
pop_loose_mob_reset(); /* start_level: mobs_count = 0 (seg003:88) */
|
||||||
|
/* «Особое событие: убрать loose-плиту» (do_startpos): без этого путь в
|
||||||
|
* комнату-чекпойнт после респавна остался бы непройденным. */
|
||||||
|
if (chkp)
|
||||||
|
pop_level_set_tile(POP_CHKP_CLEAR_ROOM,
|
||||||
|
(uint8_t)(POP_CHKP_CLEAR_ROW * 10 + POP_CHKP_CLEAR_COL),
|
||||||
|
TILE_EMPTY);
|
||||||
|
|
||||||
|
/* Кнопку жмём ДО отрисовки комнаты — как в оригинале (do_startpos
|
||||||
|
* отрабатывает раньше первого draw_room), иначе шов кадр показывал бы
|
||||||
|
* ещё не тронутое состояние решётки. */
|
||||||
|
if (entry_pose == ENTRY_POSE_FALL) {
|
||||||
|
/* get_tile(5,2,0) + trigger_button(0,0,-1) — «нажать» кнопку room5,
|
||||||
|
* col2, row0 её же типом и модификатором (в оригинале и то и другое
|
||||||
|
* берётся из curr_* после get_tile). */
|
||||||
|
uint8_t tp = 2; /* row0*10 + col2 */
|
||||||
|
pop_trigger_button(5, tp, pop_level_tile(5, tp), pop_trob_modif(5)[tp]);
|
||||||
|
seq = SEQ_FALL;
|
||||||
|
} else if (entry_pose == ENTRY_POSE_RUN) {
|
||||||
|
seq = SEQ_RUN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Тоже ДО отрисовки комнаты: дверь, через которую вошли, обязана быть
|
||||||
|
* открыта уже в первом кадре (в оригинале find_start_level_door стоит
|
||||||
|
* между do_startpos и draw_level_first). */
|
||||||
|
find_start_level_door(start_room);
|
||||||
|
|
||||||
|
enter_room(start_room);
|
||||||
|
kid_init(seq, (int8_t)(pos % 10), (int8_t)(pos / 10), dir);
|
||||||
|
/* Спецсобытие «вход падением» (set_start_pos, seg003:0196): на 7-м
|
||||||
|
* уровне Кид ставится в комнату 17, а экран тут же переводится на
|
||||||
|
* комнату ПОД ней (`goto_other_room(3)`: y −= 189, ряд пересчитать) —
|
||||||
|
* он влетает в неё сверху, продолжая падение с шестого уровня. */
|
||||||
|
if (pop_current_level == FALLING_ENTRY_LEVEL &&
|
||||||
|
start_room == FALLING_ENTRY_ROOM) {
|
||||||
|
uint8_t below = pop_room_link(start_room, LINK_DOWN);
|
||||||
|
if (below) {
|
||||||
|
Kid.y = (uint8_t)(Kid.y - 189);
|
||||||
|
Kid.curr_row = pop_y_to_row((int16_t)Kid.y);
|
||||||
|
enter_room(below);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kid.sword = 0; /* set_start_pos: sword_0_sheathed */
|
||||||
|
/* play_level (seg003:106): have_sword = level >= have_sword_from_level
|
||||||
|
* (=2) — начиная со второго уровня меч у Кида уже есть. */
|
||||||
|
pop_have_sword = (uint8_t)(pop_current_level >= 2);
|
||||||
|
pop_kid_hp_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- ОТЛАДКА: обход комнат по номеру -------------------------------- *
|
||||||
|
*
|
||||||
|
* Нужен, чтобы пройти все комнаты уровня и собрать список багов отрисовки,
|
||||||
|
* включая недостижимые обычным путём.
|
||||||
|
*
|
||||||
|
* Здесь БЫЛ pop_trob_reset() перед enter_room — «чтобы каждая комната
|
||||||
|
* смотрелась в исходном состоянии». Убран: reset обнуляет room_seen, и
|
||||||
|
* модификаторы перечитываются из уровня заново, то есть чит ОТМАТЫВАЕТ МИР
|
||||||
|
* — открытые/закрытые ворота, выдвинутые пики, нажатые кнопки. Навигация
|
||||||
|
* обязана только телепортировать; исходное состояние даёт перезапуск. */
|
||||||
|
uint8_t pop_dbg_roomnav(uint8_t dir) __banked
|
||||||
|
{
|
||||||
|
uint8_t r, i, c, pos = 20, any = 0xFF;
|
||||||
|
|
||||||
|
/* Кид МЁРТВ: сначала честный рестарт уровня, потом телепорт. Навигация
|
||||||
|
* только переставляет Кида (kid_init + сброс HP), то есть «оживляет» его
|
||||||
|
* мимо start_level — а тот обязан вернуть тайлы, модификаторы и ОЧИСТИТЬ
|
||||||
|
* слот соперника (seg003:100: Guard.direction = dir_56_none). Без этого
|
||||||
|
* после смерти оставался живой скелет рядом с вернувшейся кучей костей. */
|
||||||
|
if (pop_kid_dead) pop_start_level();
|
||||||
|
|
||||||
|
r = cur_room;
|
||||||
|
r = (dir == 1) ? (uint8_t)(r == 24 ? 1 : r + 1)
|
||||||
|
: (uint8_t)(r == 1 ? 24 : r - 1);
|
||||||
|
enter_room(r);
|
||||||
|
|
||||||
|
/* Куда поставить Кида в новой комнате: пол С МАКСИМАЛЬНЫМ Y — сначала
|
||||||
|
* НИЖНИЙ ряд (2), потом 1, и только потом 0 (пользователь 2026-08-11).
|
||||||
|
* Раньше брался первый пол подряд, то есть почти всегда (0,0) — верхний
|
||||||
|
* левый угол: это и «прилёт» в проём/ворота, и, на уровне 5, посадка
|
||||||
|
* вплотную к тени спецсобытия, которая ждёт в колонке −1 ряда 0. Вместо
|
||||||
|
* сцены кражи зелья начиналась схватка, которой в этой комнате быть не
|
||||||
|
* должно. Снизу вверх нужно и самой навигации: в верхнем ряду пола чаще
|
||||||
|
* нет, и Кид проваливается из комнаты сразу после телепорта.
|
||||||
|
*
|
||||||
|
* ИСКЛЮЧЕНИЕ — комната 14 уровня 7 (пользователь 2026-08-12): её проходят
|
||||||
|
* СВЕРХУ ВНИЗ (с ряда 0 на ряд 2 через зацеп за кромки), и с нижнего ряда
|
||||||
|
* этот спуск не проверить вовсе. Пол в ряду 0 там есть, так что Кид не
|
||||||
|
* проваливается.
|
||||||
|
*
|
||||||
|
* В каждом ряду сначала ищем ОБЫЧНЫЙ пол, и только если его там нет —
|
||||||
|
* любой проходимый tile_is_floor (seg001: всё, кроме пустоты, верха
|
||||||
|
* колонны, верха двери, стены и «половинок» 26..29). */
|
||||||
|
{
|
||||||
|
int8_t row, step = -1, first = 2;
|
||||||
|
if (pop_current_level == GRAB_DOWN_LEVEL &&
|
||||||
|
r == GRAB_DOWN_ROOM) { first = 0; step = 1; }
|
||||||
|
for (row = first; row >= 0 && row <= 2 && any == 0xFF;
|
||||||
|
row = (int8_t)(row + step)) {
|
||||||
|
uint8_t fallback = 0xFF;
|
||||||
|
for (i = (uint8_t)(row * 10); i < (uint8_t)(row * 10 + 10); i++) {
|
||||||
|
c = (uint8_t)(room_fg[i] & 0x1F);
|
||||||
|
if (c == 1) { any = i; break; }
|
||||||
|
if (fallback == 0xFF && c && c != 9 && c != 12 &&
|
||||||
|
c != 20 && !(c >= 26 && c <= 29)) fallback = i;
|
||||||
|
}
|
||||||
|
if (any == 0xFF) any = fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (any != 0xFF) pos = any;
|
||||||
|
kid_init(SEQ_STAND, (int8_t)(pos % 10), (int8_t)(pos / 10), 0);
|
||||||
|
pop_kid_hp_reset();
|
||||||
|
|
||||||
|
/* ВЫХОД ИЗ БОЯ (BUG-CHEAT-FIGHT-1). Телепорт с вынутым мечом заклинивал
|
||||||
|
* управление намертво: pop_control при Kid.sword == SWORD_2_DRAWN уходит
|
||||||
|
* в control_with_sword, а там единственный выход — ветка «убрать меч» на
|
||||||
|
* кадре FRAME_171_STAND_WITH_SWORD. После kid_init(SEQ_STAND) кадр
|
||||||
|
* обычный, стража в новой комнате нет, swordfight не запускается — и
|
||||||
|
* диспетчер каждый кадр не делает НИЧЕГО. Это болезнь ЧИТА: в оригинале
|
||||||
|
* телепорта между комнатами нет, попасть в режим боя без соперника
|
||||||
|
* нечем. Меч в инвентаре (pop_have_sword) НЕ теряем — только состояние
|
||||||
|
* схватки. */
|
||||||
|
Kid.sword = SWORD_0_SHEATHED;
|
||||||
|
holding_sword = 0;
|
||||||
|
offguard = 0;
|
||||||
|
guard_refrac = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- ОТЛАДКА: номер отрисованной комнаты в верхнем борте ------------- */
|
||||||
|
|
||||||
|
static uint8_t room_label_sig = 0xFF; /* последняя показанная комната */
|
||||||
|
static uint8_t room_label_redraw; /* остаток кадров (2 = обе страницы) */
|
||||||
|
|
||||||
|
void pop_dbg_room_label(uint8_t room) __banked
|
||||||
|
{
|
||||||
|
uint8_t prev = room_label_sig, i;
|
||||||
|
room_label_sig = room; /* СНАЧАЛА сохранить (sdcc_z80_cmp_store_a_bug) */
|
||||||
|
if (room != prev) room_label_redraw = 2;
|
||||||
|
if (!room_label_redraw) return;
|
||||||
|
/* Номер ПАЛОЧКАМИ, а не outtextxy: текст тянет системный знакогенератор
|
||||||
|
* (_gfx_font_buf, 2 КБ статики в W2) и сажает кучу. Слева десятки,
|
||||||
|
* справа единицы: || |||| = 24. */
|
||||||
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
|
bar(100, 0, 175, 5);
|
||||||
|
/* 0x57, а НЕ WHITE: палитра игровая, запись 15 в ней чёрная
|
||||||
|
* (kid.pal: 0x0F = 0,0,0; чистый белый = 0x57). */
|
||||||
|
setfillstyle(SOLID_FILL, 0x57);
|
||||||
|
for (i = 0; i < (uint8_t)(room / 10); i++) bar(100 + i * 5, 0, 101 + i * 5, 5);
|
||||||
|
for (i = 0; i < (uint8_t)(room % 10); i++) bar(130 + i * 5, 0, 131 + i * 5, 5);
|
||||||
|
room_label_redraw--;
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* roomtest_cold.h — контракт между главным циклом (резидент W1/W2) и его
|
||||||
|
* ХОЛОДНОЙ половиной, вынесенной в банк (roomtest_cold.c).
|
||||||
|
*
|
||||||
|
* Зачем. В режиме huge под кучу остаётся то, что не занял _CODE+_DATA, и к
|
||||||
|
* уровню 8 запас упал ниже килобайта. Главный цикл при этом наполовину
|
||||||
|
* состоит из кода, который отрабатывает РАЗ НА УРОВЕНЬ (старт/рестарт) или
|
||||||
|
* вообще только по отладочной клавише (обход комнат, лейбл номера) — держать
|
||||||
|
* его в дефицитном окне незачем. Кадровый путь (tick/draw/флип) остался в
|
||||||
|
* резиденте целиком: цена выноса — один трамплин на вызов, а он тут ноль раз
|
||||||
|
* за обычный кадр.
|
||||||
|
*
|
||||||
|
* Что ОСТАЛОСЬ в roomtest.c и почему: enter_room_side (её зовут и холодная
|
||||||
|
* половина, и переходы между комнатами в кадре падения) и рабочие массивы
|
||||||
|
* комнаты — они же и есть то состояние, ради которого функция вообще
|
||||||
|
* существует; банковый модуль читает их как обычные глобалы (данные банков
|
||||||
|
* линкуются в общий _DATA, --bank-data мы не включаем).
|
||||||
|
*/
|
||||||
|
#ifndef ROOMTEST_COLD_H
|
||||||
|
#define ROOMTEST_COLD_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* ---- то, что холодная половина берёт у главного цикла ---------------- */
|
||||||
|
|
||||||
|
extern uint8_t room_fg[30]; /* тайлы ОТРИСОВАННОЙ комнаты (fg & 0x1F) */
|
||||||
|
extern uint8_t cur_room; /* её номер (drawn_room) */
|
||||||
|
extern uint8_t pop_checkpoint; /* checkpoint (seg000:270) — ставит уход */
|
||||||
|
/* влево из комнаты 7 уровня 3 */
|
||||||
|
|
||||||
|
/* Вход в комнату БЕЗ перехода за Кидом (старт уровня, респавн, чит-обход):
|
||||||
|
* страж такой смены не «видит» и остаётся в своей комнате. */
|
||||||
|
void enter_room(uint8_t room);
|
||||||
|
|
||||||
|
/* ---- холодная половина ----------------------------------------------- */
|
||||||
|
|
||||||
|
/* start_level (seg003:0055) + do_startpos + set_start_pos: поставить Кида в
|
||||||
|
* стартовую позицию уровня. Один вход и для запуска, и для рестарта после
|
||||||
|
* смерти / выпадения из уровня — в оригинале это буквально один путь. */
|
||||||
|
void pop_start_level(void) __banked;
|
||||||
|
|
||||||
|
/* ОТЛАДОЧНЫЙ обход комнат по номеру ('+' / '−'). dir: 1 — следующая,
|
||||||
|
* 2 — предыдущая. Ставит Кида на подходящий пол новой комнаты и выводит
|
||||||
|
* его из боя. Возвращает 1, если комната сменилась (вызывающий обязан
|
||||||
|
* вернуть себе draw-страницу и обнулить счётчик кадров смерти). */
|
||||||
|
uint8_t pop_dbg_roomnav(uint8_t dir) __banked;
|
||||||
|
|
||||||
|
/* ОТЛАДОЧНЫЙ лейбл номера комнаты палочками в верхнем борте. Рисует ТОЛЬКО
|
||||||
|
* при смене комнаты (и по разу на каждую страницу дабл-буфера), поэтому
|
||||||
|
* звать можно каждый кадр. */
|
||||||
|
void pop_dbg_room_label(uint8_t room) __banked;
|
||||||
|
|
||||||
|
#endif /* ROOMTEST_COLD_H */
|
||||||
@@ -39,6 +39,11 @@ OBJS_grab := $(OBJS_phys)
|
|||||||
OBJS_wall := $(OBJS_phys)
|
OBJS_wall := $(OBJS_phys)
|
||||||
# t_char — физика над Char для РАЗНЫХ персонажей (Кид / страж / тень).
|
# t_char — физика над Char для РАЗНЫХ персонажей (Кид / страж / тень).
|
||||||
OBJS_char := $(OBJS_phys)
|
OBJS_char := $(OBJS_phys)
|
||||||
|
# t_mouse — спецсобытие уровня 8 (мышь). ЕДИНСТВЕННЫЙ набор, куда линкуется
|
||||||
|
# guards.c: здесь проверяется сам ИИ (autocontrol_mouse) вместе с рождением
|
||||||
|
# (pop_check_mouse) и общей физикой соперника — то есть ровно то, что в
|
||||||
|
# остальных наборах заменено ручным прогоном play_guard_frame.
|
||||||
|
OBJS_mouse := $(OBJS_phys) build/eng_guards.rel
|
||||||
|
|
||||||
include $(TESTKIT)/host-tests.mk
|
include $(TESTKIT)/host-tests.mk
|
||||||
|
|
||||||
|
|||||||
@@ -232,6 +232,14 @@ uint8_t pop_level_tile_raw(uint8_t room, uint8_t tilepos)
|
|||||||
return tk_room_fg[room][tilepos];
|
return tk_room_fg[room][tilepos];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* pop_level_tile (то же самое, но с маппингом окна W0 внутри) — её зовёт
|
||||||
|
* спецсобытие подъёма скелета из guards.c; тому единственному набору, где
|
||||||
|
* guards.c линкуется (t_mouse), достаточно чтения из tk_room_fg. */
|
||||||
|
uint8_t pop_level_tile(uint8_t room, uint8_t tilepos)
|
||||||
|
{
|
||||||
|
return pop_level_tile_raw(room, tilepos);
|
||||||
|
}
|
||||||
|
|
||||||
/* Запись в «данные уровня» — у теста это тот же tk_room_fg. Нужна с тех
|
/* Запись в «данные уровня» — у теста это тот же tk_room_fg. Нужна с тех
|
||||||
* пор, как do_pickup правит страницу уровня сразу (BUG-POTION-STRIPE). */
|
* пор, как do_pickup правит страницу уровня сразу (BUG-POTION-STRIPE). */
|
||||||
void pop_level_set_tile(uint8_t room, uint8_t tilepos, uint8_t tile)
|
void pop_level_set_tile(uint8_t room, uint8_t tilepos, uint8_t tile)
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* t_mouse — спецсобытие уровня 8: МЫШЬ (do_timers seg003:0545,
|
||||||
|
* do_mouse seg003:0ABA, autocontrol_mouse seg002:07EB).
|
||||||
|
*
|
||||||
|
* Единственный набор, куда линкуется guards.c: проверяется не физика (её
|
||||||
|
* держат t_phys/t_char), а связка «условие запуска → рождение → ИИ →
|
||||||
|
* нажатие кнопки → уход». Всё это ветки по charid внутри общего кода
|
||||||
|
* соперника, и сломать их можно, ничего не трогая в самой мыши.
|
||||||
|
*
|
||||||
|
* Кадр соперника здесь полный, с ИИ — pop_guard_tick() + pop_guard_phys_tick(),
|
||||||
|
* как в главном цикле; sc_guard_run для этого не годится (он намеренно без ИИ).
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "tcheck.h"
|
||||||
|
#include "pop_kid.h"
|
||||||
|
#include "pop_map.h"
|
||||||
|
#include "pop_guard.h"
|
||||||
|
#include "pop_geom.h"
|
||||||
|
#include "pop_state.h"
|
||||||
|
#include "pop_level.h" /* pop_current_level (под тестом — из stubs.c) */
|
||||||
|
#include "pop_tune.h"
|
||||||
|
#include "scene.h"
|
||||||
|
#include "stubs.h"
|
||||||
|
|
||||||
|
#define E 0 /* empty */
|
||||||
|
#define F 1 /* floor */
|
||||||
|
#define O 15 /* opener */
|
||||||
|
#define W 20 /* wall */
|
||||||
|
|
||||||
|
/* Верхний ряд комнаты 16 в упрощённом виде: сплошной пол и кнопка на
|
||||||
|
* колонке 7 — там же, где она стоит в данных уровня 8. Провал колонок
|
||||||
|
* 4..6 намеренно засыпан: он мешает Киду, а не мыши, и в тест на ИИ
|
||||||
|
* притащил бы падение соперника. */
|
||||||
|
static const uint8_t room_mouse[30] = {
|
||||||
|
F, F, F, F, F, F, F, O, F, F,
|
||||||
|
W, W, W, W, W, W, W, W, W, W,
|
||||||
|
W, W, W, W, W, W, W, W, W, W,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Поставить сцену уровня 8: Кид в комнате 16, дверь уровня открыта. */
|
||||||
|
static void scene_level8(void)
|
||||||
|
{
|
||||||
|
sc_room(room_mouse, POP_MOUSE_ROOM);
|
||||||
|
sc_kid_at(1, 0, 1);
|
||||||
|
Kid.room = POP_MOUSE_ROOM;
|
||||||
|
pop_guard_reset();
|
||||||
|
pop_current_level = POP_MOUSE_LEVEL;
|
||||||
|
pop_leveldoor_open = 1;
|
||||||
|
tk_log_clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Один полный кадр соперника, как в главном цикле roomtest.c. */
|
||||||
|
static void mouse_frame(void)
|
||||||
|
{
|
||||||
|
pop_guard_tick();
|
||||||
|
pop_guard_phys_tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- рождение -------------------------------------------------------- */
|
||||||
|
|
||||||
|
TC_TEST(mouse_appears_after_delay)
|
||||||
|
{
|
||||||
|
uint16_t i;
|
||||||
|
scene_level8();
|
||||||
|
|
||||||
|
/* Счётчик стартует с 1 («дверь открыта») и растёт по кадру, срабатывая
|
||||||
|
* на значении POP_MOUSE_DELAY — то есть кадров до мыши на два меньше.
|
||||||
|
* Прогоняем всё, кроме последнего: слот обязан быть ещё пуст. */
|
||||||
|
for (i = 0; i < POP_MOUSE_DELAY - 2; i++) pop_check_mouse();
|
||||||
|
TC_EQ(Guard.charid, 0);
|
||||||
|
|
||||||
|
pop_check_mouse(); /* тот самый кадр */
|
||||||
|
TC_EQ(Guard.charid, CHARID_24_MOUSE);
|
||||||
|
TC_EQ(Guard.x, POP_MOUSE_START_X);
|
||||||
|
TC_EQ(Guard.curr_row, 0);
|
||||||
|
TC_EQ(Guard.direction, -1); /* dir_FF_left */
|
||||||
|
TC_EQ(guardhp_curr, 1);
|
||||||
|
TC_EQ(Guard.room, POP_MOUSE_ROOM); /* унаследована от Кида (loadkid) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Счётчик тикает ТОЛЬКО пока Кид в комнате мыши: в оригинале это то же
|
||||||
|
* условие, что и запуск (Char.room == mouse_room), поэтому 12,5 секунды
|
||||||
|
* отсчитываются с возвращения игрока, а не с открытия двери. */
|
||||||
|
TC_TEST(mouse_timer_frozen_outside_room)
|
||||||
|
{
|
||||||
|
uint16_t i;
|
||||||
|
scene_level8();
|
||||||
|
Kid.room = POP_MOUSE_ROOM + 1;
|
||||||
|
for (i = 0; i < POP_MOUSE_DELAY * 2; i++) pop_check_mouse();
|
||||||
|
TC_EQ(Guard.charid, 0);
|
||||||
|
TC_EQ(pop_leveldoor_open, 1); /* не сдвинулся ни на кадр */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Закрытая дверь уровня — мыши нет вовсе (она подсказка на обратном пути). */
|
||||||
|
TC_TEST(mouse_needs_open_leveldoor)
|
||||||
|
{
|
||||||
|
uint16_t i;
|
||||||
|
scene_level8();
|
||||||
|
pop_leveldoor_open = 0;
|
||||||
|
for (i = 0; i < POP_MOUSE_DELAY * 2; i++) pop_check_mouse();
|
||||||
|
TC_EQ(Guard.charid, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- ИИ: пробежала, нажала, ушла ------------------------------------- */
|
||||||
|
|
||||||
|
/* Родить мышь, не гоняя POP_MOUSE_DELAY пустых кадров. */
|
||||||
|
static void spawn_mouse(void)
|
||||||
|
{
|
||||||
|
scene_level8();
|
||||||
|
pop_leveldoor_open = POP_MOUSE_DELAY - 1;
|
||||||
|
pop_check_mouse();
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(mouse_runs_left_and_presses_button)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
spawn_mouse();
|
||||||
|
TC_EQ(Guard.charid, CHARID_24_MOUSE);
|
||||||
|
|
||||||
|
/* Бежит влево: до порога разворота ей ~35 пикселей по 3-5 за кадр. */
|
||||||
|
for (i = 0; i < 24; i++) mouse_frame();
|
||||||
|
TC_TRUE(Guard.x < POP_MOUSE_START_X);
|
||||||
|
|
||||||
|
/* По дороге наступила на кнопку (0,7) — общий check_press физики
|
||||||
|
* соперника, тот же, которым кнопки жмут страж и тень. */
|
||||||
|
TC_TRUE(tk_log_count(TK_BUTTON) >= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(mouse_turns_back_and_disappears)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
spawn_mouse();
|
||||||
|
|
||||||
|
/* 120 кадров с запасом покрывают путь туда (200→166), стойку seq_107
|
||||||
|
* (11 кадров) и путь обратно (166→200). */
|
||||||
|
for (i = 0; i < 120; i++) {
|
||||||
|
mouse_frame();
|
||||||
|
if (Guard.direction == DIR_56_NONE) break;
|
||||||
|
}
|
||||||
|
TC_EQ(Guard.direction, DIR_56_NONE); /* clear_char: ушла со сцены */
|
||||||
|
TC_EQ(guardhp_curr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- ветки по charid у соседнего кода -------------------------------- */
|
||||||
|
|
||||||
|
/* Мышь Кида «не видит»: иначе он выхватит на неё меч (seg003:0965). */
|
||||||
|
TC_TEST(mouse_is_not_seen_by_kid)
|
||||||
|
{
|
||||||
|
spawn_mouse();
|
||||||
|
Guard.curr_row = Kid.curr_row; /* один ряд — условие луча */
|
||||||
|
pop_check_can_guard_see_kid();
|
||||||
|
TC_EQ(can_guard_see_kid, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* leave_guard мышь не записывает в данные уровня (seg002:02F5): вернувшись
|
||||||
|
* в комнату, игрок не должен встретить «стража» с её параметрами.
|
||||||
|
* Комнату берём из диапазона таблицы заглушки (TK_ROOMS = 8). */
|
||||||
|
TC_TEST(mouse_is_not_saved_as_room_guard)
|
||||||
|
{
|
||||||
|
spawn_mouse();
|
||||||
|
Guard.room = 1;
|
||||||
|
tk_gs_tile[0] = 0xEE; /* метка «никто сюда не писал» */
|
||||||
|
pop_guard_leave();
|
||||||
|
TC_EQ(tk_gs_tile[0], 0xEE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(void)
|
||||||
|
{
|
||||||
|
sc_init();
|
||||||
|
TC_RUN(mouse_appears_after_delay);
|
||||||
|
TC_RUN(mouse_timer_frozen_outside_room);
|
||||||
|
TC_RUN(mouse_needs_open_leveldoor);
|
||||||
|
TC_RUN(mouse_runs_left_and_presses_button);
|
||||||
|
TC_RUN(mouse_turns_back_and_disappears);
|
||||||
|
TC_RUN(mouse_is_not_seen_by_kid);
|
||||||
|
TC_RUN(mouse_is_not_saved_as_room_guard);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user