Музыка звучала там, где оригинал играет эффекты
Три места, все — наши заявки на треки, поставленные без условий оригинала. Вступление первого уровня (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 <noreply@anthropic.com>
This commit is contained in:
@@ -869,6 +869,7 @@ EMM: `pop_music_load*` всегда пишет в НЕ играющий, `pop_mu
|
|||||||
|------|---------|-------------|
|
|------|---------|-------------|
|
||||||
| 24 / 28 / 32 | смерть: обычная / в бою / от руки тени | `ctrl_kid_death` |
|
| 24 / 28 / 32 | смерть: обычная / в бою / от руки тени | `ctrl_kid_death` |
|
||||||
| 25 | вступление 1-го уровня (Кид сидит), тень 6-го | `control_crouched`, `guards.c` |
|
| 25 | вступление 1-го уровня (Кид сидит), тень 6-го | `control_crouched`, `guards.c` |
|
||||||
|
| — | НА ДЕМО-УРОВНЕ музыки нет вовсе: там одни эффекты | гейт в `pop_music_service` |
|
||||||
| 27 / 35 / 40 | сцены перед 2/4/6/12, 8/9, «времени мало» | `pop_pre_cutscene_show` |
|
| 27 / 35 / 40 | сцены перед 2/4/6/12, 8/9, «времени мало» | `pop_pre_cutscene_show` |
|
||||||
| 29 | встреча с Джафаром | `pop_meet_jaffar` |
|
| 29 | встреча с Джафаром | `pop_meet_jaffar` |
|
||||||
| 30 / 33 | большая склянка / малая | `pop_proc_get_object` |
|
| 30 / 33 | большая склянка / малая | `pop_proc_get_object` |
|
||||||
@@ -878,6 +879,14 @@ EMM: `pop_music_load*` всегда пишет в НЕ играющий, `pop_mu
|
|||||||
| 41 / 32 | конец уровня / конец 4-го (тень) | опкод SND_LEVEL в `play_seq` |
|
| 41 / 32 | конец уровня / конец 4-го (тень) | опкод SND_LEVEL в `play_seq` |
|
||||||
| 26 | встреча с принцессой | `cut_ending` |
|
| 26 | встреча с принцессой | `cut_ending` |
|
||||||
|
|
||||||
|
**Вступление первого уровня — автомат, а не «звук при приседе»** (seg005:02EB).
|
||||||
|
Пока `need_level1_music` не ноль, `control_crouched` НЕ ЧИТАЕТ управление:
|
||||||
|
Кид сидит, тема играет, и лишь когда она смолкла, он может встать. Наша
|
||||||
|
первая версия просто играла трек при первом приседе — и тема догоняла
|
||||||
|
игрока посреди уровня (пробежал, спрыгнул, присел — заиграла). Признак
|
||||||
|
«ещё звучит» берём у курсора насоса `pop_mus_left`: он резидентный, и если
|
||||||
|
музыка выключена, курсор остаётся нулём — Кид просто встаёт.
|
||||||
|
|
||||||
Темы, которые звучат один раз за заход на уровень (вступление 1-го, тень
|
Темы, которые звучат один раз за заход на уровень (вступление 1-го, тень
|
||||||
6-го), сбрасывает `pop_music_level_start()` из `pop_start_level`. Оригинал
|
6-го), сбрасывает `pop_music_level_start()` из `pop_start_level`. Оригинал
|
||||||
для этого портит переменную двери (`leveldoor_open = 0x4D`) — у нас на это
|
для этого портит переменную двери (`leveldoor_open = 0x4D`) — у нас на это
|
||||||
|
|||||||
@@ -442,6 +442,10 @@ void pop_jaffar_exit(void) __banked
|
|||||||
* Демо-уровень (0) сюда тоже не входит: его ветка — часть аттракта. */
|
* Демо-уровень (0) сюда тоже не входит: его ветка — часть аттракта. */
|
||||||
static void on_guard_killed(void)
|
static void on_guard_killed(void)
|
||||||
{
|
{
|
||||||
|
/* Демо-уровень (0) у оригинала уходит в свою ветку — «беги из комнаты»,
|
||||||
|
* без единого звука (seg006:1929). Заставочная демка озвучена только
|
||||||
|
* эффектами, музыки в ней нет вовсе. */
|
||||||
|
if (pop_current_level == 0) return;
|
||||||
if (pop_current_level != JAFFAR_LEVEL) {
|
if (pop_current_level != JAFFAR_LEVEL) {
|
||||||
/* seg006:1940 — победный трек за стража; за тень его нет: она не
|
/* seg006:1940 — победный трек за стража; за тень его нет: она не
|
||||||
* «убита», а слилась с Кидом. */
|
* «убита», а слилась с Кидом. */
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "pop_ctrl.h" /* свой API (__banked) + объявления шины control_* */
|
#include "pop_ctrl.h" /* свой API (__banked) + объявления шины control_* */
|
||||||
#include "pop_kid.h"
|
#include "pop_kid.h"
|
||||||
#include "pop_sfx.h" /* звуковые эффекты (../docs/sound_plan.md) */
|
#include "pop_sfx.h" /* звуковые эффекты (../docs/sound_plan.md) */
|
||||||
|
#include "pop_music.h" /* pop_mus_left — «тема ещё звучит» */
|
||||||
#include "pop_map.h"
|
#include "pop_map.h"
|
||||||
#include "pop_level.h" /* pop_current_level — вступительная тема 1-го */
|
#include "pop_level.h" /* pop_current_level — вступительная тема 1-го */
|
||||||
#include "pop_guard.h" /* charid, состояние меча, seq стража */
|
#include "pop_guard.h" /* charid, состояние меча, seq стража */
|
||||||
@@ -255,24 +256,45 @@ static void safe_step(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Вступительная тема уровня 1 играется один раз за заход на уровень;
|
/* Автомат вступительной темы уровня 1 (порт need_level1_music):
|
||||||
* сбрасывает флаг pop_ctrl_level_start (её зовёт запуск уровня). */
|
* 0 — не нужна, 1 — пора пустить, 2 — играет, ждём конца. */
|
||||||
static uint8_t lvl1_music;
|
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_* обработчики состояний ------------------------------ */
|
/* ---- control_* обработчики состояний ------------------------------ */
|
||||||
static void control_crouched(void)
|
static void control_crouched(void)
|
||||||
{
|
{
|
||||||
/* «Presentation» (seg005:02EB): на первом уровне Кид начинает игру
|
/* ВСТУПЛЕНИЕ ПЕРВОГО УРОВНЯ (seg005:02EB) — это не «звук при приседе», а
|
||||||
* сидя, и пока он сидит, звучит вступительная тема. Оригинал ждёт
|
* маленький автомат, и Кид на его время ОБЕЗДВИЖЕН:
|
||||||
* тишины (`if (!check_sound_playing())`) — у нас достаточно одного
|
* 1 — ничего не звучит: пустить тему, перейти в 2;
|
||||||
* запуска: заявку разберёт главный цикл, а насос сам придержит музыку,
|
* 2 — ждать, пока она доиграет, и только тогда отпустить управление.
|
||||||
* пока доигрывает эффект. */
|
* Оттого Кид и сидит в начале игры, пока не смолкнет музыка.
|
||||||
if (pop_current_level == 1 && !lvl1_music) {
|
*
|
||||||
lvl1_music = 1;
|
* Я сперва сделал проще — «сыграть при первом приседе», — и тема
|
||||||
|
* догоняла игрока посреди уровня: пробежал, спрыгнул, присел — заиграла
|
||||||
|
* (поймано пользователем).
|
||||||
|
*
|
||||||
|
* Ждём по pop_mus_left: это курсор насоса, он резидентный и правдиво
|
||||||
|
* отвечает «трек ещё звучит». Если музыка выключена или заявку никто
|
||||||
|
* не обработал, курсор так и останется нулём — Кид просто встанет, а
|
||||||
|
* не сядет навсегда. */
|
||||||
|
if (lvl1_music && pop_current_level == 1) {
|
||||||
|
if (!pop_mus_left) {
|
||||||
|
if (lvl1_music == 1) {
|
||||||
pop_sfx_play(25);
|
pop_sfx_play(25);
|
||||||
|
lvl1_music = 2;
|
||||||
|
} else {
|
||||||
|
lvl1_music = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return; /* управление на это время не читаем */
|
||||||
|
}
|
||||||
|
lvl1_music = 0;
|
||||||
/* Shift в приседе — поднять предмет (seg005 control_crouched). */
|
/* Shift в приседе — поднять предмет (seg005 control_crouched). */
|
||||||
if (control_shift2 == CONTROL_HELD && get_item_action()) return;
|
if (control_shift2 == CONTROL_HELD && get_item_action()) return;
|
||||||
if (control_y != CONTROL_HELD_DOWN) {
|
if (control_y != CONTROL_HELD_DOWN) {
|
||||||
@@ -730,11 +752,17 @@ static void ctrl_kid_death(void)
|
|||||||
/* play_death_music (seg006:15B5): траурный трек зависит от того,
|
/* play_death_music (seg006:15B5): траурный трек зависит от того,
|
||||||
* КАК умер Кид — в бою, от руки тени или просто так. Оригинал ждёт
|
* КАК умер Кид — в бою, от руки тени или просто так. Оригинал ждёт
|
||||||
* шести тиков, чтобы доиграл звук удара; у нас ждать не нужно —
|
* шести тиков, чтобы доиграл звук удара; у нас ждать не нужно —
|
||||||
* насос держит курсор музыки, пока звучит эффект. */
|
* насос держит курсор музыки, пока звучит эффект.
|
||||||
|
*
|
||||||
|
* НА ДЕМО-УРОВНЕ МУЗЫКИ НЕТ — и это буквальное условие оригинала
|
||||||
|
* (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);
|
if (Guard.charid == CHARID_1_SHADOW) pop_sfx_play(32);
|
||||||
else if (holding_sword) pop_sfx_play(28);
|
else if (holding_sword) pop_sfx_play(28);
|
||||||
else pop_sfx_play(24);
|
else pop_sfx_play(24);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Публичный API ------------------------------------------------ */
|
/* ---- Публичный API ------------------------------------------------ */
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "pop_music.h"
|
#include "pop_music.h"
|
||||||
#include "pop_music_tbl.h"
|
#include "pop_music_tbl.h"
|
||||||
#include "pop_sfx.h"
|
#include "pop_sfx.h"
|
||||||
|
#include "pop_app.h" /* POP_APP_DEMO — в демке музыки нет */
|
||||||
|
|
||||||
uint8_t pop_mus_page[POP_MUS_PAGES];
|
uint8_t pop_mus_page[POP_MUS_PAGES];
|
||||||
|
|
||||||
@@ -219,7 +220,12 @@ void pop_music_service(void) __banked
|
|||||||
/* Один вызов на кадр из ЛЮБОГО цикла — игрового, интро, катсцены.
|
/* Один вызов на кадр из ЛЮБОГО цикла — игрового, интро, катсцены.
|
||||||
* Заявку кладёт pop_sfx_play (звук без оцифровки = музыка оригинала),
|
* Заявку кладёт 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;
|
if (pop_mus_req) pop_mus_req = 0;
|
||||||
|
|||||||
@@ -156,6 +156,9 @@ void tk_key_down(uint16_t code)
|
|||||||
* проверяют их игровую логику, поэтому здесь достаточно no-op контрактов. */
|
* проверяют их игровую логику, поэтому здесь достаточно no-op контрактов. */
|
||||||
void pop_beam_sample(void) __preserves_regs(b, c, d, e) { }
|
void pop_beam_sample(void) __preserves_regs(b, c, d, e) { }
|
||||||
void pop_sfx_play(uint8_t id) { (void)id; }
|
void pop_sfx_play(uint8_t id) { (void)id; }
|
||||||
|
/* Курсор насоса: в тестах звука нет вовсе, значит «ничего не звучит» — и
|
||||||
|
* автомат вступительной темы первого уровня сразу отпускает управление. */
|
||||||
|
volatile uint16_t pop_mus_left;
|
||||||
void pop_sfx_stop(void) { }
|
void pop_sfx_stop(void) { }
|
||||||
|
|
||||||
/* ---- 2. соседние модули движка -------------------------------------- */
|
/* ---- 2. соседние модули движка -------------------------------------- */
|
||||||
|
|||||||
Reference in New Issue
Block a user