Files
Sprinter-SDCC/applications/SprPoP/tests/host/record_golden.py
T
snark13 31b82661eb SprPoP: автономное приложение, выделенное из roomtest
Порт PoP переехал в applications/SprPoP — приложение, которое собирается
само: код, оригинальные данные, конверторы ресурсов и сборка внутри одной
папки.  Наружу знает единственный путь — корень тулчейна (SPRINTER_ROOT,
по умолчанию ../..).  applications/PoP/roomtest ЗАМОРОЖЕНА и остаётся
архивом закрытых задач, багов и исполненных планов.

Скопировано из applications/PoP/roomtest@4b74478.  Перенос проверен
побайтово: собранный sprpop.exe совпал с roomtest.exe того же коммита,
все 39 дисковых ресурсов и все 16 генерируемых заголовков — тоже, host-
тесты зелёные (15/15).

Раскладка:
  src/           рукописный C (roomtest.c -> sprpop.c)
  gen/           генерируемые заголовки, в репозитории
  assets/orig/   оригинальные данные игры, вне репозитория (копирайт)
  assets/packed/ то, что ложится на диск, в раскладке диска
  tools/         конверторы; все пути — в одном tools/paths.py
  build/         выход: exe, каталоги ресурсов, hdd/, промежуточные atl/

Сборка ресурсов: assets/packed и gen — версионируемые ВХОДЫ, а не то, что
пересчитывается каждым make.  Автоматика построена на ОТСУТСТВИИ файла, а
не на таймстемпах: git не хранит времена, и в свежем клоне сравнение по
времени превращалось бы в лотерею.  Недостающий ресурс или заголовок
чинится сам, рекурсивным вызовом в ветку генерации.

Музыка собирается из любого из четырёх наборов записей (make music-mp3,
music-mt32, ...); набор входит в имя stamp'а, поэтому смена набора сама
делает музыку устаревшей.  Длины реплик больше не захардкожены: упаковщик
печатает их в gen/pop_music_ticks.h, и шкала сцены выражена через них —
иначе mt32 (реплики на 6% длиннее) молча ломал катсцену.

Тулчейн: в app.mk два обратносовместимых крючка (SRC_DIR/BUILD_DIR),
HDD_IMG стал ?=; команда сборки roomtest не изменилась.  Корневой
make host-tests переключён на SprPoP.

Подгонка тайминга катсцены с принцессой (PV_MAGIC_LEAD): сцена
render-bound и идёт ~49 тиков/с вместо 60, из-за чего кода реплики
приходила раньше молнии.  Это обход, а не лечение; разбор с замерами —
docs/BUGS_OPEN.md, записи SND-PACE-DEAD, PV-RENDER-BOUND, MUS-LEFT-TEAR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 12:12:28 +03:00

73 lines
3.8 KiB
Python
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.
#!/usr/bin/env python3
"""
record_golden.py — снять эталонные трассы для t_phys.
Зачем скрипт, а не один прогон. Отчёт tcheck ограничен 768 байтами лога, а
трасс на все сценарии сильно больше. Поэтому набор умеет снимать РОВНО
ОДИН сценарий за прогон (-DSC_RECORD=<n>), а перебор делает этот скрипт:
собирает набор N раз, каждый раз забирает из отчёта имя сценария и его
трассу и пишет golden/<имя>.h.
Эталон фиксирует ТЕКУЩЕЕ поведение. Это осознанно: набор страхует
поведение-сохраняющие правки (перенос физики Kid -> Char), а не доказывает
правильность. Правильность — сверка с SDLPoP. Поэтому переснимать эталоны
можно только вместе с внятным объяснением, что и почему поменялось.
Использование:
python3 record_golden.py # снять все сценарии
python3 record_golden.py 7 9 # только эти номера
"""
import re
import subprocess
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
GOLDEN = HERE / "golden"
# Число сценариев = число TC_RUN в t_phys.c: список ведётся там, здесь его
# дублировать незачем.
NSCEN = len(re.findall(r"^\s*TC_RUN\(", (HERE / "t_phys.c").read_text(), re.M))
def record(n: int) -> str:
# make не видит смены -DSC_RECORD (в зависимостях только файлы), поэтому
# объектник набора сносим руками — иначе следующий прогон молча снимет
# трассу предыдущего сценария под новым именем.
for f in ("phys.rel", "phys.ihx"):
(HERE / "build" / f).unlink(missing_ok=True)
subprocess.run(["make", "-s", "build/phys.ihx", f"EXTRA_CFLAGS=-DSC_RECORD={n}"],
cwd=HERE, check=True)
p = subprocess.run(["make", "-s", "run-phys", f"EXTRA_CFLAGS=-DSC_RECORD={n}"],
cwd=HERE, capture_output=True, text=True)
out = p.stdout
m = re.search(r"^\s*### (\S+)\s*$", out, re.M)
if not m:
raise SystemExit(f"сценарий {n}: в отчёте нет строки '### имя'\n{out}")
name = m.group(1)
rows = re.findall(r"^\s*([0-9A-F]{18})\s*$", out, re.M)
if not rows:
raise SystemExit(f"сценарий {n} ({name}): трасса пустая\n{out}")
lines = [f"/* Эталон трассы: {name}. Снят record_golden.py — не править руками.",
" * Поля кадра: frame, x, y, dir, col, row, action, seq_lo, seq_hi. */"]
for r in rows:
lines.append(" " + ", ".join(f"0x{r[i:i+2]}" for i in range(0, 18, 2)) + ",")
GOLDEN.mkdir(exist_ok=True)
(GOLDEN / f"{name}.h").write_text("\n".join(lines) + "\n")
return f"{name}: {len(rows)} кадров"
def main() -> int:
nums = [int(a) for a in sys.argv[1:]] or list(range(1, NSCEN + 1))
for n in nums:
print(f"[{n}/{NSCEN}] " + record(n))
# Итоговая пересборка в обычном режиме, чтобы каталог не остался с
# объектниками, собранными под SC_RECORD.
subprocess.run(["make", "-s", "clean"], cwd=HERE, check=True)
print("эталоны сняты; проверить: make -C", HERE.name, "t_phys")
return 0
if __name__ == "__main__":
sys.exit(main())