diff --git a/applications/SprPoP/docs/sdlpop_audit.md b/applications/SprPoP/docs/sdlpop_audit.md index eca8ab8..ed078ee 100644 --- a/applications/SprPoP/docs/sdlpop_audit.md +++ b/applications/SprPoP/docs/sdlpop_audit.md @@ -455,7 +455,19 @@ стены на следующем кадре. У нас его может съесть гард из находки 22. Это главная зацепка по симптому. -### 24. `in_wall`: не перезагружается кадр — ранг **Б** +### 24. `in_wall`: не перезагружается кадр — ранг **Б**, ОТЛОЖЕНА + +> **Правка сделана и откачена 2026-08-31.** По букве оригинала находка +> верна, но практического эффекта показать не удалось: все 15 наборов +> host-тестов дали одинаковый результат до и после, включая специально +> написанный тест на заход в кладку (`t_wall`). При этом правка не +> бесплатна — добавляет маппинг окна и перезагрузку кадра на каждое +> выталкивание из стены. Платить за недоказанное не стали. +> +> Задача переехала в `docs/vanilla_vs_bugfixed.md`: вернуться к ней при +> работе над двумя режимами поведения, где появится сценарий, в котором +> кадр меняется перед выталкиванием. + *Оригинал:* `in_wall` (seg006) после выталкивания персонажа из стены делает `load_fram_det_col()` — ЗАГРУЖАЕТ КАДР и следом определяет колонку, diff --git a/applications/SprPoP/docs/vanilla_vs_bugfixed.md b/applications/SprPoP/docs/vanilla_vs_bugfixed.md new file mode 100644 index 0000000..16c5e65 --- /dev/null +++ b/applications/SprPoP/docs/vanilla_vs_bugfixed.md @@ -0,0 +1,95 @@ +# Два поведения: VANILLA и BUGFIXED + +> Заведено 2026-08-31. Задача поставлена, работа НЕ начата. + +## Зачем + +У оригинального PoP есть баги, которые игроки знают наизусть и на +которых построены известные трюки. SDLPoP чинит их не молча, а +ОПЦИОНАЛЬНО: каждое исправление отдельным переключателем, по умолчанию +часть включена, часть нет. Мы до сих пор повторяли ваниль — сознательно, +чтобы порт вёл себя как оригинал. + +Задача: дать ДВА поведения на выбор, не размазывая условия по всему коду: + +* **VANILLA** — как в оригинале 1989 года, со всеми его багами; +* **BUGFIXED** — с портированными исправлениями SDLPoP. + +## Что уже известно (из аудита) + +Разбор расхождений — `docs/sdlpop_audit.md`. Оттуда прямо в эту задачу +переезжает следующее. + +### Опциональные фиксы SDLPoP, которых у нас НЕТ + +Все три относятся к прохождению сквозь препятствия и живут в `do_fall` +(seg005) и рядом: + +| фикс SDLPoP | что чинит | +|---|---| +| «скольжение сквозь стену» | Кид падает сквозь стены после разворота в беге, особенно под зельем медленного падения | +| «прыжок сквозь стену над воротами» | пролёт в тайл над воротами | +| «проваливание сквозь гобелен» | падение сквозь тайл гобелена | + +Ни один не портирован — это и есть наше нынешнее VANILLA. + +### Находка 24 — отложена сюда + +`in_wall` у нас пересчитывает только колонку, а оригинал перезагружает +ещё и данные кадра (`load_fram_det_col`, seg006). Правка сделана и +ОТКАЧЕНА 2026-08-31 по такой причине: + +* по букве оригинала находка верна; +* но практического эффекта показать НЕ УДАЛОСЬ — все 15 наборов + host-тестов дают одинаковый результат до и после, включая специально + написанный тест на заход в кладку; +* при этом правка не бесплатна: добавляет маппинг окна и перезагрузку + кадра на каждое выталкивание из стены. + +Платить за недоказанное не стали. Вернуться к ней имеет смысл именно +здесь: при работе над BUGFIXED появится сценарий, где кадр меняется перед +выталкиванием, и тогда эффект станет наблюдаемым. + +### Готовый детектор поведения + +`tests/host/t_wall.c` расширен с одной проверки до трёх. Ключевая — +`wall_stops_jump_from_left_side`: она НЕ требует нуля заходов в кладку, а +сторожит их ЧИСЛО (сейчас ровно два случая из четырнадцати стартовых +позиций). + +Это и есть переключатель ожиданий между режимами: + +* больше двух — правка сделала нас хуже ванили, регресс; +* ровно два — ведём себя как оригинал (режим VANILLA); +* меньше двух — кто-то портировал фикс; в режиме BUGFIXED тест должен + ждать нуля. + +То есть когда появится BUGFIXED, этому тесту понадобится ожидание, +зависящее от режима, — и он готов стать первым таким. + +## Что предстоит решить + +1. **Как переключать.** Ключ сборки (два разных `.exe`) или настройка в + `POP.CFG` с проверкой в рантайме. У нас уже есть VANILLA/ENHANCED в + меню настроек (`docs/menu_settings_plan.md`) — возможно, это то же + самое измерение и стоит объединить, а не заводить второе. +2. **Цена рантайм-проверки.** Условия попадают в физику и `play_seq`, то + есть в горячий путь. Если переключать в рантайме, проверка должна + быть дешевле самого фикса: флаг в резиденте, а не вызов через банк. +3. **Что считать умолчанием.** Оригинальное поведение честнее для порта, + но часть фиксов SDLPoP включает по умолчанию. +4. **Как тестировать оба режима.** Host-тесты гоняются одним прогоном; + для двух режимов нужен либо параметр сборки тестов, либо ожидания, + зависящие от флага. + +## Список кандидатов на BUGFIXED + +Пополняется по мере аудита. Пока: + +* три опциональных фикса SDLPoP выше; +* находка 24 (перезагрузка кадра в `in_wall`); +* находка 21 из аудита — общая рамка «мы намеренно повторяем ваниль». + +Не относятся сюда находки, где мы расходимся с оригиналом НЕ в его +пользу (ранги А и Б аудита): их надо чинить в обоих режимах, потому что +это не баги оригинала, а наши. diff --git a/applications/SprPoP/tests/host/t_wall.c b/applications/SprPoP/tests/host/t_wall.c index 8086372..f9e202a 100644 --- a/applications/SprPoP/tests/host/t_wall.c +++ b/applications/SprPoP/tests/host/t_wall.c @@ -87,9 +87,117 @@ TC_TEST(wall_stops_undershot_jump) TC_EQ(bad, 0); } +/* --- ГДЕ КОНЧАЕТСЯ СТЕНА ПО X ---------------------------------------- * + * + * Колонка 5 — кладка (ряды 1-2). Тайл шириной 14, колонка c занимает + * [58 + 14*c, 58 + 14*(c+1)). Для колонки 5 это [128, 142). Персонаж + * НИКОГДА не должен оказаться внутри этого отрезка ниже верхнего ряда: + * там сплошная кладка, и «падение частично в стене» — как раз оно. + * + * Проверка отдельная от той, что выше: та смотрит КОЛОНКУ в конце сцены, + * а эта — X на КАЖДОМ кадре падения. Персонаж может кончить падение в + * законной колонке, успев по дороге пройти сквозь кладку. */ +#define WALL_X_LO 128 +#define WALL_X_HI 142 + +/* Сколько кадров трассы имеют X внутри кладки, будучи ниже верхнего ряда. */ +static uint8_t frames_inside_wall(uint8_t x) +{ + uint8_t i, n = 0; + sc_room(room14, 14); + sc_kid_at_x(8, 0, x, -1); + sc_trace_clear(); + sc_run(SC_L | SC_U, 6); + sc_run(0, 34); + for (i = 0; i < sc_len; i++) { + if (sc_trace[i].row == 0) continue; /* верхний ряд — не кладка */ + if (sc_trace[i].x >= WALL_X_LO && sc_trace[i].x < WALL_X_HI) n++; + } + return n; +} + +TC_TEST(wall_x_never_inside_masonry) +{ + uint8_t x, bad = 0; + puts_("inside x=177..196: "); + for (x = X_LO; x <= X_HI; x++) { + uint8_t n = frames_inside_wall(x); + put((char)(n ? ('0' + (n > 9 ? 9 : n)) : '.')); + if (n) bad++; + } + put('\n'); + TC_EQ(bad, 0); +} + +/* Симметричный случай: прыжок ВПРАВО через провал колонок 3-4 к площадке + * (0,5). Стартовая площадка — колонки 0-2 верхнего ряда; под провалом + * дно (ряд 2), по бокам кладка. Недолёт обязан кончиться на дне, а не + * внутри кладки: выталкивание из стены работает в обе стороны. */ +static char jump_right_from(uint8_t x) +{ + sc_room(room14, 14); + sc_kid_at_x(2, 0, x, 1 /* лицом вправо */); + sc_trace_clear(); + sc_run(SC_R | SC_U, 6); + sc_run(0, 34); + if (!sc_len) return '?'; + { + int8_t col = sc_trace[sc_len - 1].col; + int8_t row = sc_trace[sc_len - 1].row; + if (row == 0) return 'R'; /* остался на верхнем ряду */ + /* Ниже верхнего ряда законны ОБА провала — слева от кладки + * (колонки 3-4, недолёт) и справа (6-7, перелёт через площадку). + * Незаконна только сама кладка колонки 5. */ + if (col != 5) return '.'; + /* Вис на уступе площадки (0,5) — законное состояние, и колонка + * при нём как раз 5. Отличаем по действию персонажа. */ + { + uint8_t act = sc_trace[sc_len - 1].action; + if (act == 2 || act == 6) return 'h'; /* hang_climb / hang_straight */ + } + /* Осталось одно: персонаж НИЖЕ верхнего ряда, в колонке кладки и + * не висит — то есть находится ВНУТРИ стены. Именно так выглядит + * «падение частично в стене» (docs/sdlpop_audit.md, находка 24). */ + return '5'; + } +} + +/* ВАНИЛЬНОЕ ПОВЕДЕНИЕ, А НЕ НАША ОШИБКА. + * + * Прыжок вправо через провал даёт ДВА случая из четырнадцати, где Кид + * оказывается в колонке кладки, будучи в воздухе, — то есть «падает + * частично в стене». Это известный баг оригинального PoP: SDLPoP держит + * для него ОТДЕЛЬНОЕ опциональное исправление (FIX_GLIDE_THROUGH_WALL, + * seg005 do_fall), которое мы намеренно не портировали, повторяя ваниль + * (docs/sdlpop_audit.md, находка 21). + * + * Поэтому тест не требует нуля, а СТОРОЖИТ ЧИСЛО: пока их ровно два, мы + * ведём себя как оригинал. Стало больше — значит правка сделала хуже + * ванили; стало меньше — кто-то портировал фикс и об этом нужно знать. + * Проверено: фикс 24 (перезагрузка кадра в in_wall) на это число НЕ + * влияет. */ +#define WALL_VANILLA_GLIDE_CASES 2 + +TC_TEST(wall_stops_jump_from_left_side) +{ + uint8_t x, bad = 0; + /* Плита колонок 3-4: [100, 128). */ + /* Плита колонок 0-2: колонка 2 — это [86, 100). */ + puts_("right x=86..99: "); + for (x = 86; x <= 99; x++) { + char r = jump_right_from(x); + put(r); + if (r != 'R' && r != '.' && r != 'h') bad++; + } + put('\n'); + TC_EQ(bad, WALL_VANILLA_GLIDE_CASES); +} + int main(void) { sc_init(); TC_RUN(wall_stops_undershot_jump); + TC_RUN(wall_x_never_inside_masonry); + TC_RUN(wall_stops_jump_from_left_side); return 0; }