Match AGE RIFF decode boundaries
This commit is contained in:
@@ -173,8 +173,11 @@ Godot's WAV loader adds one host-specific compatibility boundary. SC0000 `0xc29`
|
||||
the EV052DA glow. Its PCM is valid, but its trailing RIFF `LIST/INFO` fields contain Japanese CP932 text
|
||||
(`IPRD`, `IGNR`, and `ICMT`). Godot assumes INFO text is UTF-8 and formerly emitted one Unicode warning per
|
||||
invalid CP932 byte each time the sound was loaded; the duplicated SC0000 burst came from loading raw id
|
||||
`0x28` on two channels. A complete extracted-corpus scan found 238 RIFF/WAVE files, 61 INFO chunks, no other
|
||||
LIST type, and no invalid RIFF containers, explaining the same warnings around combat SFX.
|
||||
`0x28` on two channels. A complete extracted-corpus scan found 238 RIFF/WAVE files, 61 INFO chunks, and no
|
||||
other LIST type, explaining the same warnings around combat SFX. A later declared-extent audit corrected the
|
||||
earlier claim that every container ended exactly at its RIFF boundary: seven entries have trailing bytes.
|
||||
Six have small 74- or 234-byte tails; `A1215.WAV` is a 323,009-byte concatenation whose first RIFF ends at
|
||||
157,940, followed by a multipart-upload header and a second complete RIFF.
|
||||
|
||||
The correction is deliberately confined to the Godot frontend. `RiffWaveSanitizer` removes only `LIST` chunks
|
||||
whose form type is `INFO` from the transient byte array passed to `AudioStreamWav.LoadFromBuffer`, updates the
|
||||
@@ -184,6 +187,17 @@ byte-for-byte, including padding. The shared engine, original loose/archive payl
|
||||
bytes while retaining an identical PCM data chunk and loads headlessly without a Unicode warning. Synthetic
|
||||
chunk/padding tests and the installed E0808 regression cover the adapter.
|
||||
|
||||
`A1215.WAV` exposes a separate compatibility boundary. Native AGE's
|
||||
`wav_decoder_open@0x488790` uses WinMM `mmioDescend` to enter the first `RIFF/WAVE`, finds its first `data`
|
||||
child, and streams exactly that child's declared `cksize`; appended bytes are never decoded. Godot's
|
||||
whole-buffer importer instead walks beyond the first RIFF and interprets the multipart header's `Cont` as a
|
||||
chunk id, causing repeated out-of-range seeks. `RiffWaveSanitizer.PrepareForGodot` now validates the first
|
||||
RIFF and all of its declared children, removes any INFO chunks, and returns a transient buffer ending exactly
|
||||
at that RIFF's declared extent. It returns the original array without rewriting when the RIFF is invalid and
|
||||
preserves the packed/original bytes in every case. The installed A1215 regression proves 323,009 source bytes
|
||||
become the exact 157,940-byte first RIFF with the 157,896-byte PCM `data` chunk unchanged; the Godot selftest
|
||||
loads that result through `AudioStreamWav.LoadFromBuffer` without seek errors.
|
||||
|
||||
## Runtime asset-VFS track (VFS-A/B/C complete 2026-07-11)
|
||||
|
||||
The pre-extracted tree and `build/textures/*.BMP` pipeline were a Phase-A bootstrap, not the desired final
|
||||
|
||||
@@ -2987,6 +2987,23 @@ attenuation `-2377` to both channel loads, and the shared audio service later re
|
||||
five handlers. The bounded port does not yet import native audio preferences, so its extracted-WAV bootstrap
|
||||
uses unity gain and centered pan rather than hard-coding the captured user's setting.
|
||||
|
||||
**Native RIFF boundary contract (2026-07-29).** The RIFF branch constructs
|
||||
`wav_decoder_ctor@0x4885b0`; `wav_decoder_open@0x488790` then opens the packed-entry handle with WinMM
|
||||
`mmioOpenA`, descends into the first `RIFF/WAVE` with `MMIO_FINDRIFF`, and searches that RIFF's children for
|
||||
`fmt ` and `data` with `MMIO_FINDCHUNK`. At `0x488976`, the returned `data` `MMCKINFO.cksize` is copied into
|
||||
both remaining-byte and total-byte fields. `wav_decoder_fill_buffer@0x4889b0` reads
|
||||
`min(remaining_bytes, 0x2000)`, decrements that counter, and closes the decoder when it reaches zero.
|
||||
Native playback therefore cannot consume bytes after the first `data` chunk's declared extent.
|
||||
|
||||
This normal RIFF behavior, rather than an asset-specific exception, makes malformed `A1215.WAV` playable.
|
||||
Its packed entry is 323,009 bytes, but the first RIFF declares an extent of 157,940 bytes and its first
|
||||
`data` chunk is 157,896 bytes. A multipart-upload header and a complete second RIFF begin after that declared
|
||||
boundary; AGE ignores both. The whole-buffer Godot importer instead sees the appended `Cont` bytes as another
|
||||
RIFF chunk and repeatedly seeks beyond the buffer. The port now reproduces the native boundary in its
|
||||
transient Godot decoder copy, leaving the archive/VFS payload untouched. The saved `/v2` image names/comments
|
||||
the WAV decoder constructor, open/close, buffer-fill, rewind, and query methods, and records this boundary
|
||||
contract on the dispatcher and parser.
|
||||
|
||||
The native trace in `build/native-sfx-trace.jsonl` captures SC0000's first pair: `0xb4@0xc29` resolves
|
||||
raw catalog id `0x28` to `E0808.WAV`, loads channel 0, and `0xb5@0xc2e` starts it in the same millisecond. The next
|
||||
`0xb4@0xc31` preloads the same WAV into engine-owned secondary channel 4 for a later service start. The
|
||||
|
||||
@@ -910,6 +910,24 @@ again when FIELD resumes, while SC0600 retains its ordinary ADV marker and input
|
||||
parent-callback → child-dialogue regression preserves both halves of that lifetime. The engine suite passes
|
||||
526/526, the Godot build has zero warnings, and the Himegari-targeted threaded selftest passes.
|
||||
|
||||
**Post-boss `A1215.WAV` lockup corrected (2026-07-29):** after the first boss,
|
||||
SC0010 executes `play-sound-effect 0x125` at `0xea1`. The packed `DATA1/A1215.WAV` entry is 323,009 bytes:
|
||||
a valid 157,940-byte RIFF, then a multipart-upload header, then a second valid RIFF. Godot's whole-buffer WAV
|
||||
importer walks into the appended header, treats `Cont` as a chunk id with an impossible size, and loops through
|
||||
`FileAccessMemory.seek` errors on the main thread.
|
||||
|
||||
AGE does not special-case this resource. Its WinMM decoder (`wav_decoder_open@0x488790`) descends into the
|
||||
first `RIFF/WAVE`, finds `fmt ` and `data` within that parent, saves the first `data` chunk's declared size,
|
||||
and `wav_decoder_fill_buffer@0x4889b0` stops after exactly that many bytes. The appended upload material and
|
||||
second RIFF are unreachable.
|
||||
|
||||
`RiffWaveSanitizer.PrepareForGodot` now applies that first-RIFF boundary to the transient decoder buffer after
|
||||
validating every declared child, then performs the existing INFO-metadata removal within that boundary.
|
||||
Archive/VFS bytes remain pristine, and invalid RIFFs are returned unchanged. Synthetic concatenation and
|
||||
installed-A1215 regressions prove the exact prefix and unchanged PCM payload. Validation passes 528/528 engine
|
||||
tests, a zero-warning Godot build, and the Himegari-targeted threaded selftest, which loads the real A1215
|
||||
buffer through Godot and reports `first-riff-boundary=ok` without seek spam.
|
||||
|
||||
**Cyclic reset implemented (2026-07-29):** `0x230(handle)` now gets or creates the retained object,
|
||||
disables the four looping channels represented by the compositor, and clears the complete native
|
||||
start/period block—including the preserved raw state for the currently unmodeled second cyclic matrix.
|
||||
|
||||
Reference in New Issue
Block a user