How it works

At runtime, "load this scene" doesn't mean "load one file". A single battle scene needs character meshes, monster meshes, the active CLUTs (palettes), the per-monster sound bank, the visual-effect script archive, plus all the text the battle dialog box might print. They're scattered across dozens of PROT entries.

Each scene type has its own loader function whose job is to go fetch all that. They all share the same shape:

  • The loader is a small state machine with one numbered case per asset slot.
  • Each case calls the asset-type dispatcher FUN_8001F05C with the bytes for one entry.
  • Some cases also kick off async CD reads to stage things into memory while the rest of the scene is initialising.

There's a dev/retail split that runs through nearly every loader. Dev builds load by PROT index directly — the index numbers are baked in. Retail builds resolve dev-style paths (h:\PROT\FIELD\<scene>\name) through FUN_8003E6BC, which uses the CDNAME.TXT name map to find the corresponding PROT index. Both branches end up at the same files; only the lookup mechanism differs. That's why we don't see a literal "scene N uses PROT entries [a, b, c]" table anywhere in the executable — the table is the CDNAME block layout itself.

The asset descriptor walker (FUN_80020224) is the loop that walks a per-scene asset descriptor and calls the dispatcher per descriptor. It has zero static callers in SCUS_942.54, which would suggest "dead code" — except its sole runtime caller is the town overlay's MAIN_INIT, at the start of every field session. So it is exercised by retail gameplay; the call just lives in RAM.

Battle bundle

Function
FUN_800520F0
Shape
11-case state machine

Notable cases:

CaseLoads
6The befect_data bundle (PROT 0x369–0x36B)
0xEInitialises the runtime effect 2-pack wrapper via FUN_801DE914
0xFFDispatches 0x801F17F8, the side-band streaming-effect handler that streams summon.dat and readef.dat

Two cases call FUN_8003E104(monster_idx, slot, dst_buf) to populate slots 7 and 8 with the active battle's monster sound banks — the per-monster body of h:\mpack\monster.snd. Each monster has a (start_lba, end_lba+1) entry pair in the TOC at 0x801C8980 - 0x10. See Audio → "Monster sound bank" for the full loader contract.

The asset-viewer's --bundle battle mode mirrors this loader's PROT 865–890 set so character meshes have the right CLUT bindings.

Field / town scene loader

Functions
FUN_8001F7C0 + FUN_800255B8
Path roots
DATA\FIELD\ and h:\PROT\FIELD\<scene>\

Each scene reserves six file types in CDNAME's per-scene block, and the loader walks the scene asset table at the leading PROT entry to pull each file in turn. The on-disc form is the canonical 7-typed-asset bundle (07 00 00 00 lead).

The descriptor offsets past the first are not file-relative — they index into the loader's runtime decompression buffer. For example, 0031_izumi.BIN is 96 KB but desc[2].data_offset = 141 KB lands inside the decompressed working buffer, not the source file.

Per-scene reference resolution is partial — many scene-bundle entries cross-reference each other through indices in the descriptor table that the loader stitches at runtime. The asset chain shape is "load the scene asset table, decode each descriptor, then load each typed sub-asset using the dispatcher", but the precise indexing scheme is still under investigation for non-battle scenes.

WARP opcode → scene transition flow

Field-VM opcode
0x3E with op0 ≥ 100
Scene handler
FUN_80025980

When the field VM executes opcode 0x3E with op0 ≥ 100, it stores map_id = op0 - 100 in DAT_8007ba34 and switches game mode to 0xe (SCENE_TRANSITION). The mode handler FUN_80025980 then loads a code overlay at PROT index map_id + 0x4d (or map_id + 0x4f when map_id ≥ 6).

Only 7 distinct WARP destinations exist (map_ids 0–6), each loading a scene-type overlay at PROT 0x4D–0x55 whose entry function resides at an overlay-resident address (0x801CF070, 0x801CE8A0, etc.).

The scene name (stored at DAT_80084548, max 8 chars) is pre-set before FUN_80025980 executes. The overlay entry function reads this buffer and passes it to FUN_8001F7C0 and FUN_80020118. The mechanism that writes the scene name before the WARP fires is in a pre-transition handler not yet fully traced.

GlobalRole
DAT_80084548Scene name string (pre-set before WARP fires)
DAT_80084540Current scene PROT base index (short)
DAT_8007b768Pending destination PROT index; 0xffff = none
DAT_8007ba34Pending warp map_id (0–6); read by FUN_80025980

The DefaultMapIdResolver in engine-core::scene uses CDNAME blocks in ascending PROT-index order as a positional approximation. The actual retail warp only supports 7 destinations and the scene name is determined by a pre-WARP state-machine path still to be captured.

Asset descriptor walker

Function
FUN_80020224
Sole runtime caller
Town overlay's FUN_801D6704 (MAIN_INIT) at 0x801D6B0C with a0 = 0
Result stored at
0x80087AF8

Walks the asset descriptor format and calls the asset-type dispatcher per descriptor. So the walker IS exercised by retail gameplay, just not from a static call site inside SCUS_942.54.

CLUT-data scattering

Many character meshes reference CLUT (palette) rows that live in different PROT entries from their TMD source. The runtime asset chain stitches them together — the loader puts the relevant TIMs into VRAM before the TMD is rendered.

The asset-viewer's --vram-extra-dir flag is the workaround until the chain is fully traced for every scene type. Battle is fully traced; field, town, and level-up still rely on the workaround.

Music / SFX selection (BGM lookup)

Documented in detail under the field VM → "BGM lookup table" section. The short version: the BGM ID is a PROT-relative offset, not a literal table lookup.

// FUN_800243F0
if (bgm_id < 2000) {
    prot_idx = scene_local_base + 6 + bgm_id;     // _DAT_80084540 + 6 + bgm_id
} else {
    prot_idx = global_pool_base + (bgm_id - 2000); // _DAT_8007BC64 + bgm_id - 2000
}

The "BGM table" is the CDNAME.TXT per-scene block layout. There's no separate BGM index in SCUS_942.54.

Sound bank loader and streaming-asset loader

FunctionRole
FUN_8001FA88 Sound subsystem init / .dpk loader. Loads bse.dat master bank once at boot, then per-scene .dpk files via the path-based opener with h:\main\bg\domepack\<name>.dpk. Dev builds bypass the path-builder and load PROT index 0x37A (sound_data2) plus param_1 + 5 directly.
FUN_8001FC00 Streaming-asset loader. Builds paths under the sound\ prefix; the XA / .pac / STR consumer. Same dev/retail split as the field loader.

Full breakdown in Audio.

Top-level extraction pipeline

legaia-extract (the binary in crates/extract) drives the offline preservation pipeline:

verify → disc → PROT → categorize → streaming-format extract → TIM → PNG

See the extraction tooling page for per-stage CLI invocations.