From f03c9481bfeebc32b9d8a465ff49bce2bb0866f9 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 28 Jul 2026 23:55:14 -0400 Subject: [PATCH] Map cyclic scale and configuration audio opcodes --- docs/engine-re.md | 34 ++++++++++++++ docs/opcode-reference.md | 45 ++++++++++-------- docs/phase-b-framework.md | 17 +++++-- vm-map/opcodes.toml | 96 +++++++++++++++++++-------------------- 4 files changed, 120 insertions(+), 72 deletions(-) diff --git a/docs/engine-re.md b/docs/engine-re.md index 7546a81..5916645 100644 --- a/docs/engine-re.md +++ b/docs/engine-re.md @@ -1337,6 +1337,20 @@ annotated in Ghidra, saved. | `0x228` | `gfx_op_0x228_query_position` → `gfx_object_query_translation_target` (`0x47cdd0`) | **query** the decomposed target-translation matrix (x,y,z), `obj+0x1ac/+0x1b0/+0x1b4`, → operand slots 3/4/5; success is 0 and missing is 1 | | `0x23f` | `op_0x23f_query_surface_stop_time_ms` (`0x42a520`) | **query loaded-surface DirectShow stop position in integer milliseconds** → operand slot 1, or -1 for an empty movie surface. Implemented with an explicit warning/-1 safety path when host timing metadata is unavailable. | +**Opcode-hardening follow-up (2026-07-28):** `0x233` is the missing cyclic-scale sibling between +`0x232` color and `0x234` rotation. `op_0x233_set_scale_cycle@0x423cf0` accepts +`(handle, period_ms, scale_x_percent, scale_y_percent, scale_z_percent)`, divides the three scale operands +by 100, and calls `gfx_object_set_scale_cycle@0x47efd0`. The worker clears start timestamp `obj+0x210`, +stores period `obj+0x224`, builds the target scale matrix at `obj+0x250`, and raises retained redraw dirty. + +`gfx_object_anim_interpolate@0x473ed0` samples +`phase = 2 * min(elapsed % period, period - elapsed % period) / period` and linearly blends the identity +matrix to the target. The first half-cycle therefore grows/shrinks identity to target and the second returns +target to identity. This matrix lives in the separately anchored cyclic-animation product, independent of +the immediate/current and delayed one-shot scale matrices. Himegari has 31 sites in nine scripts: nineteen +in DEBUGADV and twelve across eight ordinary ADV scenes. The port still skips the opcode; its existing +cyclic-channel sampling and affine compositor make this the smallest high-reach next implementation. + **Follow-up resolution (2026-07-10):** `0x21f` is the one-shot axis-angle channel and is implemented with affine rasterization. `0x223` is **not affine**: `gfx_queue_surface_alpha_transition` (`0x47f440`) inserts a type-0 command-map record keyed by arg 1: start `+4`, delay/duration `+8/+0xc`, target surface slot `+0x10`, @@ -2777,6 +2791,26 @@ sound. The port now forwards the native start mode through its host seam and set Godot's WAV loop mode before playback; `0xb5` explicitly supplies mode 0 and `0xba` supplies mode 1. The focused VM regression covers both modes, and the renamed/commented `/v2` handler is saved. +**CONFIG mixer ABI reconnaissance (2026-07-28).** Four remaining opcodes form one internally dependent +settings surface and should be implemented together: + +- `0xc5 get-audio-volume(category, out)` reads `sound:Volume0..Volume4`; selectors `0..4` mean + master/music/SFX/voice/movie and values are basis points (`0..10000`). +- `0xc6 set-audio-volume(category, basis_points)` writes the same registry cell and immediately reapplies + the live master/music/SFX/voice/movie volume. Master changes propagate through active movies and sound + buffers; category changes reach active BGM, ten SFX channels, voice channel 12, or the movie basis. +- `0x1ba set-audio-route-enabled(category, enabled)` accepts selectors `1..4` for music/SFX/voice/movie, + updates the live route, stops active playback when the route-specific worker requires it, and persists + `sound:Music/SE/Voice/Movie`. +- `0xc7 get-audio-route-enabled(category, out)` queries those four settings and writes boolean `0/1`. + +CONFIG contains all 37 sites: nine volume reads, twelve volume writes, fifteen route writes, and one +route-query loop. Skipping `0xc5` leaves its output operand stale, so the visual slider calculation can be +wrong even before the user changes anything; skipping the setters makes its controls inert. The `/v2` +handlers, route helpers, and live volume-apply workers are named/commented and saved. Port implementation +should provide one shared mixer/settings state to the VM and Godot audio buses rather than treating the four +opcodes as isolated host calls. + ### 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 diff --git a/docs/opcode-reference.md b/docs/opcode-reference.md index d2195dc..9de0ebf 100644 --- a/docs/opcode-reference.md +++ b/docs/opcode-reference.md @@ -225,6 +225,26 @@ The requested CP932 face is copied into the primary LOGFONT lfFaceName and AGE a - **grounding:** source=investigation, confidence=high - **evidence:** By-ear confirmed (2026-07-06): SC0000 prologue voices play on their lines via Godot AudioStreamPlayer. Ghidra /v2 op_0xc4_handler@0x420610 passes operand 1 unchanged to voice_play_indexed_asset@0x488330, which passes it unchanged to asset_open_indexed_entry@0x44f390. That opener directly indexes the flat base table for a zero high byte or selects mounted_aai[high_byte] and low24 for append resources; it has no executing-scene input or section-base branch. SC0010 is decisive beyond base-zero SC0000: play-voice 0x120 is raw SYS4INI entry LILA1414.OGG, also SC0010 file_number 2; applying SC0010 base 0x11e again is wrong. ROOM's 0x3365 similarly resolves directly to EUA0016.OGG. While Skip is active the handler stores the latest id/zero variant at ctx+0x6dbf4/+0x6dbf8; adv_interpreter_tick starts and clears it when Skip/read-skip input ends. Lily's lines remain correctly form-gated. +### 0xc5 `get-audio-volume` (get-audio-volume, argc 2) +- **summary:** (category)(out_basis_points) — read the configured audio volume: 0=master, 1=music, 2=SFX, 3=voice, 4=movie. Native values use 0..10000 basis points. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0xc5_get_audio_volume@0x428470 selects sound:Volume0..Volume4 for operand 1 values 0..4, queries the engine settings registry, and writes the result to operand 2. CONFIG.BIN uses the results to draw and adjust four visible category sliders; its initialization also sets Volume0..Volume3 to 10000/6500/5000/8000. + +### 0xc6 `set-audio-volume` (set-audio-volume, argc 2) +- **summary:** (category)(basis_points) — update the configured 0..10000 audio volume and apply it immediately: 0=master, 1=music, 2=SFX, 3=voice, 4=movie. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0xc6_set_audio_volume@0x4206d0 writes sound:Volume0..Volume4 through the settings registry and dispatches selectors 0..4 to the live master/music/SFX/voice/movie volume workers. The master worker reapplies dependent volume to active movies and sound buffers; category workers update active BGM, ten SFX channels, voice channel 12, or the movie basis respectively. + +### 0xc7 `get-audio-route-enabled` (get-audio-route-enabled, argc 2) +- **summary:** (category)(out_enabled) — query whether an audio route is enabled: 1=music, 2=SFX, 3=voice, 4=movie. Writes boolean 0 or 1. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0xc7_get_audio_route_enabled@0x4285a0 queries sound:Music/SE/Voice/Movie for selectors 1..4 and writes boolean 0/1 to operand 2. CONFIG.BIN's only site loops selectors 1..3 to choose enabled/disabled presentation. + +### 0x1ba `set-audio-route-enabled` (set-audio-route-enabled, argc 2) +- **summary:** (category)(enabled) — enable or disable an audio route and persist the setting: 1=music, 2=SFX, 3=voice, 4=movie. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x1ba_set_audio_route_enabled@0x420860 dispatches selectors 1..4 to the music/SFX/voice/movie enable workers. Those workers update the live route, stop active playback when disabling where applicable, write sound:Music/SE/Voice/Movie through the settings registry, and reject selectors outside 1..4. All 15 CONFIG.BIN sites pass literal 0/1. + ### 0x1bd `play-history-voice` (u0041D910, argc 1) - **summary:** Replay a voice id selected from retained ADV text history using native playback/history variant 1, preserving normal Skip and Auto-voice state behavior. - **grounding:** source=investigation, confidence=high @@ -891,6 +911,11 @@ Implemented through IHost.PlayModalMovieToSurface. Its operand uses the same nat - **grounding:** source=investigation, confidence=high - **evidence:** Ghidra /v2: gfx_op_0x232_anim_color@0x423c30 resolves sentinels then calls gfx_worker_anim_color@0x47ef50; gfx_object_anim_interpolate@0x473ed0 samples static obj+0x60 toward target obj+0x240 into a temporary packed color; gfx_object_composite@0x47f650 passes that color plus unchanged selector obj+0x30 to gfx_object_blit_d3d9@0x4774c0. Blit mode 0 leaves the default path and passes RGB as modulation; mode 1 sets SRCALPHA/ONE additive composition. gfx_object_init_default@0x472810 initializes obj+0x60=0xffffffff. SC0000 0x1a0e (handle,1200,224,-1) is therefore 0xffffffff<->0xe0ffffff with inert alpha and identity RGB in mode 0: no visible pulse. C# regressions cover exact AE001H visual invariance, negative-RGB preservation, mode-0 RGB modulation, and mode-1 additive scaling. +### 0x233 `set-scale-cycle` (set-scale-cycle, argc 5) +- **summary:** (handle)(period_ms)(scale_x_percent)(scale_y_percent)(scale_z_percent) — configure a cyclic ping-pong scale matrix that moves identity→target over the first half-period and target→identity over the second. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x233_set_scale_cycle@0x423cf0 divides operands 3..5 by 100 and calls gfx_object_set_scale_cycle@0x47efd0. The worker stores start=0 at obj+0x210, period at +0x224, and the target scale matrix at +0x250. gfx_object_anim_interpolate@0x473ed0 samples a triangular phase 2*min(elapsed%period,period-elapsed%period)/period, linearly blends identity to target, and multiplies the result into the separately anchored cyclic-animation product. Corpus: 31 sites in nine scripts, including eight ordinary ADV scenes. + ### 0x234 `anim-start` (anim-start, argc 5) - **summary:** (handle)(period_ms)(axis_x)(axis_y)(axis_z) — configure cyclic rotation. Worker stores period obj+0x228, start obj+0x214=0, and float axis obj+0x244; each frame uses integer degrees floor(((now-start)%period)*360/period). gfx_object_composite right-multiplies this separately anchored transform after the one-shot scale/rotation/translation product, so cyclic rotation also rotates the translation vector. - **grounding:** source=investigation, confidence=high @@ -1695,18 +1720,6 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting - **grounding:** source=kelebek, confidence=low - **evidence:** Not observed in Himegari's script corpus; ABI label/argc come from Kelebek's AGE table. -### 0xc5 `u0041D4A0` (u0041D4A0, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - -### 0xc6 `u0041D5D0` (u0041D5D0, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - -### 0xc7 `u0041D760` (u0041D760, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0xc9 `u00415770` (u00415770, argc 0) - **summary:** Broader AGE-catalog compatibility stub; the port currently traces and skips it. - **grounding:** source=kelebek, confidence=low @@ -1945,10 +1958,6 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting - **summary:** — - **grounding:** source=kelebek, confidence=low -### 0x1ba `u0041D850` (u0041D850, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x1be `u0041D9D0` (u0041D9D0, argc 2) - **summary:** Broader AGE-catalog compatibility stub; the port currently traces and skips it. - **grounding:** source=kelebek, confidence=low @@ -2088,10 +2097,6 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting - **summary:** — - **grounding:** source=kelebek, confidence=low -### 0x233 `u00421FB0` (u00421FB0, argc 5) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x235 `u00422100` (u00422100, argc 5) - **summary:** Broader AGE-catalog compatibility stub; the port currently traces and skips it. - **grounding:** source=kelebek, confidence=low diff --git a/docs/phase-b-framework.md b/docs/phase-b-framework.md index 59b2566..28b3157 100644 --- a/docs/phase-b-framework.md +++ b/docs/phase-b-framework.md @@ -803,10 +803,19 @@ inventory has only 25 distinct effectful gaps. The first tranche closes the two Together these handlers close 521 formerly skipped instructions and reduce the corpus-wide effectful fallthrough inventory from 25 to 23 distinct opcodes. Focused regressions distinguish one-shot/looping SFX starts and prove immediate rotation does not arm the delayed `0x21f` channel. The opcode registry and -generated references are current, and the native `/v2` image is annotated and saved. **NEXT OPCODE STEP:** -rank the remaining 23 by systemic state risk, beginning with the 31-site retained-animation opcode `0x233` -and the CONFIG audio/settings cluster (`0xc5`, `0xc6`, `0x1ba`), rather than treating low-frequency -developer-only calls as equally urgent. +generated references are current, and the native `/v2` image is annotated and saved. + +**Next-opcode reconnaissance:** `0x233` is the recommended next implementation. Native proves a +self-contained cyclic scale channel: identity→target over the first half-period and target→identity over +the second. Its 31 calls cover eight ordinary ADV scenes plus DEBUGADV, and the retained model already has +the sibling color/rotation cycles and affine sampling machinery. It therefore has broader runtime reach and +lower ownership risk than the next alternative. + +After `0x233`, implement CONFIG's mixer ABI as one four-opcode tranche rather than piecemeal: +`0xc5` gets master/music/SFX/voice/movie volume, `0xc6` sets and applies it, `0x1ba` enables/disables the +four non-master routes, and `0xc7` queries route state. Those 37 CONFIG sites require shared persistent +settings plus live Godot-bus application; implementing only a setter or getter would leave the menu +internally inconsistent. Low-frequency developer-only calls remain behind these two tranches. ## Later Phase B breadth diff --git a/vm-map/opcodes.toml b/vm-map/opcodes.toml index f49edd1..9dd522c 100644 --- a/vm-map/opcodes.toml +++ b/vm-map/opcodes.toml @@ -1970,80 +1970,80 @@ observed_types = ["imm", "l-int", "l-ptr"] [[opcode]] op = 0xc5 -label = "u0041D4A0" +label = "get-audio-volume" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041D4A0" -category = "unknown" -summary = "" +name = "get-audio-volume" +category = "audio" +summary = "(category)(out_basis_points) — read the configured audio volume: 0=master, 1=music, 2=SFX, 3=voice, 4=movie. Native values use 0..10000 basis points." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0xc5_get_audio_volume@0x428470 selects sound:Volume0..Volume4 for operand 1 values 0..4, queries the engine settings registry, and writes the result to operand 2. CONFIG.BIN uses the results to draw and adjust four visible category sliders; its initialization also sets Volume0..Volume3 to 10000/6500/5000/8000." [[opcode.semantics.args]] i = 1 -role = "" +role = "category: 0=master, 1=music, 2=SFX, 3=voice, 4=movie" observed_types = ["imm", "l-int"] [[opcode.semantics.args]] i = 2 -role = "" +role = "out volume basis points" observed_types = ["l-int"] [[opcode]] op = 0xc6 -label = "u0041D5D0" +label = "set-audio-volume" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041D5D0" -category = "unknown" -summary = "" +name = "set-audio-volume" +category = "audio" +summary = "(category)(basis_points) — update the configured 0..10000 audio volume and apply it immediately: 0=master, 1=music, 2=SFX, 3=voice, 4=movie." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0xc6_set_audio_volume@0x4206d0 writes sound:Volume0..Volume4 through the settings registry and dispatches selectors 0..4 to the live master/music/SFX/voice/movie volume workers. The master worker reapplies dependent volume to active movies and sound buffers; category workers update active BGM, ten SFX channels, voice channel 12, or the movie basis respectively." [[opcode.semantics.args]] i = 1 -role = "" +role = "category: 0=master, 1=music, 2=SFX, 3=voice, 4=movie" observed_types = ["imm"] [[opcode.semantics.args]] i = 2 -role = "" +role = "volume basis points" observed_types = ["imm", "l-int"] [[opcode]] op = 0xc7 -label = "u0041D760" +label = "get-audio-route-enabled" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041D760" -category = "unknown" -summary = "" +name = "get-audio-route-enabled" +category = "audio" +summary = "(category)(out_enabled) — query whether an audio route is enabled: 1=music, 2=SFX, 3=voice, 4=movie. Writes boolean 0 or 1." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0xc7_get_audio_route_enabled@0x4285a0 queries sound:Music/SE/Voice/Movie for selectors 1..4 and writes boolean 0/1 to operand 2. CONFIG.BIN's only site loops selectors 1..3 to choose enabled/disabled presentation." [[opcode.semantics.args]] i = 1 -role = "" +role = "category: 1=music, 2=SFX, 3=voice, 4=movie" observed_types = ["l-int"] [[opcode.semantics.args]] i = 2 -role = "" +role = "out enabled" observed_types = ["l-int"] [[opcode]] @@ -4062,28 +4062,28 @@ observed_types = ["imm", "l-int"] [[opcode]] op = 0x1ba -label = "u0041D850" +label = "set-audio-route-enabled" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041D850" -category = "unknown" -summary = "" +name = "set-audio-route-enabled" +category = "audio" +summary = "(category)(enabled) — enable or disable an audio route and persist the setting: 1=music, 2=SFX, 3=voice, 4=movie." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x1ba_set_audio_route_enabled@0x420860 dispatches selectors 1..4 to the music/SFX/voice/movie enable workers. Those workers update the live route, stop active playback when disabling where applicable, write sound:Music/SE/Voice/Movie through the settings registry, and reject selectors outside 1..4. All 15 CONFIG.BIN sites pass literal 0/1." [[opcode.semantics.args]] i = 1 -role = "" +role = "category: 1=music, 2=SFX, 3=voice, 4=movie" observed_types = ["imm"] [[opcode.semantics.args]] i = 2 -role = "" +role = "enabled" observed_types = ["imm"] [[opcode]] @@ -6156,43 +6156,43 @@ observed_types = ["imm", "l-int"] [[opcode]] op = 0x233 -label = "u00421FB0" +label = "set-scale-cycle" argc = 5 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u00421FB0" -category = "unknown" -summary = "" +name = "set-scale-cycle" +category = "draw" +summary = "(handle)(period_ms)(scale_x_percent)(scale_y_percent)(scale_z_percent) — configure a cyclic ping-pong scale matrix that moves identity→target over the first half-period and target→identity over the second." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x233_set_scale_cycle@0x423cf0 divides operands 3..5 by 100 and calls gfx_object_set_scale_cycle@0x47efd0. The worker stores start=0 at obj+0x210, period at +0x224, and the target scale matrix at +0x250. gfx_object_anim_interpolate@0x473ed0 samples a triangular phase 2*min(elapsed%period,period-elapsed%period)/period, linearly blends identity to target, and multiplies the result into the separately anchored cyclic-animation product. Corpus: 31 sites in nine scripts, including eight ordinary ADV scenes." [[opcode.semantics.args]] i = 1 -role = "" +role = "retained object handle" observed_types = ["g-int"] [[opcode.semantics.args]] i = 2 -role = "" +role = "period milliseconds" observed_types = ["imm"] [[opcode.semantics.args]] i = 3 -role = "" +role = "target scale X percent" observed_types = ["l-int"] [[opcode.semantics.args]] i = 4 -role = "" +role = "target scale Y percent" observed_types = ["l-int"] [[opcode.semantics.args]] i = 5 -role = "" +role = "target scale Z percent" observed_types = ["imm"] [[opcode]]