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:
122
crates/sylpheed-formats/examples/voice_three_stream_sizes.rs
Normal file
122
crates/sylpheed-formats/examples/voice_three_stream_sizes.rs
Normal 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(®istry);
|
||||
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}");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
34
docs/re/data/voice-three-stream-sizes.txt
Normal file
34
docs/re/data/voice-three-stream-sizes.txt
Normal 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user