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()
);
}
}

View File

@@ -1033,6 +1033,24 @@ premise was wrong.**
Candidates: the **7 `.embsec_` sections** (VAs 0x84D00000x86AC000, ~129 KB
total, executable) or a hashed record in `hidden/MiscBin.pak`. **Finding it
gives the actual per-phase clear condition for every stage.**
***(2026-08-26) RETRACTED — "the resupply banks are missing audio."** The
error was mine and 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. **A cue is when the line STARTS** — the voice
plays from the cue to at most the movie's end. Under that reading all five
banks fit at plain **48 kHz** (3.31 s in a 5.30 s window, 2.26 s in 4.60 s, …),
and 23.5 s is the right length for the lines. Nothing is missing. The
17 09120 563 Hz window from the previous entry is void with it.
**Each shared bank is ONE generic line** — the 35 movies sharing a bank have
**identical subtitle text**, 5/5 banks (`examples/shared_bank_takes.rs`).
🎯 **That also explains the historical in-game rejection** of
`hokyu_DS_s13A → VOICE_D_452`: the line is the generic "Resupply complete. You
are cleared for take-off!", the same for s02A/s07A/s08A/s13A. Someone expecting
a stage-13-specific line would call the generic one wrong while the binding is
right.
❔ Still open: whether the leading XMA1-mono region is *additional* audio or an
alternate take of the same line — the totals above add it to the RIFF sub-waves
and would double-count if it is a duplicate.
***(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

View File

@@ -1,9 +1,11 @@
# The resupply voice banks — the decoder discards up to 87 % of them
Status: ❌ the recorded "multi-subwave / not cleanly sliced" explanation is
**REFUTED**. ✅ the real defect is measured. 🟡 the in-game verdict that rejected
the `hokyu_DS_s13A` voice binding is therefore **not** evidence against it. ❔ the
binding still needs audio verification, which needs a human.
**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
bank fits at ordinary 48 kHz.
Artifact: `crates/sylpheed-formats/examples/voice_bank_shape.rs`.
@@ -120,7 +122,39 @@ it.** Two measurements killed it:
The refuted attempt is recorded in `slb.rs` beside the code, so the next person
does not re-derive the arithmetic and re-make the same change.
## ✅ Audio really is missing — proven by the subtitles, not by impression
## ❌ RETRACTED — "audio is missing". The cue is a START time.
**This is the load-bearing error of this whole file, and it is mine.** I treated
a subtitle cue as a timestamp that must fall *inside* the voice clip, and
concluded that 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 in the window between the cue and the end of the movie. Under
that reading every bank fits comfortably — at plain **48 kHz**:
| bank | samples | at 48 kHz | cue | window to movie end | fits |
|---|---|---|---|---|---|
| `VOICE_D_450` | 158 967 | 3.31 s | 4.00 s | 5.30 s | ✅ |
| `VOICE_D_451` | 76 084 | 1.59 s | 3.70 s | 5.60 s | ✅ |
| `VOICE_D_452` | 119 562 | 2.49 s | 0.00 s | 8.34 s | ✅ |
| `VOICE_D_453` | 108 608 | 2.26 s | 4.70 s | 4.60 s | ✅ |
| `VOICE_D_454` | 167 828 | 3.50 s | 0.00 s | 9.50 s | ✅ |
23.5 s is also the right length for the lines themselves ("Rhino 3 has landed.
Commencing resupply."). So there is **no evidence any audio is missing**, and the
sample rate is plausibly the ordinary 48 kHz.
❌ The **17 091 20 563 Hz window** from the previous entry is void with it: its
lower bound came from the same misreading.
What survives from that work: the leading region is XMA1 mono, the decode runs to
the final frame, and the cue values really are seconds. Those were measured. Only
the interpretation of what a cue *means* was wrong — and it was wrong for three
iterations, through two write-ups that each called the result "proven".
### The original argument, kept
Below is what I wrote when I believed a cue had to fall inside the clip.
The corpus's original wording was that 0.14 s is "far too short for the spoken
line". That is a judgement, and judgements about audio cannot be made in this
@@ -144,6 +178,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.
## ✅ Each shared bank is ONE generic line
The banks are bound to 35 movie slots each, and the movies sharing a bank have
**identical subtitle text** — 5 banks out of 5 (`examples/shared_bank_takes.rs`):
| bank | movies | the line |
|---|---|---|
| `VOICE_D_450` | 3 | "Rhino 3 has landed. Commencing resupply." |
| `VOICE_D_451` | 3 (+2 with no cue) | "Rhino Leader has landed. Commencing resupply." |
| `VOICE_D_452` | 4 | "Resupply complete. You are cleared for take-off!" |
| `VOICE_D_453` | 4 | "All right, beginning resupply." |
| `VOICE_D_454` | 2 | "Resupply complete. Switching to take-off sequence." |
So a bank holds one line, reused across stages — not several takes.
### 🎯 And this explains the historical in-game rejection
`hokyu_DS_s13A → VOICE_D_452` was rejected as "the wrong recording". But
`VOICE_D_452`'s line is the **generic** "Resupply complete. You are cleared for
take-off!", identical for `s02A`, `s07A`, `s08A` *and* `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 says so, and now the subtitle text says so
independently.
## ✅ The leading region IS XMA1 — but **mono**, not stereo
Retried with the `fmt ` chunk built to `slb::synth_xma1_fmt`'s exact byte layout