Migrate audio payloads onto asset VFS

This commit is contained in:
gamer147
2026-07-11 11:01:11 -04:00
parent d6da43c818
commit 0d3078de03
9 changed files with 132 additions and 64 deletions

View File

@@ -114,10 +114,9 @@ highest-risk area of the port. This doc is the steering state; it feeds the A2b
verified SO001 is 800×300, 32-bpp, with substantial per-pixel alpha; the current rasterizer already
consumes source alpha. The missing prerequisites are system-asset resolution/conversion and retained
slot initialization, not new textbox drawing or button interaction.
4. **Audio.** **✅ WIRED (2026-07-06) — no Frida needed.** Same rule as textures:
`play-bgm(id)`/`play-voice(id)``files[section_base(scene)+id]` → OGG. `IHost.PlayBgm/PlayVoice` +
VM dispatch (`play-bgm` 0xbf / `play-voice` 0xc4, both argc 1); `ResourceMap.AudioPath` → loose
`extracted/DATA{n}/{name}.OGG`; `GodotAdvHost``Main`'s two `AudioStreamPlayer` nodes
4. **Audio.** **✅ WIRED (2026-07-06), VFS bytes complete (2026-07-11).** `IHost.PlayBgm/PlayVoice` +
VM dispatch (`play-bgm` 0xbf / `play-voice` 0xc4, both argc 1); `ResourceMap.ReadAudio` opens the
resolved catalog entry through `IAssetStore`; `GodotAdvHost` passes the bytes to `Main`'s players
(`AudioStreamOggVorbis.LoadFromBuffer`; BGM loops, voice interrupt-on-new). Non-Godot hosts no-op it
`--selftest`/8-8 byte-identical. SC0000 fires 18 BGM + 198 voice. **By-ear VALIDATED
(2026-07-06):** voices play on their lines (`play-voice` med→HIGH). **BUT the two audio ops use DIFFERENT
@@ -128,12 +127,12 @@ highest-risk area of the port. This doc is the steering state; it feeds the A2b
- **BGM** (`play-bgm`) → **DIRECT LITERAL NAME**, `id → BGM{id:03d}.OGG` (DATA3), NOT the manifest.
Confirmed by ear (`play-bgm 5→BGM005`, `8→BGM008`; the manifest gave BGM006/009 = off-by-one) and proven
by `play-bgm 0x23→BGM035.OGG` — a real standalone track (BGM set skips 030-034) the manifest mis-resolved
to a graphics entry. Implemented as `ResourceMap.BgmPathById(id)`; `GodotAdvHost.PlayBgm` uses it.
to a graphics entry. Implemented as `ResourceMap.ResolveBgm(id)`; `GodotAdvHost.PlayBgm` uses it.
The prior "Frida-confirmed play-bgm 5→BGM006" record was a mis-attribution.
Lily silent = correct (form-gated on `G[0xa57/0xa58/0xa59]`, unseeded). `play-sound-effect` (0xb4, argc 2)
left stubbed — arg roles unconfirmed. See `docs/phase-a-slice-plan.md` (A2b-Audio). Diagnostic: `Age.Cli
audio <SCENE>`.
Lily silent = correct (form-gated on `G[0xa57/0xa58/0xa59]`, unseeded). Scene-local SFX WAV entries use
the same byte path while retaining the existing channel lifecycle. See `docs/phase-a-slice-plan.md`.
Diagnostic: `Age.Cli audio <SCENE>`.
5. **Movies** (`OP`/`MVB`, MPEG) — a separate video-playback path; deferred.
## Validation reality (why this is the big haul)
@@ -162,9 +161,9 @@ The matching native trace at SC0000 `0xc29` captures resource `0x28`, channel 0;
`DATA1/E0808.WAV`, and the port trace resolves the same file. The following `0xc31` preload uses the same
resource on native secondary channel 4. `play-bgm` remains the separate direct-name exception.
The current Phase-A backend deliberately continues through the extracted-file bootstrap: `ResourceMap.AudioPath`
accepts both OGG and WAV and Godot loads the WAV bytes into its fixed SC0000 channel pool. This does not change
the scoped VFS plan below: ALF/AAI mounting and in-process asset reads remain a separate foundation track.
The Phase-A backend now resolves the OGG/WAV catalog entry and opens it through `IAssetStore`; Godot decodes
the returned bytes into its existing BGM, voice, and fixed SC0000 SFX channel players. The earlier
`ResourceMap.AudioPath` extracted-file bootstrap is retired.
## Runtime asset-VFS track (VFS-A/B/C complete 2026-07-11)
@@ -213,11 +212,12 @@ store.
on-disk `BinExtractALF.exe` are validation references; the Kelebek repository exposes no clear license,
so its code should not be copied without clarification. The focused `LzssDecoder` is shared with
`Sys4AssetCatalog`; raw and compressed information/pixel/ACIF sections use the same bounded primitive.
4. **Runtime consumers.** Script and texture loading are complete. `ResourceMap.ResolveTexture` preserves
4. **Runtime consumers (complete for scripts, textures, and audio).** `ResourceMap.ResolveTexture` preserves
scene-local resolution and falls back to universal raw ids for SYSTEM4 assets; `GodotAdvHost` caches
decoded RGBA surfaces by catalog identity and supplies synchronous dimensions to opcode `0x208`.
Godot no longer reads `build/textures/*.BMP`. OGG/WAV byte migration remains a separate follow-up;
retain extraction/conversion tools as diagnostics until parity is established.
Godot no longer reads `build/textures/*.BMP`. BGM direct-name entries plus scene-local voice/SFX entries
are opened through the same `IAssetStore`; Godot decodes their OGG/WAV bytes in process. Extraction and
conversion tools remain diagnostics.
### Acceptance gates
@@ -250,8 +250,14 @@ Five installed assets spanning raw/compressed metadata and pixel/alpha combinati
`AGF2BMP2AGF` BMP oracle pixel-for-pixel. SO001 resolves through universal raw id `0x337e`, decodes to
800×300 with intermediate alpha values, and is inherited in surface slot 17 before SC0000. A windowed
page-1 capture with `build/textures/` moved aside showed the translucent textbox edge and bottom-right
controls. Texture runtime no longer consults `extracted/` or `build/textures/`; current audio consumers
still use extracted OGG/WAV paths by design. Audio migration and movie `0x236` remain unimplemented.
controls. Texture runtime no longer consults `extracted/` or `build/textures/`.
Audio byte migration passes its bounded gate in `Sys4AssetStoreTests`: archive-only SC0000 reads identify
`BGM005.OGG` and `MAN999.OGG` as Ogg streams and `E0808.WAV` as RIFF/WAVE without consulting `extracted/`.
A windowed SC0000 run with `extracted/` moved aside crossed both voice sites and the first SFX sequence,
recording BGM005 plus `E0808.WAV` load/start/preload on channels 0/0/4 with no Godot OGG/WAV decode errors.
Channel, loop, interruption, timing, fade, load/start, and release behavior is unchanged. Movie `0x236`
remains unimplemented.
VFS-B passes its bounded gates in `Sys4AssetStoreTests`: the installed AAI expands from the LZSS stream at
`0x118` (expanded size at `0x110`, packed size at `0x114`) to one `APPEND01.ALF` archive and 81 80-byte
@@ -264,7 +270,7 @@ through `0x01xxxxxx`; direct base-name lookup deliberately does not see append r
### Deliberate non-goals
- Writing/repacking ALF or AAI; loose overrides already provide the native mod/translation workflow.
- AGF encoding, movie/video decoding for MPEG-like `OP/MVB*.AGF`, or SFX channel semantics.
- AGF encoding or movie/video decoding for MPEG-like `OP/MVB*.AGF`.
- A generalized multi-mod dependency manager. Start with native game-root loose overrides; configurable
ordered mod roots can be layered onto the same store later.
- Removing the extraction/conversion tools immediately. They remain independent parity oracles until the

View File

@@ -1169,7 +1169,7 @@ the same `(load ch0, start ch0, preload ch4)` sequence in one frame. Startup `0x
`0xc2(target,duration)` is the adjacent blocking BGM fade: 100 linear steps for durations at least 1000 ms,
10 steps below that, with target zero releasing the source. `0xd9` only clears native service bit `0x1000`;
the isolated VM recognizes it with no host-visible effect. The Godot backend retains ten `AudioStreamPlayer`
channels, loads the existing extracted WAV bytes, separates load from start, releases buffers deterministically,
channels, loads catalog-resolved WAV bytes, separates load from start, releases buffers deterministically,
and parks BGM fades on the unified virtual clock. ALF/AAI/AGF VFS work and movie `0x236` remain separate.
Ghidra `/v2` now names/comments all five handlers plus the asset-open, decode, DirectSound start/refill/stop/
@@ -1186,3 +1186,20 @@ leaving 37 GAP ops / 101 GAP instructions. Headless sequence capture still emits
**Manual validation:** the normal-speed windowed port advanced through `wait-for-input@0x1a58` at 45.6 s,
well past the first effects and dialogue pages, with no audio-related stall. The user confirmed the effects
were audible and sounded good.
### Audio-byte migration onto the asset VFS DONE (2026-07-11)
`ResourceMap.BgmPathById` and `ResourceMap.AudioPath` are removed. BGM direct-name resolution now returns a
catalog entry, while voice and SFX retain their scene-local resolution; all three flow through
`ResourceMap.ReadAudio` and the injected `IAssetStore`. `GodotAdvHost` passes owned byte arrays across the
existing deferred main-thread boundary, and `Main` decodes them with
`AudioStreamOggVorbis.LoadFromBuffer` / `AudioStreamWav.LoadFromBuffer`. The existing BGM loop and fade,
voice interruption, ten-channel SFX load/start/release, clock pacing, and gain behavior are unchanged.
Validation: engine **128/128** and both .NET/Godot builds pass. An archive-only SC0000 regression verifies
the Ogg signatures for `BGM005.OGG` and `MAN999.OGG` plus the RIFF/WAVE signature for `E0808.WAV`. With
`extracted/` physically moved aside, the CLI completed SC0000's 127-op audio trace and a windowed Godot run
crossed voice at `0xa0a`/`0xbf6`, BGM005 at `0x7fa`, and the established SFX load/start/preload sequence at
`0xc29`/`0xc2e`/`0xc31` on channels 0/0/4. Godot reported no OGG/WAV read, decode, or deferred-call errors;
`extracted/` was restored afterward. Movie `0x236`, AGF work, opcode semantics, and unrelated VM behavior
were untouched.

View File

@@ -72,7 +72,9 @@ Three layers, cleanly separated:
- **Native content foundation status (2026-07-11).** VFS-A runtime-parses the base SYS4 catalog and
applies loose-first bounded ALF reads; VFS-B mounts selector-keyed S4AC append catalogs and resolves the
native high-byte/low-24-bit id split; VFS-C decodes AGF directly to platform-neutral RGBA8 and feeds Godot
without pre-extracted or pre-converted texture files. Audio-byte consumers remain a separate follow-up.
without pre-extracted or pre-converted texture files. BGM, voice, and SFX now open catalog entries through
the same store and decode OGG/WAV from bytes in Godot, so runtime scripts, textures, and audio no longer
depend on `extracted/`.
- **Profile = manifest.** Adding a game = a new profile + its maps. Adding an engine version = a new
front-end plugin + profiles that reference it. See §5.

View File

@@ -87,7 +87,7 @@ root/call-script parsing). Generated asset/callscript JSON remains a tooling and
| `run <file.BIN>` | Execute a script; print steps, show-text count, **call-script dispatch count**, the first 30 lines (each tagged with its source script), and the distinct source scripts. | `CaptureHost` (headless); **executes call-script**. |
| `trace <out.json>` | Trace every SC/SP scene → offsets + halt + steps. **Provider-less** (call-script stubbed) = a base-ISA offset dump. | writes JSON. (Was the vm0 differential oracle; vm0 is retired from oracle duty — `TraceDiffTests` removed.) |
| `trace <SCENE.BIN> [--boot] [--state <f>] [0xADDR=VAL…] --trace-json <out>` | ★ Emit the **full per-op executed-offset path** of one scene (not just show-text), filtered to the scene's own frame — the VM side of the differential offset-path oracle (`diff_optrace.py`). `--boot` runs the SYSTEM4 state prefix; **`--state <f>` loads a captured scene-entry snapshot** (`capture_global_writes.py`) = the engine's real pre-scene state; `0xADDR=VAL` hand-seeds. | `JsonOffsetTraceSink` (observe-only, parity held) → `{scene, offsets:[…]}` JSON. |
| `audio <SCENE.BIN> [0xADDR=VAL…]` | Dump executed `play-bgm`/`play-voice` in order + resolved file. | optional seeds. provider-less (stub) for now. |
| `audio <SCENE.BIN> [0xADDR=VAL…]` | Dump executed `play-bgm`/`play-voice` in order + resolved catalog record. | optional seeds. provider-less (stub) for now. |
| `gfx [--boot] <SCENE.BIN> [0xADDR=VAL…]` | Dump executed `set-texture`/`get-texture-size`/`draw-texture` (resolved file + computed geometry) **plus the per-object gfx slots** — the headless geometry oracle. **`--boot`** runs SYSTEM4's state prefix (`INITCONFIG/INIT2/INIT`) via `GameSession` first (so INIT2's gfx handle array is present) and runs the target with call-script on; without it, seeds-only + provider-less. | gfx ops now execute against `GfxState`. |
| `play [--boot] [--state <f>] [--save-state <f>] <SCENE.BIN…> [0xADDR=VAL…]` | ★ Cross-scene **state runner**: run a scene sequence carrying persistent globals. `--boot` first runs the 9 `*INIT` data scripts (real skill/item/unit/map/stage state). `--state`/`--save-state` load/persist a JSON snapshot. | `GameSession`; **executes call-script**. |
| `sweep [--boot] [0xADDR=VAL…]` | Corpus-scale run. **With call-script execution on: 284/297 exit, 13 STEP-LIMIT** (input/state-gated ADV scenes spin headless once subroutine global-writes drive their loops — state divergence, not a bug; 0 depth-cap/unresolved). **With seeds = a story-state explorer**: reports which scenes' dialogue changes ±seed (e.g. form flag `0xa57=1` → 34/297 scenes). | |