SprPoP: музыка без перелинковки — длины и длительности уехали на диск
Часть I плана music_runtime_index_plan.md (MI0..MI5). gen/pop_music_tbl.h и gen/pop_music_ticks.h УДАЛЕНЫ: длины треков и длительности реплик читаются из MUS/mus.idx (формат PMI1, tools/pop_idx.py, тесты в make test-tools). Один и тот же sprpop.exe работает с любым из четырёх наборов записей — sha256 бинарника при смене MUSIC_FMT не меняется. ГДЕ ЖИВЁТ ИНДЕКС. 228 байт таблицы в W2 не положить (свободной кучи там порядка двух сотен), поэтому индекс лежит в одной странице EMM, а в резиденте от него два байта. Данные в странице — со смещения 0x100: gfx_w0_page_prepare пишет в неё стабы прерываний (0x38 и 0x66), и с нуля они попали бы прямо в записи id 10 и 21. Со смещением работает штатная защита, а не запрет прерываний (тот же приём, что CFG_BASE в pop_config.c). Число страниц в индексе не хранится — считается из blocks, чтобы не разъехалось. ПАУЗА КОНЦА УРОВНЯ — СОСТОЯНИЕМ, А НЕ СЧЁТЧИКОМ. pop_endmus_left и POP_MUS_TICKS_32/41 удалены; главный цикл ждёт pop_music_active() — «заявка лежит, идёт загрузка или трек звучит». Одного busy мало: между заявкой и первой нотой 190-230 мс (замер в sound_plan §9). Прежний счётчик закрывал эту щель ценой зависимости EXE от набора и жёсткого делителя /4, который врал в режимах FAST/FASTEST (там логический кадр 3 кадра луча, а не 4). Побочно исправилось расхождение с SDLPoP: при выключенном звуке заявка не кладётся, и уровень меняется сразу, как в оригинале (seg006:651 + seg003:387) — раньше игра держала пройденный уровень лишние 12 секунд в тишине. PV-СЦЕНА — на четырёх якорях (8 байт статики), которые считаются из индекса при входе в сцену; прежние выражения шкалы не изменились. План предлагал протащить структуру времён через пять функций — для сцены, которая идёт раз за запуск, это того не стоит. ПАМЯТЬ. За обе фазы резидент не вырос, а освободился: _CODE 23865 -> 23544, куча 239 -> 256 Б. Банк 9 похудел на 118 Б (ушла pop_mus_tbl из rodata), банк 11 — на длительности реплик. ПРОВЕРЕНО В MAME: exe побайтово одинаков для flac и mt32; все 22 трека в индексах различаются, и контрольные значения совпали с предсказанными планом (m41 732->685, m50 831->867, m53 985->1044, m56 9865->10462 блоков, 78->82 страницы); на mt32 PV-сцена проходит целиком по его длительностям; без mus.idx музыки нет, эффекты работают, игра проходима. НЕ ПРОВЕРЕНО: потоковый m56 на 82 страницах — до финала надо дойти в игре. Единственный оставшийся пункт приёмки, отмечен в sound_plan §11.5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY
This commit is contained in:
@@ -1194,3 +1194,93 @@ sdlpop` перепаковывал `build/atl`, а в `assets/packed` остав
|
||||
сцене с принцессой) лежит у наборов по РАЗНЫМ страницам — 8 у SDLPoP,
|
||||
9 у MSDOS. А вот 4 `gate_closing` из той же сцены совпадает побайтно и
|
||||
для проверки подмены не годится.
|
||||
|
||||
## 11. МУЗЫКА БЕЗ ПЕРЕЛИНКОВКИ: ДЛИНЫ И ДЛИТЕЛЬНОСТИ УЕХАЛИ НА ДИСК (2026-08-31)
|
||||
|
||||
Сделана часть I плана `music_runtime_index_plan.md` (этапы MI0..MI5, у нас
|
||||
B0..B7). `gen/pop_music_tbl.h` и `gen/pop_music_ticks.h` УДАЛЕНЫ: длины
|
||||
треков и длительности реплик читаются из `MUS/mus.idx` (формат `PMI1`).
|
||||
Один и тот же `sprpop.exe` работает с любым из четырёх наборов записей.
|
||||
|
||||
### 11.1 Где живёт индекс и почему именно там
|
||||
|
||||
228 байт таблицы в W2 положить нельзя — там свободной кучи порядка двух
|
||||
сотен. Поэтому индекс лежит в ОДНОЙ странице EMM, а в резиденте от него
|
||||
остаются два байта (номер блока и страницы). Наружу указатель на EMM не
|
||||
выдаётся: `pop_music_info()` подключает страницу в W0, копирует четыре
|
||||
байта в буфер вызывающего и сразу снимает окно.
|
||||
|
||||
**Данные в странице лежат со смещения `POP_MUS_IDX_BASE` (0x100), а не с
|
||||
нуля.** Причина в `gfx_w0_page_prepare`: он делает страницу безопасной
|
||||
для W0, записывая В НЕЁ переход на IRQ-восстановитель (0x38) и RETN для
|
||||
NMI (0x66). С нуля эти байты легли бы прямо в записи (id 10 и 21) и
|
||||
испортили индекс; со смещением стабы и данные не пересекаются, и работает
|
||||
штатная защита от прерываний, а не запрет их. Тот же приём и по той же
|
||||
причине — в `pop_config.c` (`CFG_BASE`).
|
||||
|
||||
Числа страниц в индексе НЕТ: это производная от `blocks`, и дублирование
|
||||
однажды разъехалось бы с ним. Считает `pop_music_pages()`.
|
||||
|
||||
### 11.2 Пауза конца уровня: состояние вместо счётчика
|
||||
|
||||
`pop_endmus_left` и `POP_MUS_TICKS_32/41` УДАЛЕНЫ. Главный цикл ждёт
|
||||
`pop_music_active()` — «заявка лежит, идёт загрузка или трек звучит».
|
||||
|
||||
Одного `pop_music_busy()` там мало: между заявкой и первой нотой проходит
|
||||
190–230 мс (§9), и в этой щели он ложен. Раньше щель закрывал счётчик по
|
||||
длине записи, но он тянул за собой две беды: зависимость EXE от набора и
|
||||
жёсткий делитель `/4`, который врал в быстрых режимах (там логический кадр
|
||||
3 кадра луча, а не 4). Три признака подряд закрывают щель без единой
|
||||
подобранной константы, потому что `pop_music_service` обнуляет `req` и
|
||||
тут же взводит `ld_busy`, а тот держится до записи `pop_mus_left`.
|
||||
|
||||
Побочно исправилось расхождение с оригиналом: при ВЫКЛЮЧЕННОМ звуке
|
||||
`pop_sfx_play` не кладёт заявку, все три признака ложны, и уровень
|
||||
меняется сразу — как в SDLPoP, где трек ставится под `if (is_sound_on)`
|
||||
(seg006:651), а ожидание это `check_sound_playing()` (seg003:387).
|
||||
Прежний счётчик взводился безусловно и держал пройденный уровень лишние
|
||||
12 секунд в тишине.
|
||||
|
||||
### 11.3 PV-сцена: четыре якоря вместо compile-time шкалы
|
||||
|
||||
Шкала сцены осталась абсолютной (переход на ожидание звука — отдельный
|
||||
разговор, см. план), но точки, зависящие от длины реплик, считаются при
|
||||
входе в сцену: `pv_m50_end`, `pv_dialog1`, `pv_exit`, `pv_anim_end`.
|
||||
Остальные выражения не изменились — они отсчитываются от якорей.
|
||||
|
||||
Восемь байт статики вместо протаскивания структуры времён через пять
|
||||
функций (`pv_princess_step`, `pv_jaffar_step`, `pv_seq_period`,
|
||||
`intro_pv_draw_frame`, `intro_pv_animated`), как предлагал план: сцена
|
||||
одна на запуск, и переделка всех сигнатур того не стоит.
|
||||
|
||||
Нет индекса — длительности нулевые, и сцена просто идёт без пауз на
|
||||
реплики: ждать нечего, звука-то нет.
|
||||
|
||||
### 11.4 Цена в памяти
|
||||
|
||||
За обе фазы (эффекты и музыка) резидент не вырос, а освободился:
|
||||
|
||||
| | до фазы A | после фазы B |
|
||||
|---|---:|---:|
|
||||
| `_CODE` | 23865 | 23544 |
|
||||
| куча резидента | 239 Б | 256 Б |
|
||||
|
||||
Банк 9 похудел на 118 Б (ушла `pop_mus_tbl` из его rodata), банк 11 — на
|
||||
длительности реплик. Прибавка: 2 байта на страницу индекса музыки и 8 на
|
||||
якоря PV.
|
||||
|
||||
### 11.5 Что проверено на живой машине
|
||||
|
||||
* `sprpop.exe` ПОБАЙТОВО одинаков для наборов flac и mt32;
|
||||
* все 22 трека в индексах различаются, и контрольные значения совпали с
|
||||
тем, что план предсказывал по замерам: m32 438→449, m41 732→685,
|
||||
m50 831→867, m53 985→1044, m56 9865→10462 блоков (78→82 страницы);
|
||||
* на mt32-наборе PV-сцена проходит целиком: m50 → m53 → m52 звучат по
|
||||
длительностям mt32, хотя в EXE их больше нет;
|
||||
* без `mus.idx` музыки нет, эффекты работают, игра проходима — сцены
|
||||
проскакивают без пауз на реплики.
|
||||
|
||||
**НЕ ПРОВЕРЕНО:** потоковый трек m56 на 82 страницах (у flac 78).
|
||||
Кольцевой проигрыватель получает число страниц из индекса, и код к этому
|
||||
готов, но живого прогона финала на mt32 не было — до него надо дойти в
|
||||
игре. Это оставшийся пункт приёмки.
|
||||
|
||||
Reference in New Issue
Block a user