From e5179af9d84e78c2d63f97e5a6808fce9d1527bf Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 26 Aug 2026 10:41:07 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9C=D1=83=D0=B7=D1=8B=D0=BA=D0=B0=20=D0=B7?= =?UTF-8?q?=D0=B2=D1=83=D1=87=D0=B0=D0=BB=D0=B0=20=D1=82=D0=B0=D0=BC,=20?= =?UTF-8?q?=D0=B3=D0=B4=D0=B5=20=D0=BE=D1=80=D0=B8=D0=B3=D0=B8=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=20=D0=B8=D0=B3=D1=80=D0=B0=D0=B5=D1=82=20=D1=8D=D1=84?= =?UTF-8?q?=D1=84=D0=B5=D0=BA=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Три места, все — наши заявки на треки, поставленные без условий оригинала. Вступление первого уровня (25) оказалось не «звуком при приседе», а автоматом (seg005:02EB): пока need_level1_music не ноль, control_crouched НЕ читает управление — Кид сидит, играет тема, и только когда она смолкла, он встаёт. Мы играли трек при любом первом приседе, и тема догоняла игрока посреди уровня: пробежал, спрыгнул, присел — заиграла. Теперь автомат портирован целиком, а «ещё звучит» спрашивается у курсора насоса pop_mus_left (если музыка выключена, курсор нулевой и Кид просто встаёт). Демо-уровень: оригинал молчит музыкой и там, где играет её в игре. Смерть Кида — прямое условие `current_level != 0 && != 15` (seg006:1366), убитый страж — отдельная ветка «беги из комнаты» без звука (seg006:1929). Плюс общий гейт в pop_music_service: заставочная демка озвучена одними эффектами, и любой новый музыкальный повод (меч, зелье) звучал бы там, где оригинал их не играет. Гейт живёт в банке, а не в главном цикле: в резиденте W1 оставалось 27 байт. Host-тесты: 15 наборов. Проверено в MAME: на старте первого уровня тема 25 отыгрывает целиком, курсор доходит до нуля. Co-Authored-By: Claude Opus 5 --- applications/PoP/docs/sound_plan.md | 9 +++ applications/PoP/roomtest/guards.c | 4 ++ applications/PoP/roomtest/pop_ctrl.c | 58 +++++++++++++++----- applications/PoP/roomtest/pop_music.c | 8 ++- applications/PoP/roomtest/tests-host/stubs.c | 3 + 5 files changed, 66 insertions(+), 16 deletions(-) diff --git a/applications/PoP/docs/sound_plan.md b/applications/PoP/docs/sound_plan.md index 15fc293..963dbc3 100644 --- a/applications/PoP/docs/sound_plan.md +++ b/applications/PoP/docs/sound_plan.md @@ -869,6 +869,7 @@ EMM: `pop_music_load*` всегда пишет в НЕ играющий, `pop_mu |------|---------|-------------| | 24 / 28 / 32 | смерть: обычная / в бою / от руки тени | `ctrl_kid_death` | | 25 | вступление 1-го уровня (Кид сидит), тень 6-го | `control_crouched`, `guards.c` | +| — | НА ДЕМО-УРОВНЕ музыки нет вовсе: там одни эффекты | гейт в `pop_music_service` | | 27 / 35 / 40 | сцены перед 2/4/6/12, 8/9, «времени мало» | `pop_pre_cutscene_show` | | 29 | встреча с Джафаром | `pop_meet_jaffar` | | 30 / 33 | большая склянка / малая | `pop_proc_get_object` | @@ -878,6 +879,14 @@ EMM: `pop_music_load*` всегда пишет в НЕ играющий, `pop_mu | 41 / 32 | конец уровня / конец 4-го (тень) | опкод SND_LEVEL в `play_seq` | | 26 | встреча с принцессой | `cut_ending` | +**Вступление первого уровня — автомат, а не «звук при приседе»** (seg005:02EB). +Пока `need_level1_music` не ноль, `control_crouched` НЕ ЧИТАЕТ управление: +Кид сидит, тема играет, и лишь когда она смолкла, он может встать. Наша +первая версия просто играла трек при первом приседе — и тема догоняла +игрока посреди уровня (пробежал, спрыгнул, присел — заиграла). Признак +«ещё звучит» берём у курсора насоса `pop_mus_left`: он резидентный, и если +музыка выключена, курсор остаётся нулём — Кид просто встаёт. + Темы, которые звучат один раз за заход на уровень (вступление 1-го, тень 6-го), сбрасывает `pop_music_level_start()` из `pop_start_level`. Оригинал для этого портит переменную двери (`leveldoor_open = 0x4D`) — у нас на это diff --git a/applications/PoP/roomtest/guards.c b/applications/PoP/roomtest/guards.c index c3b6c63..8126101 100644 --- a/applications/PoP/roomtest/guards.c +++ b/applications/PoP/roomtest/guards.c @@ -442,6 +442,10 @@ void pop_jaffar_exit(void) __banked * Демо-уровень (0) сюда тоже не входит: его ветка — часть аттракта. */ static void on_guard_killed(void) { + /* Демо-уровень (0) у оригинала уходит в свою ветку — «беги из комнаты», + * без единого звука (seg006:1929). Заставочная демка озвучена только + * эффектами, музыки в ней нет вовсе. */ + if (pop_current_level == 0) return; if (pop_current_level != JAFFAR_LEVEL) { /* seg006:1940 — победный трек за стража; за тень его нет: она не * «убита», а слилась с Кидом. */ diff --git a/applications/PoP/roomtest/pop_ctrl.c b/applications/PoP/roomtest/pop_ctrl.c index 185dfbb..a3dc5dd 100644 --- a/applications/PoP/roomtest/pop_ctrl.c +++ b/applications/PoP/roomtest/pop_ctrl.c @@ -21,6 +21,7 @@ #include "pop_ctrl.h" /* свой API (__banked) + объявления шины control_* */ #include "pop_kid.h" #include "pop_sfx.h" /* звуковые эффекты (../docs/sound_plan.md) */ +#include "pop_music.h" /* pop_mus_left — «тема ещё звучит» */ #include "pop_map.h" #include "pop_level.h" /* pop_current_level — вступительная тема 1-го */ #include "pop_guard.h" /* charid, состояние меча, seq стража */ @@ -255,24 +256,45 @@ static void safe_step(void) } } -/* Вступительная тема уровня 1 играется один раз за заход на уровень; - * сбрасывает флаг pop_ctrl_level_start (её зовёт запуск уровня). */ +/* Автомат вступительной темы уровня 1 (порт need_level1_music): + * 0 — не нужна, 1 — пора пустить, 2 — играет, ждём конца. */ static uint8_t lvl1_music; -void pop_ctrl_level_start(void) __banked { lvl1_music = 0; } +/* seg003:45 — need_level1_music = (level == 1). Ставит старт уровня. */ +void pop_ctrl_level_start(void) __banked +{ + lvl1_music = (uint8_t)(pop_current_level == 1); +} /* ---- control_* обработчики состояний ------------------------------ */ static void control_crouched(void) { - /* «Presentation» (seg005:02EB): на первом уровне Кид начинает игру - * сидя, и пока он сидит, звучит вступительная тема. Оригинал ждёт - * тишины (`if (!check_sound_playing())`) — у нас достаточно одного - * запуска: заявку разберёт главный цикл, а насос сам придержит музыку, - * пока доигрывает эффект. */ - if (pop_current_level == 1 && !lvl1_music) { - lvl1_music = 1; - pop_sfx_play(25); + /* ВСТУПЛЕНИЕ ПЕРВОГО УРОВНЯ (seg005:02EB) — это не «звук при приседе», а + * маленький автомат, и Кид на его время ОБЕЗДВИЖЕН: + * 1 — ничего не звучит: пустить тему, перейти в 2; + * 2 — ждать, пока она доиграет, и только тогда отпустить управление. + * Оттого Кид и сидит в начале игры, пока не смолкнет музыка. + * + * Я сперва сделал проще — «сыграть при первом приседе», — и тема + * догоняла игрока посреди уровня: пробежал, спрыгнул, присел — заиграла + * (поймано пользователем). + * + * Ждём по pop_mus_left: это курсор насоса, он резидентный и правдиво + * отвечает «трек ещё звучит». Если музыка выключена или заявку никто + * не обработал, курсор так и останется нулём — Кид просто встанет, а + * не сядет навсегда. */ + if (lvl1_music && pop_current_level == 1) { + if (!pop_mus_left) { + if (lvl1_music == 1) { + pop_sfx_play(25); + lvl1_music = 2; + } else { + lvl1_music = 0; + } + } + return; /* управление на это время не читаем */ } + lvl1_music = 0; /* Shift в приседе — поднять предмет (seg005 control_crouched). */ if (control_shift2 == CONTROL_HELD && get_item_action()) return; if (control_y != CONTROL_HELD_DOWN) { @@ -730,10 +752,16 @@ static void ctrl_kid_death(void) /* play_death_music (seg006:15B5): траурный трек зависит от того, * КАК умер Кид — в бою, от руки тени или просто так. Оригинал ждёт * шести тиков, чтобы доиграл звук удара; у нас ждать не нужно — - * насос держит курсор музыки, пока звучит эффект. */ - if (Guard.charid == CHARID_1_SHADOW) pop_sfx_play(32); - else if (holding_sword) pop_sfx_play(28); - else pop_sfx_play(24); + * насос держит курсор музыки, пока звучит эффект. + * + * НА ДЕМО-УРОВНЕ МУЗЫКИ НЕТ — и это буквальное условие оригинала + * (seg006:1366: `current_level != 0 && current_level != 15`). + * Заставочная демка озвучена одними эффектами. */ + if (pop_current_level != 0 && pop_current_level != 15) { + if (Guard.charid == CHARID_1_SHADOW) pop_sfx_play(32); + else if (holding_sword) pop_sfx_play(28); + else pop_sfx_play(24); + } } } diff --git a/applications/PoP/roomtest/pop_music.c b/applications/PoP/roomtest/pop_music.c index 6459c66..a3dd9ed 100644 --- a/applications/PoP/roomtest/pop_music.c +++ b/applications/PoP/roomtest/pop_music.c @@ -16,6 +16,7 @@ #include "pop_music.h" #include "pop_music_tbl.h" #include "pop_sfx.h" +#include "pop_app.h" /* POP_APP_DEMO — в демке музыки нет */ uint8_t pop_mus_page[POP_MUS_PAGES]; @@ -219,7 +220,12 @@ void pop_music_service(void) __banked /* Один вызов на кадр из ЛЮБОГО цикла — игрового, интро, катсцены. * Заявку кладёт pop_sfx_play (звук без оцифровки = музыка оригинала), * а диск трогаем только здесь. */ - if (!pop_mus_want) { + /* В ЗАСТАВОЧНОЙ ДЕМКЕ МУЗЫКИ НЕТ. Точечные условия оригинала (смерть, + * убитый страж) расставлены по своим местам, но гейт нужен и общий: + * демо проигрывает записанные нажатия, и любой музыкальный повод — + * подобранный меч, зелье — зазвучал бы там, где оригинал играет одни + * эффекты. */ + if (!pop_mus_want || pop_app_state == POP_APP_DEMO) { /* Выключили на ходу: оборвать поток и замолчать. Эффекты при этом * продолжают звучать — у них свой тумблер. */ if (pop_mus_req) pop_mus_req = 0; diff --git a/applications/PoP/roomtest/tests-host/stubs.c b/applications/PoP/roomtest/tests-host/stubs.c index 65cc4db..338cabb 100644 --- a/applications/PoP/roomtest/tests-host/stubs.c +++ b/applications/PoP/roomtest/tests-host/stubs.c @@ -156,6 +156,9 @@ void tk_key_down(uint16_t code) * проверяют их игровую логику, поэтому здесь достаточно no-op контрактов. */ void pop_beam_sample(void) __preserves_regs(b, c, d, e) { } void pop_sfx_play(uint8_t id) { (void)id; } +/* Курсор насоса: в тестах звука нет вовсе, значит «ничего не звучит» — и + * автомат вступительной темы первого уровня сразу отпускает управление. */ +volatile uint16_t pop_mus_left; void pop_sfx_stop(void) { } /* ---- 2. соседние модули движка -------------------------------------- */