Implement deferred SFX and voice duck control
This commit is contained in:
@@ -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) |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user