SprPoP: звуковые эффекты без перелинковки — раскладка уехала на диск
Часть II плана music_runtime_index_plan.md (SI0..SI4). gen/pop_sound_tbl.h БОЛЬШЕ НЕ ГЕНЕРИРУЕТСЯ: раскладка набора читается из SND/snd.idx (формат PSI1, писатель и разборщик — tools/pop_idx.py, 22 теста в make test-tools). Один и тот же sprpop.exe работает с набором SDLPoP (9 страниц) и MSDOS (10) — sha256 бинарника при смене набора не меняется. Заодно умолчание источника эффектов переведено на SDLPoP (SND_SRC=sdlpop): сборка обязана работать без оригинального дистрибутива DOS. У кого он есть, включает лучший набор явно — make SND_SRC=msdos (там полнее оцифровка: в SDLPoP звук 48 spiked пустой). Устройство: pop_snd_tbl/pop_snd_page/pop_snd_pages — резидентные данные (pop_snd_data.c), тип и инварианты — рукописный pop_snd_tbl.h. Записи читаются ОДНИМ read прямо в таблицу, поэтому sizeof(pop_snd_ent_t) == 5 стало частью дискового контракта: проверяется статически и полем размера записи в заголовке. POP_SND_PAGES как compile-time размер набора исчез — вместо него POP_SND_MAX_PAGES (вместимость, 16) и runtime pop_snd_pages. Цена: таблица переехала из _CODE в _DATA, суммарный резидент почти не изменился (куча 239 -> 229 Б); банк 8 +601 Б на чтение и валидацию. Валидация не доверяет файлу: заголовок целиком плюс каждая запись (страница, смещение, кратность блоку, непересечение с блоком тишины, выход за последнюю страницу). Последнее считается В БЛОКАХ — байтовый адрес конца не влезает в uint16, а 32-битная арифметика на Z80 дорога. НЕТ ИНДЕКСА — ЭФФЕКТОВ НЕТ, НО МУЗЫКА ИГРАЕТ. Первая версия просто возвращала ошибку, и игра становилась непроходимой: тишину льёт первый блок набора, без набора CBL не открывался, а с ним вставала музыка (её блоки считает тот же насос) — заставка ждала конца трека вечно. Теперь поднимается пустой набор с блоком тишины. Заливается ровно 128 байт и под DI: gfx_w0_page_prepare ставит в страницу IRQ-стабы, и заливка всей страницы затирала их — первое же прерывание давало чёрный экран. Грабли сборки: смена SND_SRC тихо давала неверный результат (sdlpop -> msdos -> sdlpop оставлял чужой набор в assets/packed). Причина не в логике, а в секундной гранулярности mtime. Лечение убирает время из решения: смена варианта сносит stamp'ы своего семейства, а упаковка, сборка архива и копия индекса делаются одним рецептом. То же получила и музыка (MUSIC_FMT). Проверено в MAME: таблица в памяти совпадает с файлом из образа побайтово; один EXE поднимает оба набора; отладочный --order reverse (30 из 31 записей отличаются от штатных) звучит правильно; битый индекс выключает эффекты, не роняя игру; без индекса PV-сцена проходит с музыкой; Ctrl+S работает в обоих режимах. Разбор — docs/sound_plan.md §10. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY
This commit is contained in:
+105
-13
@@ -100,6 +100,7 @@ BANK_FLAGS := $(foreach n,$(BANK_NUMS),\
|
||||
|
||||
# Модули резидента (кроме sprpop.c — его подставляет app.mk).
|
||||
EXTRA_SRCS := $(addprefix $(SRC_DIR)/,pop_vflip.c pop_pace.c pop_sfx.c pop_state.c \
|
||||
pop_snd_data.c \
|
||||
pop_draw.c pop_tile.c pop_kid.c pop_level.c pop_geom.c pop_guard.c \
|
||||
pop_qsave_io.c)
|
||||
|
||||
@@ -166,7 +167,10 @@ SHADOW_ATL := $(foreach n,$(KID_N),$(ATL)/shadow/sk$(n).atl) \
|
||||
# Звук: оригинальная оцифровка на частоте CBL (10 937,5 Гц, 8 бит моно).
|
||||
# Число файлов задаёт упаковщик (POP_SND_PAGES в gen/pop_sound_tbl.h) —
|
||||
# список ОБЯЗАН совпадать: недостающий файл это молча пропавшие эффекты.
|
||||
SND_ATL := $(foreach n,0 1 2 3 4 5 6 7 8 9,$(ATL)/sound/s$(n).bin)
|
||||
# Страниц звука СТАТИЧЕСКИ НЕ ПОСЧИТАТЬ: их число зависит от источника
|
||||
# (SDLPoP даёт 9, MSDOS — 10, см. SND_SRC). Поэтому списка s0..sN здесь нет,
|
||||
# а архив зависит от stamp'а упаковщика и берёт страницы из каталога уже
|
||||
# после того, как тот отработал (см. правило SND/snd.arc ниже).
|
||||
TITLE_ATL := $(foreach s,0 1 2 3,$(foreach p,0 1 2 3 4,$(ATL)/title/t$(s)_$(p).atl))
|
||||
# PV/INTRO: story и стадии режутся на полосы; у настоящего PV intro есть ещё
|
||||
# чистый фон b0_* и шесть <=16K actor-атласов — они остаются в EMM на время
|
||||
@@ -189,6 +193,15 @@ MUS_IDS := 24 25 26 27 28 29 30 32 33 35 36 37 39 40 41 43 50 52 53 54 55 56
|
||||
# Формат входит в имя stamp'а, поэтому смена MUSIC_FMT сама по себе делает
|
||||
# музыку устаревшей — принудительная пересборка не нужна.
|
||||
MUSIC_FMT ?= flac
|
||||
# Источник оцифрованных ЭФФЕКТОВ (digisnd1..3): sdlpop (умолчание) или msdos.
|
||||
# Умолчание — SDLPoP, потому что он выкачивается автоматически (`make fetch`)
|
||||
# и сборка обязана работать вообще без оригинального дистрибутива DOS.
|
||||
# Наборы НЕ одинаковы: в MSDOS оцифровка полнее (в SDLPoP `spiked` (48)
|
||||
# фактически пустой, `sword_vs_sword` (10) короче — разбор в
|
||||
# docs/sound_plan.md). У кого дистрибутив есть — `make SND_SRC=msdos`.
|
||||
# Источник входит в имя stamp'а, поэтому смена SND_SRC сама делает звук
|
||||
# устаревшим, как и смена MUSIC_FMT.
|
||||
SND_SRC ?= sdlpop
|
||||
# res2000 — attract-demo; res2001..res2014 — штатная игра. res2015 (copy
|
||||
# protection) намеренно не пакуется и не существует в маршруте загрузчика.
|
||||
LVL_NUMS := 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14
|
||||
@@ -209,7 +222,7 @@ DISK := README_E.TXT README_R.TXT \
|
||||
BG/bg.arc BG/pop_tile.pal BG/pal_tile.pal \
|
||||
KID/kid.arc KID/kid.pal KID/kid.ani \
|
||||
GUARD/guard.arc SKEL/skel.arc VIZIER/vizier.arc SHADOW/shadow.arc \
|
||||
SND/snd.arc \
|
||||
SND/snd.arc SND/snd.idx \
|
||||
$(foreach i,$(MUS_IDS),MUS/m$(i).bin) \
|
||||
FONT/font.atl \
|
||||
TITLE/title.arc TITLE/title.pal \
|
||||
@@ -220,8 +233,11 @@ DISK := README_E.TXT README_R.TXT \
|
||||
# список — это и зависимость exe, и то, что умеет чинить себя само.
|
||||
GEN_ARC_H := bg_arc.h kid_arc.h guard_arc.h skel_arc.h vizier_arc.h \
|
||||
shadow_arc.h title_arc.h pv_arc.h
|
||||
# pop_sound_tbl.h здесь БОЛЬШЕ НЕТ: раскладка эффектов уехала на диск
|
||||
# (SND/snd.idx), и EXE от звукового набора не зависит — пару
|
||||
# snd.idx + snd.arc можно менять без перекомпиляции.
|
||||
GEN_H := $(addprefix gen/,$(GEN_ARC_H) pop_bg_atlas.h kid_data.h \
|
||||
pop_guard_pal.h pop_shadow_atlas.h pop_sound_tbl.h \
|
||||
pop_guard_pal.h pop_shadow_atlas.h \
|
||||
pop_music_tbl.h pop_music_ticks.h pop_font.h)
|
||||
|
||||
PACKED_FILES := $(addprefix $(PACKED)/,$(DISK))
|
||||
@@ -343,6 +359,28 @@ $(STAMP)/$(1): $(2) | $(STAMP) check-orig
|
||||
@touch $$@
|
||||
endef
|
||||
|
||||
# ВАРИАНТЫ РЕСУРСА (источник звука SND_SRC, набор записей MUSIC_FMT). Оба
|
||||
# варианта пишут в ОДИН выход — build/atl/sound, build/atl/music, — поэтому
|
||||
# одного stamp'а на вариант мало: вернувшись к прежнему варианту, make видит
|
||||
# его stamp свежим и не делает НИЧЕГО, а в выходе лежит чужое (поймано на
|
||||
# sdlpop -> msdos -> sdlpop: заголовок остался с 10 страницами вместо 9).
|
||||
# Лечится файлом с именем текущего варианта: он переписывается только при
|
||||
# смене (cmp), а его свежесть делает stamp устаревшим.
|
||||
.PHONY: variant-check
|
||||
variant-check: ;
|
||||
# СМЕНА ВАРИАНТА СНОСИТ STAMP'Ы СВОЕГО СЕМЕЙСТВА, а не полагается на то,
|
||||
# что make заметит свежесть variant-файла. Причина — секундная
|
||||
# гранулярность mtime: упаковка укладывается в одну секунду с записью
|
||||
# variant, и stamp того же имени считается актуальным. Возврат к прежнему
|
||||
# варианту (`sdlpop -> msdos -> sdlpop`) тогда ТИХО оставлял чужой набор.
|
||||
# Нет файла — правило обязано выполниться, и время тут ни при чём.
|
||||
define variant_rule
|
||||
$(STAMP)/$(1).variant: variant-check | $(STAMP)
|
||||
@echo "$(2)" | cmp -s - $$@ || { echo "$(2)" > $$@; rm -f $(STAMP)/$(1)-*; }
|
||||
endef
|
||||
$(eval $(call variant_rule,sound,$(SND_SRC)))
|
||||
$(eval $(call variant_rule,music,$(MUSIC_FMT)))
|
||||
|
||||
$(eval $(call stamp_rule,bg,$(TOOLS)/pop_pack_bg.py $(TOOLS)/render_room.py $(TOOLS)/paths.py,pop_pack_bg.py dungeon && $(PY) pop_pack_bg.py palace))
|
||||
$(eval $(call stamp_rule,kid,$(TOOLS)/pop_pack_kid.py $(TOOLS)/pop_pack_bg.py $(TOOLS)/paths.py,pop_pack_kid.py))
|
||||
$(eval $(call stamp_rule,kid-bin,$(TOOLS)/pop_extract_kid_data.py $(TOOLS)/paths.py,pop_extract_kid_data.py))
|
||||
@@ -350,8 +388,25 @@ $(eval $(call stamp_rule,guard,$(TOOLS)/pop_pack_guard.py $(TOOLS)/paths.py,pop_
|
||||
$(eval $(call stamp_rule,skel,$(TOOLS)/pop_pack_guard.py $(TOOLS)/paths.py,pop_pack_guard.py SKEL))
|
||||
$(eval $(call stamp_rule,vizier,$(TOOLS)/pop_pack_guard.py $(TOOLS)/paths.py,pop_pack_guard.py VIZIER))
|
||||
$(eval $(call stamp_rule,shadow,$(TOOLS)/pop_pack_shadow.py $(TOOLS)/pop_pack_bg.py $(TOOLS)/pop_pack_kid.py $(TOOLS)/paths.py,pop_pack_shadow.py))
|
||||
$(eval $(call stamp_rule,sound,$(TOOLS)/pop_pack_sound.py $(TOOLS)/paths.py,pop_pack_sound.py))
|
||||
$(eval $(call stamp_rule,music-$(MUSIC_FMT),$(TOOLS)/pop_pack_music.py $(TOOLS)/paths.py,pop_pack_music.py --format $(MUSIC_FMT) $(MUS_IDS)))
|
||||
# ЗВУК — ОСОБЫЙ СЛУЧАЙ: упаковка, сборка архива и копия индекса делаются
|
||||
# ОДНИМ рецептом, а не тремя правилами через таймстемпы.
|
||||
#
|
||||
# Почему. Упаковщик и копирование укладываются в одну секунду, а make
|
||||
# сравнивает mtime с секундной гранулярностью: цель, сделанная в ту же
|
||||
# секунду, что и её зависимость, считается актуальной. При смене SND_SRC
|
||||
# это давало ТИХО НЕВЕРНЫЙ результат — `sdlpop -> msdos -> sdlpop`
|
||||
# перепаковывал build/atl, но в assets/packed оставался прежний набор
|
||||
# (10 страниц вместо 9; поймано 2026-08-31, с паузой в секунду не
|
||||
# воспроизводится). Внутри одного рецепта сравнивать нечего.
|
||||
$(STAMP)/sound-$(SND_SRC): $(TOOLS)/pop_pack_sound.py $(TOOLS)/pop_idx.py \
|
||||
$(TOOLS)/paths.py $(TOOLS)/pop_pack_arc.py \
|
||||
$(STAMP)/sound.variant | $(STAMP) check-orig
|
||||
cd $(TOOLS) && $(PY) pop_pack_sound.py --source $(SND_SRC)
|
||||
@mkdir -p $(PACKED)/SND
|
||||
$(ARC) $(PACKED)/SND/snd.arc $$(ls $(ATL)/sound/s*.bin | sort -V)
|
||||
@cp $(ATL)/sound/snd.idx $(PACKED)/SND/snd.idx
|
||||
@touch $@
|
||||
$(eval $(call stamp_rule,music-$(MUSIC_FMT),$(TOOLS)/pop_pack_music.py $(TOOLS)/paths.py $(STAMP)/music.variant,pop_pack_music.py --format $(MUSIC_FMT) $(MUS_IDS)))
|
||||
$(eval $(call stamp_rule,font,$(TOOLS)/pop_extract_font.py $(TOOLS)/paths.py,pop_extract_font.py))
|
||||
$(eval $(call stamp_rule,title,$(TOOLS)/pop_pack_title.py $(TOOLS)/paths.py,pop_pack_title.py))
|
||||
$(eval $(call stamp_rule,pv,$(TOOLS)/pop_pack_intro.py $(TOOLS)/paths.py,pop_pack_intro.py))
|
||||
@@ -371,7 +426,6 @@ $(eval $(call from_stamp,$(GUARD_ATL) gen/pop_guard_pal.h,guard))
|
||||
$(eval $(call from_stamp,$(SKEL_ATL),skel))
|
||||
$(eval $(call from_stamp,$(VIZIER_ATL),vizier))
|
||||
$(eval $(call from_stamp,$(SHADOW_ATL) gen/pop_shadow_atlas.h,shadow))
|
||||
$(eval $(call from_stamp,$(SND_ATL) gen/pop_sound_tbl.h,sound))
|
||||
$(eval $(call from_stamp,$(foreach i,$(MUS_IDS),$(ATL)/music/m$(i).bin) gen/pop_music_tbl.h gen/pop_music_ticks.h,music-$(MUSIC_FMT)))
|
||||
$(eval $(call from_stamp,$(ATL)/font/font.atl gen/pop_font.h,font))
|
||||
$(eval $(call from_stamp,$(TITLE_ATL) $(ATL)/title/title.pal,title))
|
||||
@@ -396,9 +450,15 @@ $(eval $(call arc_rule,TITLE,title,title,$(TITLE_ATL),TITLE))
|
||||
$(eval $(call arc_rule,PV,pv,pv,$(PV_ATL),PV))
|
||||
# У звука своего *_arc.h нет: содержимое адресуется таблицей pop_sound_tbl.h,
|
||||
# которую печатает сам упаковщик звука.
|
||||
$(PACKED)/SND/snd.arc: $(SND_ATL) $(TOOLS)/pop_pack_arc.py
|
||||
@mkdir -p $(dir $@)
|
||||
$(ARC) $@ $(SND_ATL)
|
||||
# Зависимость на stamp, а не на список страниц: сколько их будет, известно
|
||||
# только после запуска упаковщика (источник задаёт число). Порядок страниц
|
||||
# в архиве обязан быть ЧИСЛОВЫМ — pop_snd_tbl адресует их индексом, а голый
|
||||
# ls даёт s10 перед s2. sort -V, а не `-t s -k n`: буква s есть и в пути.
|
||||
# Пара snd.arc + snd.idx согласована по построению: её делает ОДИН рецепт
|
||||
# stamp'а выше (см. там же, почему не раздельными правилами). Здесь
|
||||
# только восстановление, если файл снесли руками.
|
||||
$(PACKED)/SND/snd.arc $(PACKED)/SND/snd.idx: $(STAMP)/sound-$(SND_SRC)
|
||||
@test -f $@ || { $(MAKE) GENERATE=1 -B $(STAMP)/sound-$(SND_SRC); test -f $@; }
|
||||
|
||||
# Файлы, которые ложатся на диск как есть, — простое копирование из staging.
|
||||
define copy_rule
|
||||
@@ -419,6 +479,7 @@ $(foreach i,$(MUS_IDS),$(eval $(call copy_rule,MUS/m$(i).bin,$(ATL)/music/m$(i).
|
||||
# читает как есть, упаковщика для них нет. check-orig здесь тоже order-only —
|
||||
# без оригиналов взять уровни неоткуда, и сказать об этом надо внятно.
|
||||
LVL_ORIG := assets/orig/SDLPoP/data/LEVELS
|
||||
SND_ORIG_MSDOS := assets/orig/MSDOS
|
||||
define lvl_rule
|
||||
$(PACKED)/LEVELS/res20$(1).bin: $(LVL_ORIG)/res20$(1).bin | check-orig
|
||||
@mkdir -p $$(dir $$@)
|
||||
@@ -447,7 +508,7 @@ resources-bg: $(PACKED)/BG/bg.arc $(PACKED)/BG/pop_tile.pal $(PACKED)/BG/pal
|
||||
resources-kid: $(PACKED)/KID/kid.arc $(PACKED)/KID/kid.pal $(PACKED)/KID/kid.ani
|
||||
resources-actors: $(PACKED)/GUARD/guard.arc $(PACKED)/SKEL/skel.arc \
|
||||
$(PACKED)/VIZIER/vizier.arc $(PACKED)/SHADOW/shadow.arc
|
||||
resources-sound: $(PACKED)/SND/snd.arc
|
||||
resources-sound: $(PACKED)/SND/snd.arc $(PACKED)/SND/snd.idx
|
||||
resources-music: $(foreach i,$(MUS_IDS),$(PACKED)/MUS/m$(i).bin)
|
||||
resources-font: $(PACKED)/FONT/font.atl
|
||||
resources-title: $(PACKED)/TITLE/title.arc $(PACKED)/TITLE/title.pal
|
||||
@@ -459,8 +520,12 @@ resources: resources-bg resources-kid resources-actors resources-sound \
|
||||
|
||||
check-orig:
|
||||
@test -d $(LVL_ORIG) || { \
|
||||
echo "SprPoP: нет assets/orig/ — генерировать ресурсы не из чего." >&2; \
|
||||
echo " Что и откуда положить, написано в assets/orig/README.md" >&2; false; }
|
||||
echo "SprPoP: нет assets/orig/SDLPoP — генерировать ресурсы не из чего." >&2; \
|
||||
echo " Скачать: make fetch (подробности — assets/orig/README.md)" >&2; false; }
|
||||
@test "$(SND_SRC)" != msdos || test -d $(SND_ORIG_MSDOS) || { \
|
||||
echo "SprPoP: SND_SRC=msdos, но нет $(SND_ORIG_MSDOS)." >&2; \
|
||||
echo " Дистрибутив DOS не качается — положить руками, либо собирать" >&2; \
|
||||
echo " со звуком из SDLPoP: make SND_SRC=sdlpop" >&2; false; }
|
||||
|
||||
else
|
||||
# ===========================================================================
|
||||
@@ -510,6 +575,33 @@ $(MUSIC_TARGETS):
|
||||
@$(MAKE) MUSIC_FMT=$(patsubst music-%,%,$@) resources-music
|
||||
@$(MAKE) MUSIC_FMT=$(patsubst music-%,%,$@) stage
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Внешние исходные данные (assets/orig/)
|
||||
# ---------------------------------------------------------------------------
|
||||
# В репозитории чужих данных нет — есть tools/fetch_orig.py, который знает,
|
||||
# откуда их взять, и таблица источников внутри него. Качаем только то,
|
||||
# чего нет; `FETCH_ARGS=--force` перекачивает заново, `FETCH_ARGS=--ref v1.23`
|
||||
# берёт конкретный тег SDLPoP.
|
||||
# make fetch SDLPoP + записи музыки
|
||||
# make fetch-sdlpop только SDLPoP (data/ + src/-эталон)
|
||||
# make fetch-music только записи саундтрека (43 МБ)
|
||||
# make fetch-check что уже на месте
|
||||
# make fetch-list таблица источников
|
||||
# MSDOS/ не качается — это копирайтный дистрибутив; он НЕОБЯЗАТЕЛЕН, всё
|
||||
# нужное (уровни, оцифровка) есть в SDLPoP. См. SND_SRC выше.
|
||||
FETCH := $(PY) $(TOOLS)/fetch_orig.py
|
||||
fetch: ; $(FETCH) all $(FETCH_ARGS)
|
||||
fetch-sdlpop: ; $(FETCH) sdlpop $(FETCH_ARGS)
|
||||
fetch-music: ; $(FETCH) music $(FETCH_ARGS)
|
||||
fetch-check: ; @$(FETCH) --check
|
||||
fetch-list: ; @$(FETCH) --list
|
||||
FETCH_TARGETS := fetch fetch-sdlpop fetch-music fetch-check fetch-list
|
||||
|
||||
# Тесты инструментов (хост, питон): форматы, которые читает Z80. Секунды,
|
||||
# ни эмулятора, ни оригиналов не нужно.
|
||||
test-tools:
|
||||
@cd $(CURDIR) && $(PY) -m unittest discover -s tools/tests
|
||||
|
||||
# Перелинковка приложения без пересборки библиотек и ресурсов.
|
||||
relink:
|
||||
$(MAKE) -W Makefile $(EXE)
|
||||
@@ -529,4 +621,4 @@ distclean: clean
|
||||
.PHONY: stage check-orig resources resources-rebuild resources-bg resources-kid \
|
||||
resources-actors resources-sound resources-music resources-font \
|
||||
resources-title resources-pv resources-levels $(MUSIC_TARGETS) \
|
||||
relink distclean mame-link
|
||||
$(FETCH_TARGETS) test-tools relink distclean mame-link
|
||||
|
||||
Reference in New Issue
Block a user