Confidence

Inferred — structural reading. Compact-table layout pinned from a captured FMV-overlay-resident save. Runtime FMV-state table layout lifted from the str_fmv overlay's data section, cross-validated against the play loop's offset reads. The retail trigger range (0..=8) is pinned by the per-STR FMV trigger corpus (nine save states, _DAT_8007BA78 ∈ 0..=8).

Compact table layout (0x801CAE40, 6 × 24 B)

+0x00  char[12]  filename     "MV1.STR;1\0..." (null-padded; libcd path shape)
+0x0C  u32       reserved     zero across all observed entries
+0x10  u32       bcd_msf      byte 0 = BCD minute, 1 = BCD second,
                              2 = BCD frame, 3 = zero
+0x14  u32       size         file size in bytes (LE)

bcd_msf is the standard libcd CdlLOC representation: each byte is two BCD digits (high nibble = tens, low nibble = ones). Convert to absolute LBA with the standard CD identity:

LBA = ((M * 60) + S) * 75 + F - 150

The compact table's name fields are dev-only labels and do not match what the disc reader resolves at the table's BCD MSF. The first five entries shift by one against the disc layout (entry [0] "MV1" points at disc MV2, etc.); entry [5] "MV6" points at the unrelated XA15.XA. The compact table is a separate dev/init lookup, not the FMV play engine's resolver.

Path string table (0x801CE810)

The runtime FMV-state slots' path-pointer field (+0x00) points into a packed null-terminated string table. Nine paths in storage order:

OffsetString
+0x008\DATA\MOV.STR;1
+0x018\DATA\MOV15.STR;1
+0x02C\MOV\MV1A.STR;1
+0x03C\MOV\MV6.STR;1
+0x04C\MOV\MV5.STR;1
+0x05C\MOV\MV4.STR;1
+0x06C\MOV\MV3.STR;1
+0x07C\MOV\MV2.STR;1
+0x08C\MOV\MV1.STR;1

Three of the nine paths (\DATA\MOV.STR;1, \DATA\MOV15.STR;1, \MOV\MV1A.STR;1) are dev-only — the corresponding files are not on the retail disc.

Runtime FMV-state table (0x801D0A6C, 12 × 64 B)

The play loop's selector lives at 0x801CECA0:

0x801CEC94: lh   v0, -0x4588(s0)        ; v0 = (s16) _DAT_8007BA78
0x801CEC9C: sll  v0, v0, 6              ; v0 = fmv_id * 64
0x801CECA0: jal  FUN_801CF098
0x801CECA4:  addu a1, v0, 0x801D0A6C    ; param_2 = &runtime_table[fmv_id]

The 64-byte slot has the shape [u32 path_ptr, u32 flag, u32 segment_id, u32 frame_count, ..., u32 width, u32 height, ...]. The play loop reads the path pointer at +0x00 and opens the file via libcd.

_DAT_8007BA78 is a s16 written by the field-VM FMV-trigger op (0x4C 0xE2 lo hi …); see the cutscene subsystem for the full opcode trace.

Authoritative runtime mapping

fmv_idPath resolvedNotes
0\MOV\MV1.STR;1intro logo (also fired by title-screen attract loop)
1\MOV\MV3.STR;1first segment (start sector 1)
2\MOV\MV3.STR;1second segment (start-sector offset +0x1A5)
3\MOV\MV4.STR;1
4\MOV\MV6.STR;1
5\DATA\MOV15.STR;1dev-only path (file not on retail disc)
6..=11\DATA\MOV.STR;1dev-only path (file not on retail disc)

MV2.STR and MV5.STR exist on the retail disc but are never referenced by any FMV slot — the runtime FMV play engine never opens them. The same authoritative mapping ships in legaia_engine_core::cutscene::fmv_index_to_str_filename.

Per-STR FMV trigger corpus

The current corpus carries nine save states captured RIGHT before each FMV begins playing, one per _DAT_8007BA78 value (fmv_id ∈ 0..=8). Each save pins:

  • _DAT_8007BA78 = expected_fmv_id (s16 LE)
  • _DAT_8007B83C = 0x1A (StrInit game mode)
  • _DAT_8007BAC8 = 2000 (BGM ID; global pool index 0)
  • Active scene = map01 (one of the seven mid-game FMV-trigger field scenes)
  • recover_base() = 0x80139530 (map01's field-pack base)

The 0x4C 0xE2 lo hi byte sequence does NOT appear in the field-pack RAM region for any save in the corpus — the saves were generated by debug-menu-driven trigger paths, NOT by stepping the field VM through a per-scene FMV trigger op. The corpus pins the trigger-side state across the full 0..=8 range but does not disambiguate which fmv_id each of the seven mid-game scenes' field-VM bytecode writes at runtime.

The corpus is codified at legaia_engine_core::capture_observations::cutscene_trigger_corpus and exercised by the disc-gated test cutscene_trigger_corpus_pins_fmv_id_across_nine_saves.

Rust API

use legaia_asset::str_fmv_table;
use legaia_engine_core::cutscene::fmv_index_to_str_filename;

// Slice the compact table out of a captured main-RAM image.
let off = (0x801CAE40 - 0x80000000) as usize;
let bytes = &main_ram[off..off + 6 * str_fmv_table::ENTRY_STRIDE];

// Parse 6 entries; zero-filled trailing slots are dropped silently.
let entries = str_fmv_table::parse_entries(bytes, 6).expect("table parses");

// Resolve fmv_id (the value the field VM writes to _DAT_8007BA78)
// to a STR file via the authoritative runtime mapping.
assert_eq!(fmv_index_to_str_filename(0), Some("MOV/MV1.STR"));
assert_eq!(fmv_index_to_str_filename(1), Some("MOV/MV3.STR"));
assert_eq!(fmv_index_to_str_filename(5), None); // cut/missing slot

See also