diff --git a/applications/PoP/roomtest/bug_closed.md b/applications/PoP/roomtest/bug_closed.md index 23b5405..d131b35 100644 --- a/applications/PoP/roomtest/bug_closed.md +++ b/applications/PoP/roomtest/bug_closed.md @@ -1739,7 +1739,7 @@ Kid ставится на первый пол. Номер комнаты — п Вторая волна того же прогона (вечер) дала ещё четыре наблюдения — BUG-KBD-4, BUG-RESPAWN-2, BUG-DRAWORDER-1, BUG-LOOSE-2, — все закрыты, разборы выше. Тогда же закрыт как невоспроизводящийся BUG-SEAM-DRAW-1 и заведён -BUG-GATE-PASS-1, который остаётся открытым (ждёт сценария). +BUG-GATE-PASS-1 (он закрыт ниже, 2026-08-09). --- @@ -1807,3 +1807,167 @@ pop_guard_hurt = 0;`), а не безусловно — это и экономи **Цвет брызг** зависит от палитры стража (слоты `0x90..0x9F`), поэтому он изменится вместе с [BUG-GUARD-COLOR-1](bug_list.md#bug-guard-color-1) — отдельной работы не требует. + + +--- + + +## BUG-GATE-PASS-1. Проход сквозь закрывшуюся решётку — **ЗАКРЫТ 2026-08-09** + +**Приёмка:** смоук-прогон уровня 1 (пользователь, 2026-08-09) — с проблемного +`x` Кид больше сквозь решётку не пробегает, пинг-понг у шва не вернулся. + +> **ВОСПРОИЗВЕДЁН И РАЗОБРАН.** Сценарий: уровень 1, комната 8, ряд 0, +> `Kid.x = 170`, лицом ВПРАВО, решётка шва (комната 8, колонка 9) ЗАКРЫТА. +> Разбежаться вправо и НЕ ОТПУСКАТЬ клавишу. Кид проходит сквозь решётку и +> убегает вглубь комнаты 6. +> +> Ниже — новый разбор; всё, что было раньше (гипотеза про x > 205), не +> подтвердилось и оставлено в конце для истории. + +### Корень: история флагов коллизии индексируется НЕ ТАК, как в оригинале + +Сняты две трассы — наша (брейкпоинт на резидентном `kid_tick`) и оригинала +(SDLPoP, собран с `POP_TRACE=1`, печать в `play_kid_frame` + маркеры в +`bumped` и `exit_room`). Стартовая позиция совпала до пикселя, поэтому +сравнение построчное. + +**Оригинал:** + +``` +LEAVE dir=1 x=61 cxl=184 cxr=201 room=6 смена комнаты — ШТАТНАЯ +BUMPED dx=-2 push=-1 x=63 tile=4 col=9 и СРАЗУ бамп о решётку, откат +KID f=15 x=60 act=0 col=-1 room=6 drawn=6 сел за решёткой, стоит +``` + +**Мы:** + +``` +x 170 -> 196 бегом, col 7 -> 8 -> 9, НИ ОДНОГО бампа +x=198 -> room 8 -> 6 +при удержании ВПРАВО доезжает до col 1 комнаты 6 и бежит дальше +``` + +Смена комнаты в этой точке правильна и у нас, и у оригинала: `leave_room` +(seg002:423) вправо блокируют ТОЛЬКО `doortop`-тайлы, решётки в списке нет, а +порог `char_x_right >= 201` совпадает с плоскостью ворот +(`x_bump[9+FIRST_ONSCREEN_COLUMN] + TILE_MIDX + wall_dist_from_left[1]` += 191 + 10). Оригинал полагается не на запрет ухода, а на бамп СРАЗУ ПОСЛЕ +перехода. + +**Почему бамп есть у них и нет у нас.** `check_collisions` (seg004:0004) +хранит флаги так: + +```c +row_coll_flags_ptr[tile_col] = curr_flags; // индекс — колонка В РАЗРЕШЁННОЙ комнате +row_coll_room_ptr [tile_col] = curr_room; // и рядом — НОМЕР ЭТОЙ КОМНАТЫ +... +if (curr_row_coll_room[column] >= 0 && + prev_coll_room[column] == curr_row_coll_room[column]) { /* только тогда сравниваем */ } +``` + +То есть история привязана к паре **(колонка внутри своей комнаты, комната)**. +Решётка комнаты 8 и до перехода, и после лежит в слоте 9 с `room = 8` — +переход 8->6 историю НЕ рвёт, переход флага 0->1 виден, бамп срабатывает. + +У нас массив `coll_prev/coll_curr` индексируется колонкой ОТНОСИТЕЛЬНО +отрисованной комнаты (−2…11). При смене комнаты все индексы уезжают на 10, +сопоставить прошлый кадр с текущим нечем, и `enter_room` вынужден звать +`pop_coll_invalidate()`; тот ставит «прошлые флаги = уже перекрывал» (3), то +есть ПОДАВЛЯЕТ бамп на кадре входа. А к следующему кадру Кид уже за +плоскостью ворот — перехода 0->1 не будет никогда. + +### Фикс сделан 2026-08-09 (вариант B — сдвиг истории), ЖДЁТ ПРИЁМКИ В MAME + +Рассматривались два пути: + +- **A, дословный порт.** 10 слотов + параллельный массив номера комнаты, + индекс по колонке разрешённой комнаты, бамп только при совпадении номеров; + `pop_coll_invalidate()` тогда не нужен вовсе. **Не взят:** оригиналу + хватает десяти слотов только потому, что он перебирает узкое окно вокруг + Кида, а мы намеренно считаем все четырнадцать колонок (FIX_COLL_FLAGS, + чтобы не оставалось протухших ячеек) — при таком переборе колонки −2/−1 и + 8/9 сядут в одни слоты 8/9. То есть A тянет за собой ещё и сужение окна. +- **B, сдвиг истории (сделано).** Индексацию оставляем свою, но при + БОКОВОМ переходе историю не выбрасываем, а перенумеровываем на 10 слотов: + `check_leave` двигает `x` ровно на ∓140 = 10 тайлов, координата грани + (`pop_x_bump`) едет на те же 140 вместе с габаритом Кида, значит сами + флаги инвариантны — меняется только номер слота. + +Реализация: `pop_coll_shift(int8_t dcol)` в `pop_map.c` сдвигает +`coll_curr/above/below` (не `prev` — его на следующем кадре всё равно +перезапишет `move_coll_to_prev`), освободившиеся слоты = 3 («уже +перекрывал», бампа нет — ровно то, что в оригинале даёт несовпадение номера +комнаты). `enter_room_side` зовёт её сразу после `pop_map_set_edges`: +`side == 0` (ушёл влево) → `+10`, `side == 1` (вправо) → `−10`. Переходы +вверх/вниз и все прочие входы в комнату остаются на полной инвалидации — +там колонки не сдвигаются, но тайлы под ними уже из другой комнаты. + +Расхождение с дословным вариантом A записано в `../docs/impl_diff.md` (D-1). + +**Осторожно:** это сердце коллизии, вокруг которого разбирался +BUG-SEAM-PINGPONG (см. `bug_closed.md`). Приёмка: + +1. Комнаты 6 ↔ 8 уровня 1, закрытая решётка, **обе** стороны. +2. Мелким шагом (упереться) И с разбега (не пройти насквозь) — сценарий + выше воспроизводится с `Kid.x = 170`, комната 8, ряд 0, лицом вправо. +3. Пинг-понг у шва не вернулся (экран не перескакивает туда-сюда). +4. `make -C tests-host` — прошли все 5 наборов (2026-08-09). + +**Инструмент для сверки готов:** SDLPoP собран с трассой, включается +`POP_TRACE=1` (правки в `seg000.c` play_kid_frame, `seg004.c` bumped, +`seg002.c` exit_room, чтение переменной — `main.c`). Сборка на macOS: +`make CFLAGS="-std=gnu99 -O2 -I/opt/homebrew/include -Wno-implicit-function-declaration" LIBS="-L/opt/homebrew/lib -lSDL2 -lSDL2_image"` +(в системе нет pkg-config, штатный Makefile без этого не собирается). + +--- + +### История: прежний разбор (гипотеза НЕ подтвердилась) + +**Статус: наблюдался один раз (2026-08-03), воспроизвести не удалось ни +тогда, ни прогоном всех комнат уровня 1 (2026-08-07).** Заведён, чтобы +наблюдение не потерялось; закрывать нельзя — ни как исправленный, ни как +«не баг», пока нет надёжного сценария. Оговорка: BUG-GATEMOD-1, из-за +которого решётка стартовала не в том состоянии, с тех пор закрыт, и +наблюдение могло быть его следствием. + +**Что наблюдалось.** Комната 5: Кид стоял НА тайле решётки (0,9) +и ждал, пока она опустится. После закрытия пошёл вправо — прошёл в комнату 1 +и упал на (1,1). + +**Что уже измерено и в чём загвоздка.** Сразу после наблюдения повторить не +получилось: в том же месте Кид стоит на `x = 196`, `col = 9`, и решётка его +ДЕРЖИТ — то есть штатно. + +Арифметика оригинала объясняет разницу. `is_obstacle` (seg004) ставит +плоскость блокировки в `x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX`, для +колонки 9 это **x = 205**. При этом «колонка 9» по `get_tile_div_mod_m7` — +это `x ∈ [191, 205)`. Пока `curr_col == 9`, Кид гарантированно левее +плоскости и обязан блокироваться; чтобы пройти, он должен оказаться **правее +205**, то есть уже на дальней стороне решётки, — и тогда уход вправо законен: +решётка закрылась у него за спиной, в оригинале она блокирует плоскость, а не +весь тайл. + +Отсюда рабочая гипотеза: в момент наблюдения Кид стоял правее 205 (успел зайти +по тайлу дальше, пока решётка была поднята), и поведение штатное. Но повторить +эту позу и снять `x` пока не удалось, поэтому гипотеза НЕ подтверждена. + +**Что снять в следующий раз** (без этих чисел вопрос не закрыть): + +1. `Kid.x` и `Kid.curr_col` в момент, когда решётка уже закрылась, а Кид ещё + стоит на её тайле — до шага вправо; +2. модификатор решётки (openness) комнаты 5, тайл 9 — `can_bump_into_gate()` + считает её препятствием только пока `(modif >> 2) + 6 < char_height`, то + есть пока она опустилась достаточно низко относительно РОСТА кадра; +3. `Kid.x` покадрово на самом шаге вправо — где именно перестал блокировать. + +Быстрый способ снять первое: отладочный стоп-кадр (**1** заморозить, **2** +продолжить), затем чтение `_Kid` из отладчика MAME; подгонка позы по пикселю — +читы `[`/`]`. + +**Возможный корень, если гипотеза не подтвердится.** Проверка идёт по колонке, +которая на шве уже принадлежит СОСЕДНЕЙ комнате (`curr_row_coll_room[]` в +оригинале); у нас межкомнатная коллизия на шве — исторически проблемное место +(ср. закрытый BUG-SEAM-PINGPONG). Второй кандидат — `char_height` в +`can_bump_into_gate()`: если он берётся не от того кадра, решётка может +перестать считаться препятствием раньше времени. diff --git a/applications/PoP/roomtest/bug_list.md b/applications/PoP/roomtest/bug_list.md index b9843df..bd81356 100644 --- a/applications/PoP/roomtest/bug_list.md +++ b/applications/PoP/roomtest/bug_list.md @@ -18,11 +18,11 @@ | ID | что | тип | статус | |----|-----|-----|--------| | [BUG-CHEAT-FIGHT-1](#bug-cheat-fight-1) | `+`/`−` в бою с вынутым мечом → Кид теряет управление | Major (чит) | открыт | -| [BUG-GATE-PASS-1](#bug-gate-pass-1) | проход сквозь закрытую решётку шва (комн. 8 -> 6) | Major | **фикс сделан 2026-08-09** (сдвиг истории флагов, вариант B) — ждёт приёмки в MAME | | [BUG-SPIKE-1](#bug-spike-1) | пики залипают выдвинутыми рядом с Кидом | **низкий** | маловоспроизводимый: ни сценарием, ни попиксельной подгонкой X не поднимается | | [BUG-CHOMP-JUMP-1](#bug-chomp-jump-1) | прыжок с места вплотную к чомперу: кадр с отступом назад | **низкий** | маловоспроизводимый: на повторе не поднялся; на пререлиз | | [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 пройден | --- @@ -190,168 +190,6 @@ Smoke-прогон уровня 3 (пользователь, 2026-08-05) дал --- -# Открытые баги уровня 1 - - -## BUG-GATE-PASS-1. Проход сквозь закрывшуюся решётку — **КОРЕНЬ НАЙДЕН 2026-08-08** - -> **ВОСПРОИЗВЕДЁН И РАЗОБРАН.** Сценарий: уровень 1, комната 8, ряд 0, -> `Kid.x = 170`, лицом ВПРАВО, решётка шва (комната 8, колонка 9) ЗАКРЫТА. -> Разбежаться вправо и НЕ ОТПУСКАТЬ клавишу. Кид проходит сквозь решётку и -> убегает вглубь комнаты 6. -> -> Ниже — новый разбор; всё, что было раньше (гипотеза про x > 205), не -> подтвердилось и оставлено в конце для истории. - -### Корень: история флагов коллизии индексируется НЕ ТАК, как в оригинале - -Сняты две трассы — наша (брейкпоинт на резидентном `kid_tick`) и оригинала -(SDLPoP, собран с `POP_TRACE=1`, печать в `play_kid_frame` + маркеры в -`bumped` и `exit_room`). Стартовая позиция совпала до пикселя, поэтому -сравнение построчное. - -**Оригинал:** - -``` -LEAVE dir=1 x=61 cxl=184 cxr=201 room=6 смена комнаты — ШТАТНАЯ -BUMPED dx=-2 push=-1 x=63 tile=4 col=9 и СРАЗУ бамп о решётку, откат -KID f=15 x=60 act=0 col=-1 room=6 drawn=6 сел за решёткой, стоит -``` - -**Мы:** - -``` -x 170 -> 196 бегом, col 7 -> 8 -> 9, НИ ОДНОГО бампа -x=198 -> room 8 -> 6 -при удержании ВПРАВО доезжает до col 1 комнаты 6 и бежит дальше -``` - -Смена комнаты в этой точке правильна и у нас, и у оригинала: `leave_room` -(seg002:423) вправо блокируют ТОЛЬКО `doortop`-тайлы, решётки в списке нет, а -порог `char_x_right >= 201` совпадает с плоскостью ворот -(`x_bump[9+FIRST_ONSCREEN_COLUMN] + TILE_MIDX + wall_dist_from_left[1]` -= 191 + 10). Оригинал полагается не на запрет ухода, а на бамп СРАЗУ ПОСЛЕ -перехода. - -**Почему бамп есть у них и нет у нас.** `check_collisions` (seg004:0004) -хранит флаги так: - -```c -row_coll_flags_ptr[tile_col] = curr_flags; // индекс — колонка В РАЗРЕШЁННОЙ комнате -row_coll_room_ptr [tile_col] = curr_room; // и рядом — НОМЕР ЭТОЙ КОМНАТЫ -... -if (curr_row_coll_room[column] >= 0 && - prev_coll_room[column] == curr_row_coll_room[column]) { /* только тогда сравниваем */ } -``` - -То есть история привязана к паре **(колонка внутри своей комнаты, комната)**. -Решётка комнаты 8 и до перехода, и после лежит в слоте 9 с `room = 8` — -переход 8->6 историю НЕ рвёт, переход флага 0->1 виден, бамп срабатывает. - -У нас массив `coll_prev/coll_curr` индексируется колонкой ОТНОСИТЕЛЬНО -отрисованной комнаты (−2…11). При смене комнаты все индексы уезжают на 10, -сопоставить прошлый кадр с текущим нечем, и `enter_room` вынужден звать -`pop_coll_invalidate()`; тот ставит «прошлые флаги = уже перекрывал» (3), то -есть ПОДАВЛЯЕТ бамп на кадре входа. А к следующему кадру Кид уже за -плоскостью ворот — перехода 0->1 не будет никогда. - -### Фикс сделан 2026-08-09 (вариант B — сдвиг истории), ЖДЁТ ПРИЁМКИ В MAME - -Рассматривались два пути: - -- **A, дословный порт.** 10 слотов + параллельный массив номера комнаты, - индекс по колонке разрешённой комнаты, бамп только при совпадении номеров; - `pop_coll_invalidate()` тогда не нужен вовсе. **Не взят:** оригиналу - хватает десяти слотов только потому, что он перебирает узкое окно вокруг - Кида, а мы намеренно считаем все четырнадцать колонок (FIX_COLL_FLAGS, - чтобы не оставалось протухших ячеек) — при таком переборе колонки −2/−1 и - 8/9 сядут в одни слоты 8/9. То есть A тянет за собой ещё и сужение окна. -- **B, сдвиг истории (сделано).** Индексацию оставляем свою, но при - БОКОВОМ переходе историю не выбрасываем, а перенумеровываем на 10 слотов: - `check_leave` двигает `x` ровно на ∓140 = 10 тайлов, координата грани - (`pop_x_bump`) едет на те же 140 вместе с габаритом Кида, значит сами - флаги инвариантны — меняется только номер слота. - -Реализация: `pop_coll_shift(int8_t dcol)` в `pop_map.c` сдвигает -`coll_curr/above/below` (не `prev` — его на следующем кадре всё равно -перезапишет `move_coll_to_prev`), освободившиеся слоты = 3 («уже -перекрывал», бампа нет — ровно то, что в оригинале даёт несовпадение номера -комнаты). `enter_room_side` зовёт её сразу после `pop_map_set_edges`: -`side == 0` (ушёл влево) → `+10`, `side == 1` (вправо) → `−10`. Переходы -вверх/вниз и все прочие входы в комнату остаются на полной инвалидации — -там колонки не сдвигаются, но тайлы под ними уже из другой комнаты. - -Расхождение с дословным вариантом A записано в `../docs/impl_diff.md` (D-1). - -**Осторожно:** это сердце коллизии, вокруг которого разбирался -BUG-SEAM-PINGPONG (см. `bug_closed.md`). Приёмка: - -1. Комнаты 6 ↔ 8 уровня 1, закрытая решётка, **обе** стороны. -2. Мелким шагом (упереться) И с разбега (не пройти насквозь) — сценарий - выше воспроизводится с `Kid.x = 170`, комната 8, ряд 0, лицом вправо. -3. Пинг-понг у шва не вернулся (экран не перескакивает туда-сюда). -4. `make -C tests-host` — прошли все 5 наборов (2026-08-09). - -**Инструмент для сверки готов:** SDLPoP собран с трассой, включается -`POP_TRACE=1` (правки в `seg000.c` play_kid_frame, `seg004.c` bumped, -`seg002.c` exit_room, чтение переменной — `main.c`). Сборка на macOS: -`make CFLAGS="-std=gnu99 -O2 -I/opt/homebrew/include -Wno-implicit-function-declaration" LIBS="-L/opt/homebrew/lib -lSDL2 -lSDL2_image"` -(в системе нет pkg-config, штатный Makefile без этого не собирается). - ---- - -### История: прежний разбор (гипотеза НЕ подтвердилась) - -**Статус: наблюдался один раз (2026-08-03), воспроизвести не удалось ни -тогда, ни прогоном всех комнат уровня 1 (2026-08-07).** Заведён, чтобы -наблюдение не потерялось; закрывать нельзя — ни как исправленный, ни как -«не баг», пока нет надёжного сценария. Оговорка: BUG-GATEMOD-1, из-за -которого решётка стартовала не в том состоянии, с тех пор закрыт, и -наблюдение могло быть его следствием. - -**Что наблюдалось.** Комната 5: Кид стоял НА тайле решётки (0,9) -и ждал, пока она опустится. После закрытия пошёл вправо — прошёл в комнату 1 -и упал на (1,1). - -**Что уже измерено и в чём загвоздка.** Сразу после наблюдения повторить не -получилось: в том же месте Кид стоит на `x = 196`, `col = 9`, и решётка его -ДЕРЖИТ — то есть штатно. - -Арифметика оригинала объясняет разницу. `is_obstacle` (seg004) ставит -плоскость блокировки в `x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX`, для -колонки 9 это **x = 205**. При этом «колонка 9» по `get_tile_div_mod_m7` — -это `x ∈ [191, 205)`. Пока `curr_col == 9`, Кид гарантированно левее -плоскости и обязан блокироваться; чтобы пройти, он должен оказаться **правее -205**, то есть уже на дальней стороне решётки, — и тогда уход вправо законен: -решётка закрылась у него за спиной, в оригинале она блокирует плоскость, а не -весь тайл. - -Отсюда рабочая гипотеза: в момент наблюдения Кид стоял правее 205 (успел зайти -по тайлу дальше, пока решётка была поднята), и поведение штатное. Но повторить -эту позу и снять `x` пока не удалось, поэтому гипотеза НЕ подтверждена. - -**Что снять в следующий раз** (без этих чисел вопрос не закрыть): - -1. `Kid.x` и `Kid.curr_col` в момент, когда решётка уже закрылась, а Кид ещё - стоит на её тайле — до шага вправо; -2. модификатор решётки (openness) комнаты 5, тайл 9 — `can_bump_into_gate()` - считает её препятствием только пока `(modif >> 2) + 6 < char_height`, то - есть пока она опустилась достаточно низко относительно РОСТА кадра; -3. `Kid.x` покадрово на самом шаге вправо — где именно перестал блокировать. - -Быстрый способ снять первое: отладочный стоп-кадр (**1** заморозить, **2** -продолжить), затем чтение `_Kid` из отладчика MAME; подгонка позы по пикселю — -читы `[`/`]`. - -**Возможный корень, если гипотеза не подтвердится.** Проверка идёт по колонке, -которая на шве уже принадлежит СОСЕДНЕЙ комнате (`curr_row_coll_room[]` в -оригинале); у нас межкомнатная коллизия на шве — исторически проблемное место -(ср. закрытый BUG-SEAM-PINGPONG). Второй кандидат — `char_height` в -`can_bump_into_gate()`: если он берётся не от того кадра, решётка может -перестать считаться препятствием раньше времени. - ---- - # Оптимизация отрисовки (записано 2026-07-29) Не баги — план работ. Оба пункта про одно: у оригинала пометка тайла к