Ресурсы — в архивы 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:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -121,11 +121,14 @@ LVL_DATA := $(foreach n,$(LVL_NUMS),$(LVL_DIR)/res20$(n).bin)
|
|||||||
# пламя, меч-предмет, зелья) от набора не зависит — один на оба.
|
# пламя, меч-предмет, зелья) от набора не зависит — один на оба.
|
||||||
BG_SETS := pop pal
|
BG_SETS := pop pal
|
||||||
BG_PAGES := 0 1 2 3 4 5 6 7 8 9
|
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) \
|
$(foreach n,$(BG_PAGES),$(BG_DIR)/$(s)_env$(n).atl) \
|
||||||
$(BG_DIR)/$(s)_wall.atl $(BG_DIR)/$(s)_fore.atl \
|
$(BG_DIR)/$(s)_wall.atl $(BG_DIR)/$(s)_fore.atl) \
|
||||||
$(BG_DIR)/$(s)_tile.pal) \
|
|
||||||
$(BG_DIR)/pop_pot.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)
|
# ВСЕ 28 страниц Kid — иначе кадры торможения/разворота/приседа (image>23)
|
||||||
# не рисуются (отрисовка пропускает незагруженную страницу). Палитра —
|
# не рисуются (отрисовка пропускает незагруженную страницу). Палитра —
|
||||||
# ЕДИНАЯ игровая (env+wall+kid).
|
# ЕДИНАЯ игровая (env+wall+kid).
|
||||||
@@ -140,8 +143,19 @@ KID_DATA := $(KID_PAGES) $(KID_VPAGES) $(KID_DIR)/kid.pal \
|
|||||||
# Таблицы анимации (кадры+seqtbl) — отдельный бинарь в EMM-странице, его
|
# Таблицы анимации (кадры+seqtbl) — отдельный бинарь в EMM-странице, его
|
||||||
# делает pop_extract_kid_data.py (не упаковщик спрайтов) — своё правило ниже.
|
# делает pop_extract_kid_data.py (не упаковщик спрайтов) — своё правило ниже.
|
||||||
KID_BIN := $(KID_DIR)/kid_data.bin
|
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_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)/g3.atl $(GUARD_DIR)/g4.atl \
|
||||||
$(GUARD_DIR)/g0_v.atl $(GUARD_DIR)/g1_v.atl $(GUARD_DIR)/g2_v.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
|
$(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
|
SKEL_DIR := $(CURDIR)/../poc/res/skel
|
||||||
# Зеркального набора у скелета НЕТ: зелье инверсии — уровень 9, там обычный
|
# Зеркального набора у скелета НЕТ: зелье инверсии — уровень 9, там обычный
|
||||||
# страж (tbl_guard_type[9] = 0). См. pop_pack_guard.py.
|
# страж (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
|
$(SKEL_DIR)/g3.atl
|
||||||
# Джафар — тоже отдельный набор (VIZIER.DAT): его грузит уровень 13
|
# Джафар — тоже отдельный набор (VIZIER.DAT): его грузит уровень 13
|
||||||
# (tbl_guard_type == 3). Кадров столько же, сколько у стража (34), но
|
# (tbl_guard_type == 3). Кадров столько же, сколько у стража (34), но
|
||||||
# палитра своя, из файла набора: curr_guard_color для не-стражей = 0.
|
# палитра своя, из файла набора: curr_guard_color для не-стражей = 0.
|
||||||
# Зеркального набора нет по той же причине, что у скелета.
|
# Зеркального набора нет по той же причине, что у скелета.
|
||||||
VIZIER_DIR := $(CURDIR)/../poc/res/vizier
|
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
|
$(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* — кадры
|
# половины в одном наборе: sk* — кадры вне боя (спрайты Кида), sf* — кадры
|
||||||
# 150..189 (SHADOW.DAT, это тоже графика Кида, а не стража).
|
# 150..189 (SHADOW.DAT, это тоже графика Кида, а не стража).
|
||||||
SHADOW_DIR := $(CURDIR)/../poc/res/shadow
|
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)
|
$(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
|
# ЗВУКОВЫЕ ЭФФЕКТЫ — оригинальная оцифровка, приведённая к частоте CBL
|
||||||
# (10 937,5 Гц, 8 бит моно). 8 страниц по 16 КБ; см. ../docs/sound_plan.md.
|
# (10 937,5 Гц, 8 бит моно). 8 страниц по 16 КБ; см. ../docs/sound_plan.md.
|
||||||
SND_DIR := $(CURDIR)/../poc/res/sound
|
SND_DIR := $(CURDIR)/../poc/res/sound
|
||||||
@@ -200,8 +226,9 @@ FONT_DATA := $(FONT_ATL)
|
|||||||
TITLE_DIR := $(CURDIR)/../poc/res/title
|
TITLE_DIR := $(CURDIR)/../poc/res/title
|
||||||
TITLE_SCENES := 0 1 2 3
|
TITLE_SCENES := 0 1 2 3
|
||||||
TITLE_PARTS := 0 1 2 3 4
|
TITLE_PARTS := 0 1 2 3 4
|
||||||
TITLE_DATA := $(TITLE_DIR)/title.pal \
|
TITLE_ATL := $(foreach s,$(TITLE_SCENES),$(foreach p,$(TITLE_PARTS),$(TITLE_DIR)/t$(s)_$(p).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
|
# PV/INTRO: story и fallback-стадии режутся на полосы. Для настоящего PV
|
||||||
# intro есть ещё чистый фон `b0_*` и шесть <=16K actor-атласов: они остаются
|
# intro есть ещё чистый фон `b0_*` и шесть <=16K actor-атласов: они остаются
|
||||||
# в EMM на время сцены, а не перечитывают полный 320x200 кадр с HDD.
|
# в 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)/a0.atl $(PV_DIR)/a1.atl $(PV_DIR)/a2.atl \
|
||||||
$(PV_DIR)/j0.atl $(PV_DIR)/j1.atl \
|
$(PV_DIR)/j0.atl $(PV_DIR)/j1.atl \
|
||||||
$(PV_DIR)/j2.atl $(PV_DIR)/h0.atl $(PV_DIR)/t0.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_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)) \
|
$(foreach s,$(PV_SCENES),$(foreach p,$(PV_PARTS),$(PV_DIR)/p$(s)_$(p).atl)) \
|
||||||
$(PV_ANIM)
|
$(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)
|
$(VIZIER_DATA) $(SHADOW_DATA) $(SND_DATA) $(MUS_DATA) $(FONT_DATA) $(TITLE_DATA) $(PV_DATA) $(LVL_DATA)
|
||||||
# Образ HDD для MAME: ресурсы раскладываются ПО КАТАЛОГАМ (8.3, как в DSS),
|
# Образ HDD для MAME: ресурсы раскладываются ПО КАТАЛОГАМ (8.3, как в DSS),
|
||||||
# чтобы корень диска не зарастал десятками .atl.
|
# чтобы корень диска не зарастал десятками .atl.
|
||||||
@@ -231,7 +260,7 @@ EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(GUARD_DATA) $(SKEL_DATA) \
|
|||||||
# LEVELS\ — уровни
|
# LEVELS\ — уровни
|
||||||
HDD_PACK_ARGS := $(EXAMPLE).exe \
|
HDD_PACK_ARGS := $(EXAMPLE).exe \
|
||||||
$(foreach f,$(BG_DATA),BG:$(f)) \
|
$(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,$(GUARD_DATA),GUARD:$(f)) \
|
||||||
$(foreach f,$(SKEL_DATA),SKEL:$(f)) \
|
$(foreach f,$(SKEL_DATA),SKEL:$(f)) \
|
||||||
$(foreach f,$(VIZIER_DATA),VIZIER:$(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)
|
$(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
|
cd $(TC) && python3 pop_pack_bg.py dungeon && python3 pop_pack_bg.py palace
|
||||||
touch $@
|
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 $@; }
|
@test -f $@ || { $(MAKE) -B $(BG_STAMP); test -f $@; }
|
||||||
|
|
||||||
$(GUARD_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
|
$(GUARD_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
|
||||||
cd $(TC) && python3 pop_pack_guard.py
|
cd $(TC) && python3 pop_pack_guard.py
|
||||||
touch $@
|
touch $@
|
||||||
$(GUARD_DATA): $(GUARD_STAMP)
|
$(GUARD_ATL) $(GUARD_DIR)/guard.pal: $(GUARD_STAMP)
|
||||||
@test -f $@ || { $(MAKE) -B $(GUARD_STAMP); test -f $@; }
|
@test -f $@ || { $(MAKE) -B $(GUARD_STAMP); test -f $@; }
|
||||||
|
|
||||||
$(SKEL_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
|
$(SKEL_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
|
||||||
cd $(TC) && python3 pop_pack_guard.py SKEL
|
cd $(TC) && python3 pop_pack_guard.py SKEL
|
||||||
touch $@
|
touch $@
|
||||||
$(SKEL_DATA): $(SKEL_STAMP)
|
$(SKEL_ATL): $(SKEL_STAMP)
|
||||||
@test -f $@ || { $(MAKE) -B $(SKEL_STAMP); test -f $@; }
|
@test -f $@ || { $(MAKE) -B $(SKEL_STAMP); test -f $@; }
|
||||||
|
|
||||||
$(VIZIER_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
|
$(VIZIER_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
|
||||||
cd $(TC) && python3 pop_pack_guard.py VIZIER
|
cd $(TC) && python3 pop_pack_guard.py VIZIER
|
||||||
touch $@
|
touch $@
|
||||||
$(VIZIER_DATA): $(VIZIER_STAMP)
|
$(VIZIER_ATL): $(VIZIER_STAMP)
|
||||||
@test -f $@ || { $(MAKE) -B $(VIZIER_STAMP); test -f $@; }
|
@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)
|
$(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
|
cd $(TC) && python3 pop_pack_shadow.py
|
||||||
touch $@
|
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 $@; }
|
@test -f $@ || { $(MAKE) -B $(SHADOW_STAMP); test -f $@; }
|
||||||
|
|
||||||
$(SOUND_STAMP): $(TC)/pop_pack_sound.py | $(RESOURCE_STAMP_DIR)
|
$(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
|
$(SND_ARC): $(SND_PARTS) $(TC)/pop_pack_arc.py
|
||||||
python3 $(TC)/pop_pack_arc.py $@ $(SND_PARTS)
|
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)
|
$(MUSIC_STAMP): $(TC)/pop_pack_music.py | $(RESOURCE_STAMP_DIR)
|
||||||
cd $(TC) && python3 pop_pack_music.py $(MUS_IDS)
|
cd $(TC) && python3 pop_pack_music.py $(MUS_IDS)
|
||||||
touch $@
|
touch $@
|
||||||
@@ -339,7 +400,7 @@ $(TITLE_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_title.py \
|
|||||||
$(CURDIR)/../SDLPoP/data/TITLE/res55.png | $(RESOURCE_STAMP_DIR)
|
$(CURDIR)/../SDLPoP/data/TITLE/res55.png | $(RESOURCE_STAMP_DIR)
|
||||||
cd $(PROJ_ROOT) && python3 toolchain/pop_pack_title.py
|
cd $(PROJ_ROOT) && python3 toolchain/pop_pack_title.py
|
||||||
touch $@
|
touch $@
|
||||||
$(TITLE_DATA): $(TITLE_STAMP)
|
$(TITLE_ATL) $(TITLE_DIR)/title.pal: $(TITLE_STAMP)
|
||||||
@test -f $@ || { $(MAKE) -B $(TITLE_STAMP); test -f $@; }
|
@test -f $@ || { $(MAKE) -B $(TITLE_STAMP); test -f $@; }
|
||||||
|
|
||||||
$(PV_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_intro.py \
|
$(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)
|
$(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
|
cd $(PROJ_ROOT) && python3 toolchain/pop_pack_intro.py
|
||||||
touch $@
|
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 $@; }
|
@test -f $@ || { $(MAKE) -B $(PV_STAMP); test -f $@; }
|
||||||
|
|
||||||
# Явные цели позволяют собирать ресурсы без `make -B`, который по смыслу
|
# Явные цели позволяют собирать ресурсы без `make -B`, который по смыслу
|
||||||
# принудительно пересобирает также libc, libbgi и само приложение.
|
# принудительно пересобирает также libc, libbgi и само приложение.
|
||||||
resources-bg: $(BG_DATA)
|
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-actors: $(GUARD_DATA) $(SKEL_DATA) $(VIZIER_DATA) $(SHADOW_DATA)
|
||||||
resources-sound: $(SND_DATA)
|
resources-sound: $(SND_DATA)
|
||||||
resources-music: $(MUS_DATA)
|
resources-music: $(MUS_DATA)
|
||||||
@@ -399,7 +460,7 @@ hdd: resources
|
|||||||
# широкий: цена лишней полной сборки ниже риска получить stale-бинарник.
|
# широкий: цена лишней полной сборки ниже риска получить stale-бинарник.
|
||||||
LOCAL_HEADERS := $(wildcard *.h)
|
LOCAL_HEADERS := $(wildcard *.h)
|
||||||
$(EXAMPLE).exe: Makefile $(PROJ_ROOT)/app.mk $(BANKED_SRCS) $(LOCAL_HEADERS) \
|
$(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:
|
clean-resources:
|
||||||
rm -rf $(RESOURCE_STAMP_DIR)
|
rm -rf $(RESOURCE_STAMP_DIR)
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/* bg_arc.h — индексы в архиве BG. СГЕНЕРИРОВАН
|
||||||
|
* (toolchain/pop_pack_arc.py), руками не править. */
|
||||||
|
#ifndef BG_ARC_H
|
||||||
|
#define BG_ARC_H
|
||||||
|
|
||||||
|
#define ARC_BG_COUNT 25
|
||||||
|
|
||||||
|
#define ARC_BG_POP_ENV0 0
|
||||||
|
#define ARC_BG_POP_ENV1 1
|
||||||
|
#define ARC_BG_POP_ENV2 2
|
||||||
|
#define ARC_BG_POP_ENV3 3
|
||||||
|
#define ARC_BG_POP_ENV4 4
|
||||||
|
#define ARC_BG_POP_ENV5 5
|
||||||
|
#define ARC_BG_POP_ENV6 6
|
||||||
|
#define ARC_BG_POP_ENV7 7
|
||||||
|
#define ARC_BG_POP_ENV8 8
|
||||||
|
#define ARC_BG_POP_ENV9 9
|
||||||
|
#define ARC_BG_POP_WALL 10
|
||||||
|
#define ARC_BG_POP_FORE 11
|
||||||
|
#define ARC_BG_PAL_ENV0 12
|
||||||
|
#define ARC_BG_PAL_ENV1 13
|
||||||
|
#define ARC_BG_PAL_ENV2 14
|
||||||
|
#define ARC_BG_PAL_ENV3 15
|
||||||
|
#define ARC_BG_PAL_ENV4 16
|
||||||
|
#define ARC_BG_PAL_ENV5 17
|
||||||
|
#define ARC_BG_PAL_ENV6 18
|
||||||
|
#define ARC_BG_PAL_ENV7 19
|
||||||
|
#define ARC_BG_PAL_ENV8 20
|
||||||
|
#define ARC_BG_PAL_ENV9 21
|
||||||
|
#define ARC_BG_PAL_WALL 22
|
||||||
|
#define ARC_BG_PAL_FORE 23
|
||||||
|
#define ARC_BG_POP_POT 24
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/* guard_arc.h — индексы в архиве GUARD. СГЕНЕРИРОВАН
|
||||||
|
* (toolchain/pop_pack_arc.py), руками не править. */
|
||||||
|
#ifndef GUARD_ARC_H
|
||||||
|
#define GUARD_ARC_H
|
||||||
|
|
||||||
|
#define ARC_GUARD_COUNT 10
|
||||||
|
|
||||||
|
#define ARC_GUARD_G0 0
|
||||||
|
#define ARC_GUARD_G1 1
|
||||||
|
#define ARC_GUARD_G2 2
|
||||||
|
#define ARC_GUARD_G3 3
|
||||||
|
#define ARC_GUARD_G4 4
|
||||||
|
#define ARC_GUARD_G0_V 5
|
||||||
|
#define ARC_GUARD_G1_V 6
|
||||||
|
#define ARC_GUARD_G2_V 7
|
||||||
|
#define ARC_GUARD_G3_V 8
|
||||||
|
#define ARC_GUARD_G4_V 9
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/* kid_arc.h — индексы в архиве KID. СГЕНЕРИРОВАН
|
||||||
|
* (toolchain/pop_pack_arc.py), руками не править. */
|
||||||
|
#ifndef KID_ARC_H
|
||||||
|
#define KID_ARC_H
|
||||||
|
|
||||||
|
#define ARC_KID_COUNT 58
|
||||||
|
|
||||||
|
#define ARC_KID_KID0 0
|
||||||
|
#define ARC_KID_KID1 1
|
||||||
|
#define ARC_KID_KID2 2
|
||||||
|
#define ARC_KID_KID3 3
|
||||||
|
#define ARC_KID_KID4 4
|
||||||
|
#define ARC_KID_KID5 5
|
||||||
|
#define ARC_KID_KID6 6
|
||||||
|
#define ARC_KID_KID7 7
|
||||||
|
#define ARC_KID_KID8 8
|
||||||
|
#define ARC_KID_KID9 9
|
||||||
|
#define ARC_KID_KID10 10
|
||||||
|
#define ARC_KID_KID11 11
|
||||||
|
#define ARC_KID_KID12 12
|
||||||
|
#define ARC_KID_KID13 13
|
||||||
|
#define ARC_KID_KID14 14
|
||||||
|
#define ARC_KID_KID15 15
|
||||||
|
#define ARC_KID_KID16 16
|
||||||
|
#define ARC_KID_KID17 17
|
||||||
|
#define ARC_KID_KID18 18
|
||||||
|
#define ARC_KID_KID19 19
|
||||||
|
#define ARC_KID_KID20 20
|
||||||
|
#define ARC_KID_KID21 21
|
||||||
|
#define ARC_KID_KID22 22
|
||||||
|
#define ARC_KID_KID23 23
|
||||||
|
#define ARC_KID_KID24 24
|
||||||
|
#define ARC_KID_KID25 25
|
||||||
|
#define ARC_KID_KID26 26
|
||||||
|
#define ARC_KID_KID27 27
|
||||||
|
#define ARC_KID_KID0_V 28
|
||||||
|
#define ARC_KID_KID1_V 29
|
||||||
|
#define ARC_KID_KID2_V 30
|
||||||
|
#define ARC_KID_KID3_V 31
|
||||||
|
#define ARC_KID_KID4_V 32
|
||||||
|
#define ARC_KID_KID5_V 33
|
||||||
|
#define ARC_KID_KID6_V 34
|
||||||
|
#define ARC_KID_KID7_V 35
|
||||||
|
#define ARC_KID_KID8_V 36
|
||||||
|
#define ARC_KID_KID9_V 37
|
||||||
|
#define ARC_KID_KID10_V 38
|
||||||
|
#define ARC_KID_KID11_V 39
|
||||||
|
#define ARC_KID_KID12_V 40
|
||||||
|
#define ARC_KID_KID13_V 41
|
||||||
|
#define ARC_KID_KID14_V 42
|
||||||
|
#define ARC_KID_KID15_V 43
|
||||||
|
#define ARC_KID_KID16_V 44
|
||||||
|
#define ARC_KID_KID17_V 45
|
||||||
|
#define ARC_KID_KID18_V 46
|
||||||
|
#define ARC_KID_KID19_V 47
|
||||||
|
#define ARC_KID_KID20_V 48
|
||||||
|
#define ARC_KID_KID21_V 49
|
||||||
|
#define ARC_KID_KID22_V 50
|
||||||
|
#define ARC_KID_KID23_V 51
|
||||||
|
#define ARC_KID_KID24_V 52
|
||||||
|
#define ARC_KID_KID25_V 53
|
||||||
|
#define ARC_KID_KID26_V 54
|
||||||
|
#define ARC_KID_KID27_V 55
|
||||||
|
#define ARC_KID_SWORD 56
|
||||||
|
#define ARC_KID_SWORD_V 57
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -13,6 +13,27 @@
|
|||||||
#include <sprinter_mem.h>
|
#include <sprinter_mem.h>
|
||||||
#include "pop_arc.h"
|
#include "pop_arc.h"
|
||||||
|
|
||||||
|
/* Пути 8.3, как требует DSS. Держим их ЗДЕСЬ: строка обязана лежать в том
|
||||||
|
* же банке, что и код, который её читает (см. pop_arc.h). */
|
||||||
|
static const char *const arc_path[POP_ARC_IDS] = {
|
||||||
|
"BG\\bg.arc",
|
||||||
|
"KID\\kid.arc",
|
||||||
|
"GUARD\\guard.arc",
|
||||||
|
"SKEL\\skel.arc",
|
||||||
|
"VIZIER\\vizier.arc",
|
||||||
|
"SHADOW\\shadow.arc",
|
||||||
|
"TITLE\\title.arc",
|
||||||
|
"PV\\pv.arc",
|
||||||
|
"SND\\snd.arc"
|
||||||
|
};
|
||||||
|
|
||||||
|
int8_t pop_arc_open_id(pop_arc_t *a, uint8_t id,
|
||||||
|
uint8_t *tbl, uint8_t tbl_max) __banked
|
||||||
|
{
|
||||||
|
if (id >= POP_ARC_IDS) return -1;
|
||||||
|
return pop_arc_open(a, arc_path[id], tbl, tbl_max);
|
||||||
|
}
|
||||||
|
|
||||||
int8_t pop_arc_open(pop_arc_t *a, const char *path,
|
int8_t pop_arc_open(pop_arc_t *a, const char *path,
|
||||||
uint8_t *tbl, uint8_t tbl_max) __banked
|
uint8_t *tbl, uint8_t tbl_max) __banked
|
||||||
{
|
{
|
||||||
@@ -68,6 +89,21 @@ int pop_arc_read(pop_arc_t *a, const uint8_t *tbl, uint8_t idx, uint16_t skip,
|
|||||||
return bank_read_page(a->fd, page, off, len);
|
return bank_read_page(a->fd, page, off, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int8_t pop_arc_atlas(pop_arc_t *a, const uint8_t *tbl, uint8_t idx,
|
||||||
|
atlas_t *at) __banked
|
||||||
|
{
|
||||||
|
uint8_t blk, page;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
blk = mem_alloc_pages(1);
|
||||||
|
if (!blk) return -1;
|
||||||
|
page = mem_get_page(blk, 0);
|
||||||
|
n = pop_arc_read(a, tbl, idx, 0, page, 0, 0);
|
||||||
|
/* Дальше всё общее с atlas_load: магия 'SPA1', разбор заголовка и
|
||||||
|
* gfx_w0_page_prepare. При ошибке atlas_attach сам отпускает блок. */
|
||||||
|
return (int8_t)atlas_attach(at, blk, page, n);
|
||||||
|
}
|
||||||
|
|
||||||
void pop_arc_close(pop_arc_t *a) __banked
|
void pop_arc_close(pop_arc_t *a) __banked
|
||||||
{
|
{
|
||||||
if (a->fd >= 0) close(a->fd);
|
if (a->fd >= 0) close(a->fd);
|
||||||
|
|||||||
@@ -26,17 +26,43 @@
|
|||||||
#define POP_ARC_H
|
#define POP_ARC_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <sprite.h> /* atlas_t */
|
||||||
|
|
||||||
#define POP_ARC_HEADER 512
|
#define POP_ARC_HEADER 512
|
||||||
#define POP_ARC_SECTOR 512
|
#define POP_ARC_SECTOR 512
|
||||||
#define POP_ARC_MAX 126 /* (512-8)/4 */
|
#define POP_ARC_MAX 126 /* (512-8)/4 */
|
||||||
#define POP_ARC_REC 4 /* байт на запись таблицы */
|
#define POP_ARC_REC 4 /* байт на запись таблицы */
|
||||||
|
|
||||||
typedef struct {
|
/* Имя у структуры есть намеренно: так на неё можно сослаться из заголовков,
|
||||||
|
* которым незачем тянуть весь pop_arc.h (pop_ui.h). */
|
||||||
|
typedef struct pop_arc_s {
|
||||||
int fd;
|
int fd;
|
||||||
uint8_t count;
|
uint8_t count;
|
||||||
} pop_arc_t;
|
} pop_arc_t;
|
||||||
|
|
||||||
|
/* ИМЕНА АРХИВОВ ЗНАЕТ САМ МОДУЛЬ, и это не вкусовщина: строковый литерал
|
||||||
|
* лежит в rodata СВОЕГО банка, а pop_arc.c живёт в банке 8. Указатель на
|
||||||
|
* литерал из другого банка после переключения W3 показывает на чужие
|
||||||
|
* данные — ровно так «BG\bg.arc» превращался в мусор и загрузка фона
|
||||||
|
* падала (поймано 2026-08-26). Поэтому наружу отдаём НОМЕР архива. */
|
||||||
|
typedef enum {
|
||||||
|
POP_ARC_BG = 0,
|
||||||
|
POP_ARC_KID,
|
||||||
|
POP_ARC_GUARD,
|
||||||
|
POP_ARC_SKEL,
|
||||||
|
POP_ARC_VIZIER,
|
||||||
|
POP_ARC_SHADOW,
|
||||||
|
POP_ARC_TITLE,
|
||||||
|
POP_ARC_PV,
|
||||||
|
POP_ARC_SND,
|
||||||
|
POP_ARC_IDS
|
||||||
|
} pop_arc_id_t;
|
||||||
|
|
||||||
|
/* Открыть архив по номеру: то же, что pop_arc_open, но имя берётся из
|
||||||
|
* таблицы внутри модуля. */
|
||||||
|
int8_t pop_arc_open_id(pop_arc_t *a, uint8_t id,
|
||||||
|
uint8_t *tbl, uint8_t tbl_max) __banked;
|
||||||
|
|
||||||
/* Открыть архив и вычитать таблицу в буфер вызывающего (count*4 байт).
|
/* Открыть архив и вычитать таблицу в буфер вызывающего (count*4 байт).
|
||||||
* tbl_max — сколько записей помещается в буфер.
|
* tbl_max — сколько записей помещается в буфер.
|
||||||
* Возврат: число записей; -1 — не открылся, не PBA1 либо таблица не влезла. */
|
* Возврат: число записей; -1 — не открылся, не PBA1 либо таблица не влезла. */
|
||||||
@@ -54,4 +80,11 @@ int pop_arc_read(pop_arc_t *a, const uint8_t *tbl, uint8_t idx, uint16_t skip,
|
|||||||
|
|
||||||
void pop_arc_close(pop_arc_t *a) __banked;
|
void pop_arc_close(pop_arc_t *a) __banked;
|
||||||
|
|
||||||
|
/* Атлас ПРЯМО ИЗ АРХИВА: выделить страницу, вычитать в неё элемент idx и
|
||||||
|
* отдать libbgi (atlas_attach — проверка магии + подготовка страницы к
|
||||||
|
* маппингу в W0). Полный эквивалент atlas_load, только источник другой.
|
||||||
|
* 0 — OK, -1 — ошибка (страница освобождена). */
|
||||||
|
int8_t pop_arc_atlas(pop_arc_t *a, const uint8_t *tbl, uint8_t idx,
|
||||||
|
atlas_t *at) __banked;
|
||||||
|
|
||||||
#endif /* POP_ARC_H */
|
#endif /* POP_ARC_H */
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
#include <graphics.h>
|
#include <graphics.h>
|
||||||
#include <gfx.h>
|
#include <gfx.h>
|
||||||
#include <sprite.h>
|
#include <sprite.h>
|
||||||
|
#include "pop_arc.h"
|
||||||
|
#include "guard_arc.h"
|
||||||
|
#include "skel_arc.h"
|
||||||
|
#include "vizier_arc.h"
|
||||||
#include "pop_guard.h"
|
#include "pop_guard.h"
|
||||||
#include "pop_kid.h"
|
#include "pop_kid.h"
|
||||||
#include "_pop_kdraw.h"
|
#include "_pop_kdraw.h"
|
||||||
@@ -107,29 +111,28 @@ pop_cdraw_t pop_cd[POP_CH_N];
|
|||||||
* грузит. Уровень БЕЗ соперников (type < 0) не грузит ничего. */
|
* грузит. Уровень БЕЗ соперников (type < 0) не грузит ничего. */
|
||||||
int pop_guard_load(void) __banked
|
int pop_guard_load(void) __banked
|
||||||
{
|
{
|
||||||
static const char *const guard_names[GUARD_PAGES] = {
|
uint8_t tbl[ARC_GUARD_COUNT * POP_ARC_REC]; /* самый крупный из трёх */
|
||||||
"GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl",
|
pop_arc_t arc;
|
||||||
"GUARD\\g3.atl", "GUARD\\g4.atl"
|
|
||||||
};
|
|
||||||
static const char *const skel_names[GUARD_PAGES] = {
|
|
||||||
"SKEL\\g0.atl", "SKEL\\g1.atl", "SKEL\\g2.atl",
|
|
||||||
"SKEL\\g3.atl", 0
|
|
||||||
};
|
|
||||||
/* Джафар (уровень 13, tbl_guard_type == 3) — VIZIER.DAT: кадров столько
|
|
||||||
* же, сколько у стража, страниц пять. */
|
|
||||||
static const char *const vizier_names[GUARD_PAGES] = {
|
|
||||||
"VIZIER\\g0.atl", "VIZIER\\g1.atl", "VIZIER\\g2.atl",
|
|
||||||
"VIZIER\\g3.atl", "VIZIER\\g4.atl"
|
|
||||||
};
|
|
||||||
const char *const *names;
|
|
||||||
int8_t type = pop_level_guard_type(pop_current_level);
|
int8_t type = pop_level_guard_type(pop_current_level);
|
||||||
uint8_t i;
|
uint8_t i, pages, count, id;
|
||||||
|
|
||||||
if (type < 0) return 0; /* соперников на уровне нет */
|
if (type < 0) return 0; /* соперников на уровне нет */
|
||||||
names = (type == 2) ? skel_names : (type == 3) ? vizier_names : guard_names;
|
/* Наборы разложены по своим архивам, а не в один: имена внутри у них
|
||||||
for (i = 0; i < GUARD_PAGES; i++) {
|
* совпадают (g0.atl у всех троих), да и грузится всегда ровно один. */
|
||||||
if (names[i] == 0) break;
|
if (type == 2) { id = POP_ARC_SKEL; count = ARC_SKEL_COUNT;
|
||||||
if (atlas_load(&gp[i], names[i]) != 0) return -1;
|
pages = ARC_SKEL_COUNT; }
|
||||||
|
else if (type == 3) { id = POP_ARC_VIZIER; count = ARC_VIZIER_COUNT;
|
||||||
|
pages = ARC_VIZIER_COUNT; }
|
||||||
|
else { id = POP_ARC_GUARD; count = ARC_GUARD_COUNT;
|
||||||
|
pages = GUARD_PAGES; }
|
||||||
|
if (pages > GUARD_PAGES) pages = GUARD_PAGES;
|
||||||
|
if (pop_arc_open_id(&arc, id, tbl, count) < 0) return -1;
|
||||||
|
for (i = 0; i < pages; i++)
|
||||||
|
if (pop_arc_atlas(&arc, tbl, i, &gp[i]) != 0) {
|
||||||
|
pop_arc_close(&arc);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
pop_arc_close(&arc);
|
||||||
g_ok = 1;
|
g_ok = 1;
|
||||||
/* Цвет: у скелета он СВОЙ (палитра из его набора), у стража подменяется
|
/* Цвет: у скелета он СВОЙ (палитра из его набора), у стража подменяется
|
||||||
* из res10.bin по guards_color — это делает pop_guard_set_palette при
|
* из res10.bin по guards_color — это делает pop_guard_set_palette при
|
||||||
@@ -143,24 +146,29 @@ int pop_guard_load(void) __banked
|
|||||||
* потому что gp[] и выбор GUARD/SKEL живут именно тут. */
|
* потому что gp[] и выбор GUARD/SKEL живут именно тут. */
|
||||||
int pop_guard_vflip_load(void) __banked
|
int pop_guard_vflip_load(void) __banked
|
||||||
{
|
{
|
||||||
static const char *const guard_v[GUARD_PAGES] = {
|
uint8_t tbl[ARC_GUARD_COUNT * POP_ARC_REC];
|
||||||
"GUARD\\g0_v.atl", "GUARD\\g1_v.atl", "GUARD\\g2_v.atl",
|
pop_arc_t arc;
|
||||||
"GUARD\\g3_v.atl", "GUARD\\g4_v.atl"
|
|
||||||
};
|
|
||||||
static const char *const skel_v[GUARD_PAGES] = {
|
|
||||||
"SKEL\\g0_v.atl", "SKEL\\g1_v.atl", "SKEL\\g2_v.atl",
|
|
||||||
"SKEL\\g3_v.atl", 0
|
|
||||||
};
|
|
||||||
const char *const *names;
|
|
||||||
int8_t type = pop_level_guard_type(pop_current_level);
|
int8_t type = pop_level_guard_type(pop_current_level);
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
if (type < 0 || !g_ok) return 0; /* соперников на уровне нет */
|
if (type < 0 || !g_ok) return 0; /* соперников на уровне нет */
|
||||||
names = (type == 2) ? skel_v : guard_v;
|
/* Зеркала есть ТОЛЬКО у обычного стража: зелье инверсии — уровень 9, а
|
||||||
|
* там tbl_guard_type = 0 (у скелета и Джафара своих *_v нет вовсе). */
|
||||||
|
if (type != 0 && type != 1) return 0;
|
||||||
|
if (pop_arc_open_id(&arc, POP_ARC_GUARD, tbl, ARC_GUARD_COUNT) < 0)
|
||||||
|
return -1;
|
||||||
for (i = 0; i < GUARD_PAGES; i++) {
|
for (i = 0; i < GUARD_PAGES; i++) {
|
||||||
if (names[i] == 0) break;
|
atlas_t m;
|
||||||
if (pop_vflip_add(&gp[i], names[i]) != 0) return -1;
|
|
||||||
|
if (pop_arc_atlas(&arc, tbl, (uint8_t)(ARC_GUARD_G0_V + i), &m) != 0 ||
|
||||||
|
pop_vflip_take(&gp[i], &m) != 0) {
|
||||||
|
res = -1;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return 0;
|
}
|
||||||
|
pop_arc_close(&arc);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_guard_free(void) __banked
|
void pop_guard_free(void) __banked
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
#include "pop_shadow.h"
|
#include "pop_shadow.h"
|
||||||
#include "pop_sfx.h"
|
#include "pop_sfx.h"
|
||||||
#include "pop_music.h"
|
#include "pop_music.h"
|
||||||
|
#include "pop_arc.h"
|
||||||
|
#include "pv_arc.h"
|
||||||
#include "pop_cutscene.h"
|
#include "pop_cutscene.h"
|
||||||
#include "pop_intro.h"
|
#include "pop_intro.h"
|
||||||
#include "pop_pace.h" /* pop_wait_edge */
|
#include "pop_pace.h" /* pop_wait_edge */
|
||||||
@@ -85,31 +87,15 @@ enum {
|
|||||||
INTRO_PAL_PV = 1
|
INTRO_PAL_PV = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
/* 0..4 — TITLE/res41 + тексты 42/43/45/44 и чистая рамка; 5..12 —
|
/* Экраны PV лежат в одном архиве по пять полос подряд, ровно в том же
|
||||||
* PV/res951 + персонажи.
|
* порядке, в каком идут номера сцен: сначала пять текстовых (story
|
||||||
* 8.3-имена намеренно короткие: DSS не получает длинные path-компоненты. */
|
* res41 + тексты 42/43/45/44 и чистая рамка), затем восемь стадий комнаты
|
||||||
static const char * const intro_part[13][INTRO_PARTS] = {
|
* принцессы (res951 + персонажи) и чистый фон b0. Поэтому имён здесь нет
|
||||||
{ "PV\\s0_0.atl", "PV\\s0_1.atl", "PV\\s0_2.atl", "PV\\s0_3.atl", "PV\\s0_4.atl" },
|
* — индекс первой полосы экрана считается умножением. */
|
||||||
{ "PV\\s1_0.atl", "PV\\s1_1.atl", "PV\\s1_2.atl", "PV\\s1_3.atl", "PV\\s1_4.atl" },
|
#if (ARC_PV_P0_0 - ARC_PV_S0_0) != 5 * INTRO_PARTS
|
||||||
{ "PV\\s2_0.atl", "PV\\s2_1.atl", "PV\\s2_2.atl", "PV\\s2_3.atl", "PV\\s2_4.atl" },
|
#error "экраны PV в архиве лежат не по пять полос подряд"
|
||||||
{ "PV\\s3_0.atl", "PV\\s3_1.atl", "PV\\s3_2.atl", "PV\\s3_3.atl", "PV\\s3_4.atl" },
|
#endif
|
||||||
{ "PV\\s4_0.atl", "PV\\s4_1.atl", "PV\\s4_2.atl", "PV\\s4_3.atl", "PV\\s4_4.atl" },
|
#define INTRO_SCENES 13
|
||||||
{ "PV\\p0_0.atl", "PV\\p0_1.atl", "PV\\p0_2.atl", "PV\\p0_3.atl", "PV\\p0_4.atl" },
|
|
||||||
{ "PV\\p1_0.atl", "PV\\p1_1.atl", "PV\\p1_2.atl", "PV\\p1_3.atl", "PV\\p1_4.atl" },
|
|
||||||
{ "PV\\p2_0.atl", "PV\\p2_1.atl", "PV\\p2_2.atl", "PV\\p2_3.atl", "PV\\p2_4.atl" },
|
|
||||||
{ "PV\\p3_0.atl", "PV\\p3_1.atl", "PV\\p3_2.atl", "PV\\p3_3.atl", "PV\\p3_4.atl" },
|
|
||||||
{ "PV\\p4_0.atl", "PV\\p4_1.atl", "PV\\p4_2.atl", "PV\\p4_3.atl", "PV\\p4_4.atl" },
|
|
||||||
{ "PV\\p5_0.atl", "PV\\p5_1.atl", "PV\\p5_2.atl", "PV\\p5_3.atl", "PV\\p5_4.atl" },
|
|
||||||
{ "PV\\p6_0.atl", "PV\\p6_1.atl", "PV\\p6_2.atl", "PV\\p6_3.atl", "PV\\p6_4.atl" },
|
|
||||||
{ "PV\\p7_0.atl", "PV\\p7_1.atl", "PV\\p7_2.atl", "PV\\p7_3.atl", "PV\\p7_4.atl" }
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Чистая Princess-room без персонажей. Shadow-копии обеих страниц содержат
|
|
||||||
* именно этот фон: каждый анимационный кадр получает его accelerator-копией,
|
|
||||||
* а полупрозрачные actor-спрайты не оставляют следа. */
|
|
||||||
static const char * const pv_base_part[INTRO_PARTS] = {
|
|
||||||
"PV\\b0_0.atl", "PV\\b0_1.atl", "PV\\b0_2.atl", "PV\\b0_3.atl", "PV\\b0_4.atl"
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Две текстовые половины show_title(). Между ними PV теперь идёт отдельным
|
/* Две текстовые половины show_title(). Между ними PV теперь идёт отдельным
|
||||||
* покадровым renderer-ом, а не четырьмя полными статичными композициями. */
|
* покадровым renderer-ом, а не четырьмя полными статичными композициями. */
|
||||||
@@ -225,13 +211,12 @@ static uint8_t intro_scene;
|
|||||||
static uint8_t intro_pending_pal;
|
static uint8_t intro_pending_pal;
|
||||||
static uint8_t intro_pal_pending;
|
static uint8_t intro_pal_pending;
|
||||||
|
|
||||||
/* Таблицы intro_part/pv_base_part расположены в этом же банке 11. Общий
|
/* Полосовой загрузчик рядом со своими вызовами: общий из банка 9 звать
|
||||||
* pop_screen_blit_atlas_strips находится в банке 9, и передавать ему
|
* можно (индексы и таблица — числа на стеке W2, а не указатели на W3), но
|
||||||
* указатель на W3-данные нельзя: после bcall тот же адрес уже видит другой
|
* лишний банк→банк на каждую полосу тут ни к чему.
|
||||||
* физический банк. Держим короткий полосовой loader рядом с его таблицами.
|
* pop_arc_atlas сам временно занимает W3 только на время чтения. */
|
||||||
* atlas_load сам временно занимает W3 только ПОСЛЕ open(path). */
|
static int intro_blit_arc(pop_arc_t *arc, const uint8_t *tbl,
|
||||||
static int intro_blit_atlas_strips(const char * const *parts,
|
uint8_t base, uint8_t count, int x, int y)
|
||||||
uint8_t count, int x, int y)
|
|
||||||
{
|
{
|
||||||
uint8_t part;
|
uint8_t part;
|
||||||
|
|
||||||
@@ -240,7 +225,8 @@ static int intro_blit_atlas_strips(const char * const *parts,
|
|||||||
atlas_t at;
|
atlas_t at;
|
||||||
const uint8_t *img;
|
const uint8_t *img;
|
||||||
|
|
||||||
if (atlas_load(&at, parts[part]) != 0) return -1;
|
if (pop_arc_atlas(arc, tbl, (uint8_t)(base + part), &at) != 0)
|
||||||
|
return -1;
|
||||||
gfx_w0_map(at.page);
|
gfx_w0_map(at.page);
|
||||||
img = (const uint8_t *)atlas_image(&at, 0);
|
img = (const uint8_t *)atlas_image(&at, 0);
|
||||||
gfx_blit(x, y, img);
|
gfx_blit(x, y, img);
|
||||||
@@ -251,6 +237,30 @@ static int intro_blit_atlas_strips(const char * const *parts,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Экран целиком: открыть архив, вылить пять полос, закрыть. Один `open`
|
||||||
|
* на экран вместо пяти — при 51,4 мс за открытие это больше половины
|
||||||
|
* времени показа. */
|
||||||
|
static int intro_blit_screen(uint8_t base, int y)
|
||||||
|
{
|
||||||
|
uint8_t tbl[ARC_PV_COUNT * POP_ARC_REC];
|
||||||
|
pop_arc_t arc;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (pop_arc_open_id(&arc, POP_ARC_PV, tbl, ARC_PV_COUNT) < 0) return -1;
|
||||||
|
res = intro_blit_arc(&arc, tbl, base, INTRO_PARTS, 0, y);
|
||||||
|
pop_arc_close(&arc);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Один актёрский атлас PV из архива. Открытие на каждый — всё равно
|
||||||
|
* дешевле, чем прежние отдельные файлы (тот же `open`, но без поиска имени
|
||||||
|
* в каталоге); там, где их несколько подряд, архив открывается один раз. */
|
||||||
|
static int pv_atlas(pop_arc_t *arc, const uint8_t *tbl, uint8_t idx,
|
||||||
|
atlas_t *at)
|
||||||
|
{
|
||||||
|
return pop_arc_atlas(arc, tbl, idx, at) != 0 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- «Пропустить сцену» — по НОВОМУ нажатию, а не по удержанию -------- *
|
/* ---- «Пропустить сцену» — по НОВОМУ нажатию, а не по удержанию -------- *
|
||||||
*
|
*
|
||||||
* Клавиша, которой закончили уровень (движение Кида в дверь, чит Shift+L,
|
* Клавиша, которой закончили уровень (движение Кида в дверь, чит Shift+L,
|
||||||
@@ -280,14 +290,12 @@ static uint8_t intro_skip_requested(intro_skip_t *sk)
|
|||||||
|
|
||||||
static int intro_draw_page(uint8_t scene, uint8_t page)
|
static int intro_draw_page(uint8_t scene, uint8_t page)
|
||||||
{
|
{
|
||||||
if (scene >= sizeof(intro_part) / sizeof(intro_part[0])) return -1;
|
if (scene >= INTRO_SCENES) return -1;
|
||||||
gfx_set_draw_page(page);
|
gfx_set_draw_page(page);
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
pop_screen_fill(BLACK);
|
pop_screen_fill(BLACK);
|
||||||
if (intro_blit_atlas_strips(intro_part[scene], INTRO_PARTS,
|
return intro_blit_screen((uint8_t)(ARC_PV_S0_0 + scene * INTRO_PARTS),
|
||||||
0, POP_YOFF) != 0)
|
POP_YOFF);
|
||||||
return -1;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int intro_draw(uint8_t scene)
|
static int intro_draw(uint8_t scene)
|
||||||
@@ -356,9 +364,7 @@ static int intro_pv_draw_base(void)
|
|||||||
gfx_set_draw_page(0);
|
gfx_set_draw_page(0);
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
pop_screen_fill(BLACK);
|
pop_screen_fill(BLACK);
|
||||||
if (intro_blit_atlas_strips(pv_base_part, INTRO_PARTS,
|
if (intro_blit_screen(ARC_PV_B0_0, POP_YOFF) != 0) return -1;
|
||||||
0, POP_YOFF) != 0)
|
|
||||||
return -1;
|
|
||||||
gfx_set_draw_page(1);
|
gfx_set_draw_page(1);
|
||||||
full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT;
|
full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT;
|
||||||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||||||
@@ -695,7 +701,13 @@ static int intro_pv_animated(void)
|
|||||||
uint16_t tick;
|
uint16_t tick;
|
||||||
uint16_t frame_end;
|
uint16_t frame_end;
|
||||||
uint16_t render_tick;
|
uint16_t render_tick;
|
||||||
uint16_t beam = 0; /* потрачено кадров луча с начала сцены */
|
uint16_t beam = 0; /* запасной путь: кадров луча ждали */
|
||||||
|
uint16_t snd_done = 0; /* тиков насоса прошло с начала сцены */
|
||||||
|
uint16_t snd_want = 0; /* сколько их должно было пройти */
|
||||||
|
uint16_t snd_acc = 0; /* дробный остаток пересчёта 57/40 */
|
||||||
|
uint8_t snd_prev = 0;
|
||||||
|
uint8_t snd_pace = 0;
|
||||||
|
uint8_t period;
|
||||||
uint8_t anim_step = 0;
|
uint8_t anim_step = 0;
|
||||||
uint8_t torch_left = 1;
|
uint8_t torch_left = 1;
|
||||||
uint8_t torch_right = 6;
|
uint8_t torch_right = 6;
|
||||||
@@ -717,21 +729,21 @@ static int intro_pv_animated(void)
|
|||||||
* временная EMM-страница. Поэтому фон готовится до постоянных
|
* временная EMM-страница. Поэтому фон готовится до постоянных
|
||||||
* actor-атласов: порядок сохраняет независимость загрузчика полос от
|
* actor-атласов: порядок сохраняет независимость загрузчика полос от
|
||||||
* числа одновременно живущих анимационных ресурсов. */
|
* числа одновременно живущих анимационных ресурсов. */
|
||||||
if (atlas_load(&princess, "PV\\a0.atl") != 0) return -1;
|
/* Шесть атласов сцены — из ОДНОГО открытого архива. */
|
||||||
if (atlas_load(&jaffar0, "PV\\j0.atl") != 0) {
|
{
|
||||||
goto free_princess;
|
uint8_t tbl[ARC_PV_COUNT * POP_ARC_REC];
|
||||||
}
|
pop_arc_t arc;
|
||||||
if (atlas_load(&jaffar1, "PV\\j1.atl") != 0) {
|
int8_t bad;
|
||||||
goto free_jaffar0;
|
|
||||||
}
|
if (pop_arc_open_id(&arc, POP_ARC_PV, tbl, ARC_PV_COUNT) < 0) return -1;
|
||||||
if (atlas_load(&jaffar2, "PV\\j2.atl") != 0) {
|
bad = (int8_t)(pv_atlas(&arc, tbl, ARC_PV_A0, &princess) != 0);
|
||||||
goto free_jaffar1;
|
if (!bad) bad = (int8_t)(pv_atlas(&arc, tbl, ARC_PV_J0, &jaffar0) != 0);
|
||||||
}
|
if (!bad) bad = (int8_t)(pv_atlas(&arc, tbl, ARC_PV_J1, &jaffar1) != 0);
|
||||||
if (atlas_load(&hourglass, "PV\\h0.atl") != 0) {
|
if (!bad) bad = (int8_t)(pv_atlas(&arc, tbl, ARC_PV_J2, &jaffar2) != 0);
|
||||||
goto free_jaffar2;
|
if (!bad) bad = (int8_t)(pv_atlas(&arc, tbl, ARC_PV_H0, &hourglass) != 0);
|
||||||
}
|
if (!bad) bad = (int8_t)(pv_atlas(&arc, tbl, ARC_PV_T0, &torch) != 0);
|
||||||
if (atlas_load(&torch, "PV\\t0.atl") != 0) {
|
pop_arc_close(&arc);
|
||||||
goto free_hourglass;
|
if (bad) return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pop_pal_fade_in(INTRO_FADE);
|
pop_pal_fade_in(INTRO_FADE);
|
||||||
@@ -740,6 +752,18 @@ static int intro_pv_animated(void)
|
|||||||
* Полноэкранная accel-копия кадра насосу не мешает: leaf режет DI
|
* Полноэкранная accel-копия кадра насосу не мешает: leaf режет DI
|
||||||
* бандами по 16 строк. */
|
* бандами по 16 строк. */
|
||||||
(void)pop_sfx_start();
|
(void)pop_sfx_start();
|
||||||
|
/* ЧЕМ МЕРИТЬ ВРЕМЯ. Пока сцена была немой, кадры просто считали
|
||||||
|
* ожидания vsync — и этого хватало. С музыкой выяснилось, что так
|
||||||
|
* считаются НАШИ ожидания, а не прошедшее время: полноэкранная копия
|
||||||
|
* страницы (~0,55 млн тактов) съедает больше кадра луча, и сцена
|
||||||
|
* незаметно отстаёт — в MAME молния била уже ПОСЛЕ конца реплики.
|
||||||
|
* Часы берём у насоса CBL: он идёт от расхода буфера железом, 85,4 Гц,
|
||||||
|
* и ему безразлично, чем занят главный цикл. Если звук выключен,
|
||||||
|
* часы стоят — тогда работает прежний путь по кадрам луча. */
|
||||||
|
snd_prev = pop_snd_tick;
|
||||||
|
gfx_wait_vsync();
|
||||||
|
snd_pace = (uint8_t)(pop_snd_tick != snd_prev); /* часы реально идут? */
|
||||||
|
snd_prev = pop_snd_tick;
|
||||||
result = 0;
|
result = 0;
|
||||||
princess_actor.x = 120;
|
princess_actor.x = 120;
|
||||||
princess_actor.frame = 11; /* Pstand, facing left */
|
princess_actor.frame = 11; /* Pstand, facing left */
|
||||||
@@ -763,7 +787,8 @@ static int intro_pv_animated(void)
|
|||||||
* его исходные 6/8/7 тиков, снаружи draw_frame держит результат
|
* его исходные 6/8/7 тиков, снаружи draw_frame держит результат
|
||||||
* четыре физических кадра — единые номинальные 12,5 FPS. */
|
* четыре физических кадра — единые номинальные 12,5 FPS. */
|
||||||
flash = 0;
|
flash = 0;
|
||||||
frame_end = (uint16_t)(tick + pv_seq_period(tick));
|
period = pv_seq_period(tick);
|
||||||
|
frame_end = (uint16_t)(tick + period);
|
||||||
if (frame_end > PV_ANIM_TICKS) frame_end = PV_ANIM_TICKS;
|
if (frame_end > PV_ANIM_TICKS) frame_end = PV_ANIM_TICKS;
|
||||||
do {
|
do {
|
||||||
pv_princess_step(&princess_actor, tick);
|
pv_princess_step(&princess_actor, tick);
|
||||||
@@ -811,9 +836,23 @@ static int intro_pv_animated(void)
|
|||||||
* анимации не стоят. */
|
* анимации не стоят. */
|
||||||
if (pop_music_loading()) (void)pop_music_load_step();
|
if (pop_music_loading()) (void)pop_music_load_step();
|
||||||
/* ПЕЙСИНГ ПО ОРИГИНАЛЬНОЙ ШКАЛЕ. Кадр cutscene — 6/8/7 тиков по
|
/* ПЕЙСИНГ ПО ОРИГИНАЛЬНОЙ ШКАЛЕ. Кадр cutscene — 6/8/7 тиков по
|
||||||
* 1/60 с, наш экран идёт на 1/50: без пересчёта сцена растянулась
|
* 1/60 с; один тик = 85,4/60 = 1,424 тика насоса, и 57/40 = 1,425
|
||||||
* бы на пятую часть, и реплики разъехались бы с картинкой. */
|
* повторяет это с точностью 0,07 % (30 мс на всю сцену) без
|
||||||
{
|
* деления в кадре и без 32-битной арифметики. */
|
||||||
|
if (snd_pace) {
|
||||||
|
snd_acc += (uint16_t)period * 57u;
|
||||||
|
while (snd_acc >= 40u) { snd_acc -= 40u; snd_want++; }
|
||||||
|
for (;;) {
|
||||||
|
uint8_t now = pop_snd_tick;
|
||||||
|
|
||||||
|
/* Разность в uint8_t: счётчик переполняется каждые 3 с, а
|
||||||
|
* заглядываем мы в него не реже раза в кадр. */
|
||||||
|
snd_done += (uint8_t)(now - snd_prev);
|
||||||
|
snd_prev = now;
|
||||||
|
if (snd_done >= snd_want) break;
|
||||||
|
gfx_wait_vsync();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
uint16_t target = POP_T60(tick);
|
uint16_t target = POP_T60(tick);
|
||||||
|
|
||||||
while (beam < target) { gfx_wait_vsync(); beam++; }
|
while (beam < target) { gfx_wait_vsync(); beam++; }
|
||||||
@@ -826,17 +865,11 @@ static int intro_pv_animated(void)
|
|||||||
if (result) { pop_music_free(); pop_sfx_pause(); }
|
if (result) { pop_music_free(); pop_sfx_pause(); }
|
||||||
if (!result) pop_pal_fade_out(INTRO_FADE);
|
if (!result) pop_pal_fade_out(INTRO_FADE);
|
||||||
|
|
||||||
free_torch:
|
|
||||||
atlas_free(&torch);
|
atlas_free(&torch);
|
||||||
free_hourglass:
|
|
||||||
atlas_free(&hourglass);
|
atlas_free(&hourglass);
|
||||||
free_jaffar2:
|
|
||||||
atlas_free(&jaffar2);
|
atlas_free(&jaffar2);
|
||||||
free_jaffar1:
|
|
||||||
atlas_free(&jaffar1);
|
atlas_free(&jaffar1);
|
||||||
free_jaffar0:
|
|
||||||
atlas_free(&jaffar0);
|
atlas_free(&jaffar0);
|
||||||
free_princess:
|
|
||||||
atlas_free(&princess);
|
atlas_free(&princess);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -876,8 +909,17 @@ static int pre_princess_animated(pop_pre_cutscene_t scene)
|
|||||||
* рисуется GFX_BANK_SPRITE и потому не оседает в этом источнике. */
|
* рисуется GFX_BANK_SPRITE и потому не оседает в этом источнике. */
|
||||||
if (intro_draw_page(picture, 0) != 0 ||
|
if (intro_draw_page(picture, 0) != 0 ||
|
||||||
intro_draw_page(picture, 1) != 0) return -1;
|
intro_draw_page(picture, 1) != 0) return -1;
|
||||||
if (atlas_load(&hourglass, "PV\\h0.atl") != 0) return -1;
|
{
|
||||||
if (atlas_load(&torch, "PV\\t0.atl") != 0) goto free_hourglass_pre;
|
uint8_t tbl[ARC_PV_COUNT * POP_ARC_REC];
|
||||||
|
pop_arc_t arc;
|
||||||
|
int8_t bad;
|
||||||
|
|
||||||
|
if (pop_arc_open_id(&arc, POP_ARC_PV, tbl, ARC_PV_COUNT) < 0) return -1;
|
||||||
|
bad = (int8_t)(pv_atlas(&arc, tbl, ARC_PV_H0, &hourglass) != 0);
|
||||||
|
if (!bad) bad = (int8_t)(pv_atlas(&arc, tbl, ARC_PV_T0, &torch) != 0);
|
||||||
|
pop_arc_close(&arc);
|
||||||
|
if (bad) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
pop_screen_present(1);
|
pop_screen_present(1);
|
||||||
pop_pal_fade_in(INTRO_FADE);
|
pop_pal_fade_in(INTRO_FADE);
|
||||||
@@ -912,7 +954,6 @@ static int pre_princess_animated(pop_pre_cutscene_t scene)
|
|||||||
if (!result) pop_pal_fade_out(INTRO_FADE);
|
if (!result) pop_pal_fade_out(INTRO_FADE);
|
||||||
|
|
||||||
atlas_free(&torch);
|
atlas_free(&torch);
|
||||||
free_hourglass_pre:
|
|
||||||
atlas_free(&hourglass);
|
atlas_free(&hourglass);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1026,20 +1067,35 @@ static void cut_free(void)
|
|||||||
|
|
||||||
static int cut_load(void)
|
static int cut_load(void)
|
||||||
{
|
{
|
||||||
|
uint8_t tbl[ARC_PV_COUNT * POP_ARC_REC];
|
||||||
|
pop_arc_t arc;
|
||||||
|
|
||||||
cut_atlases = 0;
|
cut_atlases = 0;
|
||||||
if (atlas_load(&cut_pv1, "PV\\a0.atl") != 0) return -1;
|
if (pop_arc_open_id(&arc, POP_ARC_PV, tbl, ARC_PV_COUNT) < 0) return -1;
|
||||||
|
if (pv_atlas(&arc, tbl, ARC_PV_A0, &cut_pv1) != 0) {
|
||||||
|
pop_arc_close(&arc); return -1;
|
||||||
|
}
|
||||||
cut_atlases = 1;
|
cut_atlases = 1;
|
||||||
if (atlas_load(&cut_pv2a, "PV\\a1.atl") != 0) { cut_free(); return -1; }
|
if (pv_atlas(&arc, tbl, ARC_PV_A1, &cut_pv2a) != 0) {
|
||||||
|
pop_arc_close(&arc); cut_free(); return -1;
|
||||||
|
}
|
||||||
cut_atlases = 2;
|
cut_atlases = 2;
|
||||||
if (atlas_load(&cut_pv2b, "PV\\a2.atl") != 0) { cut_free(); return -1; }
|
if (pv_atlas(&arc, tbl, ARC_PV_A2, &cut_pv2b) != 0) {
|
||||||
|
pop_arc_close(&arc); cut_free(); return -1;
|
||||||
|
}
|
||||||
cut_atlases = 3;
|
cut_atlases = 3;
|
||||||
/* Комната принцессы живёт и в катсценах: пламя факелов, звёзды в окне,
|
/* Комната принцессы живёт и в катсценах: пламя факелов, звёзды в окне,
|
||||||
* песок в часах — и ПЕРЕДНЯЯ КОЛОННА, которая по оригиналу закрывает
|
* песок в часах — и ПЕРЕДНЯЯ КОЛОННА, которая по оригиналу закрывает
|
||||||
* персонажей (draw_princess_room_bg + foretable). */
|
* персонажей (draw_princess_room_bg + foretable). */
|
||||||
if (atlas_load(&cut_hg, "PV\\h0.atl") != 0) { cut_free(); return -1; }
|
if (pv_atlas(&arc, tbl, ARC_PV_H0, &cut_hg) != 0) {
|
||||||
|
pop_arc_close(&arc); cut_free(); return -1;
|
||||||
|
}
|
||||||
cut_atlases = 4;
|
cut_atlases = 4;
|
||||||
if (atlas_load(&cut_torch, "PV\\t0.atl") != 0) { cut_free(); return -1; }
|
if (pv_atlas(&arc, tbl, ARC_PV_T0, &cut_torch) != 0) {
|
||||||
|
pop_arc_close(&arc); cut_free(); return -1;
|
||||||
|
}
|
||||||
cut_atlases = 5;
|
cut_atlases = 5;
|
||||||
|
pop_arc_close(&arc);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,23 +16,46 @@
|
|||||||
#include "kid_data.h"
|
#include "kid_data.h"
|
||||||
#include "pop_vflip.h"
|
#include "pop_vflip.h"
|
||||||
#include "pop_guard.h" /* pop_guard_vflip_load — зеркала соперника */
|
#include "pop_guard.h" /* pop_guard_vflip_load — зеркала соперника */
|
||||||
|
#include "pop_arc.h"
|
||||||
|
#include "kid_arc.h"
|
||||||
|
|
||||||
|
/* Серии в архиве обязаны лежать подряд — так их кладёт список в Makefile,
|
||||||
|
* и только поэтому индекс считается сложением. Если порядок собьётся,
|
||||||
|
* сборка встанет здесь, а не рассыплется картинкой на экране. */
|
||||||
|
#if (ARC_KID_KID27 - ARC_KID_KID0) != 27
|
||||||
|
#error "kid0..kid27 в архиве не подряд"
|
||||||
|
#endif
|
||||||
|
#if (ARC_KID_KID27_V - ARC_KID_KID0_V) != 27
|
||||||
|
#error "kid0_v..kid27_v в архиве не подряд"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define KID_MAXPAGES 28
|
#define KID_MAXPAGES 28
|
||||||
|
|
||||||
|
/* Таблица архива живёт на стеке вызывающего (4 байта на запись, здесь 232)
|
||||||
|
* — держать её постоянно незачем, а W2 у игры и без того тесный. */
|
||||||
int pop_kid_load(uint8_t npages) __banked
|
int pop_kid_load(uint8_t npages) __banked
|
||||||
{
|
{
|
||||||
static const char *const names[KID_MAXPAGES] = {
|
uint8_t tbl[ARC_KID_COUNT * POP_ARC_REC];
|
||||||
"KID\\kid0.atl","KID\\kid1.atl","KID\\kid2.atl","KID\\kid3.atl","KID\\kid4.atl","KID\\kid5.atl",
|
pop_arc_t arc;
|
||||||
"KID\\kid6.atl","KID\\kid7.atl","KID\\kid8.atl","KID\\kid9.atl","KID\\kid10.atl","KID\\kid11.atl",
|
|
||||||
"KID\\kid12.atl","KID\\kid13.atl","KID\\kid14.atl","KID\\kid15.atl","KID\\kid16.atl","KID\\kid17.atl",
|
|
||||||
"KID\\kid18.atl","KID\\kid19.atl","KID\\kid20.atl","KID\\kid21.atl","KID\\kid22.atl","KID\\kid23.atl",
|
|
||||||
"KID\\kid24.atl","KID\\kid25.atl","KID\\kid26.atl","KID\\kid27.atl"
|
|
||||||
};
|
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|
||||||
if (npages > KID_MAXPAGES) npages = KID_MAXPAGES;
|
if (npages > KID_MAXPAGES) npages = KID_MAXPAGES;
|
||||||
|
if (pop_arc_open_id(&arc, POP_ARC_KID, tbl, ARC_KID_COUNT) < 0) return -1;
|
||||||
for (i = 0; i < npages; i++)
|
for (i = 0; i < npages; i++)
|
||||||
if (atlas_load(&kidp[i], names[i]) != 0)
|
if (pop_arc_atlas(&arc, tbl, (uint8_t)(ARC_KID_KID0 + i),
|
||||||
|
&kidp[i]) != 0) {
|
||||||
|
pop_arc_close(&arc);
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
/* Меч — из того же открытого архива: без него кадры 229+ идут без
|
||||||
|
* клинка, а второй open стоил бы дороже самого чтения. */
|
||||||
|
{
|
||||||
|
atlas_t sword;
|
||||||
|
|
||||||
|
if (pop_arc_atlas(&arc, tbl, ARC_KID_SWORD, &sword) == 0)
|
||||||
|
(void)pop_sword_take(&sword);
|
||||||
|
}
|
||||||
|
pop_arc_close(&arc);
|
||||||
kid_npages = npages;
|
kid_npages = npages;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -73,21 +96,28 @@ void pop_kid_data_free(void) __banked
|
|||||||
* только через него). Идемпотентна. */
|
* только через него). Идемпотентна. */
|
||||||
int pop_vflip_load_all(void) __banked
|
int pop_vflip_load_all(void) __banked
|
||||||
{
|
{
|
||||||
static const char *const kidv[KID_MAXPAGES] = {
|
uint8_t tbl[ARC_KID_COUNT * POP_ARC_REC];
|
||||||
"KID\\kid0_v.atl","KID\\kid1_v.atl","KID\\kid2_v.atl","KID\\kid3_v.atl",
|
pop_arc_t arc;
|
||||||
"KID\\kid4_v.atl","KID\\kid5_v.atl","KID\\kid6_v.atl","KID\\kid7_v.atl",
|
|
||||||
"KID\\kid8_v.atl","KID\\kid9_v.atl","KID\\kid10_v.atl","KID\\kid11_v.atl",
|
|
||||||
"KID\\kid12_v.atl","KID\\kid13_v.atl","KID\\kid14_v.atl","KID\\kid15_v.atl",
|
|
||||||
"KID\\kid16_v.atl","KID\\kid17_v.atl","KID\\kid18_v.atl","KID\\kid19_v.atl",
|
|
||||||
"KID\\kid20_v.atl","KID\\kid21_v.atl","KID\\kid22_v.atl","KID\\kid23_v.atl",
|
|
||||||
"KID\\kid24_v.atl","KID\\kid25_v.atl","KID\\kid26_v.atl","KID\\kid27_v.atl"
|
|
||||||
};
|
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
int res = 0;
|
int res = 0;
|
||||||
|
|
||||||
if (pop_vflip_ready()) return 0;
|
if (pop_vflip_ready()) return 0;
|
||||||
for (i = 0; i < kid_npages; i++)
|
if (pop_arc_open_id(&arc, POP_ARC_KID, tbl, ARC_KID_COUNT) < 0) return -1;
|
||||||
if (pop_vflip_add(&kidp[i], kidv[i]) != 0) res = -1;
|
for (i = 0; i < kid_npages; i++) {
|
||||||
if (pop_sword_vflip_load() != 0) res = -1;
|
atlas_t m;
|
||||||
|
|
||||||
|
if (pop_arc_atlas(&arc, tbl, (uint8_t)(ARC_KID_KID0_V + i), &m) != 0 ||
|
||||||
|
pop_vflip_take(&kidp[i], &m) != 0)
|
||||||
|
res = -1;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
atlas_t sword_v;
|
||||||
|
|
||||||
|
if (pop_arc_atlas(&arc, tbl, ARC_KID_SWORD_V, &sword_v) != 0 ||
|
||||||
|
pop_sword_vflip_take(&sword_v) != 0)
|
||||||
|
res = -1;
|
||||||
|
}
|
||||||
|
pop_arc_close(&arc);
|
||||||
if (pop_guard_vflip_load() != 0) res = -1;
|
if (pop_guard_vflip_load() != 0) res = -1;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,18 +37,22 @@ static const int8_t SWORD_DY[KID_NSWORD] = KID_SWORD_DY_INIT;
|
|||||||
static atlas_t swordp;
|
static atlas_t swordp;
|
||||||
static uint8_t sword_ok;
|
static uint8_t sword_ok;
|
||||||
|
|
||||||
int pop_sword_load(void) __banked
|
/* Меч приходит УЖЕ ЗАГРУЖЕННЫМ: его читает pop_kid_load из того же архива,
|
||||||
|
* что и остальные страницы Кида, — открывать архив второй раз ради одного
|
||||||
|
* элемента незачем (`open` дороже самого чтения). */
|
||||||
|
int pop_sword_take(const atlas_t *a) __banked
|
||||||
{
|
{
|
||||||
if (atlas_load(&swordp, "KID\\sword.atl") != 0) return -1;
|
if (!a) return -1;
|
||||||
|
swordp = *a;
|
||||||
sword_ok = 1;
|
sword_ok = 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Зеркальная страница меча (зелье инверсии) — пара к pop_sword_load. */
|
/* Зеркальная страница меча (зелье инверсии) — пара к pop_sword_take. */
|
||||||
int pop_sword_vflip_load(void) __banked
|
int pop_sword_vflip_take(const atlas_t *m) __banked
|
||||||
{
|
{
|
||||||
if (!sword_ok) return 0;
|
if (!sword_ok) return 0;
|
||||||
return pop_vflip_add(&swordp, "KID\\sword_v.atl");
|
return pop_vflip_take(&swordp, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* add_sword_to_objtable (seg006:1798). Клинок рисуется, если персонаж в
|
/* add_sword_to_objtable (seg006:1798). Клинок рисуется, если персонаж в
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ void pop_kid_data_free(void) __banked;
|
|||||||
* подъёма меча/убирания в ножны (add_sword_to_objtable, seg006:1798).
|
* подъёма меча/убирания в ножны (add_sword_to_objtable, seg006:1798).
|
||||||
* Грузить ДО initgraph, как и страницы Kid. 0 — OK, -1 — ошибка (тогда
|
* Грузить ДО initgraph, как и страницы Kid. 0 — OK, -1 — ошибка (тогда
|
||||||
* кадры рисуются без клинка). */
|
* кадры рисуются без клинка). */
|
||||||
int pop_sword_load(void) __banked;
|
|
||||||
/* Зеркальная (вертикально) страница меча — для зелья инверсии. */
|
/* Зеркальная (вертикально) страница меча — для зелья инверсии. */
|
||||||
int pop_sword_vflip_load(void) __banked;
|
int pop_sword_take(const atlas_t *a) __banked;
|
||||||
|
int pop_sword_vflip_take(const atlas_t *m) __banked;
|
||||||
void pop_kid_free(void) __banked;
|
void pop_kid_free(void) __banked;
|
||||||
|
|
||||||
/* Сама отрисовка клинка (pop_sword_draw) и блит спрайта chtab_2 по id
|
/* Сама отрисовка клинка (pop_sword_draw) и блит спрайта chtab_2 по id
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
#include "_pop_bg.h" /* контракт с горячей половиной (pop_bg.c, банк 2) */
|
#include "_pop_bg.h" /* контракт с горячей половиной (pop_bg.c, банк 2) */
|
||||||
#include "pop_tile.h" /* общие листья слоя фона (резидент): блит, тайлы, таблица */
|
#include "pop_tile.h" /* общие листья слоя фона (резидент): блит, тайлы, таблица */
|
||||||
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
||||||
|
#include "pop_arc.h"
|
||||||
|
#include "bg_arc.h"
|
||||||
#include "pop_state.h"
|
#include "pop_state.h"
|
||||||
#include "pop_redraw.h" /* пометки тайлов: сосед падающего куска (draw_mob) */
|
#include "pop_redraw.h" /* пометки тайлов: сосед падающего куска (draw_mob) */
|
||||||
#include "pop_geom.h"
|
#include "pop_geom.h"
|
||||||
@@ -57,6 +59,12 @@ static uint8_t bg_set = 0xFF; /* какой набор сейча
|
|||||||
* персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска зарастает
|
* персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска зарастает
|
||||||
* десятками .atl. Путь склеиваем на месте: держать по строке на каждый из
|
* десятками .atl. Путь склеиваем на месте: держать по строке на каждый из
|
||||||
* двадцати атласов — лишние полкилобайта в банке. */
|
* двадцати атласов — лишние полкилобайта в банке. */
|
||||||
|
/* Шаг набора в архиве: десять env + wall + fore. */
|
||||||
|
#define BG_SET_STRIDE (POP_ENV_PAGES + 2)
|
||||||
|
#if (ARC_BG_PAL_ENV0 - ARC_BG_POP_ENV0) != BG_SET_STRIDE
|
||||||
|
#error "тайлсеты в архиве фона лежат не по двенадцать атласов"
|
||||||
|
#endif
|
||||||
|
|
||||||
static char bg_path_buf[20];
|
static char bg_path_buf[20];
|
||||||
|
|
||||||
static const char *bg_path(const char *name)
|
static const char *bg_path(const char *name)
|
||||||
@@ -198,12 +206,33 @@ int pop_bg_load(uint8_t set) __banked
|
|||||||
atlas_free(&pop_wall_at);
|
atlas_free(&pop_wall_at);
|
||||||
atlas_free(&pop_fore_at);
|
atlas_free(&pop_fore_at);
|
||||||
}
|
}
|
||||||
for (i = 0; i < POP_ENV_PAGES; i++)
|
/* Двадцать пять атласов фона лежат в одном архиве: двенадцать на
|
||||||
if (atlas_load(&pop_env[i], bg_path(pop_env_atl[set][i])) != 0) return -1;
|
* тайлсет подряд плюс общий chtab_1. Открываем его ОДИН раз на смену
|
||||||
if (atlas_load(&pop_wall_at, bg_path(pop_wall_atl[set])) != 0) return -1;
|
* набора — при цене `open` 51,4 мс это и есть основная экономия. */
|
||||||
if (atlas_load(&pop_fore_at, bg_path(pop_fore_atl[set])) != 0) return -1;
|
{
|
||||||
if (bg_set == 0xFF && /* chtab_1 от набора не зависит */
|
uint8_t tbl[ARC_BG_COUNT * POP_ARC_REC];
|
||||||
atlas_load(&pop_pot_at, bg_path(POP_POT_ATL)) != 0) return -1;
|
pop_arc_t arc;
|
||||||
|
uint8_t base = (uint8_t)(set * BG_SET_STRIDE);
|
||||||
|
int8_t bad = 0;
|
||||||
|
|
||||||
|
if (pop_arc_open_id(&arc, POP_ARC_BG, tbl, ARC_BG_COUNT) < 0) return -1;
|
||||||
|
for (i = 0; i < POP_ENV_PAGES && !bad; i++)
|
||||||
|
bad = (int8_t)(pop_arc_atlas(&arc, tbl, (uint8_t)(base + i),
|
||||||
|
&pop_env[i]) != 0);
|
||||||
|
if (!bad)
|
||||||
|
bad = (int8_t)(pop_arc_atlas(&arc, tbl,
|
||||||
|
(uint8_t)(base + POP_ENV_PAGES),
|
||||||
|
&pop_wall_at) != 0);
|
||||||
|
if (!bad)
|
||||||
|
bad = (int8_t)(pop_arc_atlas(&arc, tbl,
|
||||||
|
(uint8_t)(base + POP_ENV_PAGES + 1),
|
||||||
|
&pop_fore_at) != 0);
|
||||||
|
if (!bad && bg_set == 0xFF) /* chtab_1 от набора не зависит */
|
||||||
|
bad = (int8_t)(pop_arc_atlas(&arc, tbl, ARC_BG_POP_POT,
|
||||||
|
&pop_pot_at) != 0);
|
||||||
|
pop_arc_close(&arc);
|
||||||
|
if (bad) return -1;
|
||||||
|
}
|
||||||
bg_set = set;
|
bg_set = set;
|
||||||
pop_palace = (uint8_t)(set == 1);
|
pop_palace = (uint8_t)(set == 1);
|
||||||
mob_spr_build(); /* композит падающего куска — свой на набор */
|
mob_spr_build(); /* композит падающего куска — свой на набор */
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ volatile uint16_t pop_mus_left;
|
|||||||
* таблицах приоритета и перебиваемости, что и эффекты (см. pop_sfx_tick). */
|
* таблицах приоритета и перебиваемости, что и эффекты (см. pop_sfx_tick). */
|
||||||
uint8_t pop_mus_id;
|
uint8_t pop_mus_id;
|
||||||
|
|
||||||
|
/* Тик насоса: +1 на каждый отданный CBL блок, то есть 85,4 Гц. */
|
||||||
|
volatile uint8_t pop_snd_tick;
|
||||||
|
|
||||||
static volatile uint8_t sfx_pg; /* индекс в pop_snd_page[] */
|
static volatile uint8_t sfx_pg; /* индекс в pop_snd_page[] */
|
||||||
static volatile uint16_t sfx_ptr; /* адрес в окне W0: 0x0000..0x3FFF */
|
static volatile uint16_t sfx_ptr; /* адрес в окне W0: 0x0000..0x3FFF */
|
||||||
static volatile uint16_t sfx_left; /* сколько байт осталось (кратно 128) */
|
static volatile uint16_t sfx_left; /* сколько байт осталось (кратно 128) */
|
||||||
@@ -94,6 +97,14 @@ int pop_sfx_fill(uint16_t n)
|
|||||||
uint8_t saved, phys;
|
uint8_t saved, phys;
|
||||||
uint16_t ptr;
|
uint16_t ptr;
|
||||||
|
|
||||||
|
/* ЧАСЫ СЦЕН. Насос зовётся строго по расходу буфера CBL — 85,4 раза в
|
||||||
|
* секунду, что бы ни делал главный цикл. Это единственный источник времени,
|
||||||
|
* которому не мешают ни долгие блиты, ни чтение с диска: кадровые
|
||||||
|
* прерывания теряются в di-окнах акселератора (pop_pace.h), а счёт
|
||||||
|
* ожиданий vsync считает НАШИ ожидания, а не прошедшее время.
|
||||||
|
* Катсцены пейсятся по нему — иначе картинка уезжает от музыки. */
|
||||||
|
pop_snd_tick++;
|
||||||
|
|
||||||
/* Три источника по убыванию важности: эффект, музыка, тишина. Эффект
|
/* Три источника по убыванию важности: эффект, музыка, тишина. Эффект
|
||||||
* перебивает музыку — как у оригинала, где play_sound глушит всё, что
|
* перебивает музыку — как у оригинала, где play_sound глушит всё, что
|
||||||
* звучало; голос-то один. Музыка при этом НЕ теряет позицию: её
|
* звучало; голос-то один. Музыка при этом НЕ теряет позицию: её
|
||||||
|
|||||||
@@ -49,6 +49,16 @@ void pop_sfx_toggle(void) __banked;
|
|||||||
* честно means «звука не будет». */
|
* честно means «звука не будет». */
|
||||||
extern uint8_t pop_snd_want;
|
extern uint8_t pop_snd_want;
|
||||||
|
|
||||||
|
/* ЧАСЫ ДЛЯ СЦЕН: +1 на каждый блок, отданный CBL, то есть ровно 85,4 Гц
|
||||||
|
* (10 937,5 / 128), пока звук открыт. Идут от расхода буфера железом, а
|
||||||
|
* не от нашего кода, поэтому не врут ни на долгих блитах, ни на чтении с
|
||||||
|
* диска — в отличие от счёта ожиданий vsync (тот считает НАШИ ожидания) и
|
||||||
|
* от кадровых прерываний (те теряются в di-окнах акселератора, pop_pace.h).
|
||||||
|
* Переполняется каждые 3 с — разности брать в uint8_t и накапливать.
|
||||||
|
* Когда CBL закрыт (звук выключён), НЕ ИДУТ: у пейсинга обязан быть
|
||||||
|
* запасной путь по кадрам луча. */
|
||||||
|
extern volatile uint8_t pop_snd_tick;
|
||||||
|
|
||||||
/* Заявить эффект с номером id (0..56 — нумерация оригинала, см.
|
/* Заявить эффект с номером id (0..56 — нумерация оригинала, см.
|
||||||
* sound_plan.md). НЕ запускает его немедленно и НЕ перебивает играющий:
|
* sound_plan.md). НЕ запускает его немедленно и НЕ перебивает играющий:
|
||||||
* это порт play_sound (seg000:12C5), который лишь НОМИНИРУЕТ кандидата на
|
* это порт play_sound (seg000:12C5), который лишь НОМИНИРУЕТ кандидата на
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ int pop_sfx_init(void) __banked
|
|||||||
pop_arc_t arc;
|
pop_arc_t arc;
|
||||||
uint8_t tbl[POP_SND_PAGES * POP_ARC_REC];
|
uint8_t tbl[POP_SND_PAGES * POP_ARC_REC];
|
||||||
|
|
||||||
if (pop_arc_open(&arc, "SND\\snd.arc", tbl, POP_SND_PAGES) !=
|
if (pop_arc_open_id(&arc, POP_ARC_SND, tbl, POP_SND_PAGES) !=
|
||||||
POP_SND_PAGES)
|
POP_SND_PAGES)
|
||||||
return -1;
|
return -1;
|
||||||
blk = mem_alloc_pages(POP_SND_PAGES);
|
blk = mem_alloc_pages(POP_SND_PAGES);
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <gfx.h>
|
#include <gfx.h>
|
||||||
#include <sprite.h>
|
#include <sprite.h>
|
||||||
|
#include "pop_arc.h"
|
||||||
|
#include "shadow_arc.h"
|
||||||
#include "pop_shadow.h"
|
#include "pop_shadow.h"
|
||||||
#include "pop_shadow_atlas.h"
|
#include "pop_shadow_atlas.h"
|
||||||
|
|
||||||
@@ -37,20 +39,35 @@ int pop_shadow_load(void) __banked
|
|||||||
uint8_t i;
|
uint8_t i;
|
||||||
if (pop_shadow_kid_n || pop_shadow_fgt_n) return 0; /* уже загружен */
|
if (pop_shadow_kid_n || pop_shadow_fgt_n) return 0; /* уже загружен */
|
||||||
|
|
||||||
|
{
|
||||||
|
uint8_t tbl[ARC_SHADOW_COUNT * POP_ARC_REC];
|
||||||
|
pop_arc_t arc;
|
||||||
|
|
||||||
|
/* Тридцать два атласа — из одного архива: раньше это были тридцать
|
||||||
|
* два `open` по 51,4 мс, то есть полторы секунды на ровном месте. */
|
||||||
|
if (pop_arc_open_id(&arc, POP_ARC_SHADOW, tbl,
|
||||||
|
ARC_SHADOW_COUNT) < 0)
|
||||||
|
return -1;
|
||||||
for (i = 0; i < POP_SHADOW_KID_PAGES; i++) {
|
for (i = 0; i < POP_SHADOW_KID_PAGES; i++) {
|
||||||
if (atlas_load(&pop_shadow_kid[i], pop_shadow_kid_atl[i]) != 0) {
|
if (pop_arc_atlas(&arc, tbl, (uint8_t)(ARC_SHADOW_SK0 + i),
|
||||||
|
&pop_shadow_kid[i]) != 0) {
|
||||||
|
pop_arc_close(&arc);
|
||||||
pop_shadow_free();
|
pop_shadow_free();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
pop_shadow_kid_n = (uint8_t)(i + 1);
|
pop_shadow_kid_n = (uint8_t)(i + 1);
|
||||||
}
|
}
|
||||||
for (i = 0; i < POP_SHADOW_FGT_PAGES; i++) {
|
for (i = 0; i < POP_SHADOW_FGT_PAGES; i++) {
|
||||||
if (atlas_load(&pop_shadow_fgt[i], pop_shadow_fgt_atl[i]) != 0) {
|
if (pop_arc_atlas(&arc, tbl, (uint8_t)(ARC_SHADOW_SF0 + i),
|
||||||
|
&pop_shadow_fgt[i]) != 0) {
|
||||||
|
pop_arc_close(&arc);
|
||||||
pop_shadow_free();
|
pop_shadow_free();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
pop_shadow_fgt_n = (uint8_t)(i + 1);
|
pop_shadow_fgt_n = (uint8_t)(i + 1);
|
||||||
}
|
}
|
||||||
|
pop_arc_close(&arc);
|
||||||
|
}
|
||||||
|
|
||||||
pop_shadow_pal_apply();
|
pop_shadow_pal_apply();
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
#include "pop_bg.h" /* POP_YOFF: title занимает 200px по центру 256px */
|
#include "pop_bg.h" /* POP_YOFF: title занимает 200px по центру 256px */
|
||||||
#include "pop_sfx.h"
|
#include "pop_sfx.h"
|
||||||
#include "pop_music.h"
|
#include "pop_music.h"
|
||||||
|
#include "pop_arc.h"
|
||||||
|
#include "title_arc.h"
|
||||||
#include "pop_cutscene.h" /* POP_T60 */
|
#include "pop_cutscene.h" /* POP_T60 */
|
||||||
#include "pop_settings.h"
|
#include "pop_settings.h"
|
||||||
#include "pop_pal.h"
|
#include "pop_pal.h"
|
||||||
@@ -33,13 +35,11 @@ enum {
|
|||||||
TITLE_LOGO
|
TITLE_LOGO
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Имена короче 8.3; строка = композиция, столбец = полоса 49/49/49/49/4. */
|
/* Композиции лежат в одном архиве подряд по пять полос 49/49/49/49/4,
|
||||||
static const char * const title_part[4][TITLE_PARTS] = {
|
* поэтому имён здесь больше нет: индекс = сцена * TITLE_PARTS. */
|
||||||
{ "TITLE\\t0_0.atl", "TITLE\\t0_1.atl", "TITLE\\t0_2.atl", "TITLE\\t0_3.atl", "TITLE\\t0_4.atl" },
|
#if (ARC_TITLE_T1_0 - ARC_TITLE_T0_0) != TITLE_PARTS
|
||||||
{ "TITLE\\t1_0.atl", "TITLE\\t1_1.atl", "TITLE\\t1_2.atl", "TITLE\\t1_3.atl", "TITLE\\t1_4.atl" },
|
#error "полосы титров в архиве лежат не по пять подряд"
|
||||||
{ "TITLE\\t2_0.atl", "TITLE\\t2_1.atl", "TITLE\\t2_2.atl", "TITLE\\t2_3.atl", "TITLE\\t2_4.atl" },
|
#endif
|
||||||
{ "TITLE\\t3_0.atl", "TITLE\\t3_1.atl", "TITLE\\t3_2.atl", "TITLE\\t3_3.atl", "TITLE\\t3_4.atl" }
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Последовательность и интервалы буквально из show_title():
|
/* Последовательность и интервалы буквально из show_title():
|
||||||
* main --0x82--> presents --0xCD--> main --0x41--> game
|
* main --0x82--> presents --0xCD--> main --0x41--> game
|
||||||
@@ -74,12 +74,20 @@ static uint8_t title_skip_pressed(void)
|
|||||||
* подготовленной страницы; show=0 оставляет её скрытой для palette commit. */
|
* подготовленной страницы; show=0 оставляет её скрытой для palette commit. */
|
||||||
static int title_draw(uint8_t scene, uint8_t show)
|
static int title_draw(uint8_t scene, uint8_t show)
|
||||||
{
|
{
|
||||||
|
uint8_t tbl[ARC_TITLE_COUNT * POP_ARC_REC];
|
||||||
|
pop_arc_t arc;
|
||||||
uint8_t page = pop_screen_begin();
|
uint8_t page = pop_screen_begin();
|
||||||
|
int res;
|
||||||
|
|
||||||
pop_screen_fill(BLACK);
|
pop_screen_fill(BLACK);
|
||||||
if (pop_screen_blit_atlas_strips(title_part[scene], TITLE_PARTS,
|
if (pop_arc_open_id(&arc, POP_ARC_TITLE, tbl, ARC_TITLE_COUNT) < 0)
|
||||||
0, TITLE_Y) != 0)
|
|
||||||
return -1;
|
return -1;
|
||||||
|
res = pop_screen_blit_atlas_arc(&arc, tbl,
|
||||||
|
(uint8_t)(ARC_TITLE_T0_0 +
|
||||||
|
scene * TITLE_PARTS),
|
||||||
|
TITLE_PARTS, 0, TITLE_Y);
|
||||||
|
pop_arc_close(&arc);
|
||||||
|
if (res != 0) return -1;
|
||||||
if (show) pop_screen_present(page);
|
if (show) pop_screen_present(page);
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <sprite.h>
|
#include <sprite.h>
|
||||||
#include "pop_bg.h" /* POP_YOFF/POP_PLAYFIELD_H — граница игрового поля */
|
#include "pop_bg.h" /* POP_YOFF/POP_PLAYFIELD_H — граница игрового поля */
|
||||||
#include "pop_font.h"
|
#include "pop_font.h"
|
||||||
|
#include "pop_arc.h"
|
||||||
#include "pop_ui.h"
|
#include "pop_ui.h"
|
||||||
#include "pop_status.h"
|
#include "pop_status.h"
|
||||||
|
|
||||||
@@ -284,6 +285,22 @@ void pop_screen_present(uint8_t page) __banked
|
|||||||
gfx_set_visible_page(page);
|
gfx_set_visible_page(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Нарисовать одну полосу и отпустить её страницу. Общая часть обоих
|
||||||
|
* загрузчиков ниже: они отличаются только тем, откуда взялся атлас. */
|
||||||
|
static int strip_blit(atlas_t *at, int x, int *y)
|
||||||
|
{
|
||||||
|
const uint8_t *img;
|
||||||
|
|
||||||
|
gfx_w0_map(at->page);
|
||||||
|
img = (const uint8_t *)atlas_image(at, 0);
|
||||||
|
/* gfx_blit_noclip ограничен шириной uint8 и обрезает 320px. */
|
||||||
|
gfx_blit(x, *y, img);
|
||||||
|
*y += img[2];
|
||||||
|
gfx_w0_unmap();
|
||||||
|
atlas_free(at);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Полноэкранные изображения не помещаются одним getimage в EMM-страницу.
|
/* Полноэкранные изображения не помещаются одним getimage в EMM-страницу.
|
||||||
* Общий загрузчик полос держит лишь одну страницу и не трогает page flip:
|
* Общий загрузчик полос держит лишь одну страницу и не трогает page flip:
|
||||||
* caller уже собрал скрытую страницу и сам решает, когда её показать. */
|
* caller уже собрал скрытую страницу и сам решает, когда её показать. */
|
||||||
@@ -295,16 +312,29 @@ int pop_screen_blit_atlas_strips(const char * const *parts,
|
|||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
for (part = 0; part < count; part++) {
|
for (part = 0; part < count; part++) {
|
||||||
atlas_t at;
|
atlas_t at;
|
||||||
const uint8_t *img;
|
|
||||||
|
|
||||||
if (atlas_load(&at, parts[part]) != 0) return -1;
|
if (atlas_load(&at, parts[part]) != 0) return -1;
|
||||||
gfx_w0_map(at.page);
|
strip_blit(&at, x, &y);
|
||||||
img = (const uint8_t *)atlas_image(&at, 0);
|
}
|
||||||
/* gfx_blit_noclip ограничен шириной uint8 и обрезает 320px. */
|
return 0;
|
||||||
gfx_blit(x, y, img);
|
}
|
||||||
y += img[2];
|
|
||||||
gfx_w0_unmap();
|
/* То же самое, но полосы лежат подряд в АРХИВЕ (заставка, story, PV): один
|
||||||
atlas_free(&at);
|
* `open` на весь экран вместо пяти — при цене открытия 51,4 мс против
|
||||||
|
* 32,6 мс на чтение это больше половины времени показа. */
|
||||||
|
int pop_screen_blit_atlas_arc(pop_arc_t *arc, const uint8_t *tbl,
|
||||||
|
uint8_t base, uint8_t count,
|
||||||
|
int x, int y) __banked
|
||||||
|
{
|
||||||
|
uint8_t part;
|
||||||
|
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
for (part = 0; part < count; part++) {
|
||||||
|
atlas_t at;
|
||||||
|
|
||||||
|
if (pop_arc_atlas(arc, tbl, (uint8_t)(base + part), &at) != 0)
|
||||||
|
return -1;
|
||||||
|
strip_blit(&at, x, &y);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,4 +75,10 @@ void pop_screen_present(uint8_t page) __banked;
|
|||||||
int pop_screen_blit_atlas_strips(const char * const *parts,
|
int pop_screen_blit_atlas_strips(const char * const *parts,
|
||||||
uint8_t count, int x, int y) __banked;
|
uint8_t count, int x, int y) __banked;
|
||||||
|
|
||||||
|
/* Те же полосы, но из архива ресурсов: элементы base..base+count-1.
|
||||||
|
* Один `open` на экран вместо одного на полосу. */
|
||||||
|
int pop_screen_blit_atlas_arc(struct pop_arc_s *arc, const uint8_t *tbl,
|
||||||
|
uint8_t base, uint8_t count,
|
||||||
|
int x, int y) __banked;
|
||||||
|
|
||||||
#endif /* POP_UI_H */
|
#endif /* POP_UI_H */
|
||||||
|
|||||||
@@ -28,15 +28,27 @@ static uint8_t vf_src[VF_MAX]; /* страница оригинала
|
|||||||
static atlas_t vf_atl[VF_MAX]; /* зеркальный атлас (его page) */
|
static atlas_t vf_atl[VF_MAX]; /* зеркальный атлас (его page) */
|
||||||
static uint8_t vf_n;
|
static uint8_t vf_n;
|
||||||
|
|
||||||
int pop_vflip_add(const atlas_t *orig, const char *path)
|
/* Взять УЖЕ ЗАГРУЖЕННЫЙ зеркальный атлас под своё управление (дальше его
|
||||||
|
* освобождает pop_vflip_reset). Нужен тем, кто читает зеркала не файлом, а
|
||||||
|
* куском архива ресурсов: загрузку делает вызывающий, связывание — мы. */
|
||||||
|
int pop_vflip_take(const atlas_t *orig, const atlas_t *mirror)
|
||||||
{
|
{
|
||||||
if (!orig || vf_n >= VF_MAX) return -1;
|
if (!orig || !mirror || vf_n >= VF_MAX) return -1;
|
||||||
if (atlas_load(&vf_atl[vf_n], path) != 0) return -1;
|
vf_atl[vf_n] = *mirror;
|
||||||
vf_src[vf_n] = orig->page;
|
vf_src[vf_n] = orig->page;
|
||||||
vf_n++;
|
vf_n++;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int pop_vflip_add(const atlas_t *orig, const char *path)
|
||||||
|
{
|
||||||
|
atlas_t m;
|
||||||
|
|
||||||
|
if (!orig || vf_n >= VF_MAX) return -1;
|
||||||
|
if (atlas_load(&m, path) != 0) return -1;
|
||||||
|
return pop_vflip_take(orig, &m);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t pop_vflip_page(uint8_t src)
|
uint8_t pop_vflip_page(uint8_t src)
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
* 0 — OK, -1 — файла нет / не хватило EMM / таблица полна. */
|
* 0 — OK, -1 — файла нет / не хватило EMM / таблица полна. */
|
||||||
int pop_vflip_add(const atlas_t *orig, const char *path);
|
int pop_vflip_add(const atlas_t *orig, const char *path);
|
||||||
|
|
||||||
|
/* То же, но зеркало УЖЕ загружено (например, прочитано из архива): таблица
|
||||||
|
* забирает его себе, и дальше он освобождается вместе с остальными. */
|
||||||
|
int pop_vflip_take(const atlas_t *orig, const atlas_t *mirror);
|
||||||
|
|
||||||
/* Страница-зеркало для страницы src; 0 — пары нет (набор не загружен). */
|
/* Страница-зеркало для страницы src; 0 — пары нет (набор не загружен). */
|
||||||
uint8_t pop_vflip_page(uint8_t src);
|
uint8_t pop_vflip_page(uint8_t src);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/* pv_arc.h — индексы в архиве PV. СГЕНЕРИРОВАН
|
||||||
|
* (toolchain/pop_pack_arc.py), руками не править. */
|
||||||
|
#ifndef PV_ARC_H
|
||||||
|
#define PV_ARC_H
|
||||||
|
|
||||||
|
#define ARC_PV_COUNT 78
|
||||||
|
|
||||||
|
#define ARC_PV_S0_0 0
|
||||||
|
#define ARC_PV_S0_1 1
|
||||||
|
#define ARC_PV_S0_2 2
|
||||||
|
#define ARC_PV_S0_3 3
|
||||||
|
#define ARC_PV_S0_4 4
|
||||||
|
#define ARC_PV_S1_0 5
|
||||||
|
#define ARC_PV_S1_1 6
|
||||||
|
#define ARC_PV_S1_2 7
|
||||||
|
#define ARC_PV_S1_3 8
|
||||||
|
#define ARC_PV_S1_4 9
|
||||||
|
#define ARC_PV_S2_0 10
|
||||||
|
#define ARC_PV_S2_1 11
|
||||||
|
#define ARC_PV_S2_2 12
|
||||||
|
#define ARC_PV_S2_3 13
|
||||||
|
#define ARC_PV_S2_4 14
|
||||||
|
#define ARC_PV_S3_0 15
|
||||||
|
#define ARC_PV_S3_1 16
|
||||||
|
#define ARC_PV_S3_2 17
|
||||||
|
#define ARC_PV_S3_3 18
|
||||||
|
#define ARC_PV_S3_4 19
|
||||||
|
#define ARC_PV_S4_0 20
|
||||||
|
#define ARC_PV_S4_1 21
|
||||||
|
#define ARC_PV_S4_2 22
|
||||||
|
#define ARC_PV_S4_3 23
|
||||||
|
#define ARC_PV_S4_4 24
|
||||||
|
#define ARC_PV_P0_0 25
|
||||||
|
#define ARC_PV_P0_1 26
|
||||||
|
#define ARC_PV_P0_2 27
|
||||||
|
#define ARC_PV_P0_3 28
|
||||||
|
#define ARC_PV_P0_4 29
|
||||||
|
#define ARC_PV_P1_0 30
|
||||||
|
#define ARC_PV_P1_1 31
|
||||||
|
#define ARC_PV_P1_2 32
|
||||||
|
#define ARC_PV_P1_3 33
|
||||||
|
#define ARC_PV_P1_4 34
|
||||||
|
#define ARC_PV_P2_0 35
|
||||||
|
#define ARC_PV_P2_1 36
|
||||||
|
#define ARC_PV_P2_2 37
|
||||||
|
#define ARC_PV_P2_3 38
|
||||||
|
#define ARC_PV_P2_4 39
|
||||||
|
#define ARC_PV_P3_0 40
|
||||||
|
#define ARC_PV_P3_1 41
|
||||||
|
#define ARC_PV_P3_2 42
|
||||||
|
#define ARC_PV_P3_3 43
|
||||||
|
#define ARC_PV_P3_4 44
|
||||||
|
#define ARC_PV_P4_0 45
|
||||||
|
#define ARC_PV_P4_1 46
|
||||||
|
#define ARC_PV_P4_2 47
|
||||||
|
#define ARC_PV_P4_3 48
|
||||||
|
#define ARC_PV_P4_4 49
|
||||||
|
#define ARC_PV_P5_0 50
|
||||||
|
#define ARC_PV_P5_1 51
|
||||||
|
#define ARC_PV_P5_2 52
|
||||||
|
#define ARC_PV_P5_3 53
|
||||||
|
#define ARC_PV_P5_4 54
|
||||||
|
#define ARC_PV_P6_0 55
|
||||||
|
#define ARC_PV_P6_1 56
|
||||||
|
#define ARC_PV_P6_2 57
|
||||||
|
#define ARC_PV_P6_3 58
|
||||||
|
#define ARC_PV_P6_4 59
|
||||||
|
#define ARC_PV_P7_0 60
|
||||||
|
#define ARC_PV_P7_1 61
|
||||||
|
#define ARC_PV_P7_2 62
|
||||||
|
#define ARC_PV_P7_3 63
|
||||||
|
#define ARC_PV_P7_4 64
|
||||||
|
#define ARC_PV_B0_0 65
|
||||||
|
#define ARC_PV_B0_1 66
|
||||||
|
#define ARC_PV_B0_2 67
|
||||||
|
#define ARC_PV_B0_3 68
|
||||||
|
#define ARC_PV_B0_4 69
|
||||||
|
#define ARC_PV_A0 70
|
||||||
|
#define ARC_PV_A1 71
|
||||||
|
#define ARC_PV_A2 72
|
||||||
|
#define ARC_PV_J0 73
|
||||||
|
#define ARC_PV_J1 74
|
||||||
|
#define ARC_PV_J2 75
|
||||||
|
#define ARC_PV_H0 76
|
||||||
|
#define ARC_PV_T0 77
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -900,7 +900,6 @@ int pop_boot(void) __banked
|
|||||||
printf("kid_data.bin load failed\n");
|
printf("kid_data.bin load failed\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */
|
|
||||||
pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */
|
pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */
|
||||||
pop_shadow_load(); /* запечённый набор Тени — один раз на запуск */
|
pop_shadow_load(); /* запечённый набор Тени — один раз на запуск */
|
||||||
pop_sfx_init(); /* звук: 8 страниц оцифровки (вывод пока молчит) */
|
pop_sfx_init(); /* звук: 8 страниц оцифровки (вывод пока молчит) */
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/* shadow_arc.h — индексы в архиве SHADOW. СГЕНЕРИРОВАН
|
||||||
|
* (toolchain/pop_pack_arc.py), руками не править. */
|
||||||
|
#ifndef SHADOW_ARC_H
|
||||||
|
#define SHADOW_ARC_H
|
||||||
|
|
||||||
|
#define ARC_SHADOW_COUNT 32
|
||||||
|
|
||||||
|
#define ARC_SHADOW_SK0 0
|
||||||
|
#define ARC_SHADOW_SK1 1
|
||||||
|
#define ARC_SHADOW_SK2 2
|
||||||
|
#define ARC_SHADOW_SK3 3
|
||||||
|
#define ARC_SHADOW_SK4 4
|
||||||
|
#define ARC_SHADOW_SK5 5
|
||||||
|
#define ARC_SHADOW_SK6 6
|
||||||
|
#define ARC_SHADOW_SK7 7
|
||||||
|
#define ARC_SHADOW_SK8 8
|
||||||
|
#define ARC_SHADOW_SK9 9
|
||||||
|
#define ARC_SHADOW_SK10 10
|
||||||
|
#define ARC_SHADOW_SK11 11
|
||||||
|
#define ARC_SHADOW_SK12 12
|
||||||
|
#define ARC_SHADOW_SK13 13
|
||||||
|
#define ARC_SHADOW_SK14 14
|
||||||
|
#define ARC_SHADOW_SK15 15
|
||||||
|
#define ARC_SHADOW_SK16 16
|
||||||
|
#define ARC_SHADOW_SK17 17
|
||||||
|
#define ARC_SHADOW_SK18 18
|
||||||
|
#define ARC_SHADOW_SK19 19
|
||||||
|
#define ARC_SHADOW_SK20 20
|
||||||
|
#define ARC_SHADOW_SK21 21
|
||||||
|
#define ARC_SHADOW_SK22 22
|
||||||
|
#define ARC_SHADOW_SK23 23
|
||||||
|
#define ARC_SHADOW_SK24 24
|
||||||
|
#define ARC_SHADOW_SK25 25
|
||||||
|
#define ARC_SHADOW_SK26 26
|
||||||
|
#define ARC_SHADOW_SK27 27
|
||||||
|
#define ARC_SHADOW_SF0 28
|
||||||
|
#define ARC_SHADOW_SF1 29
|
||||||
|
#define ARC_SHADOW_SF2 30
|
||||||
|
#define ARC_SHADOW_SF3 31
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/* skel_arc.h — индексы в архиве SKEL. СГЕНЕРИРОВАН
|
||||||
|
* (toolchain/pop_pack_arc.py), руками не править. */
|
||||||
|
#ifndef SKEL_ARC_H
|
||||||
|
#define SKEL_ARC_H
|
||||||
|
|
||||||
|
#define ARC_SKEL_COUNT 4
|
||||||
|
|
||||||
|
#define ARC_SKEL_G0 0
|
||||||
|
#define ARC_SKEL_G1 1
|
||||||
|
#define ARC_SKEL_G2 2
|
||||||
|
#define ARC_SKEL_G3 3
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -387,7 +387,24 @@ void pop_mirror_heal(void) __banked { }
|
|||||||
uint8_t pop_vflip_ready(void) { return 1; } /* «уже загружено» — грузить не станет */
|
uint8_t pop_vflip_ready(void) { return 1; } /* «уже загружено» — грузить не станет */
|
||||||
int pop_vflip_add(const void *orig, const char *path)
|
int pop_vflip_add(const void *orig, const char *path)
|
||||||
{ (void)orig; (void)path; return 0; }
|
{ (void)orig; (void)path; return 0; }
|
||||||
|
int pop_vflip_take(const void *orig, const void *mirror)
|
||||||
|
{ (void)orig; (void)mirror; return 0; }
|
||||||
|
|
||||||
|
/* Архив ресурсов: на устройстве из него читаются атласы Кида, в тестах
|
||||||
|
* графики нет вовсе — pop_kboot линкуется только ради pop_kid_data_load,
|
||||||
|
* а тот по-прежнему ходит в файл (заглушка open/read выше). */
|
||||||
|
signed char pop_arc_open(void *a, const char *path, unsigned char *tbl,
|
||||||
|
unsigned char max) __banked
|
||||||
|
{ (void)a; (void)path; (void)tbl; (void)max; return -1; }
|
||||||
|
signed char pop_arc_open_id(void *a, unsigned char id, unsigned char *tbl,
|
||||||
|
unsigned char max) __banked
|
||||||
|
{ (void)a; (void)id; (void)tbl; (void)max; return -1; }
|
||||||
|
signed char pop_arc_atlas(void *a, const unsigned char *tbl,
|
||||||
|
unsigned char idx, void *at) __banked
|
||||||
|
{ (void)a; (void)tbl; (void)idx; (void)at; return -1; }
|
||||||
|
void pop_arc_close(void *a) __banked { (void)a; }
|
||||||
uint8_t pop_vflip_page(uint8_t src) { return src; }
|
uint8_t pop_vflip_page(uint8_t src) { return src; }
|
||||||
void pop_vflip_reset(void) { }
|
void pop_vflip_reset(void) { }
|
||||||
int pop_sword_vflip_load(void) __banked { return 0; }
|
int pop_sword_take(const atlas_t *a) __banked { (void)a; return 0; }
|
||||||
|
int pop_sword_vflip_take(const atlas_t *m) __banked { (void)m; return 0; }
|
||||||
int pop_guard_vflip_load(void) __banked { return 0; }
|
int pop_guard_vflip_load(void) __banked { return 0; }
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/* title_arc.h — индексы в архиве TITLE. СГЕНЕРИРОВАН
|
||||||
|
* (toolchain/pop_pack_arc.py), руками не править. */
|
||||||
|
#ifndef TITLE_ARC_H
|
||||||
|
#define TITLE_ARC_H
|
||||||
|
|
||||||
|
#define ARC_TITLE_COUNT 20
|
||||||
|
|
||||||
|
#define ARC_TITLE_T0_0 0
|
||||||
|
#define ARC_TITLE_T0_1 1
|
||||||
|
#define ARC_TITLE_T0_2 2
|
||||||
|
#define ARC_TITLE_T0_3 3
|
||||||
|
#define ARC_TITLE_T0_4 4
|
||||||
|
#define ARC_TITLE_T1_0 5
|
||||||
|
#define ARC_TITLE_T1_1 6
|
||||||
|
#define ARC_TITLE_T1_2 7
|
||||||
|
#define ARC_TITLE_T1_3 8
|
||||||
|
#define ARC_TITLE_T1_4 9
|
||||||
|
#define ARC_TITLE_T2_0 10
|
||||||
|
#define ARC_TITLE_T2_1 11
|
||||||
|
#define ARC_TITLE_T2_2 12
|
||||||
|
#define ARC_TITLE_T2_3 13
|
||||||
|
#define ARC_TITLE_T2_4 14
|
||||||
|
#define ARC_TITLE_T3_0 15
|
||||||
|
#define ARC_TITLE_T3_1 16
|
||||||
|
#define ARC_TITLE_T3_2 17
|
||||||
|
#define ARC_TITLE_T3_3 18
|
||||||
|
#define ARC_TITLE_T3_4 19
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/* vizier_arc.h — индексы в архиве VIZIER. СГЕНЕРИРОВАН
|
||||||
|
* (toolchain/pop_pack_arc.py), руками не править. */
|
||||||
|
#ifndef VIZIER_ARC_H
|
||||||
|
#define VIZIER_ARC_H
|
||||||
|
|
||||||
|
#define ARC_VIZIER_COUNT 5
|
||||||
|
|
||||||
|
#define ARC_VIZIER_G0 0
|
||||||
|
#define ARC_VIZIER_G1 1
|
||||||
|
#define ARC_VIZIER_G2 2
|
||||||
|
#define ARC_VIZIER_G3 3
|
||||||
|
#define ARC_VIZIER_G4 4
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
Данные каждого элемента выровнены на 512, поэтому его смещение всегда
|
Данные каждого элемента выровнены на 512, поэтому его смещение всегда
|
||||||
целое число секторов, а чтение попадает в границу сектора DSS.
|
целое число секторов, а чтение попадает в границу сектора DSS.
|
||||||
"""
|
"""
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -61,15 +62,59 @@ def build(out_path: Path, files: list[Path]) -> None:
|
|||||||
out_path.write_bytes(bytes(header) + bytes(blob))
|
out_path.write_bytes(bytes(header) + bytes(blob))
|
||||||
|
|
||||||
|
|
||||||
|
def const_name(prefix: str, path: Path) -> str:
|
||||||
|
"""kid12.atl -> ARC_KID_KID12; kid.pal -> ARC_KID_KID_PAL."""
|
||||||
|
stem = path.name
|
||||||
|
if stem.lower().endswith(".atl"):
|
||||||
|
stem = stem[:-4]
|
||||||
|
return f"ARC_{prefix}_" + re.sub(r"[^A-Za-z0-9]", "_", stem).upper()
|
||||||
|
|
||||||
|
|
||||||
|
def write_header(path: Path, prefix: str, files: list[Path]) -> None:
|
||||||
|
"""Индексы элементов — константами, чтобы код не считал их на память.
|
||||||
|
|
||||||
|
Порядок задаёт Makefile, и он же единственный источник истины: сдвинулся
|
||||||
|
список — сдвинулись константы, а серии вроде kid0..kid27 код проверяет
|
||||||
|
статически (`#if ARC_KID_KID27 - ARC_KID_KID0 != 27`).
|
||||||
|
"""
|
||||||
|
lines = [f"/* {path.name} — индексы в архиве {prefix}. СГЕНЕРИРОВАН",
|
||||||
|
" * (toolchain/pop_pack_arc.py), руками не править. */",
|
||||||
|
f"#ifndef {path.stem.upper()}_H",
|
||||||
|
f"#define {path.stem.upper()}_H",
|
||||||
|
"",
|
||||||
|
f"#define ARC_{prefix}_COUNT {len(files)}",
|
||||||
|
""]
|
||||||
|
width = max(len(const_name(prefix, f)) for f in files)
|
||||||
|
for i, f in enumerate(files):
|
||||||
|
lines.append(f"#define {const_name(prefix, f):<{width}} {i}")
|
||||||
|
lines += ["", "#endif", ""]
|
||||||
|
path.write_text("\n".join(lines))
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
if len(sys.argv) < 3:
|
args = sys.argv[1:]
|
||||||
raise SystemExit("usage: pop_pack_arc.py OUT.ARC file [file ...]")
|
header = prefix = None
|
||||||
out = Path(sys.argv[1])
|
while args and args[0].startswith("--"):
|
||||||
files = [Path(a) for a in sys.argv[2:]]
|
opt = args.pop(0)
|
||||||
|
if opt == "--header":
|
||||||
|
header = Path(args.pop(0))
|
||||||
|
elif opt == "--prefix":
|
||||||
|
prefix = args.pop(0)
|
||||||
|
else:
|
||||||
|
raise SystemExit(f"неизвестный ключ {opt}")
|
||||||
|
if len(args) < 2:
|
||||||
|
raise SystemExit("usage: pop_pack_arc.py [--header H.h --prefix NAME] "
|
||||||
|
"OUT.ARC file [file ...]")
|
||||||
|
out = Path(args[0])
|
||||||
|
files = [Path(a) for a in args[1:]]
|
||||||
for f in files:
|
for f in files:
|
||||||
if not f.exists():
|
if not f.exists():
|
||||||
raise SystemExit(f"нет файла: {f}")
|
raise SystemExit(f"нет файла: {f}")
|
||||||
build(out, files)
|
build(out, files)
|
||||||
|
if header:
|
||||||
|
if not prefix:
|
||||||
|
raise SystemExit("--header требует --prefix")
|
||||||
|
write_header(header, prefix, files)
|
||||||
total = out.stat().st_size
|
total = out.stat().st_size
|
||||||
print(f"{out.name}: {len(files)} записей, {total / 1024:.0f} КБ")
|
print(f"{out.name}: {len(files)} записей, {total / 1024:.0f} КБ")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* atlas_attach — принять .atl, УЖЕ прочитанный в EMM-страницу.
|
||||||
|
*
|
||||||
|
* Вторая половина atlas_load, отделённая ради архивов ресурсов: кто читает
|
||||||
|
* атлас не из отдельного файла, а куском общего архива (PoP: pop_arc.c,
|
||||||
|
* формат PBA1), делает своё чтение сам, а проверку магии, gfx_w0_page_prepare
|
||||||
|
* и разбор заголовка отдаёт сюда — иначе эти двадцать строк пришлось бы
|
||||||
|
* повторять в каждом приложении.
|
||||||
|
*
|
||||||
|
* n — сколько байт реально прочитано (или -1, если чтение не удалось).
|
||||||
|
* При любой ошибке блок ОСВОБОЖДАЕТСЯ: вызывающему остаётся только
|
||||||
|
* вернуть -1, и «полузагруженных» атласов не бывает.
|
||||||
|
*
|
||||||
|
* Ошибки: EREAD — короткое чтение, EINVAL — не .atl.
|
||||||
|
*/
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sprinter_mem.h>
|
||||||
|
#include <sprite.h>
|
||||||
|
#include "../_bgi.h"
|
||||||
|
|
||||||
|
int atlas_attach(atlas_t *a, uint8_t blk, uint8_t page, int n)
|
||||||
|
{
|
||||||
|
uint8_t ok, count, h[5];
|
||||||
|
|
||||||
|
a->blk = blk;
|
||||||
|
a->page = page;
|
||||||
|
if (n < (int)sizeof(h)) {
|
||||||
|
mem_free_block(blk);
|
||||||
|
a->page = 0;
|
||||||
|
if (n >= 0) errno = EREAD;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
bank_read(page, 0, h, sizeof(h));
|
||||||
|
ok = (n >= (int)_ATL_DATA_OFF &&
|
||||||
|
h[0] == 'S' && h[1] == 'P' && h[2] == 'A' && h[3] == '1');
|
||||||
|
count = 0;
|
||||||
|
if (ok) {
|
||||||
|
count = h[4];
|
||||||
|
gfx_w0_page_prepare(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Восстановить W3 ДО записи в *a: если atlas_t лежит в банковской
|
||||||
|
* памяти (её адрес тоже резолвится через W3), запись при ещё
|
||||||
|
* замапленной странице атласа ушла бы не в тот физический банк
|
||||||
|
* (найдено 2026-07-15 — PoP room.c держит tile_atlas[] в --bank 1,
|
||||||
|
* a->count оставался мусором, room_draw читал пиксели не с того
|
||||||
|
* оффсета). a->blk/a->page выше пишутся ДО ремапа — их это не
|
||||||
|
* касалось, только a->count. */
|
||||||
|
if (!ok) {
|
||||||
|
mem_free_block(blk);
|
||||||
|
a->page = 0;
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
a->count = count;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+12
-42
@@ -2,62 +2,32 @@
|
|||||||
* atlas_load — загрузить .atl-файл в свежую EMM-страницу (§3.1/§9в
|
* atlas_load — загрузить .atl-файл в свежую EMM-страницу (§3.1/§9в
|
||||||
* дизайна, вариант II: файл-офсет == офсет страницы == W0-адрес).
|
* дизайна, вариант II: файл-офсет == офсет страницы == W0-адрес).
|
||||||
*
|
*
|
||||||
* Порядок: выделить страницу → bank_load_file() → проверить магию 'SPA1'
|
* Порядок: выделить страницу → bank_load_file() → atlas_attach (магия
|
||||||
* → gfx_w0_page_prepare(). Никакого пересчёта офсетов: каталог и ленты
|
* 'SPA1' + gfx_w0_page_prepare). Никакого пересчёта офсетов: каталог и
|
||||||
* уже лежат по своим W0-адресам.
|
* ленты уже лежат по своим W0-адресам.
|
||||||
|
*
|
||||||
|
* Кто держит атласы в АРХИВЕ, а не отдельными файлами, повторяет ту же
|
||||||
|
* пару вызовов со своим чтением: mem_alloc_pages + своё чтение в страницу
|
||||||
|
* + atlas_attach.
|
||||||
*
|
*
|
||||||
* Вызывать ВНЕ рендера (W3 на время загрузки занята страницей атласа).
|
* Вызывать ВНЕ рендера (W3 на время загрузки занята страницей атласа).
|
||||||
* Ошибки: -1 + errno (open/read/alloc; EINVAL — не .atl).
|
* Ошибки: -1 + errno (open/read/alloc; EINVAL — не .atl).
|
||||||
*/
|
*/
|
||||||
#include <errno.h>
|
|
||||||
#include <sprinter_mem.h>
|
#include <sprinter_mem.h>
|
||||||
#include <sprite.h>
|
#include <sprite.h>
|
||||||
#include "../_bgi.h"
|
#include "../_bgi.h"
|
||||||
|
|
||||||
int atlas_load(atlas_t *a, const char *path)
|
int atlas_load(atlas_t *a, const char *path)
|
||||||
{
|
{
|
||||||
uint8_t blk, ok, count, h[5];
|
uint8_t blk, page;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
blk = mem_alloc_pages(1);
|
blk = mem_alloc_pages(1);
|
||||||
if (!blk) return -1;
|
if (!blk) return -1;
|
||||||
|
page = mem_get_page(blk, 0);
|
||||||
|
|
||||||
a->blk = blk;
|
|
||||||
a->page = mem_get_page(blk, 0);
|
|
||||||
|
|
||||||
n = bank_load_file(a->page, 0, path, 16384);
|
|
||||||
/* Не затирать errno от open/read: вызывающему важно отличить отсутствие
|
/* Не затирать errno от open/read: вызывающему важно отличить отсутствие
|
||||||
* файла или короткое чтение от файла, который действительно не .atl.
|
* файла или короткое чтение от файла, который действительно не .atl. */
|
||||||
* Раньше оба случая проваливались в EINVAL ниже, что маскировало
|
n = bank_load_file(page, 0, path, 16384);
|
||||||
* фактическую причину сбоя загрузки экранных полос. */
|
return atlas_attach(a, blk, page, n);
|
||||||
if (n < (int)sizeof(h)) {
|
|
||||||
mem_free_block(blk);
|
|
||||||
a->page = 0;
|
|
||||||
if (n >= 0) errno = EREAD;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
bank_read(a->page, 0, h, sizeof(h));
|
|
||||||
ok = (n >= (int)_ATL_DATA_OFF &&
|
|
||||||
h[0] == 'S' && h[1] == 'P' && h[2] == 'A' && h[3] == '1');
|
|
||||||
count = 0;
|
|
||||||
if (ok) {
|
|
||||||
count = h[4];
|
|
||||||
gfx_w0_page_prepare(a->page);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Восстановить W3 ДО записи в *a: если atlas_t лежит в банковской
|
|
||||||
* памяти (её адрес тоже резолвится через W3), запись при ещё
|
|
||||||
* замапленной странице атласа ушла бы не в тот физический банк
|
|
||||||
* (найдено 2026-07-15 — PoP room.c держит tile_atlas[] в --bank 1,
|
|
||||||
* a->count оставался мусором, room_draw читал пиксели не с того
|
|
||||||
* оффсета). a->blk/a->page выше пишутся ДО ремапа — их это не
|
|
||||||
* касалось, только a->count. */
|
|
||||||
if (!ok) {
|
|
||||||
mem_free_block(blk);
|
|
||||||
a->page = 0;
|
|
||||||
errno = EINVAL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
a->count = count;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,6 +290,12 @@ typedef struct {
|
|||||||
* 0 — OK; -1 + errno (файл/память/формат). */
|
* 0 — OK; -1 + errno (файл/память/формат). */
|
||||||
int atlas_load(atlas_t *a, const char *path);
|
int atlas_load(atlas_t *a, const char *path);
|
||||||
|
|
||||||
|
/* Принять .atl, уже прочитанный в страницу СВОИМИ силами — например, куском
|
||||||
|
* общего архива ресурсов. n — сколько байт прочитано (-1, если не вышло).
|
||||||
|
* Проверяет магию, готовит страницу к маппингу в W0 и заполняет *a; при
|
||||||
|
* любой ошибке освобождает блок сам. 0 — OK; -1 + errno. */
|
||||||
|
int atlas_attach(atlas_t *a, uint8_t blk, uint8_t page, int n);
|
||||||
|
|
||||||
/* Подготовить уже выделенную EMM-страницу для безопасного маппинга в W0:
|
/* Подготовить уже выделенную EMM-страницу для безопасного маппинга в W0:
|
||||||
* установить IRQ/NMI-стабы и запомнить штатную страницу DSS. */
|
* установить IRQ/NMI-стабы и запомнить штатную страницу DSS. */
|
||||||
void gfx_w0_page_prepare(uint8_t page);
|
void gfx_w0_page_prepare(uint8_t page);
|
||||||
|
|||||||
Reference in New Issue
Block a user