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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 21:29:14 +03:00
parent d86adc54c4
commit 65797af44c
40 changed files with 997 additions and 244 deletions
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.
+82 -21
View File
@@ -121,11 +121,14 @@ LVL_DATA := $(foreach n,$(LVL_NUMS),$(LVL_DIR)/res20$(n).bin)
# пламя, меч-предмет, зелья) от набора не зависит — один на оба.
BG_SETS := pop pal
BG_PAGES := 0 1 2 3 4 5 6 7 8 9
BG_DATA := $(foreach s,$(BG_SETS),\
# Двенадцать атласов на тайлсет идут подряд — код считает индекс как
# set*12 + страница, а упаковщик печатает константы в bg_arc.h.
BG_ATL := $(foreach s,$(BG_SETS),\
$(foreach n,$(BG_PAGES),$(BG_DIR)/$(s)_env$(n).atl) \
$(BG_DIR)/$(s)_wall.atl $(BG_DIR)/$(s)_fore.atl \
$(BG_DIR)/$(s)_tile.pal) \
$(BG_DIR)/$(s)_wall.atl $(BG_DIR)/$(s)_fore.atl) \
$(BG_DIR)/pop_pot.atl
BG_ARC := $(BG_DIR)/bg.arc
BG_DATA := $(BG_ARC) $(foreach s,$(BG_SETS),$(BG_DIR)/$(s)_tile.pal)
# ВСЕ 28 страниц Kid — иначе кадры торможения/разворота/приседа (image>23)
# не рисуются (отрисовка пропускает незагруженную страницу). Палитра —
# ЕДИНАЯ игровая (env+wall+kid).
@@ -140,8 +143,19 @@ KID_DATA := $(KID_PAGES) $(KID_VPAGES) $(KID_DIR)/kid.pal \
# Таблицы анимации (кадры+seqtbl) — отдельный бинарь в EMM-странице, его
# делает pop_extract_kid_data.py (не упаковщик спрайтов) — своё правило ниже.
KID_BIN := $(KID_DIR)/kid_data.bin
# АРХИВ KID. Шестьдесят файлов в одном: замер 2026-08-25 — `open` 51,4 мс
# против 32,6 мс на чтение 16 КБ, то есть на одних только открытиях набор
# Кида терял три секунды. Порядок в списке = порядок индексов, а их
# упаковщик печатает в kid_arc.h (константы ARC_KID_*).
KID_ARC := $(KID_DIR)/kid.arc
# В архив идут только атласы. Палитру читает gfx_pal_fload прямо в
# видеопамять, а kid_data.bin — host-тесты своей файловой заглушкой; ради
# двух файлов заводить обходные пути незачем.
KID_ARC_SRC := $(KID_PAGES) $(KID_VPAGES) $(KID_DIR)/sword.atl \
$(KID_DIR)/sword_v.atl
GUARD_DIR := $(CURDIR)/../poc/res/guard
GUARD_DATA := $(GUARD_DIR)/g0.atl $(GUARD_DIR)/g1.atl $(GUARD_DIR)/g2.atl \
GUARD_ATL := $(GUARD_DIR)/g0.atl $(GUARD_DIR)/g1.atl $(GUARD_DIR)/g2.atl \
$(GUARD_DIR)/g3.atl $(GUARD_DIR)/g4.atl \
$(GUARD_DIR)/g0_v.atl $(GUARD_DIR)/g1_v.atl $(GUARD_DIR)/g2_v.atl \
$(GUARD_DIR)/g3_v.atl $(GUARD_DIR)/g4_v.atl
@@ -150,14 +164,14 @@ GUARD_DATA := $(GUARD_DIR)/g0.atl $(GUARD_DIR)/g1.atl $(GUARD_DIR)/g2.atl \
SKEL_DIR := $(CURDIR)/../poc/res/skel
# Зеркального набора у скелета НЕТ: зелье инверсии — уровень 9, там обычный
# страж (tbl_guard_type[9] = 0). См. pop_pack_guard.py.
SKEL_DATA := $(SKEL_DIR)/g0.atl $(SKEL_DIR)/g1.atl $(SKEL_DIR)/g2.atl \
SKEL_ATL := $(SKEL_DIR)/g0.atl $(SKEL_DIR)/g1.atl $(SKEL_DIR)/g2.atl \
$(SKEL_DIR)/g3.atl
# Джафар — тоже отдельный набор (VIZIER.DAT): его грузит уровень 13
# (tbl_guard_type == 3). Кадров столько же, сколько у стража (34), но
# палитра своя, из файла набора: curr_guard_color для не-стражей = 0.
# Зеркального набора нет по той же причине, что у скелета.
VIZIER_DIR := $(CURDIR)/../poc/res/vizier
VIZIER_DATA := $(VIZIER_DIR)/g0.atl $(VIZIER_DIR)/g1.atl $(VIZIER_DIR)/g2.atl \
VIZIER_ATL := $(VIZIER_DIR)/g0.atl $(VIZIER_DIR)/g1.atl $(VIZIER_DIR)/g2.atl \
$(VIZIER_DIR)/g3.atl $(VIZIER_DIR)/g4.atl
# ТЕНЬ — свой набор, запечённый упаковщиком (наложение спрайта на себя со
@@ -165,9 +179,21 @@ VIZIER_DATA := $(VIZIER_DIR)/g0.atl $(VIZIER_DIR)/g1.atl $(VIZIER_DIR)/g2.atl \
# половины в одном наборе: sk* — кадры вне боя (спрайты Кида), sf* — кадры
# 150..189 (SHADOW.DAT, это тоже графика Кида, а не стража).
SHADOW_DIR := $(CURDIR)/../poc/res/shadow
SHADOW_DATA := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(SHADOW_DIR)/sk$(n).atl) \
SHADOW_ATL := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(SHADOW_DIR)/sk$(n).atl) \
$(foreach n,0 1 2 3,$(SHADOW_DIR)/sf$(n).atl)
# Четыре архива, а не один: имена внутри наборов совпадают (g0.atl есть и у
# стража, и у скелета, и у Джафара), а константы индексов упаковщик делает
# из имени файла. Наборы всё равно грузятся поодиночке — по типу уровня.
GUARD_ARC := $(GUARD_DIR)/guard.arc
SKEL_ARC := $(SKEL_DIR)/skel.arc
VIZIER_ARC := $(VIZIER_DIR)/vizier.arc
SHADOW_ARC := $(SHADOW_DIR)/shadow.arc
GUARD_DATA := $(GUARD_ARC)
SKEL_DATA := $(SKEL_ARC)
VIZIER_DATA := $(VIZIER_ARC)
SHADOW_DATA := $(SHADOW_ARC)
# ЗВУКОВЫЕ ЭФФЕКТЫ — оригинальная оцифровка, приведённая к частоте CBL
# (10 937,5 Гц, 8 бит моно). 8 страниц по 16 КБ; см. ../docs/sound_plan.md.
SND_DIR := $(CURDIR)/../poc/res/sound
@@ -200,8 +226,9 @@ FONT_DATA := $(FONT_ATL)
TITLE_DIR := $(CURDIR)/../poc/res/title
TITLE_SCENES := 0 1 2 3
TITLE_PARTS := 0 1 2 3 4
TITLE_DATA := $(TITLE_DIR)/title.pal \
$(foreach s,$(TITLE_SCENES),$(foreach p,$(TITLE_PARTS),$(TITLE_DIR)/t$(s)_$(p).atl))
TITLE_ATL := $(foreach s,$(TITLE_SCENES),$(foreach p,$(TITLE_PARTS),$(TITLE_DIR)/t$(s)_$(p).atl))
TITLE_ARC := $(TITLE_DIR)/title.arc
TITLE_DATA := $(TITLE_DIR)/title.pal $(TITLE_ARC)
# PV/INTRO: story и fallback-стадии режутся на полосы. Для настоящего PV
# intro есть ещё чистый фон `b0_*` и шесть <=16K actor-атласов: они остаются
# в EMM на время сцены, а не перечитывают полный 320x200 кадр с HDD.
@@ -213,12 +240,14 @@ PV_ANIM := $(foreach p,$(PV_PARTS),$(PV_DIR)/b0_$(p).atl) \
$(PV_DIR)/a0.atl $(PV_DIR)/a1.atl $(PV_DIR)/a2.atl \
$(PV_DIR)/j0.atl $(PV_DIR)/j1.atl \
$(PV_DIR)/j2.atl $(PV_DIR)/h0.atl $(PV_DIR)/t0.atl
PV_DATA := $(PV_DIR)/story.pal $(PV_DIR)/pv.pal \
PV_ATL := \
$(foreach s,$(PV_STORY),$(foreach p,$(PV_PARTS),$(PV_DIR)/s$(s)_$(p).atl)) \
$(foreach s,$(PV_SCENES),$(foreach p,$(PV_PARTS),$(PV_DIR)/p$(s)_$(p).atl)) \
$(PV_ANIM)
PV_ARC := $(PV_DIR)/pv.arc
PV_DATA := $(PV_DIR)/story.pal $(PV_DIR)/pv.pal $(PV_ARC)
EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(GUARD_DATA) $(SKEL_DATA) \
EXTRA_DATA := $(BG_DATA) $(KID_ARC) $(KID_DIR)/kid.pal $(KID_BIN) $(GUARD_DATA) $(SKEL_DATA) \
$(VIZIER_DATA) $(SHADOW_DATA) $(SND_DATA) $(MUS_DATA) $(FONT_DATA) $(TITLE_DATA) $(PV_DATA) $(LVL_DATA)
# Образ HDD для MAME: ресурсы раскладываются ПО КАТАЛОГАМ (8.3, как в DSS),
# чтобы корень диска не зарастал десятками .atl.
@@ -231,7 +260,7 @@ EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(GUARD_DATA) $(SKEL_DATA) \
# LEVELS\ — уровни
HDD_PACK_ARGS := $(EXAMPLE).exe \
$(foreach f,$(BG_DATA),BG:$(f)) \
$(foreach f,$(KID_DATA) $(KID_BIN),KID:$(f)) \
KID:$(KID_ARC) KID:$(KID_DIR)/kid.pal KID:$(KID_BIN) \
$(foreach f,$(GUARD_DATA),GUARD:$(f)) \
$(foreach f,$(SKEL_DATA),SKEL:$(f)) \
$(foreach f,$(VIZIER_DATA),VIZIER:$(f)) \
@@ -271,31 +300,31 @@ $(RESOURCE_STAMP_DIR):
$(BG_STAMP): $(TC)/pop_pack_bg.py $(TC)/render_room.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_bg.py dungeon && python3 pop_pack_bg.py palace
touch $@
$(BG_DATA): $(BG_STAMP)
$(BG_ATL) $(BG_DIR)/pop_tile.pal $(BG_DIR)/pal_tile.pal: $(BG_STAMP)
@test -f $@ || { $(MAKE) -B $(BG_STAMP); test -f $@; }
$(GUARD_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_guard.py
touch $@
$(GUARD_DATA): $(GUARD_STAMP)
$(GUARD_ATL) $(GUARD_DIR)/guard.pal: $(GUARD_STAMP)
@test -f $@ || { $(MAKE) -B $(GUARD_STAMP); test -f $@; }
$(SKEL_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_guard.py SKEL
touch $@
$(SKEL_DATA): $(SKEL_STAMP)
$(SKEL_ATL): $(SKEL_STAMP)
@test -f $@ || { $(MAKE) -B $(SKEL_STAMP); test -f $@; }
$(VIZIER_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_guard.py VIZIER
touch $@
$(VIZIER_DATA): $(VIZIER_STAMP)
$(VIZIER_ATL): $(VIZIER_STAMP)
@test -f $@ || { $(MAKE) -B $(VIZIER_STAMP); test -f $@; }
$(SHADOW_STAMP): $(TC)/pop_pack_shadow.py $(TC)/pop_pack_bg.py $(TC)/pop_pack_kid.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_shadow.py
touch $@
$(SHADOW_DATA) pop_shadow_atlas.h: $(SHADOW_STAMP)
$(SHADOW_ATL) pop_shadow_atlas.h: $(SHADOW_STAMP)
@test -f $@ || { $(MAKE) -B $(SHADOW_STAMP); test -f $@; }
$(SOUND_STAMP): $(TC)/pop_pack_sound.py | $(RESOURCE_STAMP_DIR)
@@ -306,6 +335,38 @@ $(SND_PARTS) pop_sound_tbl.h: $(SOUND_STAMP)
$(SND_ARC): $(SND_PARTS) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py $@ $(SND_PARTS)
$(GUARD_ARC) guard_arc.h: $(GUARD_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header guard_arc.h --prefix GUARD \
$(GUARD_ARC) $(GUARD_ATL)
$(SKEL_ARC) skel_arc.h: $(SKEL_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header skel_arc.h --prefix SKEL \
$(SKEL_ARC) $(SKEL_ATL)
$(VIZIER_ARC) vizier_arc.h: $(VIZIER_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header vizier_arc.h --prefix VIZIER \
$(VIZIER_ARC) $(VIZIER_ATL)
$(SHADOW_ARC) shadow_arc.h: $(SHADOW_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header shadow_arc.h --prefix SHADOW \
$(SHADOW_ARC) $(SHADOW_ATL)
$(BG_ARC) bg_arc.h: $(BG_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header bg_arc.h --prefix BG \
$(BG_ARC) $(BG_ATL)
$(TITLE_ARC) title_arc.h: $(TITLE_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header title_arc.h --prefix TITLE \
$(TITLE_ARC) $(TITLE_ATL)
$(PV_ARC) pv_arc.h: $(PV_ATL) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header pv_arc.h --prefix PV \
$(PV_ARC) $(PV_ATL)
$(KID_ARC) kid_arc.h: $(KID_ARC_SRC) $(TC)/pop_pack_arc.py
python3 $(TC)/pop_pack_arc.py --header kid_arc.h --prefix KID \
$(KID_ARC) $(KID_ARC_SRC)
$(MUSIC_STAMP): $(TC)/pop_pack_music.py | $(RESOURCE_STAMP_DIR)
cd $(TC) && python3 pop_pack_music.py $(MUS_IDS)
touch $@
@@ -339,7 +400,7 @@ $(TITLE_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_title.py \
$(CURDIR)/../SDLPoP/data/TITLE/res55.png | $(RESOURCE_STAMP_DIR)
cd $(PROJ_ROOT) && python3 toolchain/pop_pack_title.py
touch $@
$(TITLE_DATA): $(TITLE_STAMP)
$(TITLE_ATL) $(TITLE_DIR)/title.pal: $(TITLE_STAMP)
@test -f $@ || { $(MAKE) -B $(TITLE_STAMP); test -f $@; }
$(PV_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_intro.py \
@@ -366,13 +427,13 @@ $(PV_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_intro.py \
$(foreach n,151 152 153 154 155 156 157 158 159,$(CURDIR)/../SDLPoP/data/PRINCE/res$(n).png) | $(RESOURCE_STAMP_DIR)
cd $(PROJ_ROOT) && python3 toolchain/pop_pack_intro.py
touch $@
$(PV_DATA): $(PV_STAMP)
$(PV_ATL) $(PV_DIR)/story.pal $(PV_DIR)/pv.pal: $(PV_STAMP)
@test -f $@ || { $(MAKE) -B $(PV_STAMP); test -f $@; }
# Явные цели позволяют собирать ресурсы без `make -B`, который по смыслу
# принудительно пересобирает также libc, libbgi и само приложение.
resources-bg: $(BG_DATA)
resources-kid: $(KID_DATA) $(KID_BIN)
resources-kid: $(KID_ARC)
resources-actors: $(GUARD_DATA) $(SKEL_DATA) $(VIZIER_DATA) $(SHADOW_DATA)
resources-sound: $(SND_DATA)
resources-music: $(MUS_DATA)
@@ -399,7 +460,7 @@ hdd: resources
# широкий: цена лишней полной сборки ниже риска получить stale-бинарник.
LOCAL_HEADERS := $(wildcard *.h)
$(EXAMPLE).exe: Makefile $(PROJ_ROOT)/app.mk $(BANKED_SRCS) $(LOCAL_HEADERS) \
$(BG_DATA) $(KID_DATA) $(KID_BIN) $(FONT_DATA) $(TITLE_DATA) $(PV_DATA) $(LVL_DATA)
$(BG_DATA) $(KID_ARC) $(FONT_DATA) $(TITLE_DATA) $(PV_DATA) $(LVL_DATA)
clean-resources:
rm -rf $(RESOURCE_STAMP_DIR)
+34
View File
@@ -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
+19
View File
@@ -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
+67
View File
@@ -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
+36
View File
@@ -13,6 +13,27 @@
#include <sprinter_mem.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,
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);
}
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
{
if (a->fd >= 0) close(a->fd);
+34 -1
View File
@@ -26,17 +26,43 @@
#define POP_ARC_H
#include <stdint.h>
#include <sprite.h> /* atlas_t */
#define POP_ARC_HEADER 512
#define POP_ARC_SECTOR 512
#define POP_ARC_MAX 126 /* (512-8)/4 */
#define POP_ARC_REC 4 /* байт на запись таблицы */
typedef struct {
/* Имя у структуры есть намеренно: так на неё можно сослаться из заголовков,
* которым незачем тянуть весь pop_arc.h (pop_ui.h). */
typedef struct pop_arc_s {
int fd;
uint8_t count;
} 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 байт).
* tbl_max — сколько записей помещается в буфер.
* Возврат: число записей; -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;
/* Атлас ПРЯМО ИЗ АРХИВА: выделить страницу, вычитать в неё элемент 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 */
+43 -35
View File
@@ -18,6 +18,10 @@
#include <graphics.h>
#include <gfx.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_kid.h"
#include "_pop_kdraw.h"
@@ -107,31 +111,30 @@ pop_cdraw_t pop_cd[POP_CH_N];
* грузит. Уровень БЕЗ соперников (type < 0) не грузит ничего. */
int pop_guard_load(void) __banked
{
static const char *const guard_names[GUARD_PAGES] = {
"GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl",
"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;
uint8_t tbl[ARC_GUARD_COUNT * POP_ARC_REC]; /* самый крупный из трёх */
pop_arc_t arc;
int8_t type = pop_level_guard_type(pop_current_level);
uint8_t i;
uint8_t i, pages, count, id;
if (type < 0) return 0; /* соперников на уровне нет */
names = (type == 2) ? skel_names : (type == 3) ? vizier_names : guard_names;
for (i = 0; i < GUARD_PAGES; i++) {
if (names[i] == 0) break;
if (atlas_load(&gp[i], names[i]) != 0) return -1;
}
/* Наборы разложены по своим архивам, а не в один: имена внутри у них
* совпадают (g0.atl у всех троих), да и грузится всегда ровно один. */
if (type == 2) { id = POP_ARC_SKEL; count = ARC_SKEL_COUNT;
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;
/* Цвет: у скелета он СВОЙ (палитра из его набора), у стража подменяется
/* Цвет: у скелета он СВОЙ (палитра из его набора), у стража подменяется
* из res10.bin по guards_color — это делает pop_guard_set_palette при
* входе в комнату (seg002:186: у не-guard curr_guard_color = 0). */
pop_guard_pal_apply();
@@ -143,24 +146,29 @@ int pop_guard_load(void) __banked
* потому что gp[] и выбор GUARD/SKEL живут именно тут. */
int pop_guard_vflip_load(void) __banked
{
static const char *const guard_v[GUARD_PAGES] = {
"GUARD\\g0_v.atl", "GUARD\\g1_v.atl", "GUARD\\g2_v.atl",
"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;
uint8_t tbl[ARC_GUARD_COUNT * POP_ARC_REC];
pop_arc_t arc;
int8_t type = pop_level_guard_type(pop_current_level);
uint8_t i;
int res = 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++) {
if (names[i] == 0) break;
if (pop_vflip_add(&gp[i], names[i]) != 0) return -1;
atlas_t m;
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
+131 -75
View File
@@ -15,6 +15,8 @@
#include "pop_shadow.h"
#include "pop_sfx.h"
#include "pop_music.h"
#include "pop_arc.h"
#include "pv_arc.h"
#include "pop_cutscene.h"
#include "pop_intro.h"
#include "pop_pace.h" /* pop_wait_edge */
@@ -85,31 +87,15 @@ enum {
INTRO_PAL_PV = 1
};
/* 0..4 — TITLE/res41 + тексты 42/43/45/44 и чистая рамка; 5..12 —
* PV/res951 + персонажи.
* 8.3-имена намеренно короткие: DSS не получает длинные path-компоненты. */
static const char * const intro_part[13][INTRO_PARTS] = {
{ "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" },
{ "PV\\s2_0.atl", "PV\\s2_1.atl", "PV\\s2_2.atl", "PV\\s2_3.atl", "PV\\s2_4.atl" },
{ "PV\\s3_0.atl", "PV\\s3_1.atl", "PV\\s3_2.atl", "PV\\s3_3.atl", "PV\\s3_4.atl" },
{ "PV\\s4_0.atl", "PV\\s4_1.atl", "PV\\s4_2.atl", "PV\\s4_3.atl", "PV\\s4_4.atl" },
{ "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"
};
/* Экраны PV лежат в одном архиве по пять полос подряд, ровно в том же
* порядке, в каком идут номера сцен: сначала пять текстовых (story
* res41 + тексты 42/43/45/44 и чистая рамка), затем восемь стадий комнаты
* принцессы (res951 + персонажи) и чистый фон b0. Поэтому имён здесь нет
* — индекс первой полосы экрана считается умножением. */
#if (ARC_PV_P0_0 - ARC_PV_S0_0) != 5 * INTRO_PARTS
#error "экраны PV в архиве лежат не по пять полос подряд"
#endif
#define INTRO_SCENES 13
/* Две текстовые половины show_title(). Между ними PV теперь идёт отдельным
* покадровым renderer-ом, а не четырьмя полными статичными композициями. */
@@ -225,13 +211,12 @@ static uint8_t intro_scene;
static uint8_t intro_pending_pal;
static uint8_t intro_pal_pending;
/* Таблицы intro_part/pv_base_part расположены в этом же банке 11. Общий
* pop_screen_blit_atlas_strips находится в банке 9, и передавать ему
* указатель на W3-данные нельзя: после bcall тот же адрес уже видит другой
* физический банк. Держим короткий полосовой loader рядом с его таблицами.
* atlas_load сам временно занимает W3 только ПОСЛЕ open(path). */
static int intro_blit_atlas_strips(const char * const *parts,
uint8_t count, int x, int y)
/* Полосовой загрузчик рядом со своими вызовами: общий из банка 9 звать
* можно (индексы и таблица — числа на стеке W2, а не указатели на W3), но
* лишний банк→банк на каждую полосу тут ни к чему.
* pop_arc_atlas сам временно занимает W3 только на время чтения. */
static int intro_blit_arc(pop_arc_t *arc, const uint8_t *tbl,
uint8_t base, uint8_t count, int x, int y)
{
uint8_t part;
@@ -240,7 +225,8 @@ static int intro_blit_atlas_strips(const char * const *parts,
atlas_t at;
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);
img = (const uint8_t *)atlas_image(&at, 0);
gfx_blit(x, y, img);
@@ -251,6 +237,30 @@ static int intro_blit_atlas_strips(const char * const *parts,
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,
@@ -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)
{
if (scene >= sizeof(intro_part) / sizeof(intro_part[0])) return -1;
if (scene >= INTRO_SCENES) return -1;
gfx_set_draw_page(page);
gfx_set_bank(GFX_BANK_NORMAL);
pop_screen_fill(BLACK);
if (intro_blit_atlas_strips(intro_part[scene], INTRO_PARTS,
0, POP_YOFF) != 0)
return -1;
return 0;
return intro_blit_screen((uint8_t)(ARC_PV_S0_0 + scene * INTRO_PARTS),
POP_YOFF);
}
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_bank(GFX_BANK_NORMAL);
pop_screen_fill(BLACK);
if (intro_blit_atlas_strips(pv_base_part, INTRO_PARTS,
0, POP_YOFF) != 0)
return -1;
if (intro_blit_screen(ARC_PV_B0_0, POP_YOFF) != 0) return -1;
gfx_set_draw_page(1);
full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT;
gfx_copy_page(&full, GFX_COPY_DIRECT);
@@ -695,7 +701,13 @@ static int intro_pv_animated(void)
uint16_t tick;
uint16_t frame_end;
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 torch_left = 1;
uint8_t torch_right = 6;
@@ -717,21 +729,21 @@ static int intro_pv_animated(void)
* временная EMM-страница. Поэтому фон готовится до постоянных
* actor-атласов: порядок сохраняет независимость загрузчика полос от
* числа одновременно живущих анимационных ресурсов. */
if (atlas_load(&princess, "PV\\a0.atl") != 0) return -1;
if (atlas_load(&jaffar0, "PV\\j0.atl") != 0) {
goto free_princess;
}
if (atlas_load(&jaffar1, "PV\\j1.atl") != 0) {
goto free_jaffar0;
}
if (atlas_load(&jaffar2, "PV\\j2.atl") != 0) {
goto free_jaffar1;
}
if (atlas_load(&hourglass, "PV\\h0.atl") != 0) {
goto free_jaffar2;
}
if (atlas_load(&torch, "PV\\t0.atl") != 0) {
goto free_hourglass;
/* Шесть атласов сцены — из ОДНОГО открытого архива. */
{
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_A0, &princess) != 0);
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 (!bad) bad = (int8_t)(pv_atlas(&arc, tbl, ARC_PV_J2, &jaffar2) != 0);
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);
pop_arc_close(&arc);
if (bad) return -1;
}
pop_pal_fade_in(INTRO_FADE);
@@ -740,6 +752,18 @@ static int intro_pv_animated(void)
* Полноэкранная accel-копия кадра насосу не мешает: leaf режет DI
* бандами по 16 строк. */
(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;
princess_actor.x = 120;
princess_actor.frame = 11; /* Pstand, facing left */
@@ -763,7 +787,8 @@ static int intro_pv_animated(void)
* его исходные 6/8/7 тиков, снаружи draw_frame держит результат
* четыре физических кадра — единые номинальные 12,5 FPS. */
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;
do {
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();
/* ПЕЙСИНГ ПО ОРИГИНАЛЬНОЙ ШКАЛЕ. Кадр 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);
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_pal_fade_out(INTRO_FADE);
free_torch:
atlas_free(&torch);
free_hourglass:
atlas_free(&hourglass);
free_jaffar2:
atlas_free(&jaffar2);
free_jaffar1:
atlas_free(&jaffar1);
free_jaffar0:
atlas_free(&jaffar0);
free_princess:
atlas_free(&princess);
return result;
}
@@ -876,8 +909,17 @@ static int pre_princess_animated(pop_pre_cutscene_t scene)
* рисуется GFX_BANK_SPRITE и потому не оседает в этом источнике. */
if (intro_draw_page(picture, 0) != 0 ||
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_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);
atlas_free(&torch);
free_hourglass_pre:
atlas_free(&hourglass);
return result;
}
@@ -1026,20 +1067,35 @@ static void cut_free(void)
static int cut_load(void)
{
uint8_t tbl[ARC_PV_COUNT * POP_ARC_REC];
pop_arc_t arc;
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;
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;
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;
/* Комната принцессы живёт и в катсценах: пламя факелов, звёзды в окне,
* песок в часах — и ПЕРЕДНЯЯ КОЛОННА, которая по оригиналу закрывает
* персонажей (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;
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;
pop_arc_close(&arc);
return 0;
}
+50 -20
View File
@@ -16,23 +16,46 @@
#include "kid_data.h"
#include "pop_vflip.h"
#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
/* Таблица архива живёт на стеке вызывающего (4 байта на запись, здесь 232)
* — держать её постоянно незачем, а W2 у игры и без того тесный. */
int pop_kid_load(uint8_t npages) __banked
{
static const char *const names[KID_MAXPAGES] = {
"KID\\kid0.atl","KID\\kid1.atl","KID\\kid2.atl","KID\\kid3.atl","KID\\kid4.atl","KID\\kid5.atl",
"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 tbl[ARC_KID_COUNT * POP_ARC_REC];
pop_arc_t arc;
uint8_t i;
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++)
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;
}
/* Меч — из того же открытого архива: без него кадры 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;
return 0;
}
@@ -73,21 +96,28 @@ void pop_kid_data_free(void) __banked
* только через него). Идемпотентна. */
int pop_vflip_load_all(void) __banked
{
static const char *const kidv[KID_MAXPAGES] = {
"KID\\kid0_v.atl","KID\\kid1_v.atl","KID\\kid2_v.atl","KID\\kid3_v.atl",
"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 tbl[ARC_KID_COUNT * POP_ARC_REC];
pop_arc_t arc;
uint8_t i;
int res = 0;
if (pop_vflip_ready()) return 0;
for (i = 0; i < kid_npages; i++)
if (pop_vflip_add(&kidp[i], kidv[i]) != 0) res = -1;
if (pop_sword_vflip_load() != 0) res = -1;
if (pop_arc_open_id(&arc, POP_ARC_KID, tbl, ARC_KID_COUNT) < 0) return -1;
for (i = 0; i < kid_npages; i++) {
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;
return res;
}
+9 -5
View File
@@ -37,18 +37,22 @@ static const int8_t SWORD_DY[KID_NSWORD] = KID_SWORD_DY_INIT;
static atlas_t swordp;
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;
return 0;
}
/* Зеркальная страница меча (зелье инверсии) — пара к pop_sword_load. */
int pop_sword_vflip_load(void) __banked
/* Зеркальная страница меча (зелье инверсии) — пара к pop_sword_take. */
int pop_sword_vflip_take(const atlas_t *m) __banked
{
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). Клинок рисуется, если персонаж в
+2 -2
View File
@@ -47,9 +47,9 @@ void pop_kid_data_free(void) __banked;
* подъёма меча/убирания в ножны (add_sword_to_objtable, seg006:1798).
* Грузить ДО 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;
/* Сама отрисовка клинка (pop_sword_draw) и блит спрайта chtab_2 по id
+35 -6
View File
@@ -22,6 +22,8 @@
#include "_pop_bg.h" /* контракт с горячей половиной (pop_bg.c, банк 2) */
#include "pop_tile.h" /* общие листья слоя фона (резидент): блит, тайлы, таблица */
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
#include "pop_arc.h"
#include "bg_arc.h"
#include "pop_state.h"
#include "pop_redraw.h" /* пометки тайлов: сосед падающего куска (draw_mob) */
#include "pop_geom.h"
@@ -57,6 +59,12 @@ static uint8_t bg_set = 0xFF; /* какой набор сейча
* персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска зарастает
* десятками .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 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_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;
if (atlas_load(&pop_wall_at, bg_path(pop_wall_atl[set])) != 0) return -1;
if (atlas_load(&pop_fore_at, bg_path(pop_fore_atl[set])) != 0) return -1;
if (bg_set == 0xFF && /* chtab_1 от набора не зависит */
atlas_load(&pop_pot_at, bg_path(POP_POT_ATL)) != 0) return -1;
/* Двадцать пять атласов фона лежат в одном архиве: двенадцать на
* тайлсет подряд плюс общий chtab_1. Открываем его ОДИН раз на смену
* набора — при цене `open` 51,4 мс это и есть основная экономия. */
{
uint8_t tbl[ARC_BG_COUNT * POP_ARC_REC];
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;
pop_palace = (uint8_t)(set == 1);
mob_spr_build(); /* композит падающего куска — свой на набор */
+11
View File
@@ -52,6 +52,9 @@ volatile uint16_t pop_mus_left;
* таблицах приоритета и перебиваемости, что и эффекты (см. pop_sfx_tick). */
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 uint16_t sfx_ptr; /* адрес в окне W0: 0x0000..0x3FFF */
static volatile uint16_t sfx_left; /* сколько байт осталось (кратно 128) */
@@ -94,6 +97,14 @@ int pop_sfx_fill(uint16_t n)
uint8_t saved, phys;
uint16_t ptr;
/* ЧАСЫ СЦЕН. Насос зовётся строго по расходу буфера CBL — 85,4 раза в
* секунду, что бы ни делал главный цикл. Это единственный источник времени,
* которому не мешают ни долгие блиты, ни чтение с диска: кадровые
* прерывания теряются в di-окнах акселератора (pop_pace.h), а счёт
* ожиданий vsync считает НАШИ ожидания, а не прошедшее время.
* Катсцены пейсятся по нему — иначе картинка уезжает от музыки. */
pop_snd_tick++;
/* Три источника по убыванию важности: эффект, музыка, тишина. Эффект
* перебивает музыку — как у оригинала, где play_sound глушит всё, что
* звучало; голос-то один. Музыка при этом НЕ теряет позицию: её
+10
View File
@@ -49,6 +49,16 @@ void pop_sfx_toggle(void) __banked;
* честно means «звука не будет». */
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 — нумерация оригинала, см.
* sound_plan.md). НЕ запускает его немедленно и НЕ перебивает играющий:
* это порт play_sound (seg000:12C5), который лишь НОМИНИРУЕТ кандидата на
+1 -1
View File
@@ -31,7 +31,7 @@ int pop_sfx_init(void) __banked
pop_arc_t arc;
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)
return -1;
blk = mem_alloc_pages(POP_SND_PAGES);
+27 -10
View File
@@ -10,6 +10,8 @@
#include <stdint.h>
#include <gfx.h>
#include <sprite.h>
#include "pop_arc.h"
#include "shadow_arc.h"
#include "pop_shadow.h"
#include "pop_shadow_atlas.h"
@@ -37,19 +39,34 @@ int pop_shadow_load(void) __banked
uint8_t i;
if (pop_shadow_kid_n || pop_shadow_fgt_n) return 0; /* уже загружен */
for (i = 0; i < POP_SHADOW_KID_PAGES; i++) {
if (atlas_load(&pop_shadow_kid[i], pop_shadow_kid_atl[i]) != 0) {
pop_shadow_free();
{
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++) {
if (pop_arc_atlas(&arc, tbl, (uint8_t)(ARC_SHADOW_SK0 + i),
&pop_shadow_kid[i]) != 0) {
pop_arc_close(&arc);
pop_shadow_free();
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++) {
if (atlas_load(&pop_shadow_fgt[i], pop_shadow_fgt_atl[i]) != 0) {
pop_shadow_free();
return -1;
for (i = 0; i < POP_SHADOW_FGT_PAGES; i++) {
if (pop_arc_atlas(&arc, tbl, (uint8_t)(ARC_SHADOW_SF0 + i),
&pop_shadow_fgt[i]) != 0) {
pop_arc_close(&arc);
pop_shadow_free();
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();
+17 -9
View File
@@ -14,6 +14,8 @@
#include "pop_bg.h" /* POP_YOFF: title занимает 200px по центру 256px */
#include "pop_sfx.h"
#include "pop_music.h"
#include "pop_arc.h"
#include "title_arc.h"
#include "pop_cutscene.h" /* POP_T60 */
#include "pop_settings.h"
#include "pop_pal.h"
@@ -33,13 +35,11 @@ enum {
TITLE_LOGO
};
/* Имена короче 8.3; строка = композиция, столбец = полоса 49/49/49/49/4. */
static const char * const title_part[4][TITLE_PARTS] = {
{ "TITLE\\t0_0.atl", "TITLE\\t0_1.atl", "TITLE\\t0_2.atl", "TITLE\\t0_3.atl", "TITLE\\t0_4.atl" },
{ "TITLE\\t1_0.atl", "TITLE\\t1_1.atl", "TITLE\\t1_2.atl", "TITLE\\t1_3.atl", "TITLE\\t1_4.atl" },
{ "TITLE\\t2_0.atl", "TITLE\\t2_1.atl", "TITLE\\t2_2.atl", "TITLE\\t2_3.atl", "TITLE\\t2_4.atl" },
{ "TITLE\\t3_0.atl", "TITLE\\t3_1.atl", "TITLE\\t3_2.atl", "TITLE\\t3_3.atl", "TITLE\\t3_4.atl" }
};
/* Композиции лежат в одном архиве подряд по пять полос 49/49/49/49/4,
* поэтому имён здесь больше нет: индекс = сцена * TITLE_PARTS. */
#if (ARC_TITLE_T1_0 - ARC_TITLE_T0_0) != TITLE_PARTS
#error "полосы титров в архиве лежат не по пять подряд"
#endif
/* Последовательность и интервалы буквально из show_title():
* main --0x82--> presents --0xCD--> main --0x41--> game
@@ -74,12 +74,20 @@ static uint8_t title_skip_pressed(void)
* подготовленной страницы; show=0 оставляет её скрытой для palette commit. */
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();
int res;
pop_screen_fill(BLACK);
if (pop_screen_blit_atlas_strips(title_part[scene], TITLE_PARTS,
0, TITLE_Y) != 0)
if (pop_arc_open_id(&arc, POP_ARC_TITLE, tbl, ARC_TITLE_COUNT) < 0)
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);
return page;
}
+38 -8
View File
@@ -11,6 +11,7 @@
#include <sprite.h>
#include "pop_bg.h" /* POP_YOFF/POP_PLAYFIELD_H — граница игрового поля */
#include "pop_font.h"
#include "pop_arc.h"
#include "pop_ui.h"
#include "pop_status.h"
@@ -284,6 +285,22 @@ void pop_screen_present(uint8_t page) __banked
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-страницу.
* Общий загрузчик полос держит лишь одну страницу и не трогает page flip:
* caller уже собрал скрытую страницу и сам решает, когда её показать. */
@@ -295,16 +312,29 @@ int pop_screen_blit_atlas_strips(const char * const *parts,
gfx_set_bank(GFX_BANK_NORMAL);
for (part = 0; part < count; part++) {
atlas_t at;
const uint8_t *img;
if (atlas_load(&at, parts[part]) != 0) return -1;
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);
strip_blit(&at, x, &y);
}
return 0;
}
/* То же самое, но полосы лежат подряд в АРХИВЕ (заставка, story, PV): один
* `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;
}
+6
View File
@@ -75,4 +75,10 @@ void pop_screen_present(uint8_t page) __banked;
int pop_screen_blit_atlas_strips(const char * const *parts,
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 */
+15 -3
View File
@@ -28,15 +28,27 @@ static uint8_t vf_src[VF_MAX]; /* страница оригинала
static atlas_t vf_atl[VF_MAX]; /* зеркальный атлас (его page) */
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 (atlas_load(&vf_atl[vf_n], path) != 0) return -1;
if (!orig || !mirror || vf_n >= VF_MAX) return -1;
vf_atl[vf_n] = *mirror;
vf_src[vf_n] = orig->page;
vf_n++;
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 i;
+4
View File
@@ -30,6 +30,10 @@
* 0 — OK, -1 — файла нет / не хватило EMM / таблица полна. */
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 — пары нет (набор не загружен). */
uint8_t pop_vflip_page(uint8_t src);
+87
View File
@@ -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");
return -1;
}
pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */
pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */
pop_shadow_load(); /* запечённый набор Тени — один раз на запуск */
pop_sfx_init(); /* звук: 8 страниц оцифровки (вывод пока молчит) */
+41
View File
@@ -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
+13
View File
@@ -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
+18 -1
View File
@@ -387,7 +387,24 @@ void pop_mirror_heal(void) __banked { }
uint8_t pop_vflip_ready(void) { return 1; } /* «уже загружено» — грузить не станет */
int pop_vflip_add(const void *orig, const char *path)
{ (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; }
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; }
+29
View File
@@ -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
+14
View File
@@ -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
+49 -4
View File
@@ -26,6 +26,7 @@
Данные каждого элемента выровнены на 512, поэтому его смещение всегда
целое число секторов, а чтение попадает в границу сектора DSS.
"""
import re
import sys
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))
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:
if len(sys.argv) < 3:
raise SystemExit("usage: pop_pack_arc.py OUT.ARC file [file ...]")
out = Path(sys.argv[1])
files = [Path(a) for a in sys.argv[2:]]
args = sys.argv[1:]
header = prefix = None
while args and args[0].startswith("--"):
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:
if not f.exists():
raise SystemExit(f"нет файла: {f}")
build(out, files)
if header:
if not prefix:
raise SystemExit("--header требует --prefix")
write_header(header, prefix, files)
total = out.stat().st_size
print(f"{out.name}: {len(files)} записей, {total / 1024:.0f} КБ")
+57
View File
@@ -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
View File
@@ -2,62 +2,32 @@
* atlas_load — загрузить .atl-файл в свежую EMM-страницу (§3.1/§9в
* дизайна, вариант II: файл-офсет == офсет страницы == W0-адрес).
*
* Порядок: выделить страницу → bank_load_file() → проверить магию 'SPA1'
* gfx_w0_page_prepare(). Никакого пересчёта офсетов: каталог и ленты
* уже лежат по своим W0-адресам.
* Порядок: выделить страницу → bank_load_file() → atlas_attach (магия
* 'SPA1' + gfx_w0_page_prepare). Никакого пересчёта офсетов: каталог и
* ленты уже лежат по своим W0-адресам.
*
* Кто держит атласы в АРХИВЕ, а не отдельными файлами, повторяет ту же
* пару вызовов со своим чтением: mem_alloc_pages + своё чтение в страницу
* + atlas_attach.
*
* Вызывать ВНЕ рендера (W3 на время загрузки занята страницей атласа).
* Ошибки: -1 + errno (open/read/alloc; EINVAL — не .atl).
*/
#include <errno.h>
#include <sprinter_mem.h>
#include <sprite.h>
#include "../_bgi.h"
int atlas_load(atlas_t *a, const char *path)
{
uint8_t blk, ok, count, h[5];
uint8_t blk, page;
int n;
blk = mem_alloc_pages(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: вызывающему важно отличить отсутствие
* файла или короткое чтение от файла, который действительно не .atl.
* Раньше оба случая проваливались в EINVAL ниже, что маскировало
* фактическую причину сбоя загрузки экранных полос. */
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;
* файла или короткое чтение от файла, который действительно не .atl. */
n = bank_load_file(page, 0, path, 16384);
return atlas_attach(a, blk, page, n);
}
+6
View File
@@ -290,6 +290,12 @@ typedef struct {
* 0 — OK; -1 + errno (файл/память/формат). */
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:
* установить IRQ/NMI-стабы и запомнить штатную страницу DSS. */
void gfx_w0_page_prepare(uint8_t page);