Files
Sprinter-SDCC/applications/SprPoP/tests/host/t_wall.c
T
snark13 90e304f071 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
2026-08-31 20:13:25 +03:00

204 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* t_wall.c — удар о стену В ВОЗДУХЕ (BUG-JUMPWALL-1).
*
* Наблюдение с приёмки уровня 3 (комната 14): Кид прыгает, не долетает и
* вместо удара о кладку с последующим сползанием ВДОЛЬ неё пролетает
* сквозь стену «по свободной траектории». Класс отказа шире одной
* комнаты, поэтому проверяем его отдельным набором, а не сценарием в
* t_phys: там характеризация (трасса == эталон), здесь — утверждение о
* поведении («сквозь кладку нельзя»), которое не должно молча
* переснашиваться вместе с эталонами.
*
* Оригинал: bumped_fall (seg004:04E4) на ударе гасит `Char.fall_x`, то есть
* горизонтальный снос обнуляется и дальше Кид падает вертикально. Кто
* решает, что удар был, — check_collisions (seg004:0004) + check_bumped.
*
* Проверка — свипом по стартовому X, а не одним прыжком: исход зависит от
* фазы, и «с одной X работает» ничего не доказывает (ровно так и вышло с
* первым сценарием — из центра плиты стена держит).
*/
#include "tcheck.h"
#include "scene.h"
#include "stubs.h"
#include "pop_kid.h"
#include "pop_map.h"
#define E 0
#define F 1
#define W 20
/* Комната 14 уровня 3 с одной правкой: шахте колонок 6–7 подложено дно
* (ряд 2), потому что соседних комнат у сцены нет, а в игре Кид улетает
* вниз в комнату 13. Остальное — как в данных: площадка (0,8), провал
* (0,6)/(0,7), СТЕНА в колонке 5 рядов 1–2, площадка (0,5) за ней. */
static const uint8_t room14[30] = {
F, F, F, E, E, F, E, E, F, E,
W, W, W, E, E, W, E, E, W, E,
W, W, W, F, F, W, F, F, W, W,
};
/* Плита (0,8) по X: колонка 8 — это [177, 191), плюс шов с пустой (0,9). */
#define X_LO 177
#define X_HI 196
/* Прыгнуть влево со стартовой X. Возврат — код исхода:
* 'L' — долетел до площадки (0,5) и стоит на ней (ряд 0) — законно;
* '.' — не долетел и упал на дно шахты колонок 6–7 — тоже законно;
* цифра — колонка, в которой Кид оказался ЛЕВЕЕ стены, то есть прошёл
* сквозь кладку колонки 5 (это и есть баг). */
static char jump_left_from(uint8_t x)
{
sc_room(room14, 14);
sc_kid_at_x(8, 0, x, -1 /* лицом влево */);
sc_trace_clear();
/* Клавиши держим только на сам прыжок: если удерживать их всю сцену,
* Кид, ДОЛЕТЕВШИЙ до (0,5), тут же прыгает с неё второй раз — и исход
* говорит уже не о стене, а о втором прыжке. */
sc_run(SC_L | 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 'L'; /* остался на верхнем ряду */
if (col >= 6) return '.'; /* сполз вдоль стены в шахту */
return (char)('0' + (col < 0 ? 0 : col));
}
}
static void put(char c)
{
if (tc_result.logn < TC_LOGSZ) tc_result.log[tc_result.logn++] = (uint8_t)c;
}
static void puts_(const char *s) { while (*s) put(*s++); }
TC_TEST(wall_stops_undershot_jump)
{
uint8_t x, bad = 0;
/* Карта исходов по всей ширине плиты: 'L' — долетел до (0,5), '.' —
* сполз вдоль стены в шахту, цифра — просочился сквозь кладку. */
puts_("x=177..196: ");
for (x = X_LO; x <= X_HI; x++) {
char r = jump_left_from(x);
put(r);
if (r != 'L' && r != '.') bad++;
}
put('\n');
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;
}