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:
2026-08-31 17:17:59 +03:00
parent 2349481b86
commit df5071a967
17 changed files with 587 additions and 330 deletions
+7 -20
View File
@@ -19,7 +19,6 @@
#include "pop_geom.h"
#include "pop_level.h" /* POP_LEVEL_LAST — level 15 исключён */
#include "pop_cutscene.h" /* POP_T60 — тики оригинала в кадры луча */
#include "pop_music_ticks.h" /* длина мелодии конца уровня */
#include <fcntl.h>
#include <unistd.h>
#include <sprinter_mem.h>
@@ -38,14 +37,6 @@ int8_t knock;
* оригинале, чтобы читы «следующий/предыдущий уровень» (levels_plan.md §4)
* легли сюда же без переделки. */
uint8_t pop_next_level;
/* СКОЛЬКО ЛОГИЧЕСКИХ КАДРОВ ЕЩЁ ДЕРЖАТЬ УРОВЕНЬ, пока играет мелодия конца.
* У оригинала (seg003:387, play_level_2) уровень не сменяется, пока
* `check_sound_playing()`: экран пройденного уровня живёт с анимацией
* факелов ровно столько, сколько звучит трек. У нас признак «звучит» —
* pop_music_busy(), но он ложен при ВЫКЛЮЧЕННОЙ музыке, а оригинал
* выдерживает паузу и молча. Поэтому здесь взводится счётчик по длине
* записи (gen/pop_music_ticks.h), а главный цикл ждёт большего из двух. */
uint8_t pop_endmus_left;
/* ---- Атласы Kid (прямая адресация image>>3) ------------------------ *
@@ -367,17 +358,13 @@ void play_seq(void)
else if (ev == 4) {
/* seg006:646. На четвёртом уровне уход за дверь — это
* встреча с тенью, и трек там свой; на 13-м (Джафар) и
* 15-м оригинал молчит. Вместе с треком взводим
* задержку смены уровня (см. pop_endmus_left): кадров
* = длина записи в тиках -> кадры луча -> делённое на
* длину логического кадра (4 кадра луча вне боя). */
if (pop_current_level == 4) {
pop_sfx_play(32);
pop_endmus_left = (uint8_t)(POP_T60(POP_MUS_TICKS_32) / 4);
} else if (pop_current_level != 13) {
pop_sfx_play(41);
pop_endmus_left = (uint8_t)(POP_T60(POP_MUS_TICKS_41) / 4);
}
* 15-м оригинал молчит. Держать уровень на экране,
* пока трек звучит, — забота главного цикла: он ждёт
* pop_music_active(), и своей задержки здесь больше
* нет (была по длине записи из gen/pop_music_ticks.h,
* из-за чего EXE зависел от набора музыки). */
if (pop_current_level == 4) pop_sfx_play(32);
else if (pop_current_level != 13) pop_sfx_play(41);
}
}
break;