Закрыты BUG-GRAB-1 и BUG-GATEMOD-1; BUG-SPIKE-1 — в низкоприоритетные

Оба фикса подтверждены игрой, записи с разбором корней переехали в
bug_closed.md.  BUG-GATE-PASS-1 остался ждать сценария, но его оговорка
про BUG-GATEMOD-1 обновлена (тот закрыт).  BUG-SPIKE-1 понижен до низкого
приоритета: маловоспроизводим, смертельность пик подтверждена замером.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Александр Петров
2026-08-05 22:50:26 +03:00
parent 3078886306
commit c6cadd0140
2 changed files with 149 additions and 144 deletions
+146
View File
@@ -11,6 +11,152 @@
--- ---
## BUG-GRAB-1. Прыжок с места через провал в 3 тайла: зацепа нет — **ЗАКРЫТ 2026-08-05**
**Проверено в игре (пользователь):** «зацеп работает». Физика была верна с
самого начала, чинить пришлось клавиатуру — [BUG-KBD-5](#bug-kbd-5).
> **Итог 2026-08-05.** Физика тут ни при чём — виновата клавиатура.
> Зажатый Shift снимался автоповтором зажатой стрелки, поэтому к кадрам
> 102…106 (окно зацепа) движок видел Shift отпущенным. Полный разбор и
> фикс — [BUG-KBD-5](bug_closed.md#bug-kbd-5); поведение Shift в MAME
> проверено замером карты `_kbdraw_down`. Осталось подтвердить сам зацеп
> живой игрой; версии 2 и 3 ниже проверять только если он всё ещё не выйдет.
**Симптом.** Уровень 2, комната 9. Перепрыгнув на (1,1), Кид должен
вернуться обратно: разбегаться негде, поэтому он встаёт на самый край
плиты, прыгает с места и **цепляется руками за (1,5)**, после чего
подтягивается. У нас Кид с зажатым Shift всё равно срывается.
**Что уже точно известно (и не надо перепроверять).**
1. **Физика прыжка у нас совпадает с оригиналом кадр в кадр.** Сверено по
логу SDLPoP против трассы харнесса при одинаковом старте `x=95`:
```
кадр 16 18 22 23 24 25 102 103 104 105
SDLPoP 95 97 105 112 121 126 128 130 131 133
наш 95 97 105 112 121 126 128 130 131 133
```
Совпадает и по `y`, и по колонке/ряду, и по приземлению на 107–108.
2. **В оригинале зацеп срабатывает на кадре 106, а не 102..105.**
`check_grab` зовётся из ДВУХ мест: ветка «в воздухе» в `check_action`
(кадры 102..105) и `do_fall` (seg005) для `actions_4_in_freefall`.
Успешная попытка из лога:
```
GRAB try f=106 x=135 y=166 col=4 row=2 fall_y=18
GRAB probe x=127 col=4 through=0 front_above=3 modif=0
GRAB can_grab=1
GRAB OK dist=9
-> f=91 x=136 y=181 col=5 row=2 act=2 (повис)
```
Наш `do_fall` (`pop_map.c`) `check_grab()` из этой ветки тоже зовёт —
то есть структура на месте, расходится что-то внутри.
3. **По харнессу зацеп у нас РАБОТАЕТ**: окно стартовых `x = 91…95`, и
короткий шаг ставит Кида ровно туда (91 после первого нажатия, 95 после
второго). Зафиксировано тестом `t_grab`.
**Отсюда главный вопрос был: почему харнесс говорит «работает», а живая
машина — «нет».** Расхождение между ними и оказалось уликой; версии
выдвигались по убыванию правдоподобия, и сработала первая:
- **Shift не доезжает до движка — ПОДТВЕРЖДЕНО, это и была причина.**
Харнесс подменяет клавиатуру и потому этот путь не проверяет вовсе, а у
нас есть история проблем ровно с «Shift + стрелки» (KBD-1, BUG-KBD-3/4).
Замер в MAME: при зажатом Shift и зажатой стрелке бит `LSh` в
`_kbdraw_down` стоял в нуле. Разбор — [BUG-KBD-5](bug_closed.md#bug-kbd-5).
- **Сцена харнесса не равна комнате 9.** Там изолированная комната
(соседи — стена), а в игре слева комната 8; кромки шва участвуют в
`get_tile`. Проверять чтением `Kid.x` в момент прыжка: попал ли он в
окно 91…95 вообще.
- **Расхождение в `check_grab`.** Наш вариант зовёт `determine_col()`
там, где оригинал зовёт `load_fram_det_col()` (перезагрузка кадра +
колонка). Для Кида это обычно одно и то же (`cur_frame` в фазе физики
принадлежит ему), но проверить стоит.
**Инструменты готовы.** В SDLPoP включена отладка (пометка `DBG-GRAB`):
`JMP` — покадровая трасса прыжка/падения/виса, `GRAB try|probe|fail|OK` —
вход в `check_grab` и причина отказа. Снимается поиском по `DBG-GRAB`.
**Найдено попутно, отдельным наблюдением.** После касания площадки на
кадрах 107–108 (x=140) оба движка снова падают, но X расходится: SDLPoP
уводит Кида на 134 (колонка 4), мы — на 141 (колонка 5). Похоже на разную
отработку `in_wall()` у стены (2,7). На зацеп не влияет.
---
---
## BUG-GATEMOD-1. Ворота стартуют закрытыми, хотя в уровне открыты — **ЗАКРЫТ 2026-08-05**
**Проверено в игре (пользователь).**
**Симптом (пользователь, 2026-08-04).** Уровень 2, комната 13: решётка
между (2,5) и (2,6) обязана быть ОТКРЫТА в начале и захлопнуться, когда Кид
нажмёт кнопку (2,4) — после этого назад дороги нет. У нас она закрыта
сразу, кнопка бессмысленна, проход не работает.
**Корень.** Модификатор тайла в ФАЙЛЕ уровня и модификатор в РАНТАЙМЕ —
разные величины; оригинал переводит их при загрузке в `load_alter_mod`
(seg008:198E), которую зовёт `alter_mods_allrm` из `load_level`:
```c
case tiles_4_gate: *modif = (*modif == 1) ? 188 : 0; break;
case tiles_11_loose:*modif = 0; break;
case tiles_10_potion:*modif <<= 3; break;
```
Наш `pop_trob_modif` портировал из неё **только зелье**. Для ворот
`bg = 1` — это «открыты» (Table 8 спецификации DAT), а в рантайме открытость
измеряется высотой подъёма 0..188; мы клали в рантайм-модификатор сырую
единицу, то есть «закрыты на 1/188».
**Фикс.** Ветки ворот и loose дописаны в ленивую инициализацию
`pop_trob_modif` (`pop_trob.c`). Ветка СТЕН не портируется намеренно: у нас
`pop_bg` считает связи кладки по типам соседей прямо при отрисовке
(`wall_modifier`), сохранённый модификатор стены не читается.
**Что это ещё задевает.** Решётка (0,9) комнаты 5 уровня 1 тоже имеет
`bg = 1`, то есть обязана стартовать открытой — Кид сваливается в комнату 1
именно через неё, и она захлопывается у него за спиной. Закрывает её
стартовый триггер `do_startpos` (seg003:167): для уровней с
`tbl_entry_pose == 1` оригинал ВИРТУАЛЬНО ЖМЁТ кнопку комнаты 5 (0,2) —
```c
// Special event: press button + falling entry
get_tile(5, 2, 0); trigger_button(0, 0, -1); seqtbl_offset_char(seq_7_fall);
```
Замер в SDLPoP (лог по кадрам): `gate(5,0,9)` идёт `188 → 148 → 88 → 8 → 0`,
шаги 40/60/80 — это `gate_close_speeds`, то есть быстрое закрытие
(`trigger_gate` вернул тип 3). У нас этот триггер портирован, и закрытие
работает.
Полный список ворот с `bg = 1`: ур. 1 комн. 5 (0,9); ур. 2 комн. 13 (1,5).
Остальные ворота уровней 1–3 имеют `bg = 2` → 0, и для них ничего не
меняется (при модификаторе 2 и 0 и отрисовка, и `can_bump_into_gate` дают
одно и то же).
**Побочная находка: чит обхода комнат отматывал мир.** После фикса
пользователь увидел «ворота снова открылись», пройдя `+` в комнату 2 и `-`
обратно. Причина не в воротах: `ROOMNAV` звал `pop_trob_reset()` перед
`enter_room`, тот обнулял `room_seen[]`, и `pop_trob_modif()` перечитывал
модификаторы из уровня заново — то есть чит откатывал открытые/закрытые
ворота, выдвинутые пики и нажатые кнопки. Пока ворота с `bg=1` ошибочно
стартовали закрытыми, откат был не виден. `pop_trob_reset()` из навигации
убран: она обязана только телепортировать, исходное состояние даёт
перезапуск уровня. Замер, который это показал: `room_modif` комнаты 5
после `+`/`-` = `00 00 0B 00 09 00 08 01 00 BC` — последний байт 0xBC = 188,
файловое значение.
---
---
## BUG-GUARD-DEAF-1. Страж не оборачивался на вернувшегося Кида — **ЗАКРЫТ 2026-08-05** ## BUG-GUARD-DEAF-1. Страж не оборачивался на вернувшегося Кида — **ЗАКРЫТ 2026-08-05**
**Наблюдение (пользователь, 2026-08-05).** Уровень 2, комната 11, идёт бой **Наблюдение (пользователь, 2026-08-05).** Уровень 2, комната 11, идёт бой
+3 -144
View File
@@ -27,14 +27,12 @@ BUG-GATE-PASS-1 — однократное наблюдение прохода
| ID | что | тип | статус | | ID | что | тип | статус |
|----|-----|-----|--------| |----|-----|-----|--------|
| [BUG-SPIKE-1](#bug-spike-1) | пики залипают выдвинутыми рядом с Кидом (h=1 → пробег безвреден, острия видны на экране) | Minor | **не воспроизводится по требованию; смертельность подтверждена замером** |
| [BUG-GRAB-1](#bug-grab-1) | ур. 2 комн. 9: прыжок с места через 3 тайла — нет зацепа за кромку | Major | **причина найдена (клавиатура, BUG-KBD-5), фикс есть, ждёт игровой проверки** |
| [BUG-GATEMOD-1](#bug-gatemod-1) | ворота стартуют закрытыми, хотя в уровне открыты | Major | **фикс есть, ждёт проверки** |
| [Уровень 2](#уровень-2) | остальные баги с приёмки | — | принимаются по ходу | | [Уровень 2](#уровень-2) | остальные баги с приёмки | — | принимаются по ходу |
| [BUG-GATE-PASS-1](#bug-gate-pass-1) | проход сквозь закрывшуюся решётку (0,9) комнаты 5 | Major | **перепроверить после [BUG-GATEMOD-1](#bug-gatemod-1)** — та же решётка стартовала не в том состоянии | | [BUG-GATE-PASS-1](#bug-gate-pass-1) | проход сквозь закрывшуюся решётку (0,9) комнаты 5 | Major | **ждёт сценария** (BUG-GATEMOD-1, из-за которого решётка стартовала не в том состоянии, закрыт — перепроверить) |
| [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | открыт | | [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | открыт |
| [T-2](#t-2) | Кид перерисовывается в покое | оптимизация | открыт | | [T-2](#t-2) | Кид перерисовывается в покое | оптимизация | открыт |
| [обход 24 комнат](#обход-всех-24-комнат-уровня-1) | таблица заполнена на 5 строк из 24 | ревизия | открыт | | [обход 24 комнат](#обход-всех-24-комнат-уровня-1) | таблица заполнена на 5 строк из 24 | ревизия | открыт |
| [BUG-SPIKE-1](#bug-spike-1) | пики залипают выдвинутыми рядом с Кидом | **низкий** | маловоспроизводимый: ни сценарием, ни попиксельной подгонкой X не поднимается |
--- ---
@@ -84,7 +82,7 @@ Smoke-прогон уровня 3 (пользователь, 2026-08-05) дал
стеной). стеной).
<a id="bug-spike-1"></a> <a id="bug-spike-1"></a>
## BUG-SPIKE-1. Пики не убивают бегущего Кида — НЕ ВОСПРОИЗВОДИТСЯ, ЖДЁТ СЦЕНАРИЯ ## BUG-SPIKE-1. Пики залипают выдвинутыми — НИЗКИЙ ПРИОРИТЕТ, МАЛОВОСПРОИЗВОДИМ
> **Статус 2026-08-05, вечер.** Пользователь **повторить не смог**, а замер > **Статус 2026-08-05, вечер.** Пользователь **повторить не смог**, а замер
> (ниже) показал, что чистый пробег по убранным пикам убивает штатно. То > (ниже) показал, что чистый пробег по убранным пикам убивает штатно. То
@@ -186,145 +184,6 @@ check_spiked (seg006:0658): убивает при h>=2 на кадрах б
--- ---
<a id="bug-grab-1"></a>
## BUG-GRAB-1. Прыжок с места через провал в 3 тайла: зацепа нет — **ПРИЧИНА НАЙДЕНА, ФИКС ЕСТЬ, ЖДЁТ ИГРОВОЙ ПРОВЕРКИ**
> **Итог 2026-08-05.** Физика тут ни при чём — виновата клавиатура.
> Зажатый Shift снимался автоповтором зажатой стрелки, поэтому к кадрам
> 102…106 (окно зацепа) движок видел Shift отпущенным. Полный разбор и
> фикс — [BUG-KBD-5](bug_closed.md#bug-kbd-5); поведение Shift в MAME
> проверено замером карты `_kbdraw_down`. Осталось подтвердить сам зацеп
> живой игрой; версии 2 и 3 ниже проверять только если он всё ещё не выйдет.
**Симптом.** Уровень 2, комната 9. Перепрыгнув на (1,1), Кид должен
вернуться обратно: разбегаться негде, поэтому он встаёт на самый край
плиты, прыгает с места и **цепляется руками за (1,5)**, после чего
подтягивается. У нас Кид с зажатым Shift всё равно срывается.
**Что уже точно известно (и не надо перепроверять).**
1. **Физика прыжка у нас совпадает с оригиналом кадр в кадр.** Сверено по
логу SDLPoP против трассы харнесса при одинаковом старте `x=95`:
```
кадр 16 18 22 23 24 25 102 103 104 105
SDLPoP 95 97 105 112 121 126 128 130 131 133
наш 95 97 105 112 121 126 128 130 131 133
```
Совпадает и по `y`, и по колонке/ряду, и по приземлению на 107–108.
2. **В оригинале зацеп срабатывает на кадре 106, а не 102..105.**
`check_grab` зовётся из ДВУХ мест: ветка «в воздухе» в `check_action`
(кадры 102..105) и `do_fall` (seg005) для `actions_4_in_freefall`.
Успешная попытка из лога:
```
GRAB try f=106 x=135 y=166 col=4 row=2 fall_y=18
GRAB probe x=127 col=4 through=0 front_above=3 modif=0
GRAB can_grab=1
GRAB OK dist=9
-> f=91 x=136 y=181 col=5 row=2 act=2 (повис)
```
Наш `do_fall` (`pop_map.c`) `check_grab()` из этой ветки тоже зовёт —
то есть структура на месте, расходится что-то внутри.
3. **По харнессу зацеп у нас РАБОТАЕТ**: окно стартовых `x = 91…95`, и
короткий шаг ставит Кида ровно туда (91 после первого нажатия, 95 после
второго). Зафиксировано тестом `t_grab`.
**Отсюда главный вопрос был: почему харнесс говорит «работает», а живая
машина — «нет».** Расхождение между ними и оказалось уликой; версии
выдвигались по убыванию правдоподобия, и сработала первая:
- **Shift не доезжает до движка — ПОДТВЕРЖДЕНО, это и была причина.**
Харнесс подменяет клавиатуру и потому этот путь не проверяет вовсе, а у
нас есть история проблем ровно с «Shift + стрелки» (KBD-1, BUG-KBD-3/4).
Замер в MAME: при зажатом Shift и зажатой стрелке бит `LSh` в
`_kbdraw_down` стоял в нуле. Разбор — [BUG-KBD-5](bug_closed.md#bug-kbd-5).
- **Сцена харнесса не равна комнате 9.** Там изолированная комната
(соседи — стена), а в игре слева комната 8; кромки шва участвуют в
`get_tile`. Проверять чтением `Kid.x` в момент прыжка: попал ли он в
окно 91…95 вообще.
- **Расхождение в `check_grab`.** Наш вариант зовёт `determine_col()`
там, где оригинал зовёт `load_fram_det_col()` (перезагрузка кадра +
колонка). Для Кида это обычно одно и то же (`cur_frame` в фазе физики
принадлежит ему), но проверить стоит.
**Инструменты готовы.** В SDLPoP включена отладка (пометка `DBG-GRAB`):
`JMP` — покадровая трасса прыжка/падения/виса, `GRAB try|probe|fail|OK`
вход в `check_grab` и причина отказа. Снимается поиском по `DBG-GRAB`.
**Найдено попутно, отдельным наблюдением.** После касания площадки на
кадрах 107–108 (x=140) оба движка снова падают, но X расходится: SDLPoP
уводит Кида на 134 (колонка 4), мы — на 141 (колонка 5). Похоже на разную
отработку `in_wall()` у стены (2,7). На зацеп не влияет.
---
<a id="bug-gatemod-1"></a>
## BUG-GATEMOD-1. Ворота стартуют закрытыми, хотя в уровне открыты — **ФИКС ЕСТЬ, ЖДЁТ ПРОВЕРКИ**
**Симптом (пользователь, 2026-08-04).** Уровень 2, комната 13: решётка
между (2,5) и (2,6) обязана быть ОТКРЫТА в начале и захлопнуться, когда Кид
нажмёт кнопку (2,4) — после этого назад дороги нет. У нас она закрыта
сразу, кнопка бессмысленна, проход не работает.
**Корень.** Модификатор тайла в ФАЙЛЕ уровня и модификатор в РАНТАЙМЕ —
разные величины; оригинал переводит их при загрузке в `load_alter_mod`
(seg008:198E), которую зовёт `alter_mods_allrm` из `load_level`:
```c
case tiles_4_gate: *modif = (*modif == 1) ? 188 : 0; break;
case tiles_11_loose:*modif = 0; break;
case tiles_10_potion:*modif <<= 3; break;
```
Наш `pop_trob_modif` портировал из неё **только зелье**. Для ворот
`bg = 1` — это «открыты» (Table 8 спецификации DAT), а в рантайме открытость
измеряется высотой подъёма 0..188; мы клали в рантайм-модификатор сырую
единицу, то есть «закрыты на 1/188».
**Фикс.** Ветки ворот и loose дописаны в ленивую инициализацию
`pop_trob_modif` (`pop_trob.c`). Ветка СТЕН не портируется намеренно: у нас
`pop_bg` считает связи кладки по типам соседей прямо при отрисовке
(`wall_modifier`), сохранённый модификатор стены не читается.
**Что это ещё задевает.** Решётка (0,9) комнаты 5 уровня 1 тоже имеет
`bg = 1`, то есть обязана стартовать открытой — Кид сваливается в комнату 1
именно через неё, и она захлопывается у него за спиной. Закрывает её
стартовый триггер `do_startpos` (seg003:167): для уровней с
`tbl_entry_pose == 1` оригинал ВИРТУАЛЬНО ЖМЁТ кнопку комнаты 5 (0,2) —
```c
// Special event: press button + falling entry
get_tile(5, 2, 0); trigger_button(0, 0, -1); seqtbl_offset_char(seq_7_fall);
```
Замер в SDLPoP (лог по кадрам): `gate(5,0,9)` идёт `188 → 148 → 88 → 8 → 0`,
шаги 40/60/80 — это `gate_close_speeds`, то есть быстрое закрытие
(`trigger_gate` вернул тип 3). У нас этот триггер портирован, и закрытие
работает.
Полный список ворот с `bg = 1`: ур. 1 комн. 5 (0,9); ур. 2 комн. 13 (1,5).
Остальные ворота уровней 1–3 имеют `bg = 2` → 0, и для них ничего не
меняется (при модификаторе 2 и 0 и отрисовка, и `can_bump_into_gate` дают
одно и то же).
**Побочная находка: чит обхода комнат отматывал мир.** После фикса
пользователь увидел «ворота снова открылись», пройдя `+` в комнату 2 и `-`
обратно. Причина не в воротах: `ROOMNAV` звал `pop_trob_reset()` перед
`enter_room`, тот обнулял `room_seen[]`, и `pop_trob_modif()` перечитывал
модификаторы из уровня заново — то есть чит откатывал открытые/закрытые
ворота, выдвинутые пики и нажатые кнопки. Пока ворота с `bg=1` ошибочно
стартовали закрытыми, откат был не виден. `pop_trob_reset()` из навигации
убран: она обязана только телепортировать, исходное состояние даёт
перезапуск уровня. Замер, который это показал: `room_modif` комнаты 5
после `+`/`-` = `00 00 0B 00 09 00 08 01 00 BC` — последний байт 0xBC = 188,
файловое значение.
---
<a id="ручная-перепроверка-2026-08-03"></a> <a id="ручная-перепроверка-2026-08-03"></a>
# Ручная перепроверка фиксов (2026-08-03) # Ручная перепроверка фиксов (2026-08-03)