Implement deferred SFX and voice duck control

This commit is contained in:
gamer147
2026-07-20 11:29:12 -04:00
parent 0fb608c35e
commit e3b2cbed83
12 changed files with 182 additions and 30 deletions

View File

@@ -61,6 +61,8 @@ Struct `EngineCtx`, size `0xa1000`. Applied to the Ghidra `/v2` image (dispatch-
| `0x6dbcc` | `coroutine_resume_off` | `int` | op 0x7c resume PC offset |
| `0x6dbd4` | `adv_read_skip_state` | `int` | current ADV read/click-skip service state; queried by op 0x1cc, maintained from ReadTextSkip + per-PC read history |
| `0x6dbe4` | `adv_auto_voice_pending` | `int` | set by play-voice ops when voice service exists; Auto waits for voice completion then arms AutoMessageTime0; op 0x1bc resets |
| `0x6dbe8` | `voice_bgm_duck_saved_volume` | `int` | BGM volume saved by the voice-start duck helper before applying MusicFadeOnVoicePlayingVol |
| `0x6dbf0` | `voice_bgm_duck_control_flags` | `uint` | transient mask replaced by op 0x1cf; bit 0 suppresses automatic voice-triggered BGM attenuation |
| `0x6dbf4` | `message_skip_queued_voice_id` | `int` | latest voice resource deferred by op 0xc4 while run_state_flags message-skip bit is active |
| `0x6dbf8` | `message_skip_queued_voice_arg` | `int` | second argument retained with message_skip_queued_voice_id; Himegari op 0xc4 stores zero |
| `0x9b24c` | `dispatch_table` | `void*` | opcode->handler table base [0x400]; handler(op) = *(0x9b24c + op*4) |

View File

@@ -1640,6 +1640,17 @@ resource `0x28` to `E0808.WAV`, loads channel 0, and `0xb5@0xc2e` starts it in t
scratch global `G[0x6242d]` is maintained outside script-visible writes; the SC0000 port profile exposes it
as an external value of 4 rather than pretending the script assigned it.
That later service start is opcode `0x2bf`, now identified as the facade's `SetDelay` operation.
`op_0x2bf_schedule_sfx_start@0x425240` passes `(channel,start_mode,delay_ms)` to
`sfx_set_delay@0x482720` on the inline sound facade at `ctx+0x14024`. The worker accepts channels `0..9`
and stores five pieces of transient state: active at facade `+0x418`, elapsed/progress at `+0x440`, delay
at `+0x468`, start mode at `+0x490`, and an aggregate-active marker at `+0x4b8`. Its native error text is
literally `Function: SetDelay ... invalid Sound number`. The existing trace supplies the consumer proof:
after SC0000 executes `(channel 4, mode 0, 100 ms)`, the ordinary `sfx_channel_start` worker enters for
channel 4 about 109 ms later while the interpreter is parked at its `0x21c` presentation boundary. There
is no intervening `0xb5`. The port schedules the same already-loaded channel and invalidates the pending
callback if that channel is loaded again or released before its deadline.
Normal-speed windowed validation reached `wait-for-input@0x1a58` after 45.6 seconds without an audio stall;
the user confirmed the opening effects were audible and sounded good.
@@ -1649,6 +1660,19 @@ durations at least 1000 ms take 100 steps, shorter durations take 10, and target
The VM is parked for the requested duration. `0xd9` is adjacent startup control, not audio data: it clears
run/service bit `0x1000` in the primary and, when active, secondary context and has no VM-visible result.
Opcode `0x1cf` belongs to the voice/BGM envelope rather than SFX. Handler
`op_0x1cf_set_voice_bgm_duck_control@0x4209f0` replaces the transient mask at
`EngineCtx.voice_bgm_duck_control_flags` (`ctx+0x6dbf0`). Before ordinary or History voice playback,
`voice_bgm_duck_begin@0x406de0` checks that BGM is active, `MusicFadeOnVoicePlaying` is enabled, no explicit
`0xc2` fade is active, and mask bit 0 is clear. It then saves the current BGM volume at
`ctx+0x6dbe8` and applies `MusicFadeOnVoicePlayingVol`. The native settings registry supplies defaults of
enabled=`1` and target=`50` percent; SC0000 writes only control masks `0` and `1`.
The port therefore retains `0x1cf` as script-owned runtime state, applies the native registered default
attenuation when an unsuppressed voice begins, and restores the saved BGM level when that voice completes
or is stopped by message Skip. This introduces no profile record, boot seed, or persistence backend. A
future unified settings backend can replace the registered defaults without changing the opcode/host seam.
### Scene-entry state snapshot — auto-seeding single-scene runs (2026-07-09)
**Problem the oracle surfaced:** single-scene VM runs diverge from the engine because they lack the

View File

@@ -162,6 +162,16 @@
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x1bd_play_history_voice@0x420920 stops/replaces the active voice and calls voice_play_indexed_asset@0x488330 with operand 1 plus playback variant 1 when Skip is inactive (or queues that pair while Skip is active). When text-history recording is enabled it appends voice pair {id,1}; HISTORY.BIN suppresses recording with op 0x1bb while its UI is active, preventing replay clicks from recursively entering the backlog. The handler also sets adv_auto_voice_pending when playback exists. voice_play_indexed_asset retains the variant in sound-buffer state before starting channel 12; its exact audible meaning remains unproven.
### 0x1cf `set-voice-bgm-duck-control` (set-voice-bgm-duck-control, argc 1)
- **summary:** (flags) - replace the transient voice/BGM-duck control mask. Bit 0 suppresses automatic BGM attenuation when voice playback starts; zero permits the configured duck behavior.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x1cf_set_voice_bgm_duck_control@0x4209f0 fetches operand 1 and writes it verbatim to ctx+0x6dbf0. The voice-start helper voice_bgm_duck_begin@0x406de0 tests bit 0 before arming BGM attenuation; native settings defaults register MusicFadeOnVoicePlaying=1 and MusicFadeOnVoicePlayingVol=50. SC0000 uses values 0 and 1.
### 0x2bf `schedule-sfx-start` (schedule-sfx-start, argc 3)
- **summary:** (channel)(start_mode)(delay_ms) - schedule the already-loaded SFX channel to enter the ordinary start worker after the requested delay. Channels are limited to 0..9.
- **grounding:** source=frida, confidence=high
- **evidence:** Ghidra /v2: op_0x2bf_schedule_sfx_start@0x425240 calls sfx_set_delay@0x482720 on the ctx+0x14024 sound facade. The worker's native error text names Function:SetDelay, validates channel<=9, and stores active/progress/delay/start-mode state. Existing SC0000 trace: SetDelay(channel 4, mode 0, 100) is followed about 109 ms later by the ordinary sfx_channel_start worker on channel 4 with mode 0 and no intervening 0xb5.
## compute
### 0x61 `lookup-array` (lookup-array, argc 3)
@@ -1078,10 +1088,6 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it "
- **summary:** —
- **grounding:** source=kelebek, confidence=med
### 0x1cf `u0041DA10` (u0041DA10, argc 1)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x1f6 `u00416170` (u00416170, argc 0)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
@@ -1167,10 +1173,6 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it "
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x2bf `u00423180` (u00423180, argc 3)
- **summary:** —
- **grounding:** source=kelebek, confidence=low
### 0x2c0 `u004231C0` (u004231C0, argc 3)
- **summary:** —
- **grounding:** source=kelebek, confidence=low

View File

@@ -1952,3 +1952,28 @@ decision.
Validation: all 206 engine tests pass, opcode and EngineCtx tests/lints are clean, vm0 RECOVER passes, the
Godot build has zero warnings, and threaded `SELFTEST OK`.
### SC0000 deferred SFX start and voice/BGM duck control implemented (2026-07-20)
Native RE and the saved SFX trace resolve two high-frequency audio gaps. Opcode `0x2bf` is the sound
facade's `SetDelay(channel,start_mode,delay_ms)`: SC0000's first `(4,0,100)` call starts the preloaded
secondary E0808 channel about 109 ms later through the same worker used by `0xb5`. The port now schedules
that start against the presentation clock and attaches it to a per-channel generation, so a later load or
release cancels an obsolete callback instead of starting replacement audio.
Opcode `0x1cf` replaces a transient voice/BGM-duck control mask; bit 0 suppresses automatic ducking.
The native engine registers ducking enabled with a 50-percent target, and its voice-start helper saves the
current BGM level before applying that target. Godot now follows that contract and restores the saved level
when voice playback completes or message Skip stops it. This is runtime audio state only: no profile/save
field, boot seed, or storage-backend decision was introduced.
The canonical opcode and EngineCtx sources are regenerated, the expanded 65-field `EngineCtx` is applied,
and the `/v2` handlers/workers are named, commented, and saved. SC0000 is now 119/129 distinct opcodes and
16,238/16,257 instructions handled or safe-noop (92.2% distinct; 99.9% instruction-weighted); its ten
remaining effectful gaps total 19 instructions.
**Next:** investigate the six-call `0x1ad` cluster first. It is now the largest remaining SC0000 gap and
should be classified before choosing between it and the smaller two-call support pairs.
Validation: all 206 engine tests pass; opcode and EngineCtx lints, vm0 RECOVER, the zero-warning Godot
build, and threaded `SELFTEST OK` are clean.