Files
sylph-decoder 3dbfa320ae formats: drop the 1.5 MB cap that truncated 17 voice regions' first stream
The cause, and the fix, with a disc-wide check.

resolve_movie_voice_region picks start = the predecessor cue's trailer, then
filtered it with 'end - s < 1_500_000' -- 'only within one bank'. ADV's
predecessor sits 3 618 816 B before end, so the filter rejected it and start fell
back to anchor, which is a TOC offset and not a stream boundary. That explains
the shape of the defect exactly: it strikes regions larger than 1.5 MB, which is
why the three-stream multichannel regions are hit and single-stream ones never
are. 17 of 95 resolving movies took the fallback.

ADV's predecessor trailer at 433 425 776 plus 17 040 B of descriptor and padding
is 433 442 816 -- the -238-packet start measured against the decoder, to the byte.

Dropping the cap: unchanged 78, fixed cleanly 17, changed in any other way ZERO.
In all 17 the only difference is a larger first chunk with every later chunk
byte-identical, which is what a corrected start looks like and what pulling in a
neighbouring asset does not.

Regression test pinned to the RUNNING DECODER's byte_sizes rather than to this
crate's own output. That is the point of it: every internal check passed happily
while a third of a stream was missing, so only an external number could have
caught this class of bug.

sylpheed-formats: 136 tests pass, 0 fail (the one still running at commit time is
an unrelated long mesh test).

Exact clips for the other 16 are not independently verified -- the sweep is
strong but ADV is the only one with a decoder measurement behind it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 08:55:47 +00:00

125 lines
5.2 KiB
Rust

//! Real-disc tests for media assembly. Skipped without `SYLPHEED_DISC`.
//!
//! This logic used to live in the Bevy viewer, where it had no test at all. It
//! is the trickiest reading on the disc — a cutscene's voice is a byte region of
//! a continuous stream, not the bank its name points at — so it gets pinned here
//! before anything else is built on top of it.
use std::path::PathBuf;
use sylpheed_formats::media::{self, DirectorySource, DiscSource};
use sylpheed_formats::slb::VoiceLang;
fn disc() -> Option<DirectorySource> {
let p = PathBuf::from(std::env::var("SYLPHEED_DISC").ok()?);
p.join("dat").is_dir().then(|| DirectorySource::new(p))
}
/// A segment-spanning read returns the same bytes as slicing the whole archive.
///
/// The control that matters: `sound.pak`'s data is five segments, so a TOC
/// offset late in the archive addresses a position no single file has. If the
/// walk were off by a segment this would return plausible-looking wrong bytes
/// rather than fail, which is exactly why it is asserted against the archive's
/// own read rather than against a length.
#[test]
fn segment_range_matches_the_archive_read() {
let Some(src) = disc() else {
eprintln!("SKIP: set SYLPHEED_DISC");
return;
};
let name = "BGM_020.slb";
let hash = sylpheed_formats::hash::name_hash(name);
let via_range = media::read_sound_bank(&src, hash).expect("segment range read");
let toc = src.read_file("dat/sound.pak").unwrap();
let entries = sylpheed_formats::PakArchive::parse_toc(&toc).unwrap();
let e = entries
.iter()
.find(|e| e.name_hash == hash)
.expect("BGM_020 in the TOC");
assert_eq!(via_range.len(), e.comp_size as usize);
// And it decodes, which a misaligned read would not do.
let riffs = media::sound_bank_riffs(&src, name).expect("riffs");
assert!(!riffs.is_empty(), "no sub-waves recovered");
}
/// A movie's voice resolves to a byte region, and the region is sane.
///
/// `RT01A` is one of the cutscenes whose voice spans more than one `.slb`
/// chunk — the case that motivated regions over per-bank reads in the first
/// place. The assertions are deliberately about *shape* (ordered, non-empty,
/// smaller than one bank) rather than exact offsets, because the offsets are
/// disc facts we have no independent oracle for here; a regression that
/// reversed or emptied the region would still be caught.
#[test]
fn movie_voice_resolves_to_a_region_that_decodes() {
let Some(src) = disc() else {
eprintln!("SKIP: set SYLPHEED_DISC");
return;
};
let (start, end) = media::resolve_movie_voice_region(&src, "RT01A", VoiceLang::English)
.expect("RT01A has a bound voice track");
assert!(start < end, "region is inverted: {start}..{end}");
assert!(end - start > 4096, "region is implausibly small");
assert!(end - start < 1_500_000, "region spans more than one bank");
let riffs = media::voice_region_riffs(&src, start, end).expect("region riffs");
assert!(!riffs.is_empty(), "region decoded to no audio");
}
/// An unbound movie stays unvoiced rather than borrowing a neighbour's clip.
///
/// This is a *negative* the corpus paid for: extending resolution to unbound
/// resupply movies by shared demo line played the WRONG recording. The guard
/// keeps that door shut.
#[test]
fn manifest_binding_is_the_only_route() {
let Some(src) = disc() else {
eprintln!("SKIP: set SYLPHEED_DISC");
return;
};
// A movie the manifest does not bind must resolve to nothing, not to a guess.
assert_eq!(
media::resolve_movie_voice_clip(&src, "no_such_movie_xyz", VoiceLang::English),
None
);
}
/// The resolved `ADV` voice region must contain **all three** streams the running
/// decoder decodes — not a truncated first one.
///
/// Ground truth is the emulator, not this crate: booting with `--xma_param_probe`
/// reports three XMA contexts with `byte_size` 1 294 336 / 1 118 208 / 1 171 456
/// (`docs/re/structures/voice-three-streams-are-concurrent.md`). Until 2026-08-30
/// the resolver's start filter capped a region at 1.5 MB, `ADV`'s span is 3.6 MB,
/// so the start fell back to `anchor` — a TOC offset, 238 packets into the first
/// stream — and this returned 806 912 for the first chunk.
///
/// This is a regression test against an EXTERNAL measurement, which is the only
/// kind that can catch the class of bug it was written for: every internal check
/// passed happily while a third of a stream was missing.
#[test]
fn adv_voice_region_holds_all_three_decoded_streams() {
let Some(src) = disc() else {
eprintln!("SKIP: set SYLPHEED_DISC");
return;
};
let (start, end) = media::resolve_movie_voice_region(&src, "ADV", VoiceLang::English)
.expect("ADV voice region");
let bytes = src
.read_segment_range("dat/sound", start, (end - start) as usize)
.expect("region bytes");
let sizes: Vec<usize> = sylpheed_formats::slb::to_xma_riffs(&bytes)
.iter()
.map(|r| r.len() - 60)
.collect();
assert_eq!(
sizes,
vec![1_294_336, 1_118_208, 1_171_456],
"the region must reproduce the RUNNING DECODER's byte_sizes; \
a first chunk of 806912 means the start filter has come back"
);
}