re: RETRACTED "audio is missing" — a subtitle cue is a START time

The load-bearing error of the whole voice-bank thread, and it is mine. It
stood for three iterations across two write-ups that each called the result
proven.

I treated a subtitle cue as a timestamp that must fall INSIDE the voice clip,
and concluded a 0.07 s clip could not host a cue at 4.70 s. A cue is when the
line STARTS. The voice plays from the cue, so the clip only has to fit the
window between the cue and the end of the movie. Under that reading every
bank fits at plain 48 kHz:

  bank     samples  @48kHz    cue   window
  D_450     158967    3.31   4.00     5.30
  D_451      76084    1.59   3.70     5.60
  D_452     119562    2.49   0.00     8.34
  D_453     108608    2.26   4.70     4.60
  D_454     167828    3.50   0.00     9.50

2-3.5 s is also the right length for the lines. Nothing is missing, and the
17091-20563 Hz window from the previous commit is void with it -- its lower
bound came from the same misreading.

What survives, because it was measured rather than interpreted: the leading
region is XMA1 mono, the decode runs to the final frame, and cue values are
seconds.

Separately settled, and it is what exposed the error: each shared bank holds
ONE generic line. The 3-5 movies bound to a bank have IDENTICAL subtitle
text, 5 banks out of 5 -- "Rhino 3 has landed. Commencing resupply.",
"Resupply complete. You are cleared for take-off!", and so on.

That also explains the historical in-game rejection of hokyu_DS_s13A ->
VOICE_D_452 that started this whole thread. The line is generic, identical
for s02A/s07A/s08A/s13A. Someone expecting a stage-13-specific line would
hear the generic one and call it wrong -- while the binding is exactly right.
The disc said so; the subtitle text now says so independently.

Still open: whether the leading mono region is additional audio or an
alternate take, since the totals above add it to the RIFF sub-waves.

Artifact: examples/shared_bank_takes.rs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
Sylpheed RE agent
2026-08-26 00:20:57 +00:00
parent b324278ae9
commit b32006d2a8
3 changed files with 138 additions and 4 deletions

View File

@@ -0,0 +1,58 @@
//! Does a shared resupply voice bank hold ONE line or SEVERAL takes?
//!
//! This decides whether the `samples/movie` lower bound on the sample rate is
//! real. The test is static and does not need audio judgement: the movies that
//! share a bank each have their OWN subtitle table. If those texts are identical
//! the bank plausibly holds one generic line; if they differ, one bank is
//! serving several distinct spoken lines.
use std::collections::BTreeMap;
use sylpheed_formats::{movie_subtitle, PakArchive};
fn main() {
let disc = std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC");
let lang = PakArchive::open(format!("{disc}/dat/movie/eng.pak")).expect("eng.pak");
let text = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).expect("text pak");
let demo_text = movie_subtitle::build_demo_text(&text);
// bank → the movies bound to it, from the record table (see movie-subtitle-link).
let banks: BTreeMap<&str, Vec<&str>> = BTreeMap::from([
("VOICE_D_450", vec!["hokyu_LS_s02A", "hokyu_LS_s03A", "hokyu_LS_s06A"]),
(
"VOICE_D_451",
vec!["hokyu_LS_s09A", "hokyu_LS_s11A", "hokyu_LS_s15A", "hokyu_LS_s24A", "hokyu_LS_s27A"],
),
("VOICE_D_452", vec!["hokyu_DS_s02A", "hokyu_DS_s07A", "hokyu_DS_s08A", "hokyu_DS_s13A"]),
("VOICE_D_453", vec!["hokyu_LS_s02H", "hokyu_LS_s03H", "hokyu_LS_s06H", "hokyu_LS_s09H"]),
("VOICE_D_454", vec!["hokyu_DS_s07H", "hokyu_DS_s14H"]),
]);
for (bank, movies) in &banks {
println!("\n== {bank}");
let mut seen: BTreeMap<String, Vec<&str>> = BTreeMap::new();
for m in movies {
let cues = movie_subtitle::track_voice_cues(&lang, m);
let mut line = String::new();
for (demo, t) in &cues {
let txt = demo_text
.get(demo)
.map(|v| v.join(" "))
.unwrap_or_else(|| format!("<demo {demo}>"));
line.push_str(&format!("[{t:.2}] {txt} "));
}
if line.is_empty() {
line.push_str("<no cues>");
}
seen.entry(line.trim().to_string()).or_default().push(m);
}
for (line, ms) in &seen {
println!(" {:?}", ms);
println!(" {line}");
}
println!(
" -> {} distinct subtitle text(s) across {} movies",
seen.len(),
movies.len()
);
}
}