Files
Sprinter-SDCC/testkit
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
..

testkit — модульные тесты под ucsim_z80

Обвязка для быстрых тестов plain-C кода: секунды вместо прогона в MAME, без образа диска и без эмуляции всего Sprinter.

Сама по себе к подпроектам не привязана — так можно тестировать и модули libc, и логику приложений. Наборы тестов лежат рядом с кодом, который они проверяют, а не здесь:

каталог что проверяет
testkit/ самопроверка обвязки (t_selftest.c)
applications/PoP/roomtest/tests-host/ движок PoP
make host-tests            # из корня: все каталоги с наборами
make -C testkit            # только самопроверка обвязки
make -C <каталог> t_geom   # один набор

Почему под z80, а не хостовым gcc

Первым порывом было собирать те же .c обычным gcc — но у SDCC z80 int 16 бит, а у хоста 32, и это расходится не в объявлениях, а в выражениях: integer promotion повышает операнды до int независимо от того, объявлены они как uint8_t или uint16_t.

uint16_t a = 60000, b = 10000;
if (a + b > 65000) {  }   // z80: 4464 > 65000 → false
                           // host: 70000 > 65000 → true

Перевод всего кода на intN_t эту разницу НЕ убирает. Убирает её только исполнение с z80-семантикой. Отсюда ucsim_z80 — он идёт в комплекте нашего же SDCC (third_party/sdcc/bin), так что новых зависимостей нет.

Побочные выгоды того же решения:

  • проверяется кодогенерация SDCC, а не только исходник (см. memory sdcc_z80_cmp_store_a_bug);
  • модули с inline-asm тестируются как есть — например pop_geom.c, где LCG оригинала написан на ассемблере ради обхода __mullong.

t_selftest.c начинается с TC_EQ(sizeof(int), 2) — если обвязка однажды переедет на хостовый компилятор, это заметят сразу.

Как устроено

файл роль
crt0_ucsim.s голый z80-старт: SP, зануление _DATA/_BSS, main, halt
tcheck.h/.c микро-фреймворк: итог кладётся в tc_result в ОЗУ
run_ucsim.py гоняет ucsim, дампит tc_result, печатает отчёт
host-tests.mk общие правила сборки и прогона

Вывода через printf нет: тестовый бинарь линкуется без Sprinter-libc (там ESTEX, BIOS, файлы — в ucsim этого нет). Результат складывается в структуру в ОЗУ, а раннер читает её командой dump после остановки по halt. Через ucsim-simif сознательно не идём: номера его команд плавают от версии к версии, а halt + dump работают везде.

Завести наборы в новом месте

Создать каталог рядом с проверяемым кодом и положить туда Makefile:

TESTKIT    := $(abspath $(CURDIR)/../../testkit)
ENGINE_DIR := $(abspath $(CURDIR)/..)     # где лежат модули под тестом
OBJS_geom  := build/eng_pop_geom.rel      # что доложить в набор t_geom
include $(TESTKIT)/host-tests.mk

Добавить каталог в HOST_TEST_DIRS корневого Makefile. Сам набор — t_<имя>.c с main(), который зовёт TC_RUN; wildcard подхватит его сам.

#include "tcheck.h"

TC_TEST(что_проверяем)
{
    TC_EQ(получено, ожидание);
    TC_TRUE(условие);
}

int main(void) { TC_RUN(что_проверяем); return 0; }

Грабли

  • .area _HEADER (ABS) объявлять ровно один раз и первой. Если продублировать её в списке порядка областей, sdas заведёт _HEADER0, и линкер положит стартовый код не по 0x0000 (уезжал за _DATA на 0x830A, и с PC=0 исполнялся мусор).
  • Файл грузить командой file, а не аргументом командной строки: при передаче аргументом ucsim успевает выполнить run до загрузки.
  • Константы SDCC сворачивает. TC_EQ над константным выражением проверит компилятор, а не код (warning 110). Входные данные для арифметических тестов держать в static volatile.
  • Если тест зациклился, раннер снимет его по таймауту (60 с по умолчанию) и скажет об этом явно.

Чего эти тесты не поймают

Банки и W-окна, тайминги, корректность блитов, поведение реальной клавиатуры, отрисовку — для этого остаётся MAME (docs/mame-autotest.md).

Отдельный класс — баги порядка вызовов между модулями: общее глобальное состояние, затираемое тем, кто отработал последним. Такое ловит не unit-тест, а инвариант, вкомпилированный в safe-сборку.