BUG-TORCH-CHOMP-2: застывший чомпер накрывался пламенем факела

Регрессия от BUG-TORCH-CHOMP-1 (пламя перевели на запекание в фон).
Аргумент «запекать безопасно, кадры пламени самонакрываются» верен для
пикселей самого факела, но не для чужой графики в той же ячейке: пламя
рисуется в клетке ПРАВОГО СОСЕДА (seg008:560), и челюсти чомпера возвращал
поверх огня только его собственный trob — пока анимация жива.

SDLPoP так не делает: animate_torch (seg007:0241) заканчивается вызовом
set_redraw_anim_right(), который метит правого соседа, а redraw_needed
(seg008:0178) рисует его слой строго в порядке draw_tile_anim_topright ->
draw_tile_anim_right (пламя) -> draw_tile_anim (СВОЯ графика тайла).  То
есть челюсти возвращаются поверх огня КАЖДЫЙ кадр факела, независимо от
собственной анимации чомпера.  У нас пламя рисуется напрямую, минуя
механизм пометок, — этой второй половины не было.

Фикс: после pop_torch_draw метим правого соседа POP_RD_CHOMP на ОДНУ
страницу (факел анимируется каждый кадр -> обе страницы получат свою
перерисовку по очереди).  Порядок сходится сам: блок факелов идёт до
pop_redraw_needed.  Код соседа читается в том же префетче кодов тайлов
(trob_rcode[]), чтобы не свапать W0 второй раз за кадр.  Банк 6 +104 Б.

Осознанное расхождение (оригинал метит соседа безусловно, мы — только под
чомпера) заведено открытым: TORCH-ANIM-RIGHT в bug_list.md.  Слой
draw_tile_anim рисует ещё пики/зелье/меч, но такого соседства на уровнях
1-4 не встретилось, а безусловная пометка стоит перерисовки тайла каждый
кадр на каждый факел.

tests-host: все 5 наборов прошли.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 21:16:58 +03:00
parent e261a35acb
commit 67a4c71138
3 changed files with 116 additions and 4 deletions
+42 -4
View File
@@ -42,6 +42,11 @@ typedef struct { uint8_t room, tilepos; int8_t type; } trob_t;
static trob_t trobs[TROBS_MAX];
static uint8_t trobs_count;
static uint8_t trob_code[TROBS_MAX]; /* префетч кодов тайлов на кадр (1 map W0) */
/* Код ПРАВОГО соседа — заполняется только у факелов (0 у остальных). Нужен
* потому, что пламя рисуется в ячейке соседа и запекается в фон: если у
* соседа своя anim-графика, её надо вернуть ПОВЕРХ огня. Читается в том же
* префетче, чтобы не свапать W0 второй раз за кадр. */
static uint8_t trob_rcode[TROBS_MAX];
/* Последняя НАРИСОВАННАЯ draw-сигнатура тайла (spike: modifier; button:
* признак «нажата» = таймер связи >1). Редрой пики/кнопки — ТОЛЬКО при её
@@ -499,8 +504,15 @@ void pop_process_trobs(uint8_t cur_room) __banked
{
uint8_t k;
pop_level_access_begin();
for (k = 0; k < trobs_count; k++)
trob_code[k] = pop_level_tile_raw(trobs[k].room, trobs[k].tilepos);
for (k = 0; k < trobs_count; k++) {
uint8_t tp = trobs[k].tilepos;
uint8_t c = pop_level_tile_raw(trobs[k].room, tp);
trob_code[k] = c;
trob_rcode[k] = 0;
if ((c == TILE_TORCH || c == TILE_TORCH_DEBRIS) && TP_COL[tp] < 9)
trob_rcode[k] = (uint8_t)(pop_level_tile_raw(trobs[k].room,
(uint8_t)(tp + 1)) & 0x1F);
}
pop_level_access_end();
}
@@ -570,8 +582,34 @@ void pop_process_trobs(uint8_t cur_room) __banked
* __divuchar + __moduchar — на два факела это четыре вызова за
* кадр (замер MAME). Тайл всегда 0..29. */
uint8_t trow = TP_ROW[tp], tcol = TP_COL[tp];
if (code == TILE_POTION) pop_potion_draw(trow, tcol, mod[tp]);
else pop_torch_draw(trow, tcol, mod[tp]);
if (code == TILE_POTION) {
pop_potion_draw(trow, tcol, mod[tp]);
} else {
pop_torch_draw(trow, tcol, mod[tp]);
/* Порт set_redraw_anim_right (seg007:0101): animate_torch метит
* ПРАВОГО СОСЕДА, и redraw_needed рисует его слой anim в
* порядке draw_tile_anim_topright -> draw_tile_anim_right
* (пламя) -> draw_tile_anim (СВОЯ графика тайла). То есть в
* оригинале челюсти чомпера возвращаются поверх огня КАЖДЫЙ
* кадр факела, а не только пока жива их собственная анимация.
*
* У нас пламя запекается напрямую (pop_torch_draw), минуя
* механизм пометок, поэтому у ЗАСТЫВШЕГО чомпера возвращать
* его было нечем — огонь накрывал челюсти (баг найден на
* уровне 4). Метим сосед-тайл сами, ОДНУ страницу: факел
* анимируется каждый кадр, значит обе страницы получат свою
* перерисовку по очереди. Порядок сходится: этот блок идёт
* ДО pop_redraw_needed.
*
* РАСХОЖДЕНИЕ С ОРИГИНАЛОМ (осознанное): он метит соседа
* безусловно, мы — только когда там чомпер. Безусловная
* пометка = перерисовка тайла каждый кадр на каждый факел,
* а слой anim рисует ещё пики/зелье/меч (seg008:644). Их
* случай «застыл справа от факела» в bug_list как открытый:
* пики и меч рядом с факелом на уровнях 1-4 не встретились. */
if (trob_rcode[i] == TILE_CHOMP)
pop_set_redraw((uint8_t)(tp + 1), POP_RD_CHOMP, 1);
}
}
if (room == cur_room && code == TILE_SPIKE) {
/* ПИКИ — редрой КАЖДЫЙ кадр, пока trob жив: animate_spike