Files
Sylpheed/docs/re/movie-subtitle-link.md
Sylpheed RE agent 0cf0cda09e 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
2026-08-25 23:13:41 +00:00

99 lines
4.6 KiB
Markdown

# The movie ↔ subtitle ↔ voice link ✅ (static)
[`INDEX`](INDEX.md) carried the IXUD subtitle format as 🟡 with **"movie↔track
link unknown (dynamic item)"** — i.e. a question expected to need the running
game. It does not: the link is a config record on the disc, and it verifies
against the disc's own contents.
## Where it is
`dat/tables.pak`, the IDXD record with schema `0x067025b9`. Its keys come from
the movie game-part — the executable carries them next to the symbol
`silph::GamePart_Movie::Impl::OnPrepare`:
```
PATH = dat\movie\ SUBTITLE_FONT = fra.pak+GOTHICB.TTF
SUBTITLE_Y = 600 SUBTITLE_HEIGHT = 33
MOVIE TELOP SUBTITLE VOICETRACK
```
Each movie is one group of values:
```
S02A.wmv MOVIE
fra.pak+pwterop_s02a.prt TELOP (optional overlay)
fra.pak+SUBTITLE_S02A.tbl SUBTITLE
VOICE_S02A VOICETRACK
```
`<lang>.pak+<member>` resolves inside `dat/movie/<lang>.pak`, and the six
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),
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 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 more exceptions than were recorded:
- 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 should read this table, **not** apply the convention with a
short exception list.
## ✅ 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 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
earlier contributes no new value token and its group silently disappears. The id
list is therefore ❔ until the binary node/index region is decoded — and the CSV
is deliberately keyed by **movie file**, which is the part the data actually
supports.
## Note for the intro-movie investigation
`ADV.wmv` — the one the emulator work is about — has `VOICETRACK = VOICE_ADV`
and **no subtitle and no telop**. So a correct playback of it needs the video and
that one voice track, nothing else from this table.