`slb_leading_segment_disc` was SIGKILLed by the OOM killer in `docker/ci/run` at its 7 GB cap, so the documented 45/377 baseline did not reproduce on a 15 GB box. `PakArchive` holds the whole concatenated payload in memory and `sound.pak` is 1.01 GB (sound.p00-.p04). `bank()` and `bank_named()` opened it on every call -- inside loops -- and five tests opened their own besides, ~26 opens in all. With cargo's default thread count that is ~6.1 GB of archive in flight against a 7 GB cap with `--memory-swap` equal to `--memory`, so there is no swap to absorb it. The archive is immutable once open and every accessor takes `&self`, so one `OnceLock` instance is equivalent to N private ones at 1/N the memory. ⚠️ The failure mode is worth knowing: a SIGKILLed suite prints no `test result:` line at all, so it disappears from a scraped tally instead of failing visibly. The run still reported "0 failed" -- true, and useless. Check cargo's exit code (101), not the tally. Measured in the capped container, 7 GB, default threads: * before: SIGKILL (signal 9), 0 of 10 tests reported * after : 10 passed in 3.17s * (single-threaded before the fix: 10 passed in 22.64s -- the fix is also ~7x faster, because it no longer re-reads 1 GB from disc 26 times) * cargo fmt --check clean; cargo clippy --tests -D warnings clean Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
366 lines
15 KiB
Rust
366 lines
15 KiB
Rust
//! The `.slb` leading segment — recovered, and scoped.
|
|
//!
|
|
//! `VOICE_D_453` used to decode to 0.14 s because its line lives in a headerless
|
|
//! packet stream *before* the first `RIFF`, and the decoder started at the
|
|
//! `RIFF`. The banks that looked fine were the ones whose leading segment is
|
|
//! silence. One rule, two outcomes.
|
|
|
|
use std::path::Path;
|
|
use std::sync::OnceLock;
|
|
|
|
use sylpheed_formats::{slb, PakArchive};
|
|
|
|
mod common;
|
|
use common::skip_without_disc;
|
|
|
|
/// One archive for the whole binary.
|
|
///
|
|
/// `PakArchive` holds the entire concatenated payload in memory, and
|
|
/// `sound.pak` is **1.01 GB** (`sound.p00`-`.p04`). Opening it per call — which
|
|
/// the helpers below did, inside loops — put one copy per test thread in flight,
|
|
/// so at the default thread count the suite needed ~6 GB and was SIGKILLed by
|
|
/// the CI container's 7 GB cap (`--memory-swap` equals `--memory`, so there is
|
|
/// no swap to absorb it). A killed suite prints no `test result:` line at all,
|
|
/// so it vanishes from the tally rather than failing visibly.
|
|
///
|
|
/// The archive is immutable once open and every accessor takes `&self`, so one
|
|
/// shared instance is equivalent to N private ones — at 1/N the memory.
|
|
fn sound(root: &Path) -> &'static PakArchive {
|
|
static SOUND: OnceLock<PakArchive> = OnceLock::new();
|
|
// Every caller passes the same `disc_root()`, so first-writer-wins is the
|
|
// same archive whichever test initialises it.
|
|
SOUND.get_or_init(|| PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak"))
|
|
}
|
|
|
|
fn bank(root: &Path, n: u32) -> Vec<u8> {
|
|
let snd = sound(root);
|
|
let path = format!("eng\\etc\\VOICE_D_{n}.slb");
|
|
let entry = snd.find_by_name(&path).expect("bank present");
|
|
snd.read(entry).expect("read")
|
|
}
|
|
|
|
/// The boundary is arithmetic and has no tunable: the first `RIFF` sits at
|
|
/// exactly `HEADERLESS_DATA_OFFSET + n*XMA1_PACKET` in every resupply bank.
|
|
#[test]
|
|
fn leading_segment_is_a_whole_number_of_packets() {
|
|
skip_without_disc!(root);
|
|
for (n, packets) in [(450u32, 8usize), (451, 1), (452, 7), (453, 22), (454, 29)] {
|
|
let b = bank(&root, n);
|
|
let ri = b.windows(4).position(|w| w == b"RIFF").expect("has a RIFF");
|
|
assert!(ri > slb::HEADERLESS_DATA_OFFSET, "VOICE_D_{n}");
|
|
let lead = ri - slb::HEADERLESS_DATA_OFFSET;
|
|
assert_eq!(
|
|
lead % slb::XMA1_PACKET,
|
|
0,
|
|
"VOICE_D_{n} not a whole packet count"
|
|
);
|
|
assert_eq!(lead / slb::XMA1_PACKET, packets, "VOICE_D_{n} packet count");
|
|
}
|
|
}
|
|
|
|
/// The two banks whose line lives in the leading segment now yield it.
|
|
#[test]
|
|
fn broken_banks_recover_their_line() {
|
|
skip_without_disc!(root);
|
|
// (bank, sub-waves expected, payload of the leading one)
|
|
for (n, waves, lead_len) in [(453u32, 2usize, 45116usize), (454, 2, 59452)] {
|
|
let riffs = slb::to_xma_riffs(&bank(&root, n));
|
|
assert_eq!(riffs.len(), waves, "VOICE_D_{n} sub-wave count");
|
|
assert_eq!(riffs[0].len(), lead_len, "VOICE_D_{n} leading segment");
|
|
// It must be the LARGER part: that is the whole point.
|
|
assert!(
|
|
riffs[0].len() > riffs[1].len() * 5,
|
|
"VOICE_D_{n}: leading segment should dominate"
|
|
);
|
|
}
|
|
}
|
|
|
|
/// `VOICE_D_451`'s leading region is all zeros — the guard must skip it, so the
|
|
/// rule cannot prepend silence to a bank that does not need it.
|
|
#[test]
|
|
fn all_zero_leading_region_is_skipped() {
|
|
skip_without_disc!(root);
|
|
let b = bank(&root, 451);
|
|
let ri = b.windows(4).position(|w| w == b"RIFF").unwrap();
|
|
assert!(
|
|
b[slb::HEADERLESS_DATA_OFFSET..ri].iter().all(|x| *x == 0),
|
|
"expected an all-zero leading region"
|
|
);
|
|
// Two sub-waves, both from the RIFF section — no synthesised third.
|
|
assert_eq!(slb::to_xma_riffs(&b).len(), 2);
|
|
}
|
|
|
|
fn bank_named(root: &Path, path: &str) -> Vec<u8> {
|
|
let snd = sound(root);
|
|
let entry = snd
|
|
.find_by_name(path)
|
|
.unwrap_or_else(|| panic!("{path} present"));
|
|
snd.read(entry).expect("read")
|
|
}
|
|
|
|
/// `HEADERLESS_DATA_OFFSET` is the `<lang>\etc\` case, not the format.
|
|
///
|
|
/// The leading stream is a whole number of packets ending at the first `RIFF`,
|
|
/// so its start is `first_riff % XMA1_PACKET`. Disc-wide that takes four values
|
|
/// and only 1392 matches the old constant — assuming it elsewhere starts the
|
|
/// decode mid-packet. See docs/re/structures/slb-data-offset.md.
|
|
#[test]
|
|
fn leading_data_offset_is_derived_not_assumed() {
|
|
skip_without_disc!(root);
|
|
// (bank, expected derived offset). The `etc` banks must still land on the
|
|
// old constant — that is the no-regression half of the test.
|
|
for (path, want) in [
|
|
("eng\\etc\\VOICE_D_452.slb", 1392usize),
|
|
("eng\\etc\\VOICE_D_453.slb", 1392),
|
|
("eng\\Voice\\VOICE_TCAF_592.slb", 1468),
|
|
("jpn\\Voice\\VOICE_TCAF_592.slb", 1728),
|
|
("jpn\\etc\\VOICE_D_452.slb", 1600),
|
|
] {
|
|
let b = bank_named(&root, path);
|
|
let ri = b.windows(4).position(|w| w == b"RIFF").expect("has a RIFF");
|
|
let got = slb::leading_data_offset(ri);
|
|
assert_eq!(got, want, "{path}: derived offset");
|
|
assert_eq!(
|
|
(ri - got) % slb::XMA1_PACKET,
|
|
0,
|
|
"{path}: leading stream is not a whole packet count"
|
|
);
|
|
assert!(
|
|
got >= slb::HEADERLESS_DATA_OFFSET,
|
|
"{path}: offsets below the old constant are unexplained"
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The banks the old constant mis-decoded now carry a leading sub-wave, and the
|
|
/// ones it decoded correctly are untouched.
|
|
#[test]
|
|
fn derived_offset_recovers_voice_banks_without_regressing_etc() {
|
|
skip_without_disc!(root);
|
|
for path in [
|
|
"eng\\Voice\\VOICE_TCAF_592.slb",
|
|
"jpn\\Voice\\VOICE_TCAF_592.slb",
|
|
] {
|
|
let b = bank_named(&root, path);
|
|
let ri = b.windows(4).position(|w| w == b"RIFF").expect("has a RIFF");
|
|
// Under the old constant this leading region was not a whole packet
|
|
// count, so `to_xma_riffs` emitted no leading sub-wave at all.
|
|
assert_ne!(
|
|
(ri - slb::HEADERLESS_DATA_OFFSET) % slb::XMA1_PACKET,
|
|
0,
|
|
"{path}: expected the OLD constant to mis-align here"
|
|
);
|
|
let riffs = slb::to_xma_riffs(&b);
|
|
assert!(
|
|
riffs.len() >= 2,
|
|
"{path}: expected a leading sub-wave plus at least one RIFF, got {}",
|
|
riffs.len()
|
|
);
|
|
}
|
|
// Control: an `etc` bank still produces what it did before.
|
|
let b = bank_named(&root, "eng\\etc\\VOICE_D_452.slb");
|
|
assert_eq!(
|
|
slb::leading_data_offset(b.windows(4).position(|w| w == b"RIFF").unwrap()),
|
|
slb::HEADERLESS_DATA_OFFSET
|
|
);
|
|
}
|
|
|
|
/// The scan agrees with the truth wherever the truth is knowable.
|
|
///
|
|
/// A bank carrying a `RIFF` has its offset *forced* to `first_riff % 2048`, so
|
|
/// those banks are a labelled set for a rule meant to serve the ones without a
|
|
/// `RIFF`. Over the whole labelled set the scan is right 99.6 % of the time and
|
|
/// its only failures are ties. This test walks a slice of it.
|
|
#[test]
|
|
fn scan_data_offset_agrees_with_the_riff_derived_answer() {
|
|
skip_without_disc!(root);
|
|
let snd = sound(&root);
|
|
let mut checked = 0usize;
|
|
let mut agreed = 0usize;
|
|
for lang in ["eng", "jpn"] {
|
|
for (dir, lo, hi) in [("Voice", 1u32, 120u32), ("etc", 1, 120)] {
|
|
for n in lo..hi {
|
|
let path = format!("{lang}\\{dir}\\VOICE_TCAF_{n:03}.slb");
|
|
let Some(entry) = snd.find_by_name(&path) else {
|
|
continue;
|
|
};
|
|
let Ok(b) = snd.read(entry) else { continue };
|
|
let Some(ri) = b.windows(4).position(|w| w == b"RIFF") else {
|
|
continue;
|
|
};
|
|
if ri <= slb::HEADERLESS_DATA_OFFSET {
|
|
continue;
|
|
}
|
|
if !b[slb::HEADERLESS_DATA_OFFSET..ri].iter().any(|v| *v != 0) {
|
|
continue;
|
|
}
|
|
checked += 1;
|
|
if slb::scan_data_offset(&b) == slb::leading_data_offset(ri) {
|
|
agreed += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert!(
|
|
checked >= 20,
|
|
"expected a usable labelled set, got {checked}"
|
|
);
|
|
// The whole-disc rate is 99.62%; allow a little slack for a small slice.
|
|
let rate = agreed as f64 / checked as f64;
|
|
assert!(
|
|
rate >= 0.95,
|
|
"scan agreed on {agreed}/{checked} ({:.1}%), expected >=95%",
|
|
rate * 100.0
|
|
);
|
|
}
|
|
|
|
/// Every offset the scan can return is one of the four seen on disc.
|
|
#[test]
|
|
fn scan_only_returns_known_offsets() {
|
|
skip_without_disc!(root);
|
|
let snd = sound(&root);
|
|
let mut seen = 0usize;
|
|
for n in 1u32..200 {
|
|
for path in [
|
|
format!("eng\\Voice\\VOICE_ADAN_{n:03}.slb"),
|
|
format!("jpn\\Voice\\VOICE_ADAN_{n:03}.slb"),
|
|
] {
|
|
let Some(entry) = snd.find_by_name(&path) else {
|
|
continue;
|
|
};
|
|
let Ok(b) = snd.read(entry) else { continue };
|
|
let got = slb::scan_data_offset(&b);
|
|
assert!(
|
|
slb::DATA_OFFSET_CANDIDATES.contains(&got),
|
|
"{path}: scan returned {got}, not a known offset"
|
|
);
|
|
seen += 1;
|
|
}
|
|
}
|
|
assert!(seen >= 20, "expected banks to test, saw {seen}");
|
|
}
|
|
|
|
/// A wave's boundary is exact: `seek` magic sits at `data_at + declared_size`.
|
|
///
|
|
/// Established 2026-08-26 (docs/re/structures/slb-data-offset.md). Every
|
|
/// `RIFF`-bearing entry on the disc satisfies it — **7 620/7 620** in the full
|
|
/// sweep — and the `seek` chunk's little-endian packet count at `+12` times
|
|
/// 2048 equals the declared size. This is the decoder-independent boundary, and
|
|
/// it is what proves the declared sizes honest rather than over-stated.
|
|
///
|
|
/// The test walks a bounded slice so it stays fast; the identity is disc-wide.
|
|
#[test]
|
|
fn a_waves_declared_size_is_confirmed_by_the_next_seek() {
|
|
skip_without_disc!(root);
|
|
let snd = sound(&root);
|
|
let mut checked = 0usize;
|
|
for n in 1u32..400 {
|
|
for path in [
|
|
format!("eng\\etc\\VOICE_D_{n}.slb"),
|
|
format!("eng\\Voice\\VOICE_TCAF_{n:03}.slb"),
|
|
format!("jpn\\Voice\\VOICE_ADAN_{n:03}.slb"),
|
|
] {
|
|
let Some(entry) = snd.find_by_name(&path) else {
|
|
continue;
|
|
};
|
|
let Ok(b) = snd.read(entry) else { continue };
|
|
let Some(ri) = b.windows(4).position(|w| w == b"RIFF") else {
|
|
continue;
|
|
};
|
|
let Some(rel) = b[ri..].windows(4).position(|w| w == b"data") else {
|
|
continue;
|
|
};
|
|
let di = ri + rel;
|
|
let Some(sz) = b.get(di + 4..di + 8) else {
|
|
continue;
|
|
};
|
|
let declared = u32::from_le_bytes(sz.try_into().unwrap()) as usize;
|
|
// The boundary lies outside this entry's own TOC window whenever the
|
|
// declared size overruns it, which is the common case — so read from
|
|
// the archive's flat data rather than from the entry slice.
|
|
let probe = entry.offset as usize + di + 8 + declared;
|
|
let Some(tag) = snd.data_at(probe, 16) else {
|
|
continue;
|
|
};
|
|
assert_eq!(
|
|
&tag[0..4],
|
|
b"seek",
|
|
"{path}: expected `seek` at data_at+declared ({probe})"
|
|
);
|
|
let packets = u32::from_le_bytes(tag[12..16].try_into().unwrap()) as usize;
|
|
assert_eq!(
|
|
packets * slb::XMA1_PACKET,
|
|
declared,
|
|
"{path}: seek packet count x 2048 != declared data size"
|
|
);
|
|
checked += 1;
|
|
}
|
|
}
|
|
assert!(checked >= 30, "expected banks to check, got {checked}");
|
|
eprintln!("wave-boundary identity held for {checked} banks");
|
|
}
|
|
|
|
/// A **music** bank has no leading segment — the bytes before its first `RIFF`
|
|
/// are the bank header, and emitting them made `BGM_103` look like three stems.
|
|
///
|
|
/// The header sizes itself (`+0x24`, in 2048-byte blocks), and on every bank on
|
|
/// this disc that size lands exactly on the first `RIFF`. So the guard is not a
|
|
/// heuristic and has no threshold: if a bank states a header, believe it.
|
|
#[test]
|
|
fn a_bank_that_states_its_own_header_has_no_leading_segment() {
|
|
skip_without_disc!(root);
|
|
let snd = sound(&root);
|
|
let mut with_header = 0usize;
|
|
let mut mid_bank = 0usize;
|
|
// Peek at the 56-byte header through the archive's flat data rather than
|
|
// decompressing 9 519 entries: `sound.pak` stores them uncompressed, and a
|
|
// full read of all of them is several GB (it OOM-killed the test runner).
|
|
for entry in snd.entries() {
|
|
let Some(head) = snd.data_at(entry.offset as usize, 0x38) else {
|
|
continue;
|
|
};
|
|
match slb::bank_header_len(head) {
|
|
Some(h) => {
|
|
let b = snd.read(entry).expect("read a bank that states a header");
|
|
let ri = b.windows(4).position(|w| w == b"RIFF").expect("has a RIFF");
|
|
// Declared header ends exactly at the first RIFF: no gap, so
|
|
// nothing before it can be a packet stream.
|
|
assert_eq!(h, ri, "a bank header that does not end at its first RIFF");
|
|
with_header += 1;
|
|
}
|
|
None => mid_bank += 1,
|
|
}
|
|
}
|
|
// 28 music banks (ids 1001-1023, 1101-1105); the rest are mid-bank windows,
|
|
// where the leading region IS real and must keep being emitted.
|
|
assert_eq!(
|
|
with_header, 28,
|
|
"banks stating their own header at offset 0"
|
|
);
|
|
assert!(mid_bank > 9000, "mid-bank windows, got {mid_bank}");
|
|
eprintln!("{with_header} banks state a header; {mid_bank} mid-bank windows");
|
|
}
|
|
|
|
/// The regression itself: the menu's music bank is **two** sub-waves, and they
|
|
/// are the two the corpus names — matching the executable's `BGM_103` and the
|
|
/// two streams the runtime XMA probe saw at the main menu.
|
|
#[test]
|
|
fn the_menu_music_bank_is_exactly_two_sub_waves() {
|
|
skip_without_disc!(root);
|
|
let snd = sound(&root);
|
|
for (name, sizes) in [
|
|
("BGM_103.slb", [3_876_864usize, 3_930_112]),
|
|
("BGM_001.slb", [4_466_688, 4_673_536]),
|
|
] {
|
|
let entry = snd.find_by_name(name).expect("bank present");
|
|
let b = snd.read(entry).expect("read");
|
|
let riffs = slb::to_xma_riffs(&b);
|
|
assert_eq!(riffs.len(), 2, "{name}: sub-wave count");
|
|
for (r, want) in riffs.iter().zip(sizes) {
|
|
let di = r.windows(4).position(|w| w == b"data").expect("data chunk");
|
|
let got = u32::from_le_bytes(r[di + 4..di + 8].try_into().unwrap()) as usize;
|
|
assert_eq!(got, want, "{name}: sub-wave payload size");
|
|
}
|
|
}
|
|
}
|