Subsystems
How Legend of Legaia's engine actually works under the hood - how the retail PlayStation game boots, loads assets, renders, plays sound, and runs its battles and menus, and how this project's clean-room Rust port reimplements each piece. Each card is a self-contained page on one subsystem: what it does, where its code lives, and how it connects to the rest. Start with Boot path if you're new to the project.
The shape of the engine
Everything on these pages comes from tracing the original PS1 code with a disassembler (Ghidra) and then reimplementing the behaviour in Rust. Names like FUN_8002735c are RAM addresses of functions in that trace - the game shipped without symbols, so the address is the name. When a page cites one, it is pointing at a specific traced routine you can find in the project's function dumps.
Legaia is a fairly typical late-90s PSX RPG engine, but with a twist: most of the gameplay logic doesn't live in the main executable.
SCUS_942.54 (the game's main executable on the disc) contains the boot path, low-level I/O, the asset dispatcher, the move-table VM, the motion VM, and the audio / renderer libraries - about half the runtime.
The other half lives in RAM overlays - chunks of MIPS code the game streams from the disc into RAM on demand, one at a time, into one shared window.
There are fewer of them than the subsystem list suggests: the slot-A family (field/town 0897, battle 0898, menu 0899, cutscene/STR 0970, and the minigames) all load at the same base and are mutually exclusive, while the world map, save, shop, level-up, and options are subsystems inside the field and menu overlays, not separate ones.
Two of the runtime VMs (the field/event VM and the effect VM) live in these overlays.
0x801CE818): battle loads on top of town's slot, never coexisting.
The title overlay has no TOC entry of its own - it lives in the unindexed gap between entries 899 and 900 of PROT.DAT, the disc's single big archive - nearly everything except movies and audio streams lives in its 1233 numbered entries.
(PROT 0971 is the dev debug-menu overlay; see boot.)
Several “subsystems” are not separate overlays - the world map is a mode of the field overlay (0897), and save / shop / level-up / options all live inside the menu overlay (0899).
The minigames (fishing / slot machine / Baka Fighter / dance) have their own top-level page.
The randomizer injects a whole new vendor screen into 0899's dead space - see the overlay dead-region write-up for a zoom into that overlay.The list below groups subsystems by what layer of the engine they sit at.
Bootstrap and asset plumbing
The five runtime VMs
Legaia's runtime is driven by five independent VMs - small bytecode interpreters that execute scripts stored in the game's data files, so designers could author behaviour without recompiling the engine. All five talk to one shared actor model. Four are clean bytecode dispatchers (actor / move-table / motion / field-event); the fifth (effect) is a per-slot state machine - different shape, same architectural role. They serve different layers of the engine and were almost certainly written by different members of the dev team.
FUN_8003774C. Drives NPC pathing + camera follow + "face the speaker" dialog poses.Per-domain runtime
FUN_801E76D4 handles the debug top-view toggle, top-view camera scroll/azimuth/zoom, and normal-walk per-frame update. Entity tick SM (FUN_801DA51C, 5 states) drives encounter and location-entry sequences.FUN_801d01b0 remaps the held pad through the camera into direction + facing, steps 2 units at a time, and collides per-axis against the per-scene walkability grid (4 wall bits per 128-unit tile).XRWebGLLayer, head tracking, and thumbstick locomotion over the world overview, the assembled full-map view, and the live play page. The flat renderer stays the single source of geometry - only the framebuffer and the view-projection fork.FUN_801DC6B4: 9-state machine, entry-context pointer table at 0x801E4F40, slot-select via actor VM, save-block existence scan at DAT_80084140.FUN_801D33D8 - the Status, Magic, Moves, and Skills tabs. Content-only draw (frame is caller-drawn); pixel-pinned field offsets, submenu dispatch, the three shared string / icon / number primitives, and the DAT_8007b454 CLUT-staging global.ShopSession tracks pending item, quantity, and buy/sell direction. Sell price = half buy price (min 1). Per-scene stock comes from op-0x49 shop records in the scene's script file (MAN); prices from the static SCUS item table.InnSession { cost } gates the stay on affordability, then restores all active party members to full HP/MP.DAT_80076AF4 + formula, and per-character stat growth = DAT_800769CC / DAT_80076918, both applied by the overlay level-up function FUN_801E9504. LevelUpBanner renders for 180 frames via level_up_draws_for().StrInit / StrMode). MDEC decoder: VLC → IDCT → BT.601 YCbCr→RGBA. Clean-room port in crates/mdec.FUN_801E295C - the layer between “player picked Attack” and “HP has been deducted.” Two-level dispatch: action category (party byte) plus execution phase (ctx byte).FUN_800402F4; spirit damage is hard-coded; MP cost is ability-bit modified; engine-vm port lives in battle_formulas.rs.DAT_801C9360[char][0x0C]+0x74 escalates over base for an off-class weapon (favored 0x1E / off-class 0x2A / Astral 0x36) - not a flat double.The clean-room port
The Rust port lives in its own page - phase plan, crate layering, architectural principles, and the legal posture.