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

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

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

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

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

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

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

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