Files
Syplheed-Reborn/docs/HANDOFF-movie-voice-subtitles-2026-07-19.md
MechaCat02 95de29f290 feat(formats,viewer): movie subtitles, voice decode, and the movie manifest
The movie cutscene subtitle + voice pipeline, driven by the ADVERTISE_MOVIE
manifest (the authoritative movie -> subtitle -> voice index). Also flushes
several sessions of local WIP (async viewer loading, grouped-pool XBG7/hero-ship
decode, drawlog tooling). See docs/HANDOFF-movie-voice-subtitles-2026-07-19.md.

Subtitles (movie_subtitle.rs):
- Full movie->track->text chain; join multi-line captions sharing one timing
  (fixes S13A dropped "Look at it father" line); overlap-safe active_cues();
  Latin-1 accents preserved.

Voice (slb.rs): XACT .slb -> XMA1 RIFF; take the FIRST sub-wave bounded by its
declared data size (fixes S10-S16 alternate-take garble); list_voice_clips.

Manifest (movie_manifest.rs): parse ADVERTISE_MOVIE (0x5B983A08) for the real
movie->voice binding (not always VOICE_<movie>; e.g. hokyu -> VOICE_D_* in etc\).
Resolve the token's sound.pak path via sounds.tbl. DIRECT bindings only — the
demo-id shared-clip fallback for unbound hokyu movies was verified WRONG in-game
and reverted (unbound hokyu stay unvoiced; correct join key is an OPEN problem).

Viewer: manifest-driven voice (movie player toggle + solo button), standalone
"Voice Lines" browser, stacked caption overlay.

Tests: 46 formats-lib + 11 viewer-lib + movie_manifest/movie_subtitle/slb disc
tests; full workspace green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:15:41 +02:00

5.7 KiB
Raw Blame History

Handoff — movie subtitles, voice, and the movie manifest (2026-07-19)

Branch feature/ipfb-idxd-parser. This commit flushes several sessions of local WIP; the new, finished work is the movie subtitle + voice + audio pipeline and the movie manifest. One item is deliberately left open (see §4).

1. What's DONE and verified (static RE + tests)

Movie subtitles (crates/sylpheed-formats/src/movie_subtitle.rs)

  • Full movie→track→text chain (see docs/re/structures/movie-subtitles.md).
  • Multi-line caption fix: a caption stored as several consecutive text tokens sharing one timing (S13A: "Look at it father" + "& beautiful isn't it" @ 01:14.80) — parse_track now joins them with \n. Previously all but the last line were dropped. Disc test asserts both lines survive.
  • Overlap rendering: MovieSubtitles::active_cues(t) returns every cue active at t; the viewer stacks them (was: only the first cue shown).
  • Umlauts / Latin-1 accents preserved (utf16le_tokens); Japanese label romanized (CJK font still a known gap — see §5).

Voice decode (crates/sylpheed-formats/src/slb.rs)

  • sound.pak entries are XACT .slb banks wrapping XMA1 (fmt tag 0x0165, 48 kHz, mono content). to_xma_riff rebuilds a decodable RIFF; FFmpeg xma1 decodes it. Downmix mono via -af pan=mono|c0=c0.
  • Multi-subwave fix: take the FIRST sub-wave bounded by its declared data size — NOT data..EOF (which appended later takes = the S10S16 garble). Verified: S13A→83.78s == video.
  • list_voice_clips enumerates the voice library from sounds.tbl.

Movie manifest (crates/sylpheed-formats/src/movie_manifest.rs) — the index

  • tables.pak entry ADVERTISE_MOVIE (IDXD, hash 0x5B983A08) is the authoritative movie → subtitle → voice map. Located by shape (is_manifest), parsed by grouping the string pool on .wmv.
  • Authoritative voice binding replaces the old VOICE_<movie> guess: 101 movies, 83 with a VOICE_ token, 18 without. The token is NOT always VOICE_<movie> — 5 hokyu_* movies bind to VOICE_D_450..454 in \etc\, which a guess would miss. Token's subdir varies (Movie/etc/Voice) → resolve via sounds.tbl. Disc test: all 83 resolved entries exist in sound.pak.

Viewer wiring (iso_loader.rs, ui.rs)

  • resolve_movie_voice_clip reads the manifest + sounds.tbl (DIRECT bindings only) → handle_voice_request (movie player 🗣 toggle) and the 🎧 solo button (RequestAudio.movie) use it. Unbound movies correctly post no audio.
  • Standalone View → 🎙 Voice Lines browser (filter + ▶ play any sound.pak clip) via VoiceLibrary + AudioPreview.

Tests: 46 formats-lib + 11 viewer-lib + disc (movie_manifest_disc, movie_subtitle_disc, slb_disc) all green. Full cargo test --workspace green.

2. Also bundled in this commit (prior local WIP, not this session's focus)

  • Viewer async stage/model loading (off-thread prepare_xpr, cancellable) — iso_loader.rs/ui.rs/camera.rs. See memory reborn-viewer-async-loading.
  • Grouped-pool XBG7 / hero-ship decode refinements in mesh.rs, cli/main.rs, mesh_disc.rs. See memory reborn-ship-drawlog / reborn-saber-texture-investigation.
  • tools/analyze_drawlog_wvp.py, tools/extract_movie_subtitles.py.

These build and test green but were not re-verified for behaviour this session.

3. USER TO VERIFY IN GUI (couldn't be tested from CLI)

  1. S13A cutscene shows BOTH lines of the "Look at it father" caption.
  2. Story-movie voice (S13A etc.) lines up with the video after the subwave fix.
  3. The 5 directly-bound hokyu_* movies play voice; other hokyu are silent (see §4).
  4. View → 🎙 Voice Lines browser lists and plays clips.

4. OPEN PROBLEM — unbound-hokyu resupply voice (do NOT re-guess)

The resupply cutscenes clearly SHARE voice recordings (video-only varies per mission), but the correct join key for the 13 unbound hokyu_* movies is unknown:

  • Manifest DIRECTLY binds only 5: LS_s02A→450, LS_s09A→451, DS_s02A→452, LS_s02H→453, DS_s07H→454 (grouping verified against the raw layout).
  • Keying unbound movies by subtitle demo id (600→450…604→454, so hokyu_DS_s13A demo602→VOICE_D_452) was implemented, tested against the running game by the user, and is WRONG (wrong line). It has been reverted — unbound hokyu now stay unvoiced rather than play wrong audio.
  • Red flag: only VOICE_D_450..454 exist; decoded durations 450=2.8s 451=1.6s 452=2.2s but 453=0.14s 454=0.43s — far too short for the spoken line, so these .slb are likely multi-subwave / not cleanly sliced (same class as the deferred B/C banks below).

Next-box options: (a) get the real join key from mission-event data (mission scripts/IDXD tables that trigger resupply), or (b) build a proper multi-subwave .slb decoder and verify audio by ear. Needs a concrete calibration clue from the user (e.g. "s13A should sound like " or the actual spoken words).

5. Other deferred items (unchanged)

  • ~49 "layout-B/C" movie/voice banks (most RT + S07B/S11A/S12B/S13B) + some individual clips need an XMA1 packet/seek-table reassembler.
  • CJK/Japanese text rendering in the viewer (needs a CJK TTF via egui FontDefinitions).
  • Texture colours (channel order / sRGB) still on the dynamic-RE backlog.

6. Resume checklist (next box)

  1. git pull on feature/ipfb-idxd-parser; set SYLPHEED_DISC=<extract root> (had .../sylph_extract).
  2. Build guardrail: CARGO_BUILD_JOBS=4 always (15 GB box; full -j OOM'd before).
  3. cargo test --workspace (with SYLPHEED_DISC) should be green.
  4. Pick up §4 (unbound-hokyu voice) — the only open thread from this session.