From 7a0e18889fb66792d71f085060b797004e2f8054 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Sat, 29 Aug 2026 15:37:46 +0000 Subject: [PATCH] re: the dual-mono explanation for the extra stream bytes does not generalise The port chose a voice presentation on the argument that ADV chunk 1 is mono-in-stereo and chunk 2 is dual-mono, so chunk 2 s extra bytes encode a duplicated channel rather than fidelity -- which would explain its higher declared PsuedoBytesPerSec without appealing to encode quality. Their ADV channel measurement stands. The generalisation does not. If stream 3 were systematically the same take with its channel duplicated, its size ratio to stream 2 would be tight across the 28 three-stream cues. Measured: min 0.0778 (S00A, the silent one) median 1.2565 max 2.9163 (S06A) sd 0.5057 within 15 percent of 1.0: 12 of 28 A 37x spread is not a duplicated channel, and the declared rates scatter with them -- S06A is 5661 against 16513 B/s. Whatever distinguishes the three streams varies per cue rather than being a fixed channel-configuration triple. This does not touch the port s decision, which is to take the loudest presentation: that is a per-asset content measurement, not a structural rule, so a scattering ratio cannot undermine it. It touches the explanation, which should not harden into a fact about the format. Two curiosities recorded: S12B s three streams are byte-size identical at 14396 each, and BIRD_224 is 3-stream while being a non-movie cue, so the shape is not exclusive to cutscenes. Also narrows the settle-time page s own generalisation. The port measured its boot the way this corpus measured the game and found the sequencer NOT late -- its 0.6 s discrepancy was arrival-to-arrival timestamps compared against visible spans, the plate-delay trap in a second place. So what is supported is that rest.t is the wrong landmark for the TITLE, not that everything paced off it is late. And the offered re-take of the one-run menu figures is recorded as declined, with the reason, rather than left looking unfinished. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd --- .../examples/voice_three_stream_sizes.rs | 122 ++++++++++++++++++ docs/port/HANDOFF.md | 27 +++- docs/re/boot-settle-times-measured.md | 33 ++++- docs/re/data/voice-three-stream-sizes.txt | 34 +++++ .../structures/voice-region-leading-chunk.md | 39 ++++++ 5 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 crates/sylpheed-formats/examples/voice_three_stream_sizes.rs create mode 100644 docs/re/data/voice-three-stream-sizes.txt diff --git a/crates/sylpheed-formats/examples/voice_three_stream_sizes.rs b/crates/sylpheed-formats/examples/voice_three_stream_sizes.rs new file mode 100644 index 00000000..ced72a43 --- /dev/null +++ b/crates/sylpheed-formats/examples/voice_three_stream_sizes.rs @@ -0,0 +1,122 @@ +//! Are the three streams of a voice cue in a CONSISTENT size relationship? +//! +//! The port selected a voice presentation on this argument: `ADV` chunk 1 is +//! mono-in-stereo (channel 2 digitally silent) and chunk 2 is dual-mono (both +//! channels identical), so chunk 2's extra bytes encode a duplicate channel +//! rather than fidelity β€” which would explain its higher declared +//! `PsuedoBytesPerSec` without appealing to encode quality. +//! +//! That is a claim about the *encoding*, and it makes a structural prediction: +//! if stream 3 is always "the same take with its channel duplicated", it should +//! sit in a consistent size ratio to stream 2 across every 3-stream cue on the +//! disc. If the ratio scatters β€” or if some third streams are tiny β€” then the +//! observation is about `ADV`, not about the format. +//! +//! cargo run -p sylpheed-formats --example voice_three_stream_sizes -- +use sylpheed_formats::media::{DirectorySource, DiscSource}; +use sylpheed_formats::slb; + +const DESC_MARK: u32 = 0x11; +const DESC_REPEAT: usize = 0x800; +const ID_MAX: u32 = 0x1_0000; + +fn all_descriptors(buf: &[u8]) -> Vec<(usize, u32)> { + let be = |o: usize| u32::from_be_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]); + let mut out = Vec::new(); + if buf.len() < DESC_REPEAT + 8 { + return out; + } + let end = buf.len() - (DESC_REPEAT + 4); + let mut o = 0; + while o <= end { + let id = be(o); + if id >= 1 && id < ID_MAX && be(o + 4) == DESC_MARK && be(o + DESC_REPEAT) == id { + out.push((o, id)); + } + o += 4; + } + out +} + +/// The declared `PsuedoBytesPerSec` at `fmt +0x20` of a RIFF chunk, if present. +fn declared_rate(riff: &[u8]) -> Option { + if riff.len() < 0x28 || &riff[0..4] != b"RIFF" { + return None; + } + Some(u32::from_le_bytes(riff[0x20..0x24].try_into().ok()?)) +} + +fn main() { + let disc = std::env::args() + .nth(1) + .unwrap_or_else(|| std::env::var("SYLPHEED_DISC").expect("disc")); + let src = DirectorySource::new(std::path::PathBuf::from(&disc)); + let tpak = src.open_pak("dat/tables.pak").expect("tables.pak"); + let marker = "eng\\Movie\\VOICE_ADV.slb"; + let registry = tpak + .entries() + .iter() + .find_map(|e| { + tpak.read(e) + .ok() + .filter(|b| b.windows(marker.len()).any(|w| w == marker.as_bytes())) + }) + .expect("registry"); + let ids = sylpheed_formats::movie_voice::registry_voice_ids(®istry); + let name_of: std::collections::HashMap = + ids.iter().map(|(n, &i)| (i, n.clone())).collect(); + + // Same window the cue map uses. + let win_start: u64 = 421_739_888 & !3; + let win_len: usize = 116_300_000; + let buf = src + .read_segment_range("dat/sound", win_start, win_len) + .expect("window"); + let descs = all_descriptors(&buf); + + println!("{:<14} {:>10} {:>10} {:>10} {:>7} {:>9} {:>9}", + "cue", "stream1", "stream2", "stream3", "s3/s2", "rate2", "rate3"); + let mut ratios: Vec = Vec::new(); + let mut tiny = 0; + for w in descs.windows(2) { + let (a, b) = (w[0].0, w[1].0); + if b <= a || b - a < 4096 { + continue; + } + let span = &buf[a..b]; + let riffs = slb::to_xma_riffs(span); + if riffs.len() != 3 { + continue; + } + let name = name_of + .get(&w[1].1) + .cloned() + .unwrap_or_else(|| format!("id{}", w[1].1)); + let (s1, s2, s3) = (riffs[0].len(), riffs[1].len(), riffs[2].len()); + let r = s3 as f64 / s2 as f64; + ratios.push(r); + if r < 0.5 { + tiny += 1; + } + println!( + "{:<14} {s1:>10} {s2:>10} {s3:>10} {r:>7.4} {:>9} {:>9}", + name.trim_start_matches("VOICE_"), + declared_rate(&riffs[1]).map(|v| v.to_string()).unwrap_or("-".into()), + declared_rate(&riffs[2]).map(|v| v.to_string()).unwrap_or("-".into()), + ); + } + ratios.sort_by(|a, b| a.partial_cmp(b).unwrap()); + let n = ratios.len(); + println!("\n{n} three-stream cues"); + if n > 0 { + let mean = ratios.iter().sum::() / n as f64; + let var = ratios.iter().map(|r| (r - mean).powi(2)).sum::() / n as f64; + println!( + " stream3/stream2 ratio: min {:.4} median {:.4} max {:.4} mean {:.4} sd {:.4}", + ratios[0], ratios[n / 2], ratios[n - 1], mean, var.sqrt() + ); + println!(" cues where stream 3 is less than HALF of stream 2: {tiny}"); + let near = ratios.iter().filter(|r| (**r - 1.0).abs() < 0.15).count(); + println!(" cues where stream 3 is within 15% of stream 2: {near} of {n}"); + } +} diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index 30e0d9b5..c1a4030f 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -437,6 +437,20 @@ byte-identical across the presentations.** Nothing in the header ranks them. You "more bytes is consistent with a better encode and also with the opposite" is exactly right and the file will not adjudicate it. +πŸ”΄ **Your dual-mono explanation does NOT generalise β€” but your decision survives +it.** Checked disc-wide over all 28 three-stream cues +([`data/voice-three-stream-sizes.txt`](../re/data/voice-three-stream-sizes.txt)): +if stream 3 were systematically the same take with its channel duplicated, its +size ratio to stream 2 would be tight. It runs **min 0.0778, median 1.2565, max +2.9163, sd 0.5057**, with only **12 of 28** within 15 % of 1.0 β€” a 37Γ— spread. +Declared rates scatter with them (`S06A` 5 661 vs 16 513 B/s). βœ… Your `ADV` +channel measurement stands and **your choice of "loudest" is untouched**, because +it is a per-asset content measurement rather than a structural rule. What must +not harden is the *explanation*: "more bytes means a duplicated channel" is true +of `ADV` and is not a fact about the format. +⚠️ Two curiosities: `S12B`'s three streams are **byte-size identical** (14 396 +each), and `BIRD_224` is 3-stream while being a **non-movie** cue. + ⚠️ Also: **do not trust `sylpheed-cli audio info` on these.** Its "16 channels / 4310 Hz / 2-bit" is `wBitsPerSample`, `wEncodeOptions` and the channel fields read at wrong offsets. Its reader is misaligned for XMA1. @@ -509,7 +523,18 @@ agrees to three. ⚠️ **Reach: one run.** The plate delay and the load stall are cross-checked against independent prior evidence. **The menu build-in and β’·β†’title rest on this -run alone** β€” re-take them before anything depends on them closely. +run alone.** 🟒 Re-take offered and **declined** β€” you author neither, you are +within ~0.1 s of both from the disc's own keyframes, and a one-run measurement +over a decoded value gains nothing. Left provisional deliberately. + +⚠️ **And the generalisation is narrower than your red flag was.** You measured +your own boot the way I measured the game and found the sequencer *not* late β€” +publisher 4.25 s against 4.297 / 4.604 / 4.370, developer 3.50 s against 3.508 / +3.503 / 3.366. What this page supports is **`rest.t` is the wrong landmark for +the title**, where it overstates 4.183 s against ~2 s. It does **not** support +"everything paced off it is late", and the 0.6 s you were about to chase was +arrival-to-arrival timestamps compared against visible spans β€” the plate-delay +trap in a second place. Recorded on my side too. ## Status diff --git a/docs/re/boot-settle-times-measured.md b/docs/re/boot-settle-times-measured.md index 892506f7..d448a708 100644 --- a/docs/re/boot-settle-times-measured.md +++ b/docs/re/boot-settle-times-measured.md @@ -109,11 +109,40 @@ reproducible but not byte-identical across all three. cross-checked against independent evidence β€” the plate delay, against the corpus's two runs and the disc's 120 units; the load stall, against two prior runs β€” are the ones to lean on. The menu build-in (0.531 s) and β’·β†’title - (0.482 s) rest on **this run alone** and should be re-taken before anything - depends on them closely. + (0.482 s) rest on **this run alone**. + 🟒 **The re-take was offered and declined, 2026-08-29**: the port authors + neither number, is already within ~0.1 s of both from the disc's own + keyframes, and asked that no emulator time be spent on its account. Authoring a + one-run measurement over a decoded value would gain nothing measurable. Left + provisional deliberately rather than for want of a run. * **Sampling is 8 fps**, so every landmark carries Β±0.125 s, and the guest's own presentation rate cannot be measured from it β€” 8 fps is far below the ~28 fps the game presents at, so every sample is a distinct guest frame and repeats only appear when the guest itself stalls. * The splash dwells are **not** re-measured here; they are already in [`boot-order-and-splash-dwell.md`](boot-order-and-splash-dwell.md). + +--- + +## 🟒 The consumer's own red flag was larger than this measurement supports + +Recorded because it is the outcome of the measurement and it went the other way +from what the port expected. + +The port's standing πŸ”΄ read: *"`rest.t` is the wrong landmark, therefore +everything the sequencer paces off it is late."* **The premise is confirmed here +and the consequence is not.** Measuring the port the same way this page measures +the game β€” visible span, per-frame greyscale mean β€” its publisher wordmark runs +4.25 s against three cold boots at 4.297 / 4.604 / 4.370, and its developer logos +3.50 s against 3.508 / 3.503 / 3.366. + +⚠️ **The discrepancy it was about to chase was the plate-delay trap in a second +place.** It had been comparing *arrival-to-arrival* transition timestamps against +*visible spans*; those differ by the exit ramp plus the black hold, about 0.6 s, +which was the whole of it β€” the same shape as timing the title from where it +stops animating rather than from where it first appears. + +βœ… So the generalisation this page supports is narrower than "the sequencer is +late": **`rest.t` is the wrong landmark for the title specifically**, where it +overstates by 4.183 s against ~2 s. Whether any *other* screen is mis-paced does +not follow from it and was not measured here. \ No newline at end of file diff --git a/docs/re/data/voice-three-stream-sizes.txt b/docs/re/data/voice-three-stream-sizes.txt new file mode 100644 index 00000000..b6d7dfdf --- /dev/null +++ b/docs/re/data/voice-three-stream-sizes.txt @@ -0,0 +1,34 @@ +cue stream1 stream2 stream3 s3/s2 rate2 rate3 +ADV 1294396 1118268 1171516 1.0476 8142 8530 +S00A 1810492 1263676 98364 0.0778 13485 1049 +S01A 1640508 1390652 1552444 1.1163 6952 7760 +S02A 665660 356412 350268 0.9828 6673 6558 +S02B 919612 505916 866364 1.7125 4825 8263 +S02C 2353212 1349692 2390076 1.7708 10547 18677 +S03A 741436 540732 739388 1.3674 6549 8955 +S04A 2775100 2486332 2680892 1.0783 9688 10446 +S04B 1044540 870460 991292 1.1388 9361 10661 +S05A 161852 147516 157756 1.0694 4447 4756 +S06A 839740 294972 860220 2.9163 5661 16513 +S06B 1093692 874556 1450044 1.6580 10678 17706 +S07A 993340 473148 1040444 2.1990 6810 14977 +S07B 53308 55356 53308 0.9630 3114 2999 +S09B 665660 571452 843836 1.4767 5033 7432 +S10B 2904124 2564156 2648124 1.0327 6629 6846 +S11A 81980 81980 79932 0.9750 1842 1796 +S11C 1228860 1075260 1108028 1.0305 5594 5765 +S12A 196668 135228 211004 1.5604 7938 12388 +S12B 14396 14396 14396 1.0000 1072 1072 +S12C 2328636 1273916 2502716 1.9646 7215 14176 +S13A 890940 585788 962620 1.6433 6990 11487 +S13B 131132 116796 127036 1.0877 7488 8145 +S14A 2302012 1634364 2541628 1.5551 8211 12770 +S15A 743484 618556 1122364 1.8145 5985 10861 +S15B 475196 348220 464956 1.3352 3968 5299 +S15C 1417276 1101884 1384508 1.2565 5988 7524 +BIRD_224 2050108 1607740 1978428 1.2306 2387 2937 + +28 three-stream cues + stream3/stream2 ratio: min 0.0778 median 1.2565 max 2.9163 mean 1.3593 sd 0.5057 + cues where stream 3 is less than HALF of stream 2: 1 + cues where stream 3 is within 15% of stream 2: 12 of 28 diff --git a/docs/re/structures/voice-region-leading-chunk.md b/docs/re/structures/voice-region-leading-chunk.md index 34578560..c0fa5403 100644 --- a/docs/re/structures/voice-region-leading-chunk.md +++ b/docs/re/structures/voice-region-leading-chunk.md @@ -260,6 +260,45 @@ plays β€” and it has not been done. ❔ *Why* the disc stores three presentations β€” quality tiers, a mix the engine selects between, an authoring artefact β€” is not answered here. +### πŸ”΄ Refutation attempt, 2026-08-29 β€” "the extra bytes are a duplicated channel" does NOT generalise + +The port selected a presentation on this argument: `ADV` chunk 1 is +**mono-in-stereo** (channel 2 digitally silent) and chunk 2 is **dual-mono** +(both channels identical at βˆ’8.318574), so chunk 2's extra bytes encode a +duplicate of its own channel rather than fidelity β€” which would explain its +higher declared `PsuedoBytesPerSec` without appealing to encode quality. + +**The `ADV` measurement is theirs and stands. The generalisation does not.** If +stream 3 were systematically "the same take with its channel duplicated", its +size would sit in a tight ratio to stream 2 on every 3-stream cue. Measured over +all 28 β€” [`data/voice-three-stream-sizes.txt`](../data/voice-three-stream-sizes.txt), +`--example voice_three_stream_sizes`: + +| stream3 / stream2 | | +|---|---| +| min | **0.0778** (`S00A`, the silent one) | +| median | 1.2565 | +| max | **2.9163** (`S06A`) | +| sd | **0.5057** | +| within 15 % of 1.0 | **12 of 28** | + +**A 37Γ— spread is not a duplicated channel.** The declared rates scatter with +them β€” `S06A` is 5 661 against 16 513 B/s, `S00A` 13 485 against 1 049 β€” so +whatever distinguishes the three streams varies per cue rather than being a fixed +channel-configuration triple. + +⚠️ **Two curiosities worth someone's time:** `S12B`'s three streams are +**byte-size identical** (14 396 each), and `S11A`'s first two are (81 980). And +`BIRD_224` is 3-stream while being a non-movie cue, so the 3-stream shape is not +exclusive to cutscenes. + +βœ… **What this does and does not touch.** It does **not** touch the port's +decision, which is to take the **loudest** presentation β€” that is a per-asset +content measurement, not a structural rule, so a scattering ratio cannot +undermine it. What it touches is the *explanation*: "more bytes means a +duplicated channel, not better fidelity" is true of `ADV` and is **not** a fact +about the format. It should not harden into one. + ⚠️ Note for anyone reading our own tooling: `sylpheed-cli audio info` reports these chunks as *16 channels, 4310 Hz, 2-bit*. Those are the `wBitsPerSample` (16), `wEncodeOptions` (`0x10d6` = 4310) and channel fields read at the wrong