re: recover the .slb leading segment — mono, and scoped by measurement
to_xma_riffs now emits the leading headerless segment when it sits at a whole number of XMA1 packets and carries a non-zero byte. VOICE_D_453 goes from a 0.14 s trailing fragment to a 45116-byte leading sub-wave that dominates it. I withdrew this exact change earlier for two reasons. Both are now answered rather than argued away: * "It recovers no audio" -- it used the STEREO format. At two channels every bank yields exactly 1792 bytes, one frame, whatever its size. Mono yields up to 113x more. * "It matches 1524 of 8021 RIFF-bearing entries" -- the byte-level reach is still 1524, but the audible reach is not. Across the 84 movie-bound banks the segment adds >1 s to exactly 7, the hokyu_*_H tankers on D_453/D_454 -- precisely the broken ones -- and <=0.25 s to 66 of the rest. The largest non-resupply addition is S04A at +0.66 s on a 256 s movie. The safety oracle is recorded with its limits: 8 of the 84 banks ALREADY exceed their movie's duration before the change, by hundredths of a second, so it cannot resolve differences at that scale. It establishes scoping, not correctness. Callers clamp to the movie length regardless. VOICE_D_451's all-zero leading region is skipped by the non-zero guard, so the rule cannot prepend silence to a bank that does not need it. Pinned, as is the packet arithmetic (n = 8, 1, 7, 22, 29) which has no tunable. slb_disc, movie_subtitle_disc and movie_manifest_disc all still pass. NOT verified by ear -- that needs a human. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
@@ -133,7 +133,8 @@ fn parse_voice_clip(name: &str) -> VoiceClip {
|
||||
/// tracing confirmed the movie→voice binding; this fixes the *decode* of `RT*`.)
|
||||
pub fn to_xma_riffs(slb: &[u8]) -> Vec<Vec<u8>> {
|
||||
let mut out = Vec::new();
|
||||
if find(slb, b"RIFF", 0).is_none() {
|
||||
let first_riff = find(slb, b"RIFF", 0);
|
||||
if first_riff.is_none() {
|
||||
// Headerless single-stream bank.
|
||||
if let Some(data) = slb.get(HEADERLESS_DATA_OFFSET..) {
|
||||
if !data.is_empty() {
|
||||
@@ -142,22 +143,33 @@ pub fn to_xma_riffs(slb: &[u8]) -> Vec<Vec<u8>> {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
// ❌ A "leading headerless stream" rule was tried here and WITHDRAWN.
|
||||
// HYBRID banks: a headerless packet stream followed by RIFF sub-waves, two
|
||||
// SEQUENTIAL SEGMENTS of one clip. The branch above only fires when there is
|
||||
// no `RIFF` at all, so the leading segment used to be dropped — which is why
|
||||
// `VOICE_D_453` decoded to 0.14 s: its line is in that segment and only the
|
||||
// trailing fragment survived.
|
||||
//
|
||||
// The structure is real: in all five resupply banks the first `RIFF` sits at
|
||||
// exactly `HEADERLESS_DATA_OFFSET + n*XMA1_PACKET` (n = 8, 1, 7, 22, 29), and
|
||||
// 87 % of `VOICE_D_453` lies in front of it. Emitting that region as a
|
||||
// sub-wave raised byte coverage from 5.4 % to 89.9 %.
|
||||
// The boundary is arithmetic, not a magic: XMA1 packets are 2048 bytes, so a
|
||||
// leading stream occupies exactly `HEADERLESS_DATA_OFFSET + n*XMA1_PACKET`.
|
||||
// It decodes as **mono** — at two channels every bank yields exactly 1792
|
||||
// bytes, one frame, whatever its size.
|
||||
//
|
||||
// But byte coverage was the wrong success metric. The emitted streams decode
|
||||
// to **1792 PCM bytes** — silence — through the same FFmpeg path that decodes
|
||||
// the RIFF sub-waves fine, so the region is not XMA1 under the synthesised
|
||||
// format. And the rule is not narrow: it matches **1524 of the 8021**
|
||||
// RIFF-bearing entries in `sound.pak`, including `RT*` banks that decode
|
||||
// correctly today. Landing it would have risked a large regression to fix
|
||||
// five banks it does not actually fix.
|
||||
//
|
||||
// See docs/re/voice-bank-leading-region.md.
|
||||
// An earlier version of this was withdrawn for two good reasons, both now
|
||||
// answered: it recovered no audio (it used the stereo format), and it
|
||||
// matched 1524 of the 8021 RIFF-bearing entries. The byte-level reach is
|
||||
// still 1524, but the *audible* reach is not: across the 84 movie-bound
|
||||
// banks the segment adds >1 s to exactly **7** — the `hokyu_*_H` tankers
|
||||
// bound to `VOICE_D_453`/`454`, i.e. precisely the broken ones — and
|
||||
// ≤0.25 s to 66 of the rest. Callers clamp to the movie length anyway.
|
||||
if let Some(ri) = first_riff {
|
||||
if ri > HEADERLESS_DATA_OFFSET && (ri - HEADERLESS_DATA_OFFSET) % XMA1_PACKET == 0 {
|
||||
if let Some(data) = slb.get(HEADERLESS_DATA_OFFSET..ri) {
|
||||
if data.iter().any(|b| *b != 0) {
|
||||
out.push(build_riff(&synth_xma1_fmt(1, 0, 48000), data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut pos = 0usize;
|
||||
while let Some(ri) = find(slb, b"RIFF", pos) {
|
||||
// Parse this sub-wave's fmt + data (declared size is honest per sub-wave).
|
||||
|
||||
86
crates/sylpheed-formats/tests/slb_leading_segment_disc.rs
Normal file
86
crates/sylpheed-formats/tests/slb_leading_segment_disc.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
//! 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, PathBuf};
|
||||
|
||||
use sylpheed_formats::{slb, PakArchive};
|
||||
|
||||
fn disc_root() -> Option<PathBuf> {
|
||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
||||
let p = PathBuf::from(p);
|
||||
if p.join("dat").is_dir() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
let default = Path::new(
|
||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
||||
);
|
||||
default.join("dat").is_dir().then(|| default.to_path_buf())
|
||||
}
|
||||
|
||||
macro_rules! skip_without_disc {
|
||||
($root:ident) => {
|
||||
let Some($root) = disc_root() else {
|
||||
eprintln!("SKIP: set SYLPHEED_DISC");
|
||||
return;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
fn bank(root: &Path, n: u32) -> Vec<u8> {
|
||||
let snd = PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak");
|
||||
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);
|
||||
}
|
||||
@@ -1057,9 +1057,18 @@ premise was wrong.**
|
||||
first `RIFF`. For the first three that discards only silence, so they looked
|
||||
fine (2.8 / 1.6 / 2.2 s); for the last two it discards **the line itself**,
|
||||
leaving 0.14 s and 0.43 s. One rule, two outcomes, depending on which segment
|
||||
holds the speech. ▶️ The fix is now well-posed: emit the leading region **only
|
||||
when it carries signal**, which also avoids the 1524-bank blast radius that
|
||||
sank the earlier attempt.
|
||||
holds the speech. ✅ **(same day) LANDED.** `to_xma_riffs` emits the leading segment
|
||||
wrapped **mono** when it is a whole number of packets and carries a non-zero
|
||||
byte. Both reasons the first attempt was withdrawn are answered: it used the
|
||||
stereo format (mono yields up to 113× more), and while the byte-level reach is
|
||||
still 1524 entries the **audible** reach is not — across the 84 movie-bound
|
||||
banks it adds >1 s to exactly **7**, the `hokyu_*_H` tankers on
|
||||
`VOICE_D_453`/`454`, and ≤0.25 s to 66 of the rest. ⚠️ The safety oracle is
|
||||
weak and says so: **8 of the 84 already exceed their movie duration before the
|
||||
change**, by hundredths of a second, so it establishes scoping rather than
|
||||
correctness. Pinned by `tests/slb_leading_segment_disc.rs`, including that the
|
||||
all-zero `VOICE_D_451` region stays skipped. ❔ Not verified by ear — that
|
||||
needs a human.
|
||||
* ❌ **(2026-08-25) The `.slb` "multi-subwave" guess is REFUTED, and the voice
|
||||
decoder is discarding up to 87 % of a bank.** The record table gives a
|
||||
**direct** binding `hokyu_DS_s13A -> VOICE_D_452` where the corpus records the
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# The resupply voice banks — the decoder discards up to 87 % of them
|
||||
|
||||
Status: ❌ the recorded "multi-subwave / not cleanly sliced" explanation is
|
||||
**REFUTED**. ✅ each shared bank is **one generic line**, which explains the
|
||||
Status: ✅ **FIXED** — `to_xma_riffs` now emits the leading segment as mono, and
|
||||
`VOICE_D_453`/`454` recover their line (`tests/slb_leading_segment_disc.rs`).
|
||||
❌ the recorded "multi-subwave / not cleanly sliced" explanation is **REFUTED**. ✅ each shared bank is **one generic line**, which explains the
|
||||
in-game verdict that rejected the `hokyu_DS_s13A` binding — the line really is
|
||||
generic. ❌ **my own "audio is missing" conclusion is RETRACTED**: a subtitle cue
|
||||
is a START time, not a point inside the clip, and under the correct reading every
|
||||
@@ -178,6 +179,30 @@ Three of the five are decisive; the other two have their only cue at 0.0 s, whic
|
||||
tells us nothing in either direction. So **something is genuinely missing from
|
||||
these banks** — independent of anything above, and now measured rather than felt.
|
||||
|
||||
## ✅ Landed — and why it is safe this time
|
||||
|
||||
`to_xma_riffs` now emits the leading segment, wrapped **mono**, when it sits at a
|
||||
whole number of packets and carries a non-zero byte. The first attempt at this
|
||||
was withdrawn for two good reasons, and both are answered:
|
||||
|
||||
* *"It recovers no audio."* It used the **stereo** format. Mono yields up to 113×
|
||||
more, and `VOICE_D_453`'s 45 116-byte segment is now its largest sub-wave.
|
||||
* *"It matches 1524 of 8021 entries."* The byte-level reach is still 1524, but
|
||||
the **audible** reach is not. Across the 84 movie-bound banks the segment adds
|
||||
**more than 1 s to exactly 7** — the `hokyu_*_H` tankers on `VOICE_D_453`/`454`,
|
||||
precisely the broken ones — and ≤0.25 s to 66 of the rest. The largest
|
||||
non-resupply addition is `S04A` at +0.66 s on a **256 s** movie.
|
||||
|
||||
The safety oracle is honest about its own limits: 8 of the 84 banks *already*
|
||||
exceed their movie's duration before the change, by hundredths of a second, so it
|
||||
cannot resolve differences at that scale. What it does establish is the
|
||||
**scoping** — the change is material only where it is meant to be. Callers clamp
|
||||
to the movie length regardless.
|
||||
|
||||
`VOICE_D_451`'s all-zero leading region is skipped by the non-zero guard, so the
|
||||
rule cannot prepend silence to a bank that does not need it; that is pinned by a
|
||||
test.
|
||||
|
||||
## ✅ The two parts are SEQUENTIAL SEGMENTS — and that closes the original mystery
|
||||
|
||||
The last open question was whether the leading mono region duplicates the RIFF
|
||||
|
||||
Reference in New Issue
Block a user