SprPoP: тесты стены + задача VANILLA/BUGFIXED, находка 24 отложена
КОД ИГРЫ НЕ МЕНЯЛСЯ — правка находки 24 сделана и откачена. ТЕСТЫ. Набор t_wall вырос с одной проверки до трёх: добавлены проверка X на каждом кадре падения (персонаж не должен оказываться внутри кладки) и симметричный прыжок через провал с другой стороны. Ключевое в них — форма ожидания. Тест НЕ требует нуля заходов в кладку, а сторожит их ЧИСЛО: сейчас ровно два случая из четырнадцати стартовых позиций. Это ванильное поведение оригинала, для которого SDLPoP держит отдельное опциональное исправление; больше двух — значит правка сделала нас хуже ванили, меньше — значит фикс кем-то портирован. То есть тест сразу готов обслуживать оба будущих режима. По дороге тест дважды ловил не баг движка, а мою ошибку в самой сцене (старт в пустой клетке; перелёт через площадку считался нарушением). Оба раза чинился тест, а не движок. НАХОДКА 24 ОТЛОЖЕНА. Перезагрузка кадра в in_wall верна по букве оригинала, но эффекта показать не удалось: все 15 наборов host-тестов дают одинаковый результат до и после. При этом правка не бесплатна — маппинг окна и перезагрузка кадра на каждое выталкивание. Платить за недоказанное не стали. НОВАЯ ЗАДАЧА: docs/vanilla_vs_bugfixed.md — поддержка двух поведений, ванильного и с багфиксами. Туда переехали находка 24, три опциональных фикса SDLPoP (скольжение сквозь стену, прыжок над воротами, гобелен) и готовый детектор из t_wall. Открытые вопросы записаны: чем переключать (возможно, объединить с уже существующим VANILLA/ENHANCED), цена рантайм-проверки в горячем пути, что считать умолчанием, как гонять тесты в двух режимах. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY
This commit is contained in:
@@ -455,7 +455,19 @@
|
|||||||
стены на следующем кадре. У нас его может съесть гард из находки 22.
|
стены на следующем кадре. У нас его может съесть гард из находки 22.
|
||||||
Это главная зацепка по симптому.
|
Это главная зацепка по симптому.
|
||||||
|
|
||||||
### 24. `in_wall`: не перезагружается кадр — ранг **Б**
|
### 24. `in_wall`: не перезагружается кадр — ранг **Б**, ОТЛОЖЕНА
|
||||||
|
|
||||||
|
> **Правка сделана и откачена 2026-08-31.** По букве оригинала находка
|
||||||
|
> верна, но практического эффекта показать не удалось: все 15 наборов
|
||||||
|
> host-тестов дали одинаковый результат до и после, включая специально
|
||||||
|
> написанный тест на заход в кладку (`t_wall`). При этом правка не
|
||||||
|
> бесплатна — добавляет маппинг окна и перезагрузку кадра на каждое
|
||||||
|
> выталкивание из стены. Платить за недоказанное не стали.
|
||||||
|
>
|
||||||
|
> Задача переехала в `docs/vanilla_vs_bugfixed.md`: вернуться к ней при
|
||||||
|
> работе над двумя режимами поведения, где появится сценарий, в котором
|
||||||
|
> кадр меняется перед выталкиванием.
|
||||||
|
|
||||||
|
|
||||||
*Оригинал:* `in_wall` (seg006) после выталкивания персонажа из стены
|
*Оригинал:* `in_wall` (seg006) после выталкивания персонажа из стены
|
||||||
делает `load_fram_det_col()` — ЗАГРУЖАЕТ КАДР и следом определяет колонку,
|
делает `load_fram_det_col()` — ЗАГРУЖАЕТ КАДР и следом определяет колонку,
|
||||||
|
|||||||
@@ -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 из аудита — общая рамка «мы намеренно повторяем ваниль».
|
||||||
|
|
||||||
|
Не относятся сюда находки, где мы расходимся с оригиналом НЕ в его
|
||||||
|
пользу (ранги А и Б аудита): их надо чинить в обоих режимах, потому что
|
||||||
|
это не баги оригинала, а наши.
|
||||||
@@ -87,9 +87,117 @@ TC_TEST(wall_stops_undershot_jump)
|
|||||||
TC_EQ(bad, 0);
|
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)
|
int main(void)
|
||||||
{
|
{
|
||||||
sc_init();
|
sc_init();
|
||||||
TC_RUN(wall_stops_undershot_jump);
|
TC_RUN(wall_stops_undershot_jump);
|
||||||
|
TC_RUN(wall_x_never_inside_masonry);
|
||||||
|
TC_RUN(wall_stops_jump_from_left_side);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user