Реплики сцены с принцессой: вернуть подкачку в кадр

Регрессия 4086dde: тот коммит перенёс шаг постраничной подкачки «в паузу
кадра» — внутрь цикла ожидания тика насоса — и одновременно ввёл защиту от
промотки (snd_want подтягивается к snd_done). Вместе это убило подкачку:
паузы в этой сцене почти нет, цикл ожидания выходит сразу, шаг не
вызывается. Реплики Джафара не успевали загрузиться к своему тику,
pop_music_play() молча ничего не делал, и после первой реплики сцена шла
в тишине.

Замер в MAME (адреса статиков pop_music.c — от public _pop_mus_page по
смещениям из .sym): до фикса ld_next стоял на нуле 20 секунд при активной
загрузке; после — 11 страниц из 11, mus_ready=1, и звучат все три реплики
m50 → m53 → m52.

Фикс: один безусловный load_step в кадре сцены; шаг в паузе оставлен, он
бесплатный, когда время есть.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 17:00:16 +03:00
parent a3c5c600da
commit 747783c422
3 changed files with 54 additions and 34 deletions
+36
View File
@@ -3593,3 +3593,39 @@ FIRST_LEVEL`. Рестарт ТОГО ЖЕ уровня (смерть, чекп
пишет. Всё, что нарисовано текстом, обязано рисоваться ПОСЛЕ перехода, иначе пишет. Всё, что нарисовано текстом, обязано рисоваться ПОСЛЕ перехода, иначе
переезжает только фон (так вышла пустая таблица рекордов, см. переезжает только фон (так вышла пустая таблица рекордов, см.
[HOF-ENTRY](TASKS_CLOSED.md#hof-entry)). [HOF-ENTRY](TASKS_CLOSED.md#hof-entry)).
---
<a id="pv-music-stall"></a>
## PV-MUSIC-STALL. В сцене с принцессой звучала только первая реплика — **ЗАКРЫТ 2026-08-26**
**Симптом (пользователь).** «Музыка после открытия двери для входа Джафара
пропала» — дальше сцена шла в тишине до конца: не звучали ни реплика входа
Джафара (m53), ни его ухода (m52).
**Корень.** Регрессия коммита `4086dde` («убрана промотка кадров»). До него
шаг постраничной подкачки стоял БЕЗУСЛОВНО в теле кадра сцены (плюс там же
вызывался `pop_music_service`). Тот коммит перенёс шаг «в паузу кадра» —
внутрь цикла ожидания тика насоса — и одновременно ввёл правильную защиту от
промотки (`if (snd_done > snd_want) snd_want = snd_done`). Вместе это и убило
подкачку: пауза в этой сцене почти не выпадает, цикл ожидания выходит сразу,
и шаг не вызывается.
**Замер, который это показал** (адреса статиков `pop_music.c` вычислены от
public `_pop_mus_page` по смещениям из `.sym`):
| | до фикса | после фикса |
|---|---|---|
| `ld_next` (прочитано страниц m53) | **0** за 20 с | **11 из 11** |
| `mus_ready` | 0 | 1 |
| что звучит | только m50 | m50 → m53 → m52 |
**Фикс.** Один безусловный `pop_music_load_step()` в кадре сцены (шаг в паузе
оставлен: он бесплатный, когда время есть). Страница стоит 33 мс против
кадра сцены в 100-133 мс, а шкала работает по факту прошедшего времени,
поэтому сцена от этого не разъезжается.
**Ловушка отладки, стоившая ложного диагноза.** Пока машина стоит на
брейкпоинте, значения в памяти застывают — картина «`busy=0` при `next=3` из
20» выглядела как обрыв чтения с ошибкой, хотя это был просто стоп-кадр.
Прежде чем толковать значения, проверять `status`: `state=run` или `stop`.
+2 -34
View File
@@ -26,7 +26,6 @@
| [BUG-SHADOW-SET](#bug-shadow-set) | Тень на 12-м уровне дерётся спрайтами СТРАЖА: SHADOW.DAT у нас нет вовсе | порт | открыт, разбор готов | | [BUG-SHADOW-SET](#bug-shadow-set) | Тень на 12-м уровне дерётся спрайтами СТРАЖА: SHADOW.DAT у нас нет вовсе | порт | открыт, разбор готов |
| [PAL-L1-AFTER-INTRO](#pal-l1-after-intro) | вход в игру на уровень 1 после интро — игровая палитра не установлена (чёрный экран) | палитра/fade | открыт: воспроизведение нестабильно, корень не установлен | | [PAL-L1-AFTER-INTRO](#pal-l1-after-intro) | вход в игру на уровень 1 после интро — игровая палитра не установлена (чёрный экран) | палитра/fade | открыт: воспроизведение нестабильно, корень не установлен |
| [PAL-DUNGEON-STALE](#pal-dungeon-stale) | переход 3→4 (подземелье→дворец): остаётся палитра подземелья | палитра/fade | открыт: КОРЕНЬ ЯСЕН — fade_in восстанавливает из протухшего снимка | | [PAL-DUNGEON-STALE](#pal-dungeon-stale) | переход 3→4 (подземелье→дворец): остаётся палитра подземелья | палитра/fade | открыт: КОРЕНЬ ЯСЕН — fade_in восстанавливает из протухшего снимка |
| [CUTSCENE-LTR](#cutscene-ltr) | `Marry Jaffar` → title → credits должны проявляться полосами слева направо | заставка | открыт: зафиксировано 2026-08-24, взять после текущего цикла анимации PV |
| [BUG-CORPSE-AIR](#bug-corpse-air) | труп Кида висит в воздухе, когда убившая его плита улетела вниз | физика | открыт | | [BUG-CORPSE-AIR](#bug-corpse-air) | труп Кида висит в воздухе, когда убившая его плита улетела вниз | физика | открыт |
| [LOOSE-ROOM-CHANGE](#loose-room-change) | расшатанная плита не доваливается, если Кид ушёл в соседнюю комнату | порт | закрыт 2026-08-13, кроме [LOOSE-SEAM-ANIM](#loose-seam-anim) | | [LOOSE-ROOM-CHANGE](#loose-room-change) | расшатанная плита не доваливается, если Кид ушёл в соседнюю комнату | порт | закрыт 2026-08-13, кроме [LOOSE-SEAM-ANIM](#loose-seam-anim) |
| [JUMP-FLOOR-LEGS](#jump-floor-legs) | в прыжке с пола ноги Кида рисуются ПОВЕРХ кромки пола, а должны за ней | окклюзия | **корень найден и починен, ждёт проверки в MAME** | | [JUMP-FLOOR-LEGS](#jump-floor-legs) | в прыжке с пола ноги Кида рисуются ПОВЕРХ кромки пола, а должны за ней | окклюзия | **корень найден и починен, ждёт проверки в MAME** |
@@ -169,40 +168,9 @@ Rx-overrun не срабатывала. Гашение бита отладчи
--- ---
<a id="pv-music-stall"></a> <a id="pv-music-stall"></a>
## PV-MUSIC-STALL. В сцене с принцессой звучит только первая реплика ## PV-MUSIC-STALL. В сцене с принцессой звучала только первая реплика — ЗАКРЫТ 2026-08-26
**Наблюдение пользователя (2026-08-26).** «Музыка после открытия двери для Разбор — [`BUGS_CLOSED.md`](BUGS_CLOSED.md#pv-music-stall).
входа Джафара пропала» — и дальше сцена идёт в тишине до самого конца.
**Что измерено** (MAME, чтение переменных `pop_music.c` мостом; адреса
считаны от `_pop_mus_page` по `.sym`):
| момент | `ld_busy` | `mus_ready` | `slot_id[]` |
|---|---|---|---|
| m50 играет, тик 60 — старт загрузки m53 | 1 | 0 | {55, 50} |
| через 28 с | 1 | 0 | {55, 50} |
| конец сцены | 0 | 0 | {55, 50} |
То есть **m53 и m52 вообще не доходят до слотов**: постраничная загрузка
(`pop_music_load_begin` + `pop_music_load_step`) за 17 секунд сцены (1040
тиков) не успевает прочитать 11 страниц, `mus_ready` так и остаётся нулём, и
`pop_music_play()` на своём тике молча ничего не делает. Блок при этом
выделяется (`slot_blk[0] != 0`), то есть память и `open` ни при чём —
не хватает именно ШАГОВ чтения.
Начатую загрузку m53 затем отменяет заявка на m52 (`load_begin` начинается с
`if (ld_busy) load_abort()`), поэтому не звучит и она.
**Где искать.** `intro_pv_animated()` (pop_intro.c): `pop_music_load_step()`
вызывается ТОЛЬКО внутри цикла ожидания тика насоса, а тот выполняется лишь
пока кадр «в плюсе» по времени; после каждой подкачки сцена уходит в долг
(«ДОЛГ НЕ ДОГОНЯЕМ»), и следующий шаг случается через несколько кадров.
Замер 33 мс на страницу делался на железе — в MAME чтение заметно дороже.
**Варианты, которые надо взвесить, а не хвататься за первый.** Читать по
несколько страниц за ожидание; начинать загрузку раньше (m53 — ещё до сцены,
вместе с m50); либо признать, что реплика длиной 16 с не обязана грузиться
по странице за кадр, и читать её целиком под чёрным экраном.
--- ---
+16
View File
@@ -849,6 +849,22 @@ static int intro_pv_animated(void)
torch_right, sand_frame, flash, torch_right, sand_frame, flash,
render_tick); render_tick);
pop_sfx_tick(); /* как в оригинале — в конце отрисовки кадра */ pop_sfx_tick(); /* как в оригинале — в конце отрисовки кадра */
/* ГАРАНТИРОВАННЫЙ ШАГ ПОДКАЧКИ, а не «если останется время».
*
* Ниже страница следующей реплики читается в ПАУЗЕ кадра, и это
* верно ровно до тех пор, пока пауза есть. В этой сцене её почти
* нет: кадр дорогой, шкала работает по принципу «долг не догоняем»
* (snd_want подтягивается к snd_done), и цикл ожидания выходит
* сразу. Замер в MAME 2026-08-26: ОДНА страница за семь секунд,
* то есть одиннадцать страниц m53 не успевали за всю сцену
* pop_music_play() на своём тике не находил готового трека, и
* реплики Джафара пропадали совсем.
*
* Страница стоит 33 мс против кадра сцены в 100-133 мс, поэтому
* один безусловный шаг в кадр сцену не ломает: он лишь тратит
* часть той же паузы, а если паузы не было добавляет к кадру
* четверть его длины, и шкала это учтёт сама. */
if (pop_music_loading()) (void)pop_music_load_step();
/* Первый кадр собран под чёрной палитрой — теперь проявляем его /* Первый кадр собран под чёрной палитрой — теперь проявляем его
* целиком, вместе с принцессой и факелами. */ * целиком, вместе с принцессой и факелами. */
if (!faded_in) { faded_in = 1; pop_pal_fade_in(INTRO_FADE); } if (!faded_in) { faded_in = 1; pop_pal_fade_in(INTRO_FADE); }