re: rebuild the movie manifest on the record table — the old counts measured pool strings
movie_manifest::parse now reads BASE_INFO's positional field keys (the game's own cutscene ids, stage*100 + slot) and follows each to its record, instead of scraping the string pool. The pool stores each distinct string once, so a REPEAT reference produced no token and read as "no binding". That single cause explains every wrong cell: 13 later references to VOICE_D_450..454, two to SUBTITLE_hokyu_LS_s11A.tbl, and MS01A's share of pwterop_s01a.prt. All 18 hokyu movies are bound, not five. Counts, verified independently by me against the disc before recording: 104 cutscene SLOTS binding 101 distinct MOVIES; 99 slots / 96 movies with a voice track, 99 / 96 with a subtitle, 22 / 22 with a telop. The docs' old 94 / 83 / 21 are exactly the counts of DISTINCT POOL STRINGS -- not wrong measurements, measurements of the wrong thing. Three denominators were being conflated; the new test pins all three. Two assertions in movie_manifest_disc.rs were false and are corrected: hokyu_DS_s13A binds VOICE_D_452 and resolves to eng\etc\VOICE_D_452.slb. The in-game verdict that rejected that value tested an INFERENCE from a shared demo id, on a decoder that discards 85-87% of banks in this class -- see voice-bank-leading-region.md, committed earlier today. The ~104 script ids are no longer open: they are literal positional keys, each naming its record, and all 104 resolve. The old "counts differ by three, positional pairing does not work" has a concrete cause -- three resupply movies are bound by TWO slots each. Also corrected: the naming convention has 3 subtitle exceptions (s24A/s27A borrow s11A's track) and 18 voice exceptions, not one and five. The legacy scraper is kept as a fallback for blobs with no record table, so the synthetic unit fixtures still exercise it. Artifacts: examples/movie_map_csv.rs regenerates the CSV, now slot-keyed (104 rows; the movie-keyed version silently dropped one slot of each duplicate). Disc tests green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
27
crates/sylpheed-formats/examples/movie_map_csv.rs
Normal file
27
crates/sylpheed-formats/examples/movie_map_csv.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
//! Regenerate `docs/re/captures/movie-subtitle-voice-map.csv` from the record
|
||||
//! table. Keyed by cutscene SLOT, not by movie: three resupply movies are bound
|
||||
//! by two slots each, and the old movie-keyed CSV silently dropped one of each.
|
||||
use sylpheed_formats::{movie_manifest, IdxdObject, PakArchive};
|
||||
|
||||
fn main() {
|
||||
let disc = std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC");
|
||||
let arc = PakArchive::open(format!("{disc}/dat/tables.pak")).expect("tables.pak");
|
||||
let manifest = arc
|
||||
.entries()
|
||||
.iter()
|
||||
.filter_map(|e| arc.read(e).ok())
|
||||
.find(|b| IdxdObject::is_idxd(b) && movie_manifest::is_manifest(b))
|
||||
.expect("movie manifest");
|
||||
|
||||
println!("slot,movie,telop,subtitle,voicetrack");
|
||||
for e in movie_manifest::parse(&manifest) {
|
||||
println!(
|
||||
"{},{}.wmv,{},{},{}",
|
||||
e.slot,
|
||||
e.movie,
|
||||
e.telop.unwrap_or_default(),
|
||||
e.subtitle.unwrap_or_default(),
|
||||
e.voice_token.unwrap_or_default()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,61 @@ const FIELD_KEYS: [&str; 4] = ["MOVIE", "VOICETRACK", "SUBTITLE", "TELOP"];
|
||||
/// mission/phase; otherwise (a blob without the key array) the kind is inferred
|
||||
/// from the movie name and `slot` is empty.
|
||||
pub fn parse(bytes: &[u8]) -> Vec<MovieEntry> {
|
||||
if let Some(rows) = parse_records(bytes) {
|
||||
return rows;
|
||||
}
|
||||
parse_string_pool(bytes)
|
||||
}
|
||||
|
||||
/// Read the manifest out of the IDXD **record table** — the authoritative path.
|
||||
///
|
||||
/// `BASE_INFO` carries 104 *positional* fields whose keys are the game's own
|
||||
/// cutscene ids (`stage*100 + slot`) and whose values name sibling records; each
|
||||
/// of those records carries `MOVIE` / `VOICETRACK` / `SUBTITLE` / `TELOP`.
|
||||
///
|
||||
/// This replaces a string-pool scrape that could not see a **repeat reference**:
|
||||
/// the pool stores each distinct string once, so the 13 later references to
|
||||
/// `VOICE_D_450..454`, the two to `SUBTITLE_hokyu_LS_s11A.tbl` and `MS01A`'s
|
||||
/// share of `pwterop_s01a.prt` produced no token and read as "no binding". That
|
||||
/// is why `hokyu_DS_s13A` was recorded as having no voice-over.
|
||||
///
|
||||
/// Returns `None` for a blob with no usable record table, so the synthetic
|
||||
/// fixtures in this module's tests still exercise the old reader.
|
||||
fn parse_records(bytes: &[u8]) -> Option<Vec<MovieEntry>> {
|
||||
let obj = crate::IdxdObject::parse(bytes).ok()?;
|
||||
let base = obj.record("BASE_INFO")?;
|
||||
// Ids ascend; the game's own order is play order.
|
||||
let mut ids: Vec<(u32, &str)> = base
|
||||
.fields
|
||||
.iter()
|
||||
.filter_map(|f| f.index().map(|i| (i, f.value.as_str())))
|
||||
.collect();
|
||||
if ids.is_empty() {
|
||||
return None;
|
||||
}
|
||||
ids.sort_unstable();
|
||||
let mut out = Vec::with_capacity(ids.len());
|
||||
for (_, slot) in ids {
|
||||
let Some(rec) = obj.record(slot) else { continue };
|
||||
let Some(movie) = rec.get("MOVIE") else { continue };
|
||||
let (kind, mission, phase) = classify_slot(slot);
|
||||
out.push(MovieEntry {
|
||||
slot: slot.to_string(),
|
||||
kind,
|
||||
mission,
|
||||
phase,
|
||||
movie: movie.trim_end_matches(".wmv").to_string(),
|
||||
voice_token: rec.get("VOICETRACK").map(after_plus),
|
||||
subtitle: rec.get("SUBTITLE").map(after_plus),
|
||||
telop: rec.get("TELOP").map(after_plus),
|
||||
});
|
||||
}
|
||||
(!out.is_empty()).then_some(out)
|
||||
}
|
||||
|
||||
/// The legacy string-pool reader. Kept for blobs with no record table; see
|
||||
/// [`parse_records`] for why it is no longer the primary path.
|
||||
fn parse_string_pool(bytes: &[u8]) -> Vec<MovieEntry> {
|
||||
let toks = ascii_runs(bytes, 3);
|
||||
|
||||
// Locate the slot-key array (`LOGO1` … first `<movie>.wmv`) and the value
|
||||
@@ -334,7 +389,11 @@ mod tests {
|
||||
assert_eq!(m[0].subtitle.as_deref(), Some("SUBTITLE_S13A.tbl"));
|
||||
assert_eq!(m[1].voice_token.as_deref(), Some("VOICE_D_450"));
|
||||
assert_eq!(m[1].kind, MovieKind::Supply);
|
||||
assert_eq!(m[2].voice_token, None, "hokyu_DS_s13A has no voice-over");
|
||||
// NB: in the synthetic blob below this movie really has no token. On the
|
||||
// retail disc it does — `VOICE_D_452` — which the string-pool reader
|
||||
// cannot see because the pool stores each value once and this is a
|
||||
// repeat reference. The disc test pins the real value.
|
||||
assert_eq!(m[2].voice_token, None);
|
||||
assert_eq!(m[2].kind, MovieKind::Supply);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,14 @@ fn binds_and_resolves_movie_voice() {
|
||||
};
|
||||
let (manifest, sounds) = load_manifest_and_sounds(&root);
|
||||
let entries = movie_manifest::parse(&manifest);
|
||||
assert!(entries.len() > 90, "expected ~101 movies, got {}", entries.len());
|
||||
// 104 cutscene SLOTS binding 101 distinct movies — three resupply movies are
|
||||
// bound by two slots each. Conflating the two is how the old counts went
|
||||
// wrong, so state which one this is.
|
||||
assert!(
|
||||
entries.len() > 90,
|
||||
"expected 101 distinct movies (104 slots), got {}",
|
||||
entries.len()
|
||||
);
|
||||
|
||||
// Standard story movie → VOICE_<movie> in <lang>\Movie\.
|
||||
assert_eq!(
|
||||
@@ -65,17 +72,26 @@ fn binds_and_resolves_movie_voice() {
|
||||
Some("eng\\etc\\VOICE_D_450.slb")
|
||||
);
|
||||
|
||||
// A resupply movie the game leaves unbound in the manifest → no DIRECT
|
||||
// binding. (Extending to unbound movies by shared demo line was verified
|
||||
// WRONG against the running game, so we do NOT resolve these.)
|
||||
// ❌ This movie was recorded as having NO voice binding, on the strength of
|
||||
// an in-game test that rejected `VOICE_D_452`. Both halves of that were
|
||||
// wrong. The record table binds it directly — record `S13_SUPPLY_ACROPOLIS`,
|
||||
// slot 1391 — and the value the game rejected was reached by *inferring*
|
||||
// from a shared demo id, which is different evidence for the same claim.
|
||||
// The decoder was also discarding 85–87 % of banks in this class, so the
|
||||
// listening test was not a test of the binding. See
|
||||
// docs/re/voice-bank-leading-region.md.
|
||||
//
|
||||
// The old reader could not see it: the pool stores each string once, so the
|
||||
// 13 later references to `VOICE_D_450..454` contribute no token at all.
|
||||
let e = entries
|
||||
.iter()
|
||||
.find(|e| e.movie == "hokyu_DS_s13A")
|
||||
.expect("hokyu_DS_s13A present in manifest");
|
||||
assert_eq!(e.voice_token, None, "hokyu_DS_s13A has no direct voice binding");
|
||||
assert_eq!(e.voice_token.as_deref(), Some("VOICE_D_452"));
|
||||
assert_eq!(
|
||||
movie_manifest::resolve_voice_entry(&manifest, &sounds, "hokyu_DS_s13A", VoiceLang::English),
|
||||
None
|
||||
movie_manifest::resolve_voice_entry(&manifest, &sounds, "hokyu_DS_s13A", VoiceLang::English)
|
||||
.as_deref(),
|
||||
Some("eng\\etc\\VOICE_D_452.slb")
|
||||
);
|
||||
|
||||
// Every resolved voice entry must actually exist in sound.pak.
|
||||
@@ -100,3 +116,54 @@ fn binds_and_resolves_movie_voice() {
|
||||
assert!(resolved > 80, "expected 80+ voiced movies, got {resolved}");
|
||||
assert!(missing.is_empty(), "resolved but absent in sound.pak: {missing:?}");
|
||||
}
|
||||
|
||||
/// The manifest's shape, pinned. These counts were wrong in the docs for a long
|
||||
/// time in a specific and instructive way: 94 / 83 / 21 are the numbers of
|
||||
/// **distinct pool strings**, which is exactly what a string-pool scraper can
|
||||
/// see, not the numbers of bound slots or movies.
|
||||
#[test]
|
||||
fn manifest_slot_and_movie_counts() {
|
||||
let Some(root) = disc_root() else {
|
||||
eprintln!("SKIP: set SYLPHEED_DISC");
|
||||
return;
|
||||
};
|
||||
let (manifest, _sounds) = load_manifest_and_sounds(&root);
|
||||
let entries = movie_manifest::parse(&manifest);
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
let movies: HashSet<&str> = entries.iter().map(|e| e.movie.as_str()).collect();
|
||||
assert_eq!(entries.len(), 104, "cutscene slots");
|
||||
assert_eq!(movies.len(), 101, "distinct movies");
|
||||
|
||||
let bound = |f: fn(&movie_manifest::MovieEntry) -> Option<&String>| {
|
||||
let slots = entries.iter().filter(|e| f(e).is_some()).count();
|
||||
let movies: HashSet<&str> = entries
|
||||
.iter()
|
||||
.filter(|e| f(e).is_some())
|
||||
.map(|e| e.movie.as_str())
|
||||
.collect();
|
||||
let distinct: HashSet<&str> = entries.iter().filter_map(|e| f(e)).map(String::as_str).collect();
|
||||
(slots, movies.len(), distinct.len())
|
||||
};
|
||||
// (slots, movies, distinct strings) — the third is what the docs used to report.
|
||||
assert_eq!(bound(|e| e.voice_token.as_ref()), (99, 96, 83));
|
||||
assert_eq!(bound(|e| e.subtitle.as_ref()), (99, 96, 94));
|
||||
assert_eq!(bound(|e| e.telop.as_ref()), (22, 22, 21));
|
||||
|
||||
// Three resupply movies are bound by two slots each — which is why "slots"
|
||||
// and "movies" are not interchangeable.
|
||||
let mut per_movie: HashMap<&str, Vec<&str>> = HashMap::new();
|
||||
for e in &entries {
|
||||
per_movie.entry(&e.movie).or_default().push(&e.slot);
|
||||
}
|
||||
let mut shared: Vec<&str> = per_movie
|
||||
.iter()
|
||||
.filter(|(_, slots)| slots.len() > 1)
|
||||
.map(|(m, _)| *m)
|
||||
.collect();
|
||||
shared.sort_unstable();
|
||||
assert_eq!(shared, ["hokyu_DS_s07A", "hokyu_DS_s07H", "hokyu_LS_s02A"]);
|
||||
|
||||
// Every id names a real record, so nothing dangles.
|
||||
assert!(entries.iter().all(|e| !e.slot.is_empty() && !e.movie.is_empty()));
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ Promote to a prose `structures/…md` file when a format needs behavioural notes
|
||||
| T8aD 2D texture | ✅ | `sylpheed-formats/src/t8ad.rs` | **100 % of the disc decodes** (19 216/19 216, measured). The "~15 % deferred variants" were a wrong model, not a variant: a surface is a list of **arbitrary sub-rectangles**, each with a 16-byte header of `dst X, dst Y, width, height`, not a 256×256 grid — `0x1c` is the **rectangle count**. Uncovered area stays transparent. **Colours ✅ CONFIRMED** ([k8888](structures/texture-color-k8888.md)) |
|
||||
| RATC bundle | ✅ | `sylpheed-formats/src/ratc.rs` | child listing confirmed. **"One level deep" is not a limitation — there is nothing deeper**: 2 859 bundles hold 18 002 children at depth 1 and **0 at depth 2**, with no parse failures. Nested RATC blobs are **leaf records that reference siblings by name** (`opt `, the sprite name): 3 311 leaves, all embedding sibling names, **10 144 of 10 148 references resolve**. The 4 that do not are one dangling asset — `pmbase.rat` → `pmbase.t32` in `GP_STAGE_CLEAR.pak`'s four language builds, and `pmbase.t32` is **on the disc nowhere** |
|
||||
| LSTA sprite list | ✅ | `sylpheed-formats/src/lsta.rs` | A display list of inline elements: **T8aD sprites and `PRMD` primitives**. The `count` at `0x04` is **exact and counts both** — `count == T8aD + PRMD` for **64/64** lists on the disc, which retires the old "a few entries disagree" note (it compared sprites against a total including primitives). **All 1 281 sprite frames decode** after the T8aD rectangle-list fix |
|
||||
| IXUD subtitle | 🟡/✅ | `sylpheed-formats/src/ixud.rs` + [movie link](movie-subtitle-link.md) | timed cues. **The movie↔subtitle↔voice link is solved — statically**, from the movie config record in `tables.pak` (schema `0x067025b9`), not from the running game as this row previously assumed: [101 movies mapped](captures/movie-subtitle-voice-map.csv), 94 with subtitles, 83 with voice, 21 with a telop overlay. 93 of 94 subtitle refs resolve in the language paks; **`SUBTITLE_S12B.tbl` is missing from all six languages** — a dangling reference on the disc. Naming is `SUBTITLE_<base>.tbl` / `VOICE_<base>` with six documented exceptions. The record's ~104 **script ids** are ❔ — positional pairing drifts by three because the IDXD pool dedupes repeated values |
|
||||
| IXUD subtitle | 🟡/✅ | `sylpheed-formats/src/ixud.rs` + [movie link](movie-subtitle-link.md) | timed cues. **The movie↔subtitle↔voice link is solved — statically**, and as of 2026-08-25 read from the IDXD **record table** rather than scraped from the string pool: **104 cutscene slots binding 101 distinct movies**, 99 slots / 96 movies with a subtitle, 99 / 96 with a voice track, 22 / 22 with a telop. ⚠️ The previous counts (94 / 83 / 21) were the numbers of **distinct pool strings** — a repeat reference contributes no token, so 13 later `VOICE_D_450..454` references read as "no binding". **All 18 hokyu movies are bound**, not five. 93 of the 94 distinct subtitle members resolve; `SUBTITLE_S12B.tbl` resolves in none of the six languages — a dangling reference on the disc. The ~104 **script ids are no longer ❔**: they are literal positional field keys in `BASE_INFO`, each naming its record, and all 104 resolve. `movie_manifest::parse` now reads the record table; CSV regenerated by `examples/movie_map_csv.rs` |
|
||||
| Fonts (ttf/otf/ttc) | ✅ | `sylpheed-formats/src/font.rs` | standard OpenType, parsed via ttf-parser |
|
||||
| XBG7 mesh | ✅/🟡 | `sylpheed-formats/src/mesh.rs` + `tests/mesh_disc.rs` ([xbg7](structures/xbg7-mesh.md)) | **6 294 resources, 6 209 decode (98.7 %), 82 searched-and-missed** (2026-08-12, up from 5 480 / 87.1 %). Five evidence-driven fixes got there: **distinct anchor assignment** (no two resources may claim one buffer — proved by a capture showing the container holds both mirrored `e106` hull halves), the connectivity cap replaced by a **winding-consistency gate at 0.70**, **structural requirements on pre-pivot sub-meshes** (index range, then exact pool coverage), and **filtering after the assignment** so a subset query cannot differ from the full decode. Validated against a runtime capture that names the file offset of every buffer the engine drew: **46/46 drawn buffers claimed, 45 anchored exactly**. **No real mesh now decodes differently in different containers** — all 89 remaining cross-container disagreements are interchangeable 24-vertex bounding boxes, which no anchoring rule can pin (monotone order re-tested and refuted). Remaining misses attribute to the degeneracy/extent gate (42), winding (31) and coverage (9); the first was probed and its "obvious" fix refuted. Every decoded sub-mesh covers its own vertex pool. **The `[index buffer][vertex buffer]` layout is now runtime-verified** (2026-08-13): with the F10 capture extended to log each draw's index buffer, all **42** drawn `Stage_S02` buffers match our decoded index count exactly, all 42 have their index union cover the pool exactly, and the 30 single-block cases all sit at `pad ≤ 3` — so `e106_eng_02_l`'s old rejection was the connectivity gate, not a misplaced index buffer. The `indices=` mystery was the capture keeping only the **first of several index batches** per buffer. **And comparing index VALUES found the biggest silent defect yet**: the anchor took the first `pad` that validated, so a block whose index data sits at pad 2 was read **one element late** — 76/93 captured runs matched, all 17 differences a one-element shift. Scoring pads by degenerate triangles + winding fixes it: **93/93** captured runs now match byte for byte, disc-wide degenerate runs **582 → 1** (the grouped path had the same bug; and two resources were anchored on a degenerate lookalike earlier in file order), **590 of 8 850** sub-meshes re-wired with 10 vertex anchors moved, resources decoded unchanged at 6 209. Cross-container minority decodes 89 → 96 — *because* the decoder improved: `_rou_f402_dead` now has a majority (32×25×8) so its seven wrong copies are named instead of hidden. One dirty run remains, blocked by distinct assignment on a 24-vertex box. **Then the descriptor gave up its last structural secret**: it declares a vertex layout **per sub-mesh** (`n201_01` → strides 24/24/24/**28**, capture-confirmed), and grouped selection must prefer the candidate explaining the **whole** pool rather than the first whose pivot validates — together they take never-decoding resources **85 → 47** (**6 247 / 6 294 = 99.25 %** decode), put `n201_01` on all four capture-proven offsets and raise the stage-05 capture oracle to **128/128**. The residual 47 is 30 pose/proxy composites (0.010-unit marker boxes), 6 `.DAT` particle composites, 8 damage/LOD variants and 3 props — not a threshold away |
|
||||
| Capital-ship part placement | ✅ | `sylpheed-formats/src/ship.rs` (static) + [runtime capture](ship-placement-runtime-capture.md) | Placement is **sound** (hull static-exact against the `e106` capture; cross-id mounting genuinely narrow, 2 pairs across 335 ships). The XBG7 mis-decode this row used to blame for "ships assemble wrong" — a shared turret ~100× too large in some containers — is **fixed** (2026-08-12, the exact-coverage requirement): `e303_wep_01` now decodes 49×23×42 everywhere and places at ±179 on the `e106` hull, and no real mesh disagrees across containers. A composite-node audit confirmed the assembler itself never applied a bad scale (all nodes scale 1.0, orthonormal). Still open: `static_assembly_matches_runtime_capture` walks capture parts only, so **extra** static placements cannot fail it |
|
||||
|
||||
@@ -1,102 +1,105 @@
|
||||
movie,telop,subtitle,voicetrack
|
||||
logo1.wmv,,,
|
||||
logo2.wmv,,,
|
||||
logo3.wmv,,,
|
||||
logo4.wmv,,,
|
||||
ADV.wmv,,,VOICE_ADV
|
||||
SYLPH_HD720p_8M-CBR_2ch.wmv,pwterop_s01a.prt,SYLPH_HD720p_8M-CBR_2ch.tbl,
|
||||
S00A.wmv,,SUBTITLE_S00A.tbl,VOICE_S00A
|
||||
S01A.wmv,,SUBTITLE_S01A.tbl,VOICE_S01A
|
||||
RT01A.wmv,pwrt01.prt,SUBTITLE_RT01A.tbl,VOICE_RT01A
|
||||
RT01B.wmv,,SUBTITLE_RT01B.tbl,VOICE_RT01B
|
||||
RT01C_1.wmv,,SUBTITLE_RT01C_1.tbl,VOICE_RT01C_1
|
||||
RT01C_2.wmv,,SUBTITLE_RT01C_2.tbl,VOICE_RT01C_2
|
||||
S02A.wmv,pwterop_s02a.prt,SUBTITLE_S02A.tbl,VOICE_S02A
|
||||
S02B.wmv,,SUBTITLE_S02B.tbl,VOICE_S02B
|
||||
S02C.wmv,,SUBTITLE_S02C.tbl,VOICE_S02C
|
||||
RT02A.wmv,pwrt02.prt,SUBTITLE_RT02A.tbl,VOICE_RT02A
|
||||
RT02B.wmv,,SUBTITLE_RT02B.tbl,VOICE_RT02B
|
||||
RT02C.wmv,,SUBTITLE_RT02C.tbl,VOICE_RT02C
|
||||
RT02D_1.wmv,,SUBTITLE_RT02D_1.tbl,VOICE_RT02D_1
|
||||
RT02D_2.wmv,,SUBTITLE_RT02D_2.tbl,VOICE_RT02D_2
|
||||
hokyu_LS_s02A.wmv,,SUBTITLE_hokyu_LS_s02A.tbl,VOICE_D_450
|
||||
hokyu_LS_s02H.wmv,,SUBTITLE_hokyu_LS_s02H.tbl,VOICE_D_453
|
||||
S03A.wmv,,SUBTITLE_S03A.tbl,VOICE_S03A
|
||||
RT03A.wmv,pwrt03.prt,SUBTITLE_RT03A.tbl,VOICE_RT03A
|
||||
RT03B.wmv,,SUBTITLE_RT03B.tbl,VOICE_RT03B
|
||||
RT03C.wmv,,SUBTITLE_RT03C.tbl,VOICE_RT03C
|
||||
RT03D.wmv,,SUBTITLE_RT03D.tbl,VOICE_RT03D
|
||||
hokyu_LS_s03A.wmv,,SUBTITLE_hokyu_LS_s03A.tbl,
|
||||
hokyu_LS_s03H.wmv,,SUBTITLE_hokyu_LS_s03H.tbl,
|
||||
S04A.wmv,,SUBTITLE_S04A.tbl,VOICE_S04A
|
||||
S04B.wmv,,SUBTITLE_S04B.tbl,VOICE_S04B
|
||||
RT04A.wmv,pwrt04.prt,SUBTITLE_RT04A.tbl,VOICE_RT04A
|
||||
RT04B.wmv,,SUBTITLE_RT04B.tbl,VOICE_RT04B
|
||||
hokyu_DS_s02A.wmv,,SUBTITLE_hokyu_DS_s02A.tbl,VOICE_D_452
|
||||
S05A.wmv,,SUBTITLE_S05A.tbl,VOICE_S05A
|
||||
RT05A.wmv,pwrt05.prt,SUBTITLE_RT05A.tbl,VOICE_RT05A
|
||||
RT05B.wmv,,SUBTITLE_RT05B.tbl,VOICE_RT05B
|
||||
RT05C.wmv,,SUBTITLE_RT05C.tbl,VOICE_RT05C
|
||||
S06A.wmv,pwterop_s06a.prt,SUBTITLE_S06A.tbl,VOICE_S06A
|
||||
S06B.wmv,,SUBTITLE_S06B.tbl,VOICE_S06B
|
||||
RT06A.wmv,pwrt06.prt,SUBTITLE_RT06A.tbl,VOICE_RT06A
|
||||
RT06B.wmv,,SUBTITLE_RT06B.tbl,VOICE_RT06B
|
||||
RT06C.wmv,,SUBTITLE_RT06C.tbl,VOICE_RT06C
|
||||
RT06D.wmv,,SUBTITLE_RT06D.tbl,VOICE_RT06D
|
||||
hokyu_LS_s06A.wmv,,SUBTITLE_hokyu_LS_s06A.tbl,
|
||||
hokyu_LS_s06H.wmv,,SUBTITLE_hokyu_LS_s06H.tbl,
|
||||
S07A.wmv,,SUBTITLE_S07A.tbl,VOICE_S07A
|
||||
S07B.wmv,,SUBTITLE_S07B.tbl,VOICE_S07B
|
||||
RT07A.wmv,pwrt07.prt,SUBTITLE_RT07A.tbl,VOICE_RT07A
|
||||
RT07B.wmv,,SUBTITLE_RT07B.tbl,VOICE_RT07B
|
||||
RT07C.wmv,,SUBTITLE_RT07C.tbl,VOICE_RT07C
|
||||
hokyu_DS_s07A.wmv,,SUBTITLE_hokyu_DS_s07A.tbl,
|
||||
hokyu_DS_s07H.wmv,,SUBTITLE_hokyu_DS_s07H.tbl,VOICE_D_454
|
||||
RT08A.wmv,pwrt08.prt,SUBTITLE_RT08A.tbl,VOICE_RT08A
|
||||
RT08B.wmv,,SUBTITLE_RT08B.tbl,VOICE_RT08B
|
||||
RT08C.wmv,,SUBTITLE_RT08C.tbl,VOICE_RT08C
|
||||
hokyu_DS_s08A.wmv,,SUBTITLE_hokyu_DS_s08A.tbl,
|
||||
S09B.wmv,,SUBTITLE_S09B.tbl,VOICE_S09B
|
||||
RT09A.wmv,pwrt09.prt,SUBTITLE_RT09A.tbl,VOICE_RT09A
|
||||
RT09B.wmv,,SUBTITLE_RT09B.tbl,VOICE_RT09B
|
||||
RT09C.wmv,,SUBTITLE_RT09C.tbl,VOICE_RT09C
|
||||
RT09D.wmv,,SUBTITLE_RT09D.tbl,VOICE_RT09D
|
||||
hokyu_LS_s09A.wmv,,SUBTITLE_hokyu_LS_s09A.tbl,VOICE_D_451
|
||||
hokyu_LS_s09H.wmv,,SUBTITLE_hokyu_LS_s09H.tbl,
|
||||
S10B.wmv,,SUBTITLE_S10B.tbl,VOICE_S10B
|
||||
RT10A.wmv,pwrt10.prt,SUBTITLE_RT10A.tbl,VOICE_RT10A
|
||||
RT10B.wmv,,SUBTITLE_RT10B.tbl,VOICE_RT10B
|
||||
S11A.wmv,,SUBTITLE_S11A.tbl,VOICE_S11A
|
||||
S11C.wmv,,SUBTITLE_S11C.tbl,VOICE_S11C
|
||||
RT11A.wmv,pwrt11.prt,SUBTITLE_RT11A.tbl,VOICE_RT11A
|
||||
RT11B.wmv,,SUBTITLE_RT11B.tbl,VOICE_RT11B
|
||||
RT11C.wmv,,SUBTITLE_RT11C.tbl,VOICE_RT11C
|
||||
hokyu_LS_s11A.wmv,,SUBTITLE_hokyu_LS_s11A.tbl,
|
||||
S12A.wmv,,SUBTITLE_S12A.tbl,VOICE_S12A
|
||||
S12B.wmv,,SUBTITLE_S12B.tbl,VOICE_S12B
|
||||
S12C.wmv,,SUBTITLE_S12C.tbl,VOICE_S12C
|
||||
RT12A.wmv,pwrt12.prt,SUBTITLE_RT12A.tbl,VOICE_RT12A
|
||||
RT12B_1.wmv,,SUBTITLE_RT12B_1.tbl,VOICE_RT12B_1
|
||||
RT12B_2.wmv,,SUBTITLE_RT12B_2.tbl,VOICE_RT12B_2
|
||||
S13A.wmv,,SUBTITLE_S13A.tbl,VOICE_S13A
|
||||
S13B.wmv,,SUBTITLE_S13B.tbl,VOICE_S13B
|
||||
RT13A.wmv,pwrt13.prt,SUBTITLE_RT13A.tbl,VOICE_RT13A
|
||||
RT13B_1.wmv,,SUBTITLE_RT13B_1.tbl,VOICE_RT13B_1
|
||||
RT13B_2.wmv,,SUBTITLE_RT13B_2.tbl,VOICE_RT13B_2
|
||||
hokyu_DS_s13A.wmv,,SUBTITLE_hokyu_DS_s13A.tbl,
|
||||
S14A.wmv,,SUBTITLE_S14A.tbl,VOICE_S14A
|
||||
RT14A.wmv,pwrt14.prt,SUBTITLE_RT14A.tbl,VOICE_RT14A
|
||||
RT14B.wmv,,SUBTITLE_RT14B.tbl,VOICE_RT14B
|
||||
RT14C.wmv,,SUBTITLE_RT14C.tbl,VOICE_RT14C
|
||||
hokyu_DS_s14H.wmv,,SUBTITLE_hokyu_DS_s14H.tbl,
|
||||
S15A.wmv,pwterop_s15a.prt,SUBTITLE_S15A.tbl,VOICE_S15A
|
||||
S15B.wmv,,SUBTITLE_S15B.tbl,VOICE_S15B
|
||||
S15C.wmv,,SUBTITLE_S15C.tbl,VOICE_S15C
|
||||
RT15A.wmv,pwrt15.prt,SUBTITLE_RT15A.tbl,VOICE_RT15A
|
||||
RT15B.wmv,,SUBTITLE_RT15B.tbl,VOICE_RT15B
|
||||
RT15C.wmv,,SUBTITLE_RT15C.tbl,VOICE_RT15C
|
||||
hokyu_LS_s15A.wmv,,SUBTITLE_hokyu_LS_s15A.tbl,
|
||||
S16A.wmv,pwterop_s16a.prt,SUBTITLE_S16A.tbl,VOICE_S16A
|
||||
RT16C.wmv,pwrt16.prt,SUBTITLE_RT16C.tbl,VOICE_RT16C
|
||||
hokyu_LS_s24A.wmv,,,
|
||||
hokyu_LS_s27A.wmv,,,
|
||||
slot,movie,telop,subtitle,voicetrack
|
||||
LOGO1,logo1.wmv,,,
|
||||
LOGO2,logo2.wmv,,,
|
||||
LOGO3,logo3.wmv,,,
|
||||
LOGO4,logo4.wmv,,,
|
||||
ADVERTISE_MOVIE,ADV.wmv,,,VOICE_ADV
|
||||
STAFF_ROLL,SYLPH_HD720p_8M-CBR_2ch.wmv,pwterop_s01a.prt,SYLPH_HD720p_8M-CBR_2ch.tbl,
|
||||
MS00A,S00A.wmv,,SUBTITLE_S00A.tbl,VOICE_S00A
|
||||
MS01A,S01A.wmv,pwterop_s01a.prt,SUBTITLE_S01A.tbl,VOICE_S01A
|
||||
STAGE01_PHASE01,RT01A.wmv,pwrt01.prt,SUBTITLE_RT01A.tbl,VOICE_RT01A
|
||||
STAGE01_PHASE02,RT01B.wmv,,SUBTITLE_RT01B.tbl,VOICE_RT01B
|
||||
STAGE01_PHASE_END_01,RT01C_1.wmv,,SUBTITLE_RT01C_1.tbl,VOICE_RT01C_1
|
||||
STAGE01_PHASE_END_02,RT01C_2.wmv,,SUBTITLE_RT01C_2.tbl,VOICE_RT01C_2
|
||||
MS02A,S02A.wmv,pwterop_s02a.prt,SUBTITLE_S02A.tbl,VOICE_S02A
|
||||
MS02B,S02B.wmv,,SUBTITLE_S02B.tbl,VOICE_S02B
|
||||
MS02C,S02C.wmv,,SUBTITLE_S02C.tbl,VOICE_S02C
|
||||
STAGE02_PHASE01,RT02A.wmv,pwrt02.prt,SUBTITLE_RT02A.tbl,VOICE_RT02A
|
||||
STAGE02_PHASE02,RT02B.wmv,,SUBTITLE_RT02B.tbl,VOICE_RT02B
|
||||
STAGE02_PHASE03,RT02C.wmv,,SUBTITLE_RT02C.tbl,VOICE_RT02C
|
||||
STAGE02_PHASE_END_01,RT02D_1.wmv,,SUBTITLE_RT02D_1.tbl,VOICE_RT02D_1
|
||||
STAGE02_PHASE_END_02,RT02D_2.wmv,,SUBTITLE_RT02D_2.tbl,VOICE_RT02D_2
|
||||
S02_SUPPLY_ACROPOLIS,hokyu_LS_s02A.wmv,,SUBTITLE_hokyu_LS_s02A.tbl,VOICE_D_450
|
||||
S02_SUPPLY_TANKER,hokyu_LS_s02H.wmv,,SUBTITLE_hokyu_LS_s02H.tbl,VOICE_D_453
|
||||
MS03A,S03A.wmv,,SUBTITLE_S03A.tbl,VOICE_S03A
|
||||
STAGE03_PHASE01,RT03A.wmv,pwrt03.prt,SUBTITLE_RT03A.tbl,VOICE_RT03A
|
||||
STAGE03_PHASE02,RT03B.wmv,,SUBTITLE_RT03B.tbl,VOICE_RT03B
|
||||
STAGE03_PHASE03,RT03C.wmv,,SUBTITLE_RT03C.tbl,VOICE_RT03C
|
||||
STAGE03_PHASE_END,RT03D.wmv,,SUBTITLE_RT03D.tbl,VOICE_RT03D
|
||||
S03_SUPPLY_ACROPOLIS,hokyu_LS_s03A.wmv,,SUBTITLE_hokyu_LS_s03A.tbl,VOICE_D_450
|
||||
S03_SUPPLY_TANKER,hokyu_LS_s03H.wmv,,SUBTITLE_hokyu_LS_s03H.tbl,VOICE_D_453
|
||||
MS04A,S04A.wmv,,SUBTITLE_S04A.tbl,VOICE_S04A
|
||||
MS04B,S04B.wmv,,SUBTITLE_S04B.tbl,VOICE_S04B
|
||||
STAGE04_PHASE01,RT04A.wmv,pwrt04.prt,SUBTITLE_RT04A.tbl,VOICE_RT04A
|
||||
STAGE04_PHASE02,RT04B.wmv,,SUBTITLE_RT04B.tbl,VOICE_RT04B
|
||||
S04_SUPPLY_ACROPOLIS,hokyu_DS_s02A.wmv,,SUBTITLE_hokyu_DS_s02A.tbl,VOICE_D_452
|
||||
MS05A,S05A.wmv,,SUBTITLE_S05A.tbl,VOICE_S05A
|
||||
STAGE05_PHASE01,RT05A.wmv,pwrt05.prt,SUBTITLE_RT05A.tbl,VOICE_RT05A
|
||||
STAGE05_PHASE02,RT05B.wmv,,SUBTITLE_RT05B.tbl,VOICE_RT05B
|
||||
STAGE05_PHASE_END,RT05C.wmv,,SUBTITLE_RT05C.tbl,VOICE_RT05C
|
||||
S05_SUPPLY_ACROPOLIS,hokyu_LS_s02A.wmv,,SUBTITLE_hokyu_LS_s02A.tbl,VOICE_D_450
|
||||
MS06A,S06A.wmv,pwterop_s06a.prt,SUBTITLE_S06A.tbl,VOICE_S06A
|
||||
MS06B,S06B.wmv,,SUBTITLE_S06B.tbl,VOICE_S06B
|
||||
STAGE06_PHASE01,RT06A.wmv,pwrt06.prt,SUBTITLE_RT06A.tbl,VOICE_RT06A
|
||||
STAGE06_PHASE02,RT06B.wmv,,SUBTITLE_RT06B.tbl,VOICE_RT06B
|
||||
STAGE06_PHASE03,RT06C.wmv,,SUBTITLE_RT06C.tbl,VOICE_RT06C
|
||||
STAGE06_PHASE_END,RT06D.wmv,,SUBTITLE_RT06D.tbl,VOICE_RT06D
|
||||
S06_SUPPLY_ACROPOLIS,hokyu_LS_s06A.wmv,,SUBTITLE_hokyu_LS_s06A.tbl,VOICE_D_450
|
||||
S06_SUPPLY_TANKER,hokyu_LS_s06H.wmv,,SUBTITLE_hokyu_LS_s06H.tbl,VOICE_D_453
|
||||
MS07A,S07A.wmv,,SUBTITLE_S07A.tbl,VOICE_S07A
|
||||
MS07B,S07B.wmv,,SUBTITLE_S07B.tbl,VOICE_S07B
|
||||
STAGE07_PHASE01,RT07A.wmv,pwrt07.prt,SUBTITLE_RT07A.tbl,VOICE_RT07A
|
||||
STAGE07_PHASE02,RT07B.wmv,,SUBTITLE_RT07B.tbl,VOICE_RT07B
|
||||
STAGE07_PHASE_END,RT07C.wmv,,SUBTITLE_RT07C.tbl,VOICE_RT07C
|
||||
S07_SUPPLY_ACROPOLIS,hokyu_DS_s07A.wmv,,SUBTITLE_hokyu_DS_s07A.tbl,VOICE_D_452
|
||||
S07_SUPPLY_TANKER,hokyu_DS_s07H.wmv,,SUBTITLE_hokyu_DS_s07H.tbl,VOICE_D_454
|
||||
STAGE08_PHASE01,RT08A.wmv,pwrt08.prt,SUBTITLE_RT08A.tbl,VOICE_RT08A
|
||||
STAGE08_PHASE02,RT08B.wmv,,SUBTITLE_RT08B.tbl,VOICE_RT08B
|
||||
STAGE08_PHASE_END,RT08C.wmv,,SUBTITLE_RT08C.tbl,VOICE_RT08C
|
||||
S08_SUPPLY_ACROPOLIS,hokyu_DS_s08A.wmv,,SUBTITLE_hokyu_DS_s08A.tbl,VOICE_D_452
|
||||
MS09B,S09B.wmv,,SUBTITLE_S09B.tbl,VOICE_S09B
|
||||
STAGE09_PHASE01,RT09A.wmv,pwrt09.prt,SUBTITLE_RT09A.tbl,VOICE_RT09A
|
||||
STAGE09_PHASE02,RT09B.wmv,,SUBTITLE_RT09B.tbl,VOICE_RT09B
|
||||
STAGE09_PHASE03,RT09C.wmv,,SUBTITLE_RT09C.tbl,VOICE_RT09C
|
||||
STAGE09_PHASE_END,RT09D.wmv,,SUBTITLE_RT09D.tbl,VOICE_RT09D
|
||||
S09_SUPPLY_ACROPOLIS,hokyu_LS_s09A.wmv,,SUBTITLE_hokyu_LS_s09A.tbl,VOICE_D_451
|
||||
S09_SUPPLY_TANKER,hokyu_LS_s09H.wmv,,SUBTITLE_hokyu_LS_s09H.tbl,VOICE_D_453
|
||||
MS10B,S10B.wmv,,SUBTITLE_S10B.tbl,VOICE_S10B
|
||||
STAGE10_PHASE01,RT10A.wmv,pwrt10.prt,SUBTITLE_RT10A.tbl,VOICE_RT10A
|
||||
STAGE10_PHASE_END,RT10B.wmv,,SUBTITLE_RT10B.tbl,VOICE_RT10B
|
||||
MS11A,S11A.wmv,,SUBTITLE_S11A.tbl,VOICE_S11A
|
||||
MS11C,S11C.wmv,,SUBTITLE_S11C.tbl,VOICE_S11C
|
||||
STAGE11_PHASE01,RT11A.wmv,pwrt11.prt,SUBTITLE_RT11A.tbl,VOICE_RT11A
|
||||
STAGE11_PHASE02,RT11B.wmv,,SUBTITLE_RT11B.tbl,VOICE_RT11B
|
||||
STAGE11_PHASE_END,RT11C.wmv,,SUBTITLE_RT11C.tbl,VOICE_RT11C
|
||||
S11_SUPPLY_ACROPOLIS,hokyu_LS_s11A.wmv,,SUBTITLE_hokyu_LS_s11A.tbl,VOICE_D_451
|
||||
MS12A,S12A.wmv,,SUBTITLE_S12A.tbl,VOICE_S12A
|
||||
MS12B,S12B.wmv,,SUBTITLE_S12B.tbl,VOICE_S12B
|
||||
MS12C,S12C.wmv,,SUBTITLE_S12C.tbl,VOICE_S12C
|
||||
STAGE12_PHASE01,RT12A.wmv,pwrt12.prt,SUBTITLE_RT12A.tbl,VOICE_RT12A
|
||||
STAGE12_PHASE_END_01,RT12B_1.wmv,,SUBTITLE_RT12B_1.tbl,VOICE_RT12B_1
|
||||
STAGE12_PHASE_END_02,RT12B_2.wmv,,SUBTITLE_RT12B_2.tbl,VOICE_RT12B_2
|
||||
S12_SUPPLY_ACROPOLIS,hokyu_DS_s07A.wmv,,SUBTITLE_hokyu_DS_s07A.tbl,VOICE_D_452
|
||||
S12_SUPPLY_TANKER,hokyu_DS_s07H.wmv,,SUBTITLE_hokyu_DS_s07H.tbl,VOICE_D_454
|
||||
MS13A,S13A.wmv,,SUBTITLE_S13A.tbl,VOICE_S13A
|
||||
MS13B,S13B.wmv,,SUBTITLE_S13B.tbl,VOICE_S13B
|
||||
STAGE13_PHASE01,RT13A.wmv,pwrt13.prt,SUBTITLE_RT13A.tbl,VOICE_RT13A
|
||||
STAGE13_PHASE_END_01,RT13B_1.wmv,,SUBTITLE_RT13B_1.tbl,VOICE_RT13B_1
|
||||
STAGE13_PHASE_END_02,RT13B_2.wmv,,SUBTITLE_RT13B_2.tbl,VOICE_RT13B_2
|
||||
S13_SUPPLY_ACROPOLIS,hokyu_DS_s13A.wmv,,SUBTITLE_hokyu_DS_s13A.tbl,VOICE_D_452
|
||||
MS14A,S14A.wmv,,SUBTITLE_S14A.tbl,VOICE_S14A
|
||||
STAGE14_PHASE01,RT14A.wmv,pwrt14.prt,SUBTITLE_RT14A.tbl,VOICE_RT14A
|
||||
STAGE14_PHASE02,RT14B.wmv,,SUBTITLE_RT14B.tbl,VOICE_RT14B
|
||||
STAGE14_PHASE_END,RT14C.wmv,,SUBTITLE_RT14C.tbl,VOICE_RT14C
|
||||
S14_SUPPLY_TANKER,hokyu_DS_s14H.wmv,,SUBTITLE_hokyu_DS_s14H.tbl,VOICE_D_454
|
||||
MS15A,S15A.wmv,pwterop_s15a.prt,SUBTITLE_S15A.tbl,VOICE_S15A
|
||||
MS15B,S15B.wmv,,SUBTITLE_S15B.tbl,VOICE_S15B
|
||||
MS15C,S15C.wmv,,SUBTITLE_S15C.tbl,VOICE_S15C
|
||||
STAGE15_PHASE01,RT15A.wmv,pwrt15.prt,SUBTITLE_RT15A.tbl,VOICE_RT15A
|
||||
STAGE15_PHASE02,RT15B.wmv,,SUBTITLE_RT15B.tbl,VOICE_RT15B
|
||||
STAGE15_PHASE_END,RT15C.wmv,,SUBTITLE_RT15C.tbl,VOICE_RT15C
|
||||
S15_SUPPLY_ACROPOLIS,hokyu_LS_s15A.wmv,,SUBTITLE_hokyu_LS_s15A.tbl,VOICE_D_451
|
||||
MS16A,S16A.wmv,pwterop_s16a.prt,SUBTITLE_S16A.tbl,VOICE_S16A
|
||||
STAGE16_PHASE01,RT16C.wmv,pwrt16.prt,SUBTITLE_RT16C.tbl,VOICE_RT16C
|
||||
S24_SUPPLY_ACROPOLIS,hokyu_LS_s24A.wmv,,SUBTITLE_hokyu_LS_s11A.tbl,VOICE_D_451
|
||||
S27_SUPPLY_ACROPOLIS,hokyu_LS_s27A.wmv,,SUBTITLE_hokyu_LS_s11A.tbl,VOICE_D_451
|
||||
|
||||
|
@@ -30,36 +30,59 @@ VOICE_S02A VOICETRACK
|
||||
language paks (`eng`, `fra`, `deu`, `esp`, `ita`, `jpn`) each carry the same
|
||||
member names — so the prefix in the table is just the build's language and the
|
||||
member name is the portable part. The recovered map is
|
||||
[`captures/movie-subtitle-voice-map.csv`](captures/movie-subtitle-voice-map.csv):
|
||||
**101 movies, 94 with a subtitle, 83 with a voice track, 21 with a telop.**
|
||||
[`captures/movie-subtitle-voice-map.csv`](captures/movie-subtitle-voice-map.csv),
|
||||
now regenerated from the **record table** (`examples/movie_map_csv.rs`) and keyed
|
||||
by cutscene **slot**:
|
||||
|
||||
**104 slots binding 101 distinct movies.** 99 slots / 96 movies have a subtitle,
|
||||
99 slots / 96 movies have a voice track, 22 slots / 22 movies have a telop —
|
||||
drawing on 94 distinct subtitle tables, 83 distinct voice banks and 21 distinct
|
||||
telop overlays.
|
||||
|
||||
### ❌ The old counts were measuring the wrong thing
|
||||
|
||||
This file used to say *"101 movies, 94 with a subtitle, 83 with a voice track, 21
|
||||
with a telop"*. Those three numbers are exactly the counts of **distinct pool
|
||||
strings** — which is all a string-pool scraper can see. The pool stores each
|
||||
value once, so a *repeat* reference contributes no token and read as "no
|
||||
binding": 13 later references to `VOICE_D_450..454`, two to
|
||||
`SUBTITLE_hokyu_LS_s11A.tbl`, and `MS01A`'s share of `pwterop_s01a.prt`.
|
||||
|
||||
Slots, movies and distinct strings are three different denominators and this file
|
||||
conflated them. Pinned by `manifest_slot_and_movie_counts`.
|
||||
|
||||
## Verified, and one real defect
|
||||
|
||||
Every subtitle reference was looked up in the language paks. **93 of 94 resolve.
|
||||
`SUBTITLE_S12B.tbl` resolves in none of the six languages** — a dangling
|
||||
reference on the retail disc, not a decode failure on our side. Worth knowing
|
||||
before the reimplementation treats a missing subtitle table as a bug of its own.
|
||||
Every subtitle reference was looked up in the language paks. **93 of the 94
|
||||
distinct members resolve. `SUBTITLE_S12B.tbl` resolves in none of the six
|
||||
languages** — a dangling reference on the retail disc, not a decode failure on
|
||||
our side. Worth knowing before the reimplementation treats a missing subtitle
|
||||
table as a bug of its own.
|
||||
|
||||
The naming is a convention with two documented exceptions:
|
||||
The naming is a convention, with more exceptions than were recorded:
|
||||
|
||||
- subtitles are `SUBTITLE_<movie base>.tbl` — the only departure is the staff
|
||||
roll, `SYLPH_HD720p_8M-CBR_2ch.wmv` → `SYLPH_HD720p_8M-CBR_2ch.tbl`;
|
||||
- voice tracks are `VOICE_<movie base>` — except five supply-run movies
|
||||
(`hokyu_LS_s02A`, `hokyu_LS_s02H`, `hokyu_DS_s02A`, `hokyu_DS_s07H`,
|
||||
`hokyu_LS_s09A`), which point at shared lines `VOICE_D_450…454`.
|
||||
- subtitles are `SUBTITLE_<movie base>.tbl`, with **3** departures — the staff
|
||||
roll (`SYLPH_HD720p_8M-CBR_2ch.wmv` → `SYLPH_HD720p_8M-CBR_2ch.tbl`), and
|
||||
`hokyu_LS_s24A` / `hokyu_LS_s27A`, which **borrow `SUBTITLE_hokyu_LS_s11A.tbl`**;
|
||||
- voice tracks are `VOICE_<movie base>` except **all 18 hokyu movies** (21 supply
|
||||
slots), which share `VOICE_D_450…454`. The old text said five; the other 13
|
||||
were invisible to the scraper for the dedup reason above.
|
||||
|
||||
So a reimplementation can resolve a movie's subtitle and voice **by convention**,
|
||||
and should fall back to this table for those six cases rather than assuming it.
|
||||
So a reimplementation should read this table, **not** apply the convention with a
|
||||
short exception list.
|
||||
|
||||
## What is *not* recoverable from this record
|
||||
## ✅ The script ids ARE recoverable — the old ❔ is closed
|
||||
|
||||
The record also lists 104 **script ids** — the names stage scripts use to trigger
|
||||
a movie. This file used to say pairing them with the movie groups "does not
|
||||
work: the counts differ by three". The counts differ by three for a concrete
|
||||
reason: **three resupply movies are bound by two slots each**
|
||||
(`hokyu_LS_s02A` by S02 and S05, `hokyu_DS_s07A` and `hokyu_DS_s07H` by S07 and
|
||||
S12). Positional pairing was never the right model — the ids are stored as
|
||||
**literal positional field keys** in `BASE_INFO`, each naming its record
|
||||
directly, so every one of the 104 resolves with nothing to infer. The id is
|
||||
`stage*100 + slot`, with 91/92 for `_SUPPLY_ACROPOLIS`/`_SUPPLY_TANKER`.
|
||||
|
||||
The record also lists ~104 **script ids** (`LOGO1`, `ADVERTISE_MOVIE`,
|
||||
`STAFF_ROLL`, `MS00A`, `STAGE01_PHASE01`, `S02_SUPPLY_ACROPOLIS`, …) — the names
|
||||
stage scripts use to trigger a movie. Pairing them positionally with the 101
|
||||
movie groups **does not work**: the counts differ by three and the drift is
|
||||
visible at the tail, where the last ids (`S24_SUPPLY_ACROPOLIS`,
|
||||
`S27_SUPPLY_ACROPOLIS`) would have to map to `hokyu_LS_s24A.wmv` /
|
||||
`hokyu_LS_s27A.wmv` and under positional pairing do not.
|
||||
|
||||
The cause is the same IDXD property that bit the Arsenal table: **the string pool
|
||||
stores each distinct string once**, so an id whose movie was already named
|
||||
|
||||
@@ -73,7 +73,7 @@ guessing both misses real bindings and invents tracks for silent movies:
|
||||
- **5 `hokyu_*` resupply movies bind to in-mission radio clips** — e.g.
|
||||
`hokyu_LS_s02A → VOICE_D_450`, which lives in `<lang>\etc\`, *not* `Movie`.
|
||||
A `VOICE_<movie>` guess would never find these.
|
||||
- **18 movies have no direct voice token** = 4 boot logos + 1 HD test pattern +
|
||||
- **❌ WITHDRAWN — 18 movies have no direct voice token** = 4 boot logos + 1 HD test pattern +
|
||||
**13 `hokyu_*` movies** (incl. `hokyu_DS_s13A`). Only the manifest's **direct**
|
||||
bindings are trusted for playback.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user