2349481b86
Часть 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
51 lines
3.2 KiB
C
51 lines
3.2 KiB
C
/*
|
||
* pop_snd_tbl.h — РАСКЛАДКА ОЦИФРОВАННЫХ ЭФФЕКТОВ: тип и инварианты.
|
||
*
|
||
* Раньше этот файл ПЕЧАТАЛ упаковщик (gen/pop_sound_tbl.h), и конкретные
|
||
* page/off/len становились частью EXE — из-за чего замена звукового набора
|
||
* (SDLPoP <-> MSDOS) требовала перекомпиляции игры. Теперь значения
|
||
* приезжают с диска (SND/snd.idx, формат PSI1 — tools/pop_idx.py), а
|
||
* здесь остаётся только то, что от набора НЕ зависит: устройство записи и
|
||
* пределы, на которые рассчитан загрузчик.
|
||
*
|
||
* ЗАПИСЬ ЧИТАЕТСЯ С ДИСКА ПОБАЙТОВО, прямо в pop_snd_tbl[]. Значит
|
||
* раскладка структуры в ABI SDCC z80 (uint8 + uint16 + uint16 подряд, без
|
||
* выравнивания = 5 байт) — часть дискового контракта, а не деталь
|
||
* компилятора. Расхождение молча разъехало бы весь набор, поэтому оно
|
||
* ловится статически (pop_snd_data.c) и проверяется загрузчиком по полю
|
||
* размера записи в заголовке индекса.
|
||
*/
|
||
#ifndef POP_SND_TBL_H
|
||
#define POP_SND_TBL_H
|
||
|
||
#include <stdint.h>
|
||
|
||
/* Пространство id оригинала: 0..56. От набора не зависит. */
|
||
#define POP_SND_COUNT 57
|
||
/* Блок запроса CBL при 8 бит; страница EMM кратна ему, поэтому ни один
|
||
* блок не пересекает границу страницы (см. docs/sound_plan.md). */
|
||
#define POP_SND_BLOCK 128
|
||
/* СКОЛЬКО СТРАНИЦ ГОТОВ ПРИНЯТЬ ЗАГРУЗЧИК — вместимость, а НЕ размер
|
||
* конкретного набора: тот приходит в индексе (pop_snd_pages). Запас
|
||
* ради будущих наборов без перекомпиляции; цена — 16 байт таблицы
|
||
* страниц вместо 9-10. */
|
||
#define POP_SND_MAX_PAGES 16
|
||
|
||
/* ПЕРВЫЙ БЛОК СТРАНИЦЫ 0 — ТИШИНА. Насос льёт её, когда играть нечего:
|
||
* без этого железо доигрывает по кругу хвост своего буфера (жужжание).
|
||
* Это инвариант ПРОГРАММЫ — упаковщик обязан оставить блок свободным, а
|
||
* загрузчик проверяет, что ни одна запись в него не залезла. */
|
||
#define POP_SND_SILENCE_PAGE 0
|
||
#define POP_SND_SILENCE_OFF 0x0000
|
||
|
||
/* page — индекс в pop_snd_page[] (физические страницы EMM), off — начало
|
||
* внутри страницы, len — длина PCM. Оба кратны POP_SND_BLOCK; len == 0
|
||
* значит «у этого id нет оцифровки» (это музыка, см. sound_plan.md). */
|
||
typedef struct {
|
||
uint8_t page;
|
||
uint16_t off;
|
||
uint16_t len;
|
||
} pop_snd_ent_t;
|
||
|
||
#endif /* POP_SND_TBL_H */
|