Implement forced BGM opcodes

This commit is contained in:
gamer147
2026-07-29 12:38:54 -04:00
parent cbd1521872
commit e24fcfff1f
14 changed files with 259 additions and 45 deletions

View File

@@ -563,6 +563,11 @@ name = "current_bgm_track_id"
type = "int"
note = "direct-name BGM track id retained by the music facade; op 0xbf starts/replaces it, ops 0xc0/0xc3 get/set it, and numbered-save layouts restore it"
[[field]]
offset = 0xa0b8c
name = "current_bgm_start_mode"
type = "int"
note = "logical playback mode retained by the music facade and forwarded to its backend: zero stops at decoder EOF, nonzero rewinds; ordinary op 0xbf and forced op 0xb7 use one, forced op 0xb9 uses zero"
[[field]]
offset = 0xa0cc0
name = "screen_w"
type = "int"

View File

@@ -1804,60 +1804,63 @@ observed_types = ["imm"]
[[opcode]]
op = 0xb7
label = "u0041D0E0"
label = "restart-bgm-loop"
argc = 1
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "u0041D0E0"
category = "unknown"
summary = ""
name = "restart-bgm-loop"
category = "audio"
summary = "(track_or_zero) — force-start looping BGM even when the requested nonzero track already matches the retained current track. A zero operand restarts the retained track; when no track is retained it resolves to an idempotent stop. Any active BGM fade is completed/cancelled first."
noop_headless = false
source = "kelebek"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = ""
evidence = "Ghidra /v2: op_0xb7_force_play_bgm_loop@0x4202d0 first clears run-state bit 0x200 and forces bgm_fade_tick(...,100) when a fade is active, then calls bgm_force_play_track@0x464a90 with operand 1 and logical start mode 1. Unlike ordinary bgm_play_track@0x464750 (op 0xbf), the force worker has no same-track early return. A nonzero operand replaces EngineCtx.current_bgm_track_id; zero reuses it, or releases when it is also zero. The OGG backend forwards mode 1 through sound_buffer_start; sound_stream_fill_quarter rewinds at EOF only for nonzero mode. Corpus: CALLBACK_LOAD uses zero to restart restored music; CONFIG uses BGM029 for its music preview."
details = "Implemented: the VM resolves zero through its retained current track and calls the explicit forced-restart host seam with mode 1. Godot replaces the OGG stream with Loop=true and cancels any deferred fade; ordinary op 0xbf keeps its same-track idempotence."
[[opcode.semantics.args]]
i = 1
role = ""
role = "track_or_zero"
observed_types = ["imm"]
[[opcode]]
op = 0xb8
label = "u00415520"
label = "stop-bgm"
argc = 0
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "u00415520"
category = "unknown"
summary = ""
name = "stop-bgm"
category = "audio"
summary = "Stop and release the active BGM source and clear the retained current track. Any active BGM fade is completed/cancelled first."
noop_headless = false
source = "kelebek"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = ""
evidence = "Ghidra /v2: op_0xb8_stop_bgm@0x416ba0 clears run-state bit 0x200 and forces bgm_fade_tick(...,100) when a fade is active, then calls bgm_stop_release@0x464690. The worker clears music-facade current track/status fields and invokes the active backend's stop/release method. Corpus: CONFIG stops the BGM029 preview while closing; MMODE stops current music before building the music-room screen."
details = "Implemented: the VM clears its retained track and calls StopBgm. Godot cancels voice ducking and any fade, stops playback, releases the stream, and restores neutral gain."
[[opcode]]
op = 0xb9
label = "u0041D140"
label = "restart-bgm-once"
argc = 1
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "u0041D140"
category = "unknown"
summary = ""
name = "restart-bgm-once"
category = "audio"
summary = "(track_or_zero) — force-start one-shot BGM even when the requested nonzero track already matches the retained current track. A zero operand restarts the retained track once; when no track is retained it resolves to an idempotent stop. Any active BGM fade is completed/cancelled first."
noop_headless = false
source = "kelebek"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = ""
evidence = "Ghidra /v2: op_0xb9_force_play_bgm_once@0x420330 shares op 0xb7's fade-finalization and bgm_force_play_track@0x464a90 path but supplies logical start mode 0. The OGG backend forwards that mode through sound_buffer_start; sound_stream_fill_quarter pads/stops at decoder EOF instead of rewinding. Corpus: STAGECLEAR's sole site force-starts BGM025 as its one-shot clear fanfare."
details = "Implemented: the VM shares op 0xb7's retained-track/zero handling but supplies mode 0. Godot replaces the OGG stream with Loop=false, so BGM025 naturally stops at EOF."
[[opcode.semantics.args]]
i = 1
role = ""
role = "track_or_zero"
observed_types = ["imm"]
[[opcode]]
@@ -1938,6 +1941,7 @@ source = "investigation"
confidence = "high"
depends_on = []
evidence = "Ghidra op 0xc2 handler 0x4204c0 sets run-state 0x200, arms the service timer, and calls bgm_fade_arm@0x464830; bgm_fade_tick@0x464960 interpolates current/target percent and applies volume, releasing at target 0. Native SC0000 trace at 0x7c1/0x126c shows target 0, duration 3000, 1% ticks at about 30 ms."
details = "Implemented through the blocking host fade clock. Target zero clears the VM's retained track after the wait and invokes the shared BGM stop/release seam, so Godot does not retain a silent bound stream."
[[opcode.semantics.args]]
i = 1