Закрыты 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**
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
| [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | открыт |
|
||||
| ~~T-2~~ | Кид перерисовывается в покое | оптимизация | **ЗАКРЫТ 2026-08-08** — [DRAW-COST шаг 1](TASKS_OPEN.md#draw-cost) |
|
||||
| ~~BUG-GATE-PASS-1~~ | проход сквозь закрытую решётку шва | Major | **ЗАКРЫТ 2026-08-09** — [bug_closed.md](bug_closed.md#bug-gate-pass-1), смоук уровня 1 пройден |
|
||||
| ~~BUG-GUARD-IX-1~~ | «зависание» в бою со стражем: затёрт IX главного цикла | Blocker | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-guard-ix-1) |
|
||||
| ~~BUG-GATE-SEAM-ROW1~~ | решётка в шве не анимируется, если ворота не в ряду 0 | Major | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-gate-seam-row1) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ void pop_potion_draw(int row, int col, uint8_t modif) __banked;
|
||||
/* Ворота на ЛЕВОМ шве (col0) — перерисовать по живому modif соседа слева.
|
||||
* g_lbg (lcol_bg) должен быть обновлён вызывающим. Звать из основного цикла
|
||||
* на back-странице, когда сосед слева анимируется. См. pop_bg.c. */
|
||||
void pop_room_redraw_seam_left(void) __banked;
|
||||
void pop_room_redraw_seam_left(uint8_t rows) __banked; /* rows = маска рядов 0..2 */
|
||||
|
||||
/* Перерисовать ПОЛ тайла (row,col) + правую грань в соседе на back-странице
|
||||
* (wipe по площади пола, БЕЗ стены ряда ниже). Для смены тайла на месте:
|
||||
|
||||
@@ -1636,8 +1636,23 @@ static void coll_row_bases(int8_t row)
|
||||
}
|
||||
}
|
||||
|
||||
/* Одиночный ряд во ВНЕШНИЙ массив (единственный вызывающий —
|
||||
* check_chomped_guard).
|
||||
*
|
||||
* coll_scan_prepare() тут ОБЯЗАТЕЛЕН, и это не перестраховка: coll_row
|
||||
* пишет по `flags + scan_off`, а число байт берёт из win_lo/win_hi. Без
|
||||
* prepare scan_off/scan_left0 остаются от ПРОШЛОГО окна (его считает
|
||||
* check_collisions для Кида), а окно тут уже своё, стражево. Когда
|
||||
* стражево окно шире или правее кидова, запись уезжает за границу
|
||||
* flags[COLL_N] — а сразу за ней в кадре check_chomped_guard лежит
|
||||
* СОХРАНЁННЫЙ IX. Затёртый младший байт превращал кадровый указатель
|
||||
* main (0xBFFA) в 0xBF00, и дальше main читал свои локали из чужого
|
||||
* стекового мусора: игра «зависала», потому что отладочный `frozen`
|
||||
* становился ненулевым (BUG-GUARD-IX-1). Заодно чинится и сам расчёт:
|
||||
* scan_left0 задаёт x колонок, и страж считался по координатам Кида. */
|
||||
static void get_row_collision_data(int8_t row, uint8_t *flags)
|
||||
{
|
||||
coll_scan_prepare();
|
||||
coll_row_bases(row);
|
||||
coll_row(flags);
|
||||
}
|
||||
|
||||
@@ -516,19 +516,28 @@ void pop_chomp_redraw(int row, int col) __banked
|
||||
* room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake):
|
||||
* heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары
|
||||
* оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */
|
||||
void pop_room_redraw_seam_left(void) __banked
|
||||
void pop_room_redraw_seam_left(uint8_t rows) __banked
|
||||
{
|
||||
pop_cd_touch(0, POP_YOFF + 3, 32, 62); /* bar мимо pop_blit_b (DRAW-COST) */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
/* Полосу кладки У ПОТОЛКА (ряд −1) НЕ трогаем: её низ на room-space y=2,
|
||||
* а бары ворот начинаются с y=3 (gate_top_y = dby−62). Раньше bar шёл от
|
||||
* POP_YOFF и стирал потолок, из-за чего приходилось перерисовывать ещё и
|
||||
* draw_tile(-1,0) — вдвое дороже на КАЖДОМ кадре анимации решётки. */
|
||||
bar(0, POP_YOFF + 3, 31, POP_YOFF + 64); /* col0, ряд 0 (бары ворот) */
|
||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||
draw_tile(0, 0); /* фон col0 + бары ворот (живой modif) */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
int8_t r;
|
||||
/* rows — битовая маска рядов 0..2: перерисовываем ТОЛЬКО те, где openness
|
||||
* реально изменился. Раньше ряд был жёстко нулевым, потому что на уровне 1
|
||||
* решётка соседа стояла в (0,9); на уровне 4 она в (1,9), и открытые ворота
|
||||
* оставались нарисованными закрытыми (BUG-GATE-SEAM-ROW1). */
|
||||
for (r = 0; r < 3; r++) {
|
||||
int yt = POP_YOFF + 63 * r;
|
||||
if (!(rows & (1 << r))) continue;
|
||||
pop_cd_touch(0, yt + 3, 32, 62); /* bar мимо pop_blit_b (DRAW-COST) */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
/* Полосу кладки НАД рядом НЕ трогаем: её низ на room-space 63*r+2, а
|
||||
* бары ворот начинаются с 63*r+3 (gate_top_y = dby−62). Раньше bar шёл
|
||||
* от POP_YOFF и стирал потолок, из-за чего приходилось перерисовывать
|
||||
* ещё и draw_tile(-1,0) — вдвое дороже на КАЖДОМ кадре анимации. */
|
||||
bar(0, yt + 3, 31, yt + 64); /* col0 этого ряда (бары ворот) */
|
||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||
draw_tile(r, 0); /* фон col0 + бары ворот (живой modif) */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Плита-потолок (loose ряда 2 комнаты СВЕРХУ) ------------------- *
|
||||
|
||||
@@ -228,8 +228,16 @@ static uint8_t guard_over_kid(void)
|
||||
* draw_tile(0,0) в pop_room_redraw_seam_left зовём ТОЛЬКО когда openness гейта
|
||||
* реально изменился. Пока Кид держит кнопку и гейт открыт — modifier стабилен
|
||||
* → 0 редроёв (раньше шов рисовался безусловно КАЖДЫЙ кадр, пока сосед активен
|
||||
* — тот же класс, что был у кнопки). seam_redraw=2 = обе страницы дабл-буфера. */
|
||||
static uint8_t seam_sig; /* последняя нарисованная openness гейта в шве */
|
||||
* — тот же класс, что был у кнопки). seam_redraw=2 = обе страницы дабл-буфера.
|
||||
*
|
||||
* Сигнатура — ВСЕ ТРИ ряда колонки 9 соседа, а не только ряд 0: ворота в шве
|
||||
* бывают в любом ряду. На уровне 1 решётка соседа стояла в (0,9), и одного
|
||||
* байта хватало; на уровне 4 плита комнаты 1 открывает ворота (1,9) комнаты 8
|
||||
* — изменение приходило в m[19], сигнатура его не видела, редрой не
|
||||
* заказывался, и открытые ворота оставались нарисованными закрытыми
|
||||
* (BUG-GATE-SEAM-ROW1). */
|
||||
static uint8_t seam_sig[3]; /* последняя нарисованная openness гейта в шве */
|
||||
static uint8_t seam_rows; /* маска рядов, ждущих редроя (переживает 2 кадра) */
|
||||
static uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */
|
||||
|
||||
/* Отладочный лейбл номера комнаты — рисуем только при смене (outtextxy тяжёлая):
|
||||
@@ -331,11 +339,12 @@ static void enter_room_side(uint8_t room, uint8_t side)
|
||||
uint8_t lft = pop_room_link(room, 0), rgt = pop_room_link(room, 1);
|
||||
if (lft) { uint8_t *m = pop_trob_modif(lft);
|
||||
lcol_bg[0] = m[9]; lcol_bg[1] = m[19]; lcol_bg[2] = m[29];
|
||||
seam_sig = m[9]; } /* шов рисуется в обе страницы ниже — синхр. сигнатуру */
|
||||
/* шов рисуется в обе страницы ниже — синхр. сигнатуру */
|
||||
seam_sig[0] = m[9]; seam_sig[1] = m[19]; seam_sig[2] = m[29]; }
|
||||
if (rgt) { uint8_t *m = pop_trob_modif(rgt);
|
||||
rcol_bg[0] = m[0]; rcol_bg[1] = m[10]; rcol_bg[2] = m[20]; }
|
||||
}
|
||||
seam_redraw = 0; /* шов свежий на обеих страницах — редрой не нужен */
|
||||
seam_redraw = 0; seam_rows = 0; /* шов свежий на обеих страницах — редрой не нужен */
|
||||
for (pg = 0; pg < 2; pg++) {
|
||||
gfx_set_draw_page(pg);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
@@ -733,15 +742,30 @@ int main(void)
|
||||
uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */
|
||||
if (lft) {
|
||||
uint8_t *m = pop_trob_modif(lft);
|
||||
uint8_t g = m[9], prev = seam_sig;
|
||||
seam_sig = g; /* СНАЧАЛА сохранить (обход SDCC z80 sub-store */
|
||||
uint8_t i, changed = 0;
|
||||
/* Все ТРИ ряда колонки 9: ворота в шве бывают в любом из них
|
||||
* (BUG-GATE-SEAM-ROW1, см. объявление seam_sig). */
|
||||
for (i = 0; i < 3; i++) {
|
||||
uint8_t g = m[9 + i * 10], prev = seam_sig[i];
|
||||
seam_sig[i] = g; /* СНАЧАЛА сохранить (обход SDCC z80 sub-store */
|
||||
/* бага: `if(g!=s)s=g` эмитит store РАЗНОСТИ g-s, */
|
||||
/* не g — sdcc_z80_cmp_store_a_bug) */
|
||||
if (g != prev) seam_redraw = 2; /* openness изменился → обе страницы дабл-буфера */
|
||||
if (g != prev) changed |= (uint8_t)(1 << i);
|
||||
}
|
||||
if (changed) { /* openness изменился → обе страницы дабл-буфера */
|
||||
/* Плюс ряд ВЫШЕ: верх решётки (draw_tile_anim_topright,
|
||||
* seg008:0568 — маска 68 + кадр DOOR_FRAM_TOP) рисует не
|
||||
* сам тайл ворот, а тайл над-справа от него, то есть у
|
||||
* нас (r−1, 0). Он тоже анимируется вместе с openness;
|
||||
* без этого чёрный треугольник над воротами оставался
|
||||
* статичным, пока бары ездили (BUG-GATE-SEAM-ROW1). */
|
||||
seam_rows |= (uint8_t)(changed | (changed >> 1));
|
||||
seam_redraw = 2;
|
||||
}
|
||||
if (seam_redraw) {
|
||||
lcol_bg[0] = m[9]; lcol_bg[1] = m[19]; lcol_bg[2] = m[29];
|
||||
pop_room_redraw_seam_left();
|
||||
seam_redraw--;
|
||||
pop_room_redraw_seam_left(seam_rows);
|
||||
if (--seam_redraw == 0) seam_rows = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user