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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
This commit is contained in:
sylph-decoder
2026-08-29 15:37:46 +00:00
parent 4bd4779f8b
commit 7a0e18889f
5 changed files with 252 additions and 3 deletions

View File

@@ -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 -- <disc>
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<u32> {
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(&registry);
let name_of: std::collections::HashMap<u32, String> =
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<f64> = 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::<f64>() / n as f64;
let var = ratios.iter().map(|r| (r - mean).powi(2)).sum::<f64>() / 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}");
}
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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