diff --git a/authored/audio.json b/authored/audio.json index 7c480d0e..df8aca33 100644 --- a/authored/audio.json +++ b/authored/audio.json @@ -102,7 +102,7 @@ "bank": "BGM_103.slb", "loop": "restart", "kind": "measured", - "why": "MEASURED, HANDOFF Q10 -- NOT a port choice. `GamePart_Title`'s phase handler `sub_821C5580` plays cue 1103 = `BGM_103`, and `BGM_103.slb`'s two declared waves (3 876 864 / 3 930 112 B) are byte-for-byte the two streams the XMA probe saw decoding at the main menu. Static code, disc census and runtime all agree; see docs/re/menu-audio-cues.md and docs/re/structures/bgm-two-stems.md. The name carries its `.slb` extension because that is what `sound.pak` hashes -- `BGM_103` alone resolves to nothing, which is how the first draft of this file failed.", + "why": "MEASURED, HANDOFF Q10 -- NOT a port choice. `GamePart_Title`'s phase handler `sub_821C5580` plays cue 1103 = `BGM_103`, and `BGM_103.slb`'s two declared waves (3 876 864 / 3 930 112 B) are byte-for-byte the two streams the XMA probe saw decoding at the main menu. Static code, disc census and runtime all agree; see docs/re/menu-audio-cues.md and docs/re/structures/bgm-two-stems.md. The name carries its `.slb` extension because that is what `sound.pak` hashes -- `BGM_103` alone resolves to nothing, which is how the first draft of this file failed. ✅ AUDITED 2026-08-31 -- the THREE legs are three, and that is now measured rather than asserted. Prompted by the Decoder's point that a decorative second support is worse than none, since a conclusion with two supports reads as better evidenced than one and apparent redundancy is itself the misinformation. Read literally, 'disc census' and 'runtime' could be ONE comparison -- declared wave sizes matched byte-for-byte against the probe -- which would make three legs two. It is a real third leg only if the census EXCLUDES alternatives: if another bank carried the same two sizes, the byte match would not distinguish BGM_103. Measured with this port's own reader (`crates/sylpheed-export/examples/bgm_size_census.rs`): of 32 readable BGM_* banks on the disc, EXACTLY ONE carries waves of that size. The census therefore excludes, the static-code leg names the cue independently, and the three legs stand.", "loop_why": "MEASURED, and this field's own history is why it says so first. The bed loops; the loop is a RUNTIME field -- `loop_start`/`loop_end` in the XMA decoder context, set by `XMASetLoopData` and logged by Xenia -- and the cycle was watched directly: three wraps, both contexts wrapping at the same instant every time, mean 61.81 s against the 61.93 s authored in `loop_end_s`, 0.2 % apart from instruments sharing nothing. The export is TRIMMED to that window, because Godot loops a whole file and a loop region therefore has to BE the file. ⚠️ The window's START is not measured and is authored as 0, which is known to be wrong -- see `loop_start_why`. 🔴 EVERY SENTENCE THAT PRECEDED THIS ONE WAS REFUTED, and the previous text survived in the manifest for two days after the corrections were written. It said the loop would be `AUDIBLY WRONG AT THE SEAM [refuted]`, that `no loop-point field has been identified [refuted] anywhere`, and that trimming `would INVENT a loop point`. All three are false: the field exists, the 3.4 s of near-silence was the PORT'S loop and not the game's, and the trim is now what the measurement says. The corrections went into `loop_end_why` and `loop_start_why`; this field is the one the exporter concatenates into `manifest.json`, so the export went on telling readers the refuted story. A correction that does not reach the artifact a consumer reads has not been made.", "stems": "sum", "stems_why": "MEASURED, HANDOFF Q10: a bank is exactly TWO waves of identical duration (32/32 banks on the disc), sample-synchronous -- transient correlation peaks at lag 0.00 s over +/-5 s and both stop at the same millisecond. Concatenating them plays the piece twice, the second time as a bass-less stem; that was the previous reading and it is refuted. Emitting two files would be wrong for a second reason: MODDING rule 1 is one logical asset, one file, and handing a modder two stems to line up by hand is the reassembly the exporter exists to have already done. WHAT IS SUMMED IS SETTLED; WHAT WAVE 1 IS, IS NOT -- HANDOFF calls it quieter, far more L/R-decorrelated and almost bass-free, so it reads as a surround-rear pair OR a second intensity layer, and `ChannelMask` is 0x0002 on both so the file will not say. A unity sum is right under either reading; a weighting would only be justified once that is settled.", diff --git a/crates/sylpheed-export/examples/bgm_size_census.rs b/crates/sylpheed-export/examples/bgm_size_census.rs new file mode 100644 index 00000000..878e654d --- /dev/null +++ b/crates/sylpheed-export/examples/bgm_size_census.rs @@ -0,0 +1,47 @@ +//! Is `BGM_103` the ONLY bank with those two wave sizes? +//! +//! `authored/audio.json` says *"Static code, disc census and runtime all agree"* +//! — three legs. Reading the sentence beneath it, legs two and three are **one** +//! comparison: the disc's declared wave sizes matched byte-for-byte against what +//! the XMA probe saw at the menu. That is a disc-to-runtime match, not two +//! independent confirmations. +//! +//! It is a third leg only if the census independently EXCLUDES alternatives — if +//! some other bank carried the same two sizes, the byte match would not +//! distinguish it. So the sizes are counted across every `BGM_*` bank on the +//! disc. +//! +//! Prompted by the Decoder's point that a decorative second support is worse +//! than none: **a conclusion with two supports reads as better evidenced than +//! one with a single support, so apparent redundancy is itself the +//! misinformation.** +use sylpheed_formats::media; + +fn main() { + let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into()); + let src = media::DirectorySource::new(&root); + const WANT: [usize; 2] = [3_876_864, 3_930_112]; + let (mut found, mut matches) = (0usize, Vec::new()); + for n in 0..=199u32 { + let name = format!("BGM_{n:03}.slb"); + let Ok(riffs) = media::sound_bank_riffs(&src, &name) else { continue }; + if riffs.is_empty() { continue } + found += 1; + let sizes: Vec = riffs.iter().map(|r| r.len()).collect(); + // Compare on the DATA payload the port sums, not on the RIFF wrapper: + // a wrapper differs by header bytes and would hide a real collision. + let near = sizes.iter().any(|s| WANT.iter().any(|w| s.abs_diff(*w) < 4096)); + if near { + matches.push((name.clone(), sizes.clone())); + } + } + println!(" {found} BGM_* bank(s) readable on this disc"); + for (n, s) in &matches { + println!(" {n:<14} wave sizes {s:?}"); + } + println!("\n {} bank(s) carry a wave within 4 KiB of {WANT:?}", matches.len()); + println!(" Exactly 1 means the census EXCLUDES alternatives and is a real third"); + println!(" leg. More than 1 means the byte match does not distinguish BGM_103,"); + println!(" and \"three legs\" is two. Zero means this reader cannot see the"); + println!(" incumbent and its answer means nothing."); +} diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 70751600..eb49421d 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -294 sections. Search this before re-deriving anything. +295 sections. Search this before re-deriving anything. * [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28) * [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28) @@ -305,6 +305,7 @@ dies, which is what this file is for. * [The incentive they named, stated plainly](#the-incentive-they-named-stated-plainly) * [They closed the 37 — conclusion confirmed, one supporting leg does not reproduce](#they-closed-the-37--conclusion-confirmed-one-supporting-leg-does-not-reproduce) * [Naming an untested bound is what got it tested](#naming-an-untested-bound-is-what-got-it-tested) +* [Auditing my own multi-leg claims: the one that mattered holds, and now says why](#auditing-my-own-multi-leg-claims-the-one-that-mattered-holds-and-now-says-why) ## P0 — the exporter, 2026-08-28 @@ -14638,3 +14639,37 @@ find asides that cross domains, and an aside correctly about its own domain and still wrong **has no tell in either corpus**. Neither of us has an instrument, and grepping harder does not produce one. Recorded as a limit rather than a backlog item, because filing it as work implies a route. + +## Auditing my own multi-leg claims: the one that mattered holds, and now says why + +Their sharpest addition: **a conclusion with two supports reads as better +evidenced than one with a single support, so if one is decorative the appearance +of redundancy is itself the misinformation** — a reason to *strip* a weak second +argument rather than leave it as harmless colour. + +Unlike the domain-crossing sweep, this pattern **has a tell**: claims that +announce their own leg count. Six in my authored data. The load-bearing one is +`audio.json`'s *"Static code, disc census and runtime all agree"*. + +🔴 **Read literally, two of those three could be one comparison.** The sentence +beneath it says `BGM_103.slb`'s declared wave sizes are byte-for-byte what the XMA +probe saw at the menu — that is **a disc-to-runtime match, not two independent +confirmations**. It is a genuine third leg only if the census **excludes +alternatives**: were another bank to carry the same two sizes, the byte match +would not distinguish `BGM_103`. + +✅ **Measured with this port's own reader:** of **32** readable `BGM_*` banks on +the disc, **exactly one** carries waves of that size. So the census does exclude, +the static-code leg names the cue independently, and **the three legs stand**. + +📌 **The `why` now records that reasoning instead of the count.** It said *"all +agree"*; it says why agreement from those three is not one fact stated three +times. **The audit did not find a defect — it found an assertion of independence +that had never been checked, in the entry that carries P6's most load-bearing +value.** + +⚠️ Reach: I checked **one** of the six. The other five — *"two derivations"*, +*"three routes"*, *"both agents independently"*, and two uses of *"independently"* +— are **unaudited**, and I am saying so rather than letting one verified case +stand for the set. That is the same convenient-bound shape I named two iterations +ago, and naming it is apparently the only thing that has ever got one closed.