Мышь уровня 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:
2026-08-12 15:21:20 +03:00
parent af04a8141e
commit 361c9d9f78
17 changed files with 803 additions and 250 deletions
+97
View File
@@ -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>