Смерть на кнопке ломает её насовсем (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
+98
View File
@@ -2987,3 +2987,101 @@ mid-оверлей) переведены на него. Заодно `pop_chomp
запечка» обязана спрашивать САМА функция «кадр по модификатору», а не
вызывающий слой: слоёв отрисовки у тайла четыре, и забыть один слишком легко.
Для новой анимированной ловушки это теперь обязательный пункт.
---
<a id="died-on-button"></a>
## DIED-ON-BUTTON. Смерть на кнопке не ломала её: решётка закрывалась обратно
**Наблюдение (пользователь, 2026-08-18).** Уровень 7 начинается падением, и
если не зацепиться за кромку, Кид разбивается на `(2,1)` комнаты 3 — а там
кнопка открытия решётки. В оригинале решётка после этого открыта НАСОВСЕМ,
у нас отжималась.
Баг был заведён ещё при закрытии
[BUG-LOOSE-BUTTON-1](#bug-loose-button-1) как «`died_on_button` (seg007:776)
не портирован», но оказался ДВОЙНЫМ — до недостающей функции ещё и не было
пути.
### Половина 1: физика трупа выключена целиком
```c
void pop_phys_tick(void) __banked
{
if (pop_kid_dead) return; /* <- вся цепочка, включая check_press */
```
У оригинала `play_kid_frame` (seg000:1231) гейт ровно один — `Char.room != 0`;
весь список крутится и на трупе (по «жив/мёртв» разведены `check_spiked`/
`check_chomped_kid`, и то через `resurrect_time`). Ранний выход выглядел
безобидной экономией — «труп не шевелится», — но `check_press` на мёртвом
персонаже делает СОВСЕМ ДРУГОЕ, чем на живом, и именно это выключалось.
Отсюда наблюдаемое: кнопка получала РОВНО ОДНО нажатие — в кадре самой
смерти, потому что `pop_kid_dead` ставит `land()` уже ВНУТРИ цепочки, после
раннего возврата. Решётка приоткрывалась и закрывалась.
Снято в MAME до фикса: Кид `frame 185, action 5 (bumped), alive 0`, тайл
`(2,1)` = `0x0F` opener с модификатором `0x12`, openness решётки ползёт
вниз 146 → 112. Кадр смерти при этом проходит все проверки `check_press`
штатно (`action == ACT_BUMPED`, флаги кадра 185 = `0x49`, бит
`FRAME_NEEDS_FLOOR` есть) — то есть путь был закрыт ровно одним `return`.
«Труп не шевелится» держится и без него, тем же, чем в оригинале: кадр
смерти не двигается сам, а уход из комнаты и сотрясение отсечены ВНУТРИ
`kid_phys` (там свой `if (pop_kid_dead) return;` перед `pop_check_knock`).
### Половина 2: сам `died_on_button`
```c
if (Char.alive < 0) pop_trigger_button(...); /* жив — нажатие на 5 кадров */
else died_on_button(...); /* мёртв — кнопка СЛОМАНА */
```
Открывалка превращается в обычный пол с нулевым модификатором, а связь
дёргается типом «щебень» (`TILE_DEBRIS`) — у `trigger_gate` это отдельная
ветка «открыть насовсем». Любая другая кнопка становится заклиненной
(`TILE_STUCK` = 5). Тайл пишем и в страницу уровня (переживает выход из
комнаты), и в живую `g_fg`, с пометкой на перерисовку своей ячейки и правой.
Инфраструктура была готова давно: `TILE_DEBRIS` и ветка «насовсем» в
`trigger_gate` написаны ещё тогда, с комментарием-ссылкой именно на
`died_on_button` — из-за чего механизм и казался реализованным. Опасение
старой записи, что нужен «тайл заклиненной кнопки в атласе, а его там нет»,
не подтвердилось: `pop_tile_table[5]` (stuck floor) заполнен и уже
рисуется — на него подставляется НАЖАТАЯ кнопка-закрывалка.
### Половина 3 (вылезла сразу): таймер связи переживал рестарт уровня
Пользователь поймал на первом же прогоне: **до первого падения кнопка
рисуется правильно, после — выглядит нажатой с самого начала уровня**, хотя
логически не нажата.
`died_on_button` ставит таймер связи в 5 и заводит trob кнопки — но тайл к
этому моменту УЖЕ пол, и trob умирает в первом же обходе
(`default: type = -1`), ни разу не уменьшив таймер. У оригинала ровно то же
самое, только там `load_level()` перечитывает файл ЦЕЛИКОМ, а наш
`pop_level_reset_tiles` восстанавливал одну foretable. Остаток `dl2[18] = 5`
переживал респавн, а `pop_tile_code_drawn` рисует кнопку нажатой, пока
таймер > 1.
Фикс — восстанавливать в рестарте и LINKMAP. Отдельная эталонная копия не
нужна: в странице уровня LINKMAP остаётся нетронутой, игра правит только
ОЗУ-копию `pop_dl2`.
**Проверка в MAME (полный цикл).**
| момент | тайл `(2,1)` | `dl2[18]` | `pop_kid_dead` | решётка `(2,6)` |
|--------|--------------|-----------|----------------|-----------------|
| труп на кнопке | `01` floor | 5 | 1 | `FF` открыта навсегда |
| после респавна | `0F` opener | **`00`** | 0 | `00` закрыта |
| второе падение | `01` floor | 5 | 1 | открывается заново |
Плюс попиксельная сверка кадров: решётка, до фикса стоявшая опущенной,
поднята. Цена: резидент без изменений, банк 3 +193 Б, банк 8 +22 Б.
**Осталось невыясненным.** На СТАРОМ поведении openness решётки замирал на
112 и дальше не убывал, хотя `animate_door` (совпадает с seg007:0522) обязан
досчитать до 0 и снять trob. С фиксом сцена так не воспроизводится
(решётка уходит в `FF`), проверить нечем; если всплывёт где-то ещё — это
отдельный баг, к `died_on_button` отношения не имеющий.