Ресурсы — в архивы PBA1; тайминги сцены PV — по часам насоса CBL

ТАЙМИНГИ.  Пользователь заметил, что заклинание Джафара не совпадает с
музыкой. Замер в MAME (breakpoint на вспышке + курсор трека): реплика m53
успевала ДОИГРАТЬ до молнии, хотя по шкале оригинала между входом Джафара
и заклинанием ровно 822 тика = 13,7 с.

Причина: пейсинг считал НАШИ ожидания vsync, а не прошедшее время.
Полноэкранная копия страницы стоит больше кадра луча, и разница копилась.
Кадровые прерывания для счёта не годятся (теряются в di-окнах
акселератора), поэтому часами стал насос CBL: он идёт от расхода буфера
железом, 85,4 Гц, и ему безразлично, чем занят главный цикл. Один тик
оригинала = 57/40 тика насоса (0,07 % ошибки, без деления в кадре).
Когда звук выключен, работает прежний путь по кадрам луча.

После фикса замер даёт 229 блоков остатка m53 против расчётных 233 —
расхождение 47 мс.

АРХИВЫ.  Группы ресурсов сложены в PBA1: kid (58 атласов), pv (78),
shadow (32), bg (25), title (20), guard (10), vizier (5), skel (4). Было
232 файла на образе, стало 8 архивов плюс шесть палитр и таблицы уровней.
При цене `open` 51,4 мс против 32,6 мс за чтение 16 КБ это возвращает
секунды на каждой загрузке.

- pop_pack_arc.py печатает индексы элементов константами (ARC_<GRP>_<FILE>),
  порядок задаёт Makefile, а серии код проверяет статически;
- atlas_load разделён: atlas_attach принимает уже прочитанную страницу,
  поэтому чтение из архива не дублирует проверку магии и подготовку W0;
- имена архивов живут в pop_arc.c и адресуются номером. Строковый литерал
  лежит в rodata своего банка, и указатель на него из другого банка после
  переключения W3 показывает на чужие данные — ровно так падала загрузка
  фона (поймано брейкпоинтом на puts).

Проверено в MAME: заставка, интро и уровень 1 собираются из архивов,
Тень грузит все 32 страницы. Host-тесты: 15 наборов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 21:29:14 +03:00
parent d86adc54c4
commit 65797af44c
40 changed files with 997 additions and 244 deletions
+82 -21
View File
@@ -121,11 +121,14 @@ LVL_DATA := $(foreach n,$(LVL_NUMS),$(LVL_DIR)/res20$(n).bin)
# пламя, меч-предмет, зелья) от набора не зависит — один на оба.
BG_SETS := pop pal
BG_PAGES := 0 1 2 3 4 5 6 7 8 9
BG_DATA := $(foreach s,$(BG_SETS),\
# Двенадцать атласов на тайлсет идут подряд — код считает индекс как
# set*12 + страница, а упаковщик печатает константы в bg_arc.h.
BG_ATL := $(foreach s,$(BG_SETS),\
$(foreach n,$(BG_PAGES),$(BG_DIR)/$(s)_env$(n).atl) \
$(BG_DIR)/$(s)_wall.atl $(BG_DIR)/$(s)_fore.atl \
$(BG_DIR)/$(s)_tile.pal) \
$(BG_DIR)/$(s)_wall.atl $(BG_DIR)/$(s)_fore.atl) \
$(BG_DIR)/pop_pot.atl
BG_ARC := $(BG_DIR)/bg.arc
BG_DATA := $(BG_ARC) $(foreach s,$(BG_SETS),$(BG_DIR)/$(s)_tile.pal)
# ВСЕ 28 страниц Kid — иначе кадры торможения/разворота/приседа (image>23)
# не рисуются (отрисовка пропускает незагруженную страницу). Палитра —
# ЕДИНАЯ игровая (env+wall+kid).
@@ -140,8 +143,19 @@ KID_DATA := $(KID_PAGES) $(KID_VPAGES) $(KID_DIR)/kid.pal \
# Таблицы анимации (кадры+seqtbl) — отдельный бинарь в EMM-странице, его
# делает pop_extract_kid_data.py (не упаковщик спрайтов) — своё правило ниже.
KID_BIN := $(KID_DIR)/kid_data.bin
# АРХИВ KID. Шестьдесят файлов в одном: замер 2026-08-25 — `open` 51,4 мс
# против 32,6 мс на чтение 16 КБ, то есть на одних только открытиях набор
# Кида терял три секунды. Порядок в списке = порядок индексов, а их
# упаковщик печатает в kid_arc.h (константы ARC_KID_*).
KID_ARC := $(KID_DIR)/kid.arc
# В архив идут только атласы. Палитру читает gfx_pal_fload прямо в
# видеопамять, а kid_data.bin — host-тесты своей файловой заглушкой; ради
# двух файлов заводить обходные пути незачем.
KID_ARC_SRC := $(KID_PAGES) $(KID_VPAGES) $(KID_DIR)/sword.atl \
$(KID_DIR)/sword_v.atl
GUARD_DIR := $(CURDIR)/../poc/res/guard
GUARD_DATA := $(GUARD_DIR)/g0.atl $(GUARD_DIR)/g1.atl $(GUARD_DIR)/g2.atl \
GUARD_ATL := $(GUARD_DIR)/g0.atl $(GUARD_DIR)/g1.atl $(GUARD_DIR)/g2.atl \
$(GUARD_DIR)/g3.atl $(GUARD_DIR)/g4.atl \
$(GUARD_DIR)/g0_v.atl $(GUARD_DIR)/g1_v.atl $(GUARD_DIR)/g2_v.atl \
$(GUARD_DIR)/g3_v.atl $(GUARD_DIR)/g4_v.atl
@@ -150,14 +164,14 @@ GUARD_DATA := $(GUARD_DIR)/g0.atl $(GUARD_DIR)/g1.atl $(GUARD_DIR)/g2.atl \
SKEL_DIR := $(CURDIR)/../poc/res/skel
# Зеркального набора у скелета НЕТ: зелье инверсии — уровень 9, там обычный
# страж (tbl_guard_type[9] = 0). См. pop_pack_guard.py.
SKEL_DATA := $(SKEL_DIR)/g0.atl $(SKEL_DIR)/g1.atl $(SKEL_DIR)/g2.atl \
SKEL_ATL := $(SKEL_DIR)/g0.atl $(SKEL_DIR)/g1.atl $(SKEL_DIR)/g2.atl \
$(SKEL_DIR)/g3.atl
# Джафар — тоже отдельный набор (VIZIER.DAT): его грузит уровень 13
# (tbl_guard_type == 3). Кадров столько же, сколько у стража (34), но
# палитра своя, из файла набора: curr_guard_color для не-стражей = 0.
# Зеркального набора нет по той же причине, что у скелета.
VIZIER_DIR := $(CURDIR)/../poc/res/vizier
VIZIER_DATA := $(VIZIER_DIR)/g0.atl $(VIZIER_DIR)/g1.atl $(VIZIER_DIR)/g2.atl \
VIZIER_ATL := $(VIZIER_DIR)/g0.atl $(VIZIER_DIR)/g1.atl $(VIZIER_DIR)/g2.atl \
$(VIZIER_DIR)/g3.atl $(VIZIER_DIR)/g4.atl
# ТЕНЬ — свой набор, запечённый упаковщиком (наложение спрайта на себя со
@@ -165,9 +179,21 @@ VIZIER_DATA := $(VIZIER_DIR)/g0.atl $(VIZIER_DIR)/g1.atl $(VIZIER_DIR)/g2.atl \
# половины в одном наборе: sk* — кадры вне боя (спрайты Кида), sf* — кадры
# 150..189 (SHADOW.DAT, это тоже графика Кида, а не стража).
SHADOW_DIR := $(CURDIR)/../poc/res/shadow
SHADOW_DATA := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(SHADOW_DIR)/sk$(n).atl) \
SHADOW_ATL := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(SHADOW_DIR)/sk$(n).atl) \
$(foreach n,0 1 2 3,$(SHADOW_DIR)/sf$(n).atl)
# Четыре архива, а не один: имена внутри наборов совпадают (g0.atl есть и у
# стража, и у скелета, и у Джафара), а константы индексов упаковщик делает
# из имени файла. Наборы всё равно грузятся поодиночке — по типу уровня.
GUARD_ARC := $(GUARD_DIR)/guard.arc
SKEL_ARC := $(SKEL_DIR)/skel.arc
VIZIER_ARC := $(VIZIER_DIR)/vizier.arc
SHADOW_ARC := $(SHADOW_DIR)/shadow.arc
GUARD_DATA := $(GUARD_ARC)
SKEL_DATA := $(SKEL_ARC)
VIZIER_DATA := $(VIZIER_ARC)
SHADOW_DATA := $(SHADOW_ARC)
# ЗВУКОВЫЕ ЭФФЕКТЫ — оригинальная оцифровка, приведённая к частоте CBL
# (10 937,5 Гц, 8 бит моно). 8 страниц по 16 КБ; см. ../docs/sound_plan.md.
SND_DIR := $(CURDIR)/../poc/res/sound
@@ -200,8 +226,9 @@ FONT_DATA := $(FONT_ATL)
TITLE_DIR := $(CURDIR)/../poc/res/title
TITLE_SCENES := 0 1 2 3
TITLE_PARTS := 0 1 2 3 4
TITLE_DATA := $(TITLE_DIR)/title.pal \
$(foreach s,$(TITLE_SCENES),$(foreach p,$(TITLE_PARTS),$(TITLE_DIR)/t$(s)_$(p).atl))
TITLE_ATL := $(foreach s,$(TITLE_SCENES),$(foreach p,$(TITLE_PARTS),$(TITLE_DIR)/t$(s)_$(p).atl))
TITLE_ARC := $(TITLE_DIR)/title.arc
TITLE_DATA := $(TITLE_DIR)/title.pal $(TITLE_ARC)
# PV/INTRO: story и fallback-стадии режутся на полосы. Для настоящего PV
# intro есть ещё чистый фон `b0_*` и шесть <=16K actor-атласов: они остаются
# в EMM на время сцены, а не перечитывают полный 320x200 кадр с HDD.
@@ -213,12 +240,14 @@ PV_ANIM := $(foreach p,$(PV_PARTS),$(PV_DIR)/b0_$(p).atl) \
$(PV_DIR)/a0.atl $(PV_DIR)/a1.atl $(PV_DIR)/a2.atl \
$(PV_DIR)/j0.atl $(PV_DIR)/j1.atl \
$(PV_DIR)/j2.atl $(PV_DIR)/h0.atl $(PV_DIR)/t0.atl
PV_DATA := $(PV_DIR)/story.pal $(PV_DIR)/pv.pal \
PV_ATL := \
$(foreach s,$(PV_STORY),$(foreach p,$(PV_PARTS),$(PV_DIR)/s$(s)_$(p).atl)) \
$(foreach s,$(PV_SCENES),$(foreach p,$(PV_PARTS),$(PV_DIR)/p$(s)_$(p).atl)) \
$(PV_ANIM)
PV_ARC := $(PV_DIR)/pv.arc
PV_DATA := $(PV_DIR)/story.pal $(PV_DIR)/pv.pal $(PV_ARC)
EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(GUARD_DATA) $(SKEL_DATA) \
EXTRA_DATA := $(BG_DATA) $(KID_ARC) $(KID_DIR)/kid.pal $(KID_BIN) $(GUARD_DATA) $(SKEL_DATA) \
$(VIZIER_DATA) $(SHADOW_DATA) $(SND_DATA) $(MUS_DATA) $(FONT_DATA) $(TITLE_DATA) $(PV_DATA) $(LVL_DATA)
# Образ HDD для MAME: ресурсы раскладываются ПО КАТАЛОГАМ (8.3, как в DSS),
# чтобы корень диска не зарастал десятками .atl.
@@ -231,7 +260,7 @@ EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(GUARD_DATA) $(SKEL_DATA) \
# LEVELS\ — уровни
HDD_PACK_ARGS := $(EXAMPLE).exe \
$(foreach f,$(BG_DATA),BG:$(f)) \
$(foreach f,$(KID_DATA) $(KID_BIN),KID:$(f)) \
KID:$(KID_ARC) KID:$(KID_DIR)/kid.pal KID:$(KID_BIN) \
$(foreach f,$(GUARD_DATA),GUARD:$(f)) \
$(foreach f,$(SKEL_DATA),SKEL:$(f)) \
$(foreach f,$(VIZIER_DATA),VIZIER:$(f)) \
@@ -271,31 +300,31 @@ $(RESOURCE_STAMP_DIR):
$(BG_STAMP): $(TC)/pop_pack_bg.py $(TC)/render_room.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_bg.py dungeon && python3 pop_pack_bg.py palace
touch $@
$(BG_DATA): $(BG_STAMP)
$(BG_ATL) $(BG_DIR)/pop_tile.pal $(BG_DIR)/pal_tile.pal: $(BG_STAMP)
@test -f $@ || { $(MAKE) -B $(BG_STAMP); test -f $@; }
$(GUARD_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_guard.py
touch $@
$(GUARD_DATA): $(GUARD_STAMP)
$(GUARD_ATL) $(GUARD_DIR)/guard.pal: $(GUARD_STAMP)
@test -f $@ || { $(MAKE) -B $(GUARD_STAMP); test -f $@; }
$(SKEL_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_guard.py SKEL
touch $@
$(SKEL_DATA): $(SKEL_STAMP)
$(SKEL_ATL): $(SKEL_STAMP)
@test -f $@ || { $(MAKE) -B $(SKEL_STAMP); test -f $@; }
$(VIZIER_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_guard.py VIZIER
touch $@
$(VIZIER_DATA): $(VIZIER_STAMP)
$(VIZIER_ATL): $(VIZIER_STAMP)
@test -f $@ || { $(MAKE) -B $(VIZIER_STAMP); test -f $@; }
$(SHADOW_STAMP): $(TC)/pop_pack_shadow.py $(TC)/pop_pack_bg.py $(TC)/pop_pack_kid.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_shadow.py
touch $@
$(SHADOW_DATA) pop_shadow_atlas.h: $(SHADOW_STAMP)
$(SHADOW_ATL) pop_shadow_atlas.h: $(SHADOW_STAMP)
@test -f $@ || { $(MAKE) -B $(SHADOW_STAMP); test -f $@; }
$(SOUND_STAMP): $(TC)/pop_pack_sound.py | $(RESOURCE_STAMP_DIR)
@@ -306,6 +335,38 @@ $(SND_PARTS) pop_sound_tbl.h: $(SOUND_STAMP)
$(SND_ARC): $(SND_PARTS) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py $@ $(SND_PARTS)
$(GUARD_ARC) guard_arc.h: $(GUARD_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header guard_arc.h --prefix GUARD \
$(GUARD_ARC) $(GUARD_ATL)
$(SKEL_ARC) skel_arc.h: $(SKEL_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header skel_arc.h --prefix SKEL \
$(SKEL_ARC) $(SKEL_ATL)
$(VIZIER_ARC) vizier_arc.h: $(VIZIER_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header vizier_arc.h --prefix VIZIER \
$(VIZIER_ARC) $(VIZIER_ATL)
$(SHADOW_ARC) shadow_arc.h: $(SHADOW_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header shadow_arc.h --prefix SHADOW \
$(SHADOW_ARC) $(SHADOW_ATL)
$(BG_ARC) bg_arc.h: $(BG_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header bg_arc.h --prefix BG \
$(BG_ARC) $(BG_ATL)
$(TITLE_ARC) title_arc.h: $(TITLE_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header title_arc.h --prefix TITLE \
$(TITLE_ARC) $(TITLE_ATL)
$(PV_ARC) pv_arc.h: $(PV_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header pv_arc.h --prefix PV \
$(PV_ARC) $(PV_ATL)
$(KID_ARC) kid_arc.h: $(KID_ARC_SRC) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header kid_arc.h --prefix KID \
$(KID_ARC) $(KID_ARC_SRC)
$(MUSIC_STAMP): $(TC)/pop_pack_music.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_music.py $(MUS_IDS)
touch $@
@@ -339,7 +400,7 @@ $(TITLE_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_title.py \
$(CURDIR)/../SDLPoP/data/TITLE/res55.png | $(RESOURCE_STAMP_DIR)
cd $(PROJ_ROOT) && python3 toolchain/pop_pack_title.py
touch $@
$(TITLE_DATA): $(TITLE_STAMP)
$(TITLE_ATL) $(TITLE_DIR)/title.pal: $(TITLE_STAMP)
@test -f $@ || { $(MAKE) -B $(TITLE_STAMP); test -f $@; }
$(PV_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_intro.py \
@@ -366,13 +427,13 @@ $(PV_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_intro.py \
$(foreach n,151 152 153 154 155 156 157 158 159,$(CURDIR)/../SDLPoP/data/PRINCE/res$(n).png) | $(RESOURCE_STAMP_DIR)
cd $(PROJ_ROOT) && python3 toolchain/pop_pack_intro.py
touch $@
$(PV_DATA): $(PV_STAMP)
$(PV_ATL) $(PV_DIR)/story.pal $(PV_DIR)/pv.pal: $(PV_STAMP)
@test -f $@ || { $(MAKE) -B $(PV_STAMP); test -f $@; }
# Явные цели позволяют собирать ресурсы без `make -B`, который по смыслу
# принудительно пересобирает также libc, libbgi и само приложение.
resources-bg: $(BG_DATA)
resources-kid: $(KID_DATA) $(KID_BIN)
resources-kid: $(KID_ARC)
resources-actors: $(GUARD_DATA) $(SKEL_DATA) $(VIZIER_DATA) $(SHADOW_DATA)
resources-sound: $(SND_DATA)
resources-music: $(MUS_DATA)
@@ -399,7 +460,7 @@ hdd: resources
# широкий: цена лишней полной сборки ниже риска получить stale-бинарник.
LOCAL_HEADERS := $(wildcard *.h)
$(EXAMPLE).exe: Makefile $(PROJ_ROOT)/app.mk $(BANKED_SRCS) $(LOCAL_HEADERS) \
$(BG_DATA) $(KID_DATA) $(KID_BIN) $(FONT_DATA) $(TITLE_DATA) $(PV_DATA) $(LVL_DATA)
$(BG_DATA) $(KID_ARC) $(FONT_DATA) $(TITLE_DATA) $(PV_DATA) $(LVL_DATA)
clean-resources:
rm -rf $(RESOURCE_STAMP_DIR)