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:
@@ -15,7 +15,6 @@
|
||||
#include "pop_shadow.h"
|
||||
#include "pop_sfx.h"
|
||||
#include "pop_music.h"
|
||||
#include "pop_music_ticks.h" /* POP_MUS_TICKS_<id> — длина реплик набора */
|
||||
#include "pop_arc.h"
|
||||
#include "pv_arc.h"
|
||||
#include "pop_cutscene.h"
|
||||
@@ -60,9 +59,10 @@
|
||||
* своего куска (например, всё поведение Джафара — от PV_DIALOG1_START).
|
||||
* 2) «ЖДЁМ КОНЦА РЕПЛИКИ» — PV_M50_END и PV_EXIT_START. Это целиком
|
||||
* длина НАШЕЙ записи, и она у каждого набора своя: m50 — 831 тик на
|
||||
* flac против 867 на mt32, m53 — 985 против 1044. Поэтому берём их
|
||||
* из POP_MUS_TICKS_<id>, которые печатает упаковщик того набора,
|
||||
* которым собрана сборка (gen/pop_music_tbl.h).
|
||||
* flac против 867 на mt32, m53 — 985 против 1044. Раньше их
|
||||
* печатал упаковщик в gen/pop_music_ticks.h, из-за чего EXE был
|
||||
* привязан к набору; теперь они приезжают из индекса набора
|
||||
* (MUS/mus.idx) при входе в сцену — см. ЯКОРЯ ниже.
|
||||
*
|
||||
* Всё, что стоит ПОСЛЕ реплики, сдвигается вместе с её концом — отсюда
|
||||
* сложения от PV_M50_END и PV_EXIT_START вместо литералов. */
|
||||
@@ -72,46 +72,76 @@ enum {
|
||||
/* Конец m50: кадр становится 8. Прежний литерал 846 отвечал длине 834
|
||||
* тика — на три больше нашей записи (831); расхождение осталось с
|
||||
* времён, когда длину подставляли руками. */
|
||||
PV_M50_END = PV_MUS_2_START + POP_MUS_TICKS_50,
|
||||
PV_WAIT_END = PV_M50_END + 40, /* +frame(5) -> створка ворот */
|
||||
PV_GATE_END = PV_WAIT_END + 48,
|
||||
PV_DOOR_END = PV_GATE_END + 24,
|
||||
PV_TURN_START = PV_DOOR_END,
|
||||
PV_WALK1_START = PV_DOOR_END + 40,
|
||||
PV_DIALOG1_START = PV_WALK1_START + 48, /* Джафар входит — реплика m53 */
|
||||
/* Ниже — биты сценария внутри реплики Джафара, все от её начала. */
|
||||
PV_MUS_4_LOAD = PV_DIALOG1_START + 54, /* подкачка m52 */
|
||||
PV_WALK2_START = PV_DIALOG1_START + 176,
|
||||
PV_DIALOG2_START = PV_DIALOG1_START + 416,
|
||||
/* ПОДГОНКА ЗАКЛИНАНИЯ ПОД МУЗЫКУ СНЯТА (2026-08-28): теперь 0.
|
||||
*
|
||||
* История: сцена была render-bound — кадр рисовался дороже своего
|
||||
* бюджета, кадровая ветка пейсинга догона не имела, и анимация шла
|
||||
* ~49 тиков в секунду вместо 60 (замер 2026-08-27). Реплика Джафара
|
||||
* при этом играет по РЕАЛЬНОМУ времени, поэтому кода приходила
|
||||
* примерно на секунду раньше молнии, и жест целиком (замах, шаг
|
||||
* назад, вспышка) сдвигали раньше сценария на подобранные на слух
|
||||
* 100 тиков.
|
||||
*
|
||||
* Причину убрали: отрисовка разложена по кадровым интервалам
|
||||
* (pv_restore_bg), сцена идёт по единственным часам — кадрам луча, —
|
||||
* и подкачка трека оплачивается интервалом (pop_music_load_step).
|
||||
* После этого подгонка стала вредной: молния била больше чем на
|
||||
* секунду раньше коды (проверка пользователем 2026-08-28). Константу
|
||||
* оставляем на месте — если запись другого набора (mt32/ogg) снова
|
||||
* разъедется, крутить надо её, а не тайминги сценария. */
|
||||
PV_MAGIC_LEAD = 0,
|
||||
PV_RAISE_START = PV_DIALOG1_START + 696 - PV_MAGIC_LEAD,
|
||||
PV_STEPBACK_START = PV_DIALOG1_START + 703 - PV_MAGIC_LEAD,
|
||||
PV_MAGIC_START = PV_DIALOG1_START + 822 - PV_MAGIC_LEAD,
|
||||
/* Конец m53 — снова длина записи, а не бит сценария. */
|
||||
PV_EXIT_START = PV_DIALOG1_START + POP_MUS_TICKS_53,
|
||||
PV_MUS_4_START = PV_EXIT_START + 42, /* +frame(6) -> «Джафар уходит» */
|
||||
PV_GLASS_DONE = PV_EXIT_START + 210,
|
||||
PV_SLUMP_START = PV_EXIT_START + 273,
|
||||
PV_ANIM_TICKS = PV_EXIT_START + 469
|
||||
PV_MAGIC_LEAD = 0
|
||||
};
|
||||
|
||||
/* ПОДГОНКА ЗАКЛИНАНИЯ ПОД МУЗЫКУ СНЯТА (2026-08-28): теперь 0.
|
||||
*
|
||||
* История: сцена была render-bound — кадр рисовался дороже своего
|
||||
* бюджета, кадровая ветка пейсинга догона не имела, и анимация шла
|
||||
* ~49 тиков в секунду вместо 60 (замер 2026-08-27). Реплика Джафара
|
||||
* при этом играет по РЕАЛЬНОМУ времени, поэтому кода приходила
|
||||
* примерно на секунду раньше молнии, и жест целиком (замах, шаг
|
||||
* назад, вспышка) сдвигали раньше сценария на подобранные на слух
|
||||
* 100 тиков.
|
||||
*
|
||||
* Причину убрали: отрисовка разложена по кадровым интервалам
|
||||
* (pv_restore_bg), сцена идёт по единственным часам — кадрам луча, —
|
||||
* и подкачка трека оплачивается интервалом (pop_music_load_step).
|
||||
* После этого подгонка стала вредной: молния била больше чем на
|
||||
* секунду раньше коды (проверка пользователем 2026-08-28). Константу
|
||||
* оставляем на месте — если запись другого набора (mt32/ogg) снова
|
||||
* разъедется, крутить надо её, а не тайминги сценария. */
|
||||
|
||||
/* ЯКОРЯ ШКАЛЫ. Четыре точки, от которых отсчитывается всё остальное;
|
||||
* считаются ОДИН РАЗ при входе в сцену из длительностей набора
|
||||
* (pop_music_info). Держать их статикой, а не тащить структуру времён
|
||||
* через пять функций: восемь байт против переделки всех сигнатур, а
|
||||
* сцена всё равно одна на запуск.
|
||||
*
|
||||
* Нет индекса или трека — длительность нулевая, и сцена просто проходит
|
||||
* без пауз на реплики: ждать нечего, звука-то нет. */
|
||||
static uint16_t pv_m50_end; /* конец «принцесса ждёт» (m50) */
|
||||
static uint16_t pv_dialog1; /* Джафар входит — начало реплики m53 */
|
||||
static uint16_t pv_exit; /* конец m53: Джафар уходит */
|
||||
static uint16_t pv_anim_end; /* конец всей сцены */
|
||||
|
||||
/* Ниже — те же выражения, что были в enum: биты сценария отсчитываются от
|
||||
* своего якоря и от записи НЕ зависят. */
|
||||
#define PV_M50_END pv_m50_end
|
||||
#define PV_WAIT_END ((uint16_t)(pv_m50_end + 40))
|
||||
#define PV_GATE_END ((uint16_t)(pv_m50_end + 88))
|
||||
#define PV_DOOR_END ((uint16_t)(pv_m50_end + 112))
|
||||
#define PV_TURN_START PV_DOOR_END
|
||||
#define PV_WALK1_START ((uint16_t)(pv_m50_end + 152))
|
||||
#define PV_DIALOG1_START pv_dialog1
|
||||
#define PV_MUS_4_LOAD ((uint16_t)(pv_dialog1 + 54))
|
||||
#define PV_WALK2_START ((uint16_t)(pv_dialog1 + 176))
|
||||
#define PV_DIALOG2_START ((uint16_t)(pv_dialog1 + 416))
|
||||
#define PV_RAISE_START ((uint16_t)(pv_dialog1 + 696 - PV_MAGIC_LEAD))
|
||||
#define PV_STEPBACK_START ((uint16_t)(pv_dialog1 + 703 - PV_MAGIC_LEAD))
|
||||
#define PV_MAGIC_START ((uint16_t)(pv_dialog1 + 822 - PV_MAGIC_LEAD))
|
||||
#define PV_EXIT_START pv_exit
|
||||
#define PV_MUS_4_START ((uint16_t)(pv_exit + 42))
|
||||
#define PV_GLASS_DONE ((uint16_t)(pv_exit + 210))
|
||||
#define PV_SLUMP_START ((uint16_t)(pv_exit + 273))
|
||||
#define PV_ANIM_TICKS pv_anim_end
|
||||
|
||||
/* Собрать шкалу под НАШ набор записей. 200 — цепочка битов сценария от
|
||||
* конца m50 до входа Джафара (40+48+24+40+48), 469 — хвост после его
|
||||
* ухода; оба от записи не зависят. */
|
||||
static void pv_timing_init(void)
|
||||
{
|
||||
pop_mus_info_t m50, m53;
|
||||
|
||||
(void)pop_music_info(POP_MUS_STORY_2, &m50);
|
||||
(void)pop_music_info(POP_MUS_STORY_3, &m53);
|
||||
pv_m50_end = (uint16_t)(PV_MUS_2_START + m50.ticks);
|
||||
pv_dialog1 = (uint16_t)(pv_m50_end + 200);
|
||||
pv_exit = (uint16_t)(pv_dialog1 + m53.ticks);
|
||||
pv_anim_end = (uint16_t)(pv_exit + 469);
|
||||
}
|
||||
|
||||
/* Возврат долга шкалы (см. цикл сцены): сколько порций насоса отдаём за один
|
||||
* кадр сцены и сколько их вообще имеет смысл копить. 1 порция — 11,7 мс;
|
||||
* 128 порций — полторы секунды, дальше догонять уже нечего. */
|
||||
@@ -870,6 +900,7 @@ static int intro_pv_animated(void)
|
||||
jaffar_actor.visible = 0;
|
||||
jaffar_actor.step = 0;
|
||||
tick = 0;
|
||||
pv_timing_init(); /* шкала под длительности НАШЕГО набора */
|
||||
intro_skip_begin(&skip);
|
||||
while (tick < PV_ANIM_TICKS) {
|
||||
if (intro_skip_requested(&skip)) {
|
||||
|
||||
Reference in New Issue
Block a user