Закрыты BUG-GUARD-IX-1 и BUG-GATE-SEAM-ROW1 (уровень 4)
BUG-GUARD-IX-1 — «зависание» в бою со стражем. Не зависание: главный цикл крутился, а отладочная локаль frozen сама становилась ненулевой. Корень — у main затирался IX (0xBFFA -> 0xBF00), и все его локали адресовали живой стековый мусор. Затирал check_chomped_guard: coll_row() пишет по flags + scan_off, длину берёт из win_lo/win_hi, а scan_off выставляла только coll_scan_prepare() из пути Кида. Ряд стража писался по смещению Кида длиной стража и при Киде у правого края комнаты вылезал за flags[13] — прямо в сохранённый IX. Фикс: coll_scan_prepare() в начале get_row_collision_data(); заодно чинится расчёт (scan_left0 задаёт x колонок, чомпер-коллизия стража считалась по координатам Кида). На уровне 1 не проявлялось: бой идёт левее середины, запись оставалась внутри массива — данные были неверны молча. BUG-GATE-SEAM-ROW1 — решётка в шве не анимировалась. Плита комнаты 1 открывает ворота комнаты 8 в (1,9), видимые через левый шов, а change-driven редрой смотрел только m[9] (ряд 0) и перерисовывал жёстко draw_tile(0,0). На уровне 1 та же связка работала лишь потому, что решётка соседа стояла в (0,9). Фикс: сигнатура по всем трём рядам, маска изменившихся рядов в seam_rows (переживает оба кадра дабл-буфера), pop_room_redraw_seam_left(rows) перерисовывает только помеченные. Плюс ряд выше (changed | changed>>1): верх решётки (draw_tile_anim_topright, seg008:0568) рисует тайл над-справа от ворот, без этого чёрный треугольник над ними оставался статичным. Проверено пользователем в MAME (бой в комнате 18; анимация решётки в стартовой комнате), детекторы IX висели без починки и не сработали; make -C tests-host — все 5 наборов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,106 @@
|
||||
|
||||
---
|
||||
|
||||
<a id="bug-guard-ix-1"></a>
|
||||
## BUG-GUARD-IX-1. «Зависание» при бое со стражем — затёртый IX главного цикла — **ЗАКРЫТ 2026-08-10**
|
||||
|
||||
**Симптом (пользователь, уровень 4).** Дважды подряд при ручном входе в
|
||||
комнату 18 со стражем игра «повисала»: картинка стоит, управление не
|
||||
отвечает. Нажатие «2» ненадолго возвращало движение, потом всё повторялось.
|
||||
|
||||
**Диагноз — НЕ зависание.** Главный цикл всё это время крутился (маркер
|
||||
верха цикла тикал раз в кадр). Стоял отладочный стоп-кадр: локаль `frozen`
|
||||
в `main` сама собой становилась ненулевой.
|
||||
|
||||
**Корень — однобайтовый выход за границу локального массива.** У `main` все
|
||||
локали лежат по `IX-1..IX-19`, и IX затирался: вместо `0xBFFA` в нём
|
||||
оказывался `0xBF00`. Признак железный — в теле цикла обязано выполняться
|
||||
`SP == IX-19`, а по факту было `SP=0xBFE7, IX=0xBF00`. Дальше `main` читал
|
||||
`frozen`, `dbuf`, `front/back`, `dead_frames` и edge-флаги читов из живого
|
||||
стекового мусора.
|
||||
|
||||
Виновник — `check_chomped_guard` (pop_map.c):
|
||||
|
||||
```c
|
||||
uint8_t flags[COLL_N]; /* 14 байт в кадре функции */
|
||||
calc_coll_window(); /* окно СТРАЖА -> win_lo/win_hi */
|
||||
get_row_collision_data(Char.curr_row, flags);
|
||||
```
|
||||
|
||||
`coll_row()` пишет по `flags + scan_off`, а число байт берёт из
|
||||
`win_lo/win_hi`. `scan_off`/`scan_left0` выставляет `coll_scan_prepare()`,
|
||||
которую звал ТОЛЬКО `check_collisions` — путь Кида. То есть ряд стража
|
||||
писался **по смещению Кида, длиной стража**. Диапазон записи
|
||||
`[kid_win_lo+2 … kid_win_lo+2 + ширина_окна_стража − 1]`; при обычной ширине
|
||||
4 он уезжает за `flags[13]`, когда `kid_win_lo > 8` — то есть **когда Кид
|
||||
стоит у правого края комнаты** (взаимное расположение Кида и стража ни при
|
||||
чём, стража даёт только длину). Сразу за массивом лежит сохранённый IX;
|
||||
затёртый младший байт и превращал `0xBFFA` в `0xBF00`.
|
||||
|
||||
Перелёт в отрицательные индексы был невозможен: `scan_off = kid_win_lo + 2`,
|
||||
а `kid_win_lo >= COLL_C0 = −2` из-за клампа в `calc_coll_window`.
|
||||
|
||||
**Почему не ловилось раньше.** На уровне 1 (комната 3) бой идёт левее
|
||||
середины — запись оставалась внутри массива. Ничего не портилось, но флаги
|
||||
стража всё равно читались по чужим смещениям и с иксами Кида: тихо неверные
|
||||
данные, без последствий (чомперов там нет).
|
||||
|
||||
**Фикс.** `coll_scan_prepare()` в начале `get_row_collision_data()` — теперь
|
||||
запись всегда ложится в `[win_lo+2 … win_hi+2]`, а `calc_coll_window` клампит
|
||||
окно в `[−2 … 11]`, то есть индексы гарантированно `0…13`. Заодно чинится
|
||||
сам расчёт: `scan_left0` задаёт x колонок, и чомпер-коллизия стража считалась
|
||||
по координатам Кида.
|
||||
|
||||
**Как ловилось (приём на будущее).** Детекторы `ix != 0xBFFA` на границах
|
||||
фаз `PROF()` с починкой IX в верху цикла: игра остаётся живой, а первый
|
||||
сработавший детектор называет фазу. Дальше — инструкционная трасса MAME
|
||||
(`trace file,0`), включаемая на подозрительном вызове и выключаемая сразу
|
||||
после, со стопом только на плохом кадре: в файле остаётся ровно тот вызов,
|
||||
где IX испортился. В хвосте трассы видно `ld sp,ix / pop ix`, достающий
|
||||
`BF00` вместо `BFFA`. См. memory `z80_profiling_method`.
|
||||
|
||||
**Проверка.** Бой со стражем в комнате 18 уровня 4 (пользователь) —
|
||||
детекторы IX висели без починки и не сработали ни разу; `make -C tests-host`
|
||||
— все 5 наборов.
|
||||
|
||||
---
|
||||
|
||||
<a id="bug-gate-seam-row1"></a>
|
||||
## BUG-GATE-SEAM-ROW1. Решётка в шве не анимируется, если ворота НЕ в ряду 0 — **ЗАКРЫТ 2026-08-10**
|
||||
|
||||
**Симптом (пользователь, уровень 4, стартовая комната).** Плита нажимается,
|
||||
ворота физически открываются (Кид проходит), но визуально с решёткой ничего
|
||||
не происходит. Отдельно: чёрный треугольник над воротами (верх решётки под
|
||||
ковром) статичен, тогда как в оригинале он ездит вместе с барами.
|
||||
|
||||
**Корень.** Зонд на `pop_add_trob` показал `room=8 tp=19 type=1`: плита
|
||||
комнаты 1 открывает ворота **комнаты 8 в (1,9)** — соседней комнаты, видимые
|
||||
через ЛЕВЫЙ шов (бары ворот в col9 рисует col0 соседа справа). Наш
|
||||
change-driven редрой шва смотрел только один байт `m[9]` (ряд 0), а
|
||||
`pop_room_redraw_seam_left()` перерисовывал жёстко `draw_tile(0, 0)`.
|
||||
Изменение приходило в `m[19]` — сигнатура его не видела.
|
||||
|
||||
На уровне 1 та же связка комнат 6/8 работала только потому, что решётка
|
||||
соседа стояла в **(0,9)**. Тайлсет ни при чём: в подземелье просто не
|
||||
попадалось ворот в ряду 1 у шва (и треугольника не было — над решётками
|
||||
всегда потолок).
|
||||
|
||||
**Фикс.**
|
||||
1. `seam_sig` — массив на три ряда, сравниваются все три; накопленная маска
|
||||
изменившихся рядов живёт в `seam_rows` и переживает оба кадра
|
||||
дабл-буфера.
|
||||
2. `pop_room_redraw_seam_left(uint8_t rows)` берёт маску и перерисовывает
|
||||
только помеченные ряды — не все три, чтобы не платить каждый кадр
|
||||
анимации.
|
||||
3. В маску добавляется ряд ВЫШЕ (`changed | (changed >> 1)`): верх решётки
|
||||
(`draw_tile_anim_topright`, seg008:0568 — маска 68 + кадр
|
||||
`DOOR_FRAM_TOP`) рисует не сам тайл ворот, а тайл над-справа от него, у
|
||||
нас `(r−1, 0)`. Без этого бары ездили, а треугольник над ними стоял.
|
||||
|
||||
**Проверка.** Уровень 4, стартовая комната: нажатие плиты — решётка шва
|
||||
поднимается/опускается вместе с верхом (пользователь).
|
||||
|
||||
|
||||
<a id="bug-guard-color-1"></a>
|
||||
## BUG-GUARD-COLOR-1. Страж и его полоса HP — всегда одного цвета — **ЗАКРЫТ 2026-08-07**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user