Files
Sprinter-SDCC/applications/PoP/roomtest/tests-host/README.md
T
snark13 6673279cef PoP: скелет уровня 3, цвета стражей, окклюзия соперника; кэш соседних комнат
Скелет (L3-SKEL, ассеты + механика):
- pop_pack_guard.py получил параметр набора (GUARD/SKEL): атлас скелета
  poc/res/skel/g0..g3.atl (28 кадров), палитра — из его res750.pal (на ур. 3
  curr_guard_color = 0, оригинал палитру не подменяет);
- pop_guard_load выбирает набор по tbl_guard_type и перезагружается ПРИ СМЕНЕ
  УРОВНЯ (load_lev_spr, seg000:1092) — без этого скелет рисовался атласом
  стража и был невидим;
- load_frame: charid_4_skeleton идёт по таблице стража (seg006:529), тень —
  только в кадрах 150..189.  Пока ветка была одна (charid_2_guard), скелет
  получал image из таблицы Кида (180 при 28 спрайтах) и не рисовался;
- check_skel (seg002:1042), ветка charid_4 в enter_guard, возрождение в
  комнате 3 при падении (seg002:252), autocontrol_skeleton;
- leveldoor_open (seg007:456) — новый флаг, сбрасывается стартом уровня.

Цвета стражей (BUG-GUARD-COLOR-1, закрыт):
- все 7 палитр res10.bin -> pop_guard_pal.h, заливка 16 слотов по
  guards_color комнаты перед отрисовкой (set_chtab_palette, seg003:257).
  Проверено в MAME: ур. 2 комн. 11 = цвет 1, комн. 7 = цвет 3, полоса HP
  меняется вместе со стражем.  Грабля: gfx_pal_load отдаёт указатель в BIOS,
  а тот читает только #4000-#BFFF — таблицу из банка копируем в стек.

Кэш соседних комнат (BUG-SWORD-GHOST-1, закрыт):
- pop_map кэширует fg соседей слева/справа ЦЕЛИКОМ и резолвит col -10..19.
  Было -2..11, дальше мнимая стена: луч видимости упирался в неё (страж
  после follow_guard в col 12), Кид прятал меч посреди боя и не мог достать
  обратно.  +48 байт W2.

Окклюзия соперника:
- pop_fore_over_char получил проход other_overlay_tile (порядок midtable,
  seg008:1B06) и расширение перебора объединённым прямоугольником
  «персонаж + клинок + брызги» — падающий скелет больше не рисуется поверх
  кладки и верхней грани пола;
- клип полем 192 строк (reset_obj_clip, seg006:0507) для спрайта, клинка
  (общий pop_sword_draw) и брызг — спрайт не залезает на полосу HP;
- ROOMNAV после смерти Кида делает честный pop_start_level: телепорт
  «оживлял» мёртвого мимо старта уровня, оставляя живого скелета рядом с
  вернувшейся кучей костей.

Ассеты чомпера (под L3-CHOMP): весь набор кадров в атласе явным списком
(101-105 низ, 111-113 верх, 106-110 фронт, 114-123 кровь mono-силуэтом) —
render_room анимированные тайлы пропускает, и в атласе не было ни одного.
Число EMM-страниц не изменилось.

Тесты: tests-host все 5 наборов зелёные, t_char вырос до 65 проверок
(резолв колонок за краем комнаты, возрождение скелета); в testkit добавлен
гард «код наехал на данные» (DATA_LOC).

Доски: TASKS.md разнесён на TASKS_OPEN/TASKS_CLOSED, закрытые баги с
разбором корней — в bug_closed.md; заведены DRAW-CHAR (отрисовка одна на всех
Char, как физика после GUARD-PHYS) и L3-COLOR (зелёная кладка уровня 3:
level_var_palettes = ресурс 20, есть в MSDOS/PRINCE.DAT).

В roomtest.c временно оставлен автостоп на падении соперника (отладка
падений скелета) — помечен ВРЕМЕННО.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 22:11:03 +03:00

9.5 KiB
Raw Blame History

tests-host — модульные тесты движка roomtest

Прогоняются под ucsim_z80, обвязка общая — testkit/ (там же объяснение, почему под z80, а не хостовым gcc, и грабли).

make host-tests                                  # из корня, вместе с остальными
make -C applications/PoP/roomtest/tests-host     # только эти
make -C applications/PoP/roomtest/tests-host t_geom

Что покрыто

набор модуль что проверяет
t_geom pop_geom.c геометрия комнаты (x_bump, y_land, y_to_row) и PRNG оригинала
t_phys pop_map.c + pop_kid.c + pop_ctrl.c покадровые трассы физики Кида: стойка, бег, шаг с Shift, разворот, присед, прыжок вверх, падение с кромки, loose-плита, разбег-прыжок через провал
t_char те же + pop_guard.c физика над Char для РАЗНЫХ персонажей: кому достаётся урон (take_hp по charid), ветки land() (Кид −1 HP с двух рядов, страж гибнет, тень цела), падение и гард по X у слота Guard, check_guard_fallout, «окна Char не путают персонажей»

Главный тест в t_geomgeom_lcg_matches_reference. LCG оригинала (s = s*214013 + 2531011) написан в pop_geom.c на ассемблере по схеме Горнера ради обхода __mullong, и заявка «бит-в-бит как в SDLPoP» до появления теста держалась только на комментарии. Тест сверяет рукописный asm с наивной 32-битной формулой на 128 шагах — и по возвращаемому значению, и по обеим половинам сида.

t_phys: характеризация, а не проверка правильности

Набор существует ради страховки под рефакторинг. Эталоны сняты с текущего билда, то есть консервируют и текущие баги — правильность по-прежнему проверяется сверкой с SDLPoP/src/. Ценность в другом: ближайший крупный шаг (перенос физики с Kid. на Char., чтобы её мог переиспользовать страж) обязан быть поведение-сохраняющим, и «трасса до == трасса после» ловит ровно тот класс ошибок, который там возможен.

Поэтому эталон переснимается только осознанно, и в коммите должно быть написано, что и почему поменялось. Молча обновлённый эталон обесценивает весь набор.

Своё дело набор сделал: перенос физики на Char (GUARD-PHYS, 2026-08-07) прошёл при неизменных трассах — 1723 проверки без единого расхождения.

Грабли раскладки: DATA_LOC

Набор линкует уже полдвижка, и его код перевалил за --data-loc 0x8000. Линкер sdld это НЕ ошибка: хвост кода (_HOME, деления из libsdcc) он молча кладёт ПОВЕРХ _DATA, после чего тест ведёт себя как «зациклился» — хотя логика ни при чём. Час на поиск стоило именно это.

Поэтому: DATA_LOC в Makefile набора (сейчас 0x9000), а host-tests.mk перед каждым прогоном проверяет CODE_LOC + l__CODE <= DATA_LOC и падает с внятным сообщением. Упёрлись снова — поднять DATA_LOC (стек в crt0_ucsim начинается с 0xFEF0).

python3 record_golden.py          # переснять все трассы
python3 record_golden.py 7 9      # только сценарии 7 и 9

Трасса — по одной записи на кадр: frame, x, y, dir, col, row, action и смещение curr_seq от SEQTBL_BASE. Кадр гоняется ровно в том порядке, что и в главном цикле roomtest.c:

pop_ctrl_tick();   // ввод -> control(): смена последовательности
kid_tick();        // play_seq: следующий кадр
pop_phys_tick();   // падение/приземление/стена
pop_loose_tick();  // досчёт тряски и снятие провалившейся плиты

Покадровость здесь принципиальна: порт seg005/seg006 — конечный автомат, и ошибка почти всегда проявляется не в одной функции, а в расхождении траектории через несколько кадров. Плюс главный риск переноса на Char — не арифметика, а то, кто владеет окном Char внутри кадра; поймать это можно только прогоном полного кадра.

Проверка, что харнесс воспроизводит устройство: сценарий stand даёт frame=15, x=114, y=118, curr_seq=0x19A3 — те же значения, что читаются из _Kid в живом MAME.

Как это заработало под ucsim

Три шва, каждый закрыт без правок продуктового кода:

  1. Таблицы анимации. pop_kid.c ходит по ним абсолютными адресами от KD_DATA_OFF (0x100) — там, куда на устройстве их приводит маппинг W0. В тесте kid_data.bin просто кладётся по 0x100 (gen_kid_blob.py), а gfx_w0_map заглушен. Следствие: код набора линкуется с --code-loc 0x1000 (CODE_LOC в Makefile), чтобы не налезть на блоб.
  2. Окружение. stubs.c — libc/libbgi (графика, память, файлы), соседние модули (pop_bg/pop_trob/pop_level/pop_redraw) и состояние стража. Заглушки не пустые там, где это меняет смысл: файловое чтение реально отдаёт kid_data.bin (иначе pop_kid_data_load тихо сдаётся, kdat_ok остаётся нулём и play_seq не делает ничего — трасса выходит из одного застывшего кадра), клавиатура отвечает по набору «нажатых» скан-кодов (так под тест попадают read_input и read_user_control, а не только диспетчер), а обращения к соседям журналируются (tk_log) — можно проверять «плита отвалилась и попросила перерисовку», а не только координаты.
  3. __banked. Модули физики помечены как банковые, SDCC генерирует на них трамплин ___sdcc_bcall_ehl, которому нужны set_bank/get_bank. В тесте память плоская — bank_stub.s отдаёт нулевой банк и пустое переключение, соблюдая контракт по регистрам.

Что харнесс НЕ покрывает и остаётся за MAME: отрисовка и окклюзия, реальные переключения банков, тайминги кадра, настоящая клавиатура.

Что нужно, чтобы двинуться дальше

  • Доступ к странице уровня. pop_level.c ходит по абсолютным адресам ((uint8_t *)(LVL_DATA_OFF + …) после gfx_w0_map). Для pop_map это не потребовалось — карту комнаты в него ИНЪЕКТИРУЮТ через pop_map_set, — но набор на сам pop_level/pop_trob в это упрётся. Решение то же, что для kid_data: положить страницу по нужному адресу и поднять CODE_LOC.
  • Сценарии из bug_closed.md. У каждой закрытой записи есть симптом и ожидаемое поведение — готовый список регрессионных кейсов, который стоит переносить в t_phys по мере надобности.
  • BUG-LOOSE-2 (гонка «уйти из комнаты раньше, чем долетит плита») — через мост MAME воспроизвести не удалось, а на уровне логики это несколько строк: заспавнить кусок, сменить комнату, тикать до приземления, проверить щебень.