Смерть на кнопке ломает её насовсем (died_on_button, seg007:776)

Уровень 7 начинается падением; разбившись на кнопке открытия решётки
(комната 3, тайл 2,1), Кид в оригинале открывает решётку НАСОВСЕМ, а у нас
она закрывалась обратно.  Чинить пришлось три места.

1. pop_phys_tick выходил по pop_kid_dead, то есть check_press до трупа не
   доходил вовсе.  У оригинала play_kid_frame гейтится только Char.room
   != 0.  Кнопка получала одно нажатие — в кадре смерти, потому что флаг
   ставит land() уже внутри цепочки.  «Труп не шевелится» держит внутренний
   выход в kid_phys, он остался.

2. Портирован died_on_button: открывалка → пол + связь дёргается типом
   «щебень» (открыть насовсем), прочие кнопки → TILE_STUCK.  Ветки по
   Char.alive в check_press не было вовсе.

3. Рестарт уровня восстанавливал только foretable, а died_on_button
   оставляет таймер связи нажатым (trob кнопки умирает сразу — тайл уже
   пол).  Остаток переживал респавн, и кнопка рисовалась нажатой с первого
   кадра.  pop_level_reset_tiles теперь возвращает и LINKMAP.

Проверено в MAME по всему циклу: смерть → FF (открыта навсегда), респавн →
кнопка цела и не нажата, второе падение → снова ломается.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 15:27:36 +03:00
parent b49ba15666
commit 6feab5d2f2
4 changed files with 182 additions and 71 deletions
+64 -3
View File
@@ -58,6 +58,7 @@
#define TILE_TORCH_DEBRIS 30 /* факел + щебень (loose_land) */
#define TILE_SPIKE 2 /* пики */
#define TILE_CLOSER 6 /* кнопка-закрывалка (DROP) */
#define TILE_STUCK 5 /* заклиненная кнопка (died_on_button) */
#define TILE_POTION 10 /* зелье */
#define TILE_SWORD 22 /* меч на полу */
#define TILE_OPENER 15 /* кнопка-открывалка (RAISE, 0x0F) */
@@ -2144,6 +2145,47 @@ static void make_loose_fall(int pos, uint8_t modifier)
pop_loose_modif[pos] = modifier;
}
/* died_on_button (seg007:0776): на кнопке КТО-ТО УМЕР — эффект становится
* ПОСТОЯННЫМ, а сама кнопка с тайла снимается.
*
* Открывалка превращается в обычный пол с нулевым модификатором, а связь
* дёргается типом «щебень» (TILE_DEBRIS) — у trigger_gate это отдельная
* ветка «открыть насовсем»: недооткрытые ворота получают trob-тип 2, уже
* открытые — modif 0xFF (открыты навсегда). Любая другая кнопка становится
* заклиненной (TILE_STUCK): нажатой она и останется.
*
* ЗАЧЕМ ЭТО ВООБЩЕ НУЖНО. Труп не «держит» кнопку — он её ЛОМАЕТ. Разница
* видна сразу: держал бы — ворота закрылись бы, как только тело убрали или
* уровень перезапустили. У Кида перезапуск всё равно перечитывает тайлы, а
* вот когда на кнопке гибнет СТРАЖ, перезапуска нет и кнопка заклинена до
* конца уровня.
*
* Тайл пишем и в страницу уровня (переживёт выход из комнаты, как do_pickup),
* и в живую g_fg отрисованной комнаты — вместе с пометкой на перерисовку
* своей ячейки и правой (у кнопки и пола разные правые грани). */
static void died_on_button(uint8_t room, uint8_t tp, uint8_t code)
{
uint8_t *mod = pop_trob_modif(room);
uint8_t modifier = mod[tp]; /* индекс связи — ДО затирания */
uint8_t button_type = code;
uint8_t newtile;
if (code == TILE_OPENER) {
newtile = TILE_FLOOR;
mod[tp] = 0;
button_type = TILE_DEBRIS; /* trigger_gate: «открыть насовсем» */
} else {
newtile = TILE_STUCK;
}
pop_level_set_tile(room, tp, newtile);
if (room == g_room) {
g_fg[tp] = newtile;
pop_set_redraw(tp, POP_RD_FLOOR, 2); /* обе страницы */
if (tp % 10 < 9)
pop_set_redraw((uint8_t)(tp + 1), POP_RD_FLOOR, 2);
}
pop_trigger_button(room, tp, button_type, modifier);
}
/* check_press (seg006:0EC8, упрощ.): Kid стоит на loose → make_loose_fall;
* frame 79 (прыжок вверх) с loose СВЕРХУ → пробить сверху. */
static void check_press(void)
@@ -2191,8 +2233,14 @@ static void check_press(void)
* (curr_col = 1 или 10). Комнату и tilepos берём резолвнутые,
* иначе индекс связи читается из чужого тайла (см. tile_room_of). */
uint8_t btn_room = tile_room_of(Char.curr_col, Char.curr_row, &tp);
if ((t == TILE_OPENER || t == TILE_CLOSER) && btn_room)
pop_trigger_button(btn_room, tp, t, pop_trob_modif(btn_room)[tp]);
if ((t == TILE_OPENER || t == TILE_CLOSER) && btn_room) {
/* seg006:1707 — ЖИВОЙ жмёт кнопку на 5 кадров, МЁРТВЫЙ ломает её
* насовсем. `alive < 0` = жив (соглашение оригинала). */
if (Char.alive < 0)
pop_trigger_button(btn_room, tp, t, pop_trob_modif(btn_room)[tp]);
else
died_on_button(btn_room, tp, t);
}
else if (t == TILE_LOOSE) {
/* loose живёт в СВОЕЙ комнате (make_loose_fall работает с g_fg),
* поэтому здесь по-прежнему координаты персонажа. */
@@ -3008,7 +3056,20 @@ static void guard_phys(void)
* физика меняет персонажу всё — координаты, ряд/колонку, кадр, seq. */
void pop_phys_tick(void) __banked
{
if (pop_kid_dead) return; /* труп на пиках — без физики (только seq смерти) */
/* РАННЕГО ВЫХОДА ПО pop_kid_dead ЗДЕСЬ НЕТ — и это принципиально.
* У оригинала play_kid_frame (seg000:1231) гейт ровно один, `Char.room
* != 0`: вся цепочка крутится и на ТРУПЕ. Стоял `if (pop_kid_dead)
* return;` — дёшево и почти незаметно, но вместе с физикой он выключал
* check_press, а тот на мёртвом персонаже делает совсем не то, что на
* живом (died_on_button — кнопка ломается насовсем). Наблюдалось как
* «Кид разбился на кнопке открытия решётки, а решётка закрылась»
* (уровень 7, комната 3, тайл 2,1): кнопка получала РОВНО ОДНО нажатие —
* в кадре самой смерти, потому что pop_kid_dead ставит land() уже внутри
* цепочки. Разбор — BUGS_CLOSED.md#died-on-button.
*
* «Труп не шевелится» держится не этим выходом, а тем же, чем в
* оригинале: кадр смерти не двигается сам (dx/dy нулевые, sequence
* кончился), а уход из комнаты и сотрясение отсечены внутри kid_phys. */
pop_loadkid_and_opp();
kid_phys();
pop_savekid_and_opp();