At a glance

1
Boot the engine
One command opens Rim Elm from the disc, with the full gameplay loop.
play-window
2
Pick a scene, or a fight
--scene, --battle, --party.
list-scenes
3
Play the movies
FMV with its XA audio in sync.
play-str
4
Minigames, audio, saves
Fishing, dance, slots, Baka Fighter, Muscle Dome from any live scene.
play-window keys
5
Record + replay
Deterministic session files that need no disc to play back.
record / replay
6
Browse + read
Asset viewer, script disassembler, the browser build.
asset-viewer / field-disasm

Where the data comes from

Every subcommand accepts the disc directly via --disc - no extraction step required to play. Without --disc, tools read an extracted/ tree (--extracted-root, default extracted, resolved against the current directory) produced by legaia-extract (getting started).

1 · Boot the engine

./legaia-engine play-window --disc "/path/to/Legend of Legaia (USA).bin"

This boots the scene town01 (Rim Elm) straight off the disc: field rendering, BGM, NPC scripts, dialog, and the full gameplay loop - walking rolls the scene's own random encounters, a battle opens the command menu, and victory returns you to the field with XP, gold and drops.

Keys

Arrows are the D-pad; the rest of the desktop defaults:

KeyPad button
ZCross
SCircle
ATriangle
XSquare
Q / WL1 / R1
1 / 2L2 / R2
Enter, SpaceStart
RShiftSelect

Esc quits. The browser play page ships a different default layout (X is Circle there, C / V carry Triangle / Square, and WASD doubles the arrows), so a key that works in the tab is not necessarily the same button in the window. config show prints the desktop table, including any rebinding.

Window toggles

  • Left-mouse drag orbits the camera; T cycles the camera-distance preset.
  • R toggles precise free-angle movement (an opt-in enhancement - retail-style movement is the default).
  • I toggles dynamic lighting (also opt-in; --dynamic-lighting starts with it on).
  • F1 shows the engine's diagnostic text rows (off by default); F2 mutes audio; F3 swaps the field camera for the wide debug orbit.
  • F4 toggles the camera-occlusion fade (on by default: walls between the camera and your character dissolve to a dither so you can always see yourself; --no-occlusion-fade starts with it off).

Window toggles and pad bindings cannot collide: every toggle lives on an F-key or a letter outside the bindable vocabulary (the table above plus C, V, D, E), so no rebinding can silently delete a button.

Flags

  • --boot-ui starts at the title screen → save-select flow instead of jumping into the scene.
  • --no-live-loop stops random encounters from rolling, leaving field VM + locomotion only - the mode to use when you are inspecting a scene rather than playing it. A battle the engine is already in still resolves.
  • --no-player-battle auto-attacks each party turn instead of opening the command menu.

Towns have no random encounters, by design. Rim Elm is one of them, and so are most scenes with a shop in them: their encounter regions carry a zero trigger rate, exactly as on the retail disc. The window says so in the corner (no random encounters in this scene) so a quiet walk does not look like a broken engine - step onto the overworld, or --scene map03, to fight.

2 · Pick a scene, or a fight

./legaia-engine list-scenes --disc "/path/to/disc.bin"
./legaia-engine play-window --disc "/path/to/disc.bin" --scene town04

list-scenes prints every scene name the game's file map exposes with the PROT entry range each covers - the same names feed --scene, and a range start is where that scene's files land in extracted/PROT/.

Getting into a battle on purpose

Waiting for a step roll is fine for playing and useless for checking something on the battle screen: the retail counter takes several hundred steps to drain. --battle names the fight instead:

./legaia-engine play-window --disc "$DISC" --scene map03 --battle first
./legaia-engine play-window --disc "$DISC" --scene rikuroa --battle 17

first is the scene's lowest formation row that carries monsters; a number is a specific row of the scene's own MAN encounter table (rikuroa row 17 = the lone Caruban boss) - the same id space the region roll produces, so --battle 12 fights what walking into that region would have. Unregistered rows are refused with the scene's row list logged.

The fight is armed, not injected: the row goes into the encounter session's transition state machine exactly as a roll does, so the intro transition, the BGM swap and the battle load are the ordinary ones. It also means the fight opens a beat after boot rather than on frame 0 - allow ~40 ticks when pairing it with a capture. --battle turns the live loop on even under --no-live-loop, because the loop is what drains the transition.

./legaia-engine play-window --disc "$DISC" --scene rikuroa --battle 17 --seed-party --screenshot /tmp/battle.png --screenshot-tick 200

Choosing the party

--seed-party alone seeds retail's New Game roster, which is Vahn alone - correct for the early game, and not enough for a three-member fight. Name the members you want and each one is seeded from its own row of the executable's starting-party template, so it arrives with real level-1 stats and its own name:

./legaia-engine play-window --disc "$DISC" --scene rikuroa --battle 17 --seed-party --party vahn,noa,gala

The list is in battle order (vahn/noa/gala/terra, or roster indices) and caps at the three on-screen positions. A member whose record already carries stats - a loaded save - is never overwritten.

3 · Play the FMVs

./legaia-engine play-str MOV/MV1.STR --disc "/path/to/disc.bin"

With --disc, the argument is the movie's path inside the disc image and the interleaved XA audio track plays in sync (the video clock is driven off the audio cursor). Without --disc it plays a raw extracted .STR file (video only). To dump frames as PNGs instead, use mdec (extracting assets).

4 · Start a minigame from a live scene

Every ported minigame is a mode suspend on the running world, not a separate program: the field scene stays loaded underneath and comes back when you leave, so you start one from wherever you happen to be standing.

In play-window each is one key, and the same key leaves again:

KeyMinigame
LFishing
KNoa's dance
OCasino slot machine
BBaka Fighter
MMuscle Dome

Each loads that minigame's overlay off the disc and installs a session, so the rules, tables and scoring all come from the disc rather than from hardcoded numbers.

The browser play page carries the fishing entry as a Fish here button under the canvas. Cross casts and reels, Square reels harder, Cross recasts once a catch resolves - the same pad the field controller reads. Points bank into the world's persistent pool, so they survive leaving and re-entering, and the prize-exchange rows come off the same overlay with retail availability gating.

One HUD caveat that looks like a bug and is not: the fishing sprite page is the one asset in that chain nobody has decoded, so both hosts draw the HUD's digit and caption rows from the dialog font and skip its glyphs. The captions are engine-side English placeholders at the retail pen positions, so a long placeholder can overlap the count beside it.

5 · What you hear

Both hosts decode their audio from the disc you supply - no samples ship with the engine or the site. Music is a SEQ played through the from-scratch SPU against the scene's own sound bank; sound effects come from the executable's descriptor table plus the resident program bank, and both hosts key them into the same SPU the music uses, so a cue shares the voice pool exactly as it does on hardware.

Before judging a sound, know its provenance: a cue is either disc (the id is retail's, fired where retail fires it) or site (retail's id there is unpinned, so the port reuses the closest one), and the browser page reports the split per event. The footstep is the clearest example - its timing is the retail cadence, while the cue id it fires is the port's pick.

6 · Saves and config live next to you

The engine resolves its files against the current directory: key bindings in legaia-input.toml, options (camera preset, movement mode) in legaia-options.toml, and save slots under saves/. Run from the same directory each time - or pass explicit paths where supported (--save-dir, config set --config-file).

Rebind keys with config:

./legaia-engine config show
./legaia-engine config set --binding Space=Cross
./legaia-engine config set --binding Enter=Start

KEY=BUTTON uses friendly key names (Z, Up, Enter, RShift) and PSX pad button names (Cross, Circle, Start, L1).

7 · Record and replay a session

./legaia-engine record --disc "/path/to/disc.bin" --out session.toml
./legaia-engine replay --input session.toml

record is play-window plus input capture into a small j-replay-v1 TOML file. The file is checkpointed to disk about once a second and finalized on window close (Esc), so an interrupted session still yields a valid file up to the last checkpoint. replay runs it back headless and deterministic - the same file always produces a bit-identical engine trace, and it needs no disc at all.

legaia-engine --help lists many more subcommands; the COMMAND GROUPS footer separates the player-facing ones above from the development diagnostics (parity oracles, synthetic state drivers) you can ignore.

8 · Browse assets interactively

asset-viewer reads the extracted/ tree (there is no --disc here - run legaia-extract first). The field and dialog demos additionally need the dialog font at extracted/font/, which the pipeline writes by default (or font-extract --disc rebuilds).

./asset-viewer prot extracted/PROT.DAT --cdname extracted/CDNAME.TXT
./asset-viewer tim extracted/tim_scan/<entry>/<tim-file>
./asset-viewer tmd extracted/tmd_scan
./asset-viewer vab extracted/PROT/<entry>.BIN --sample 0
./asset-viewer field town01

Those are, in order: the archive browser, one texture, a walk through every mesh in a directory, playing one sample from a sound bank, and the playable field demo. The scene name is a positional argument here, not a --scene flag (field, world and dialog all take their subject that way).

In the PROT browser: N / P = next/prev entry, PgDn / PgUp = jump 10, Esc = quit; each entry's format is auto-detected and the first viewable sub-asset is shown. tmd pointed at a directory walks every mesh with the same keys. The tim subcommand also takes extracted/PROT.DAT itself with --offset/--clut for the system-UI textures that live outside any TOC entry.

9 · Read the game's scripts

Every scene - who says what, which door leads where, what a chest holds - is driven by the field/event VM's bytecode. Its disassembler is a release binary too:

./field-disasm scan-prot --prot extracted/PROT.DAT
./field-disasm file <extracted-script-body>

scan-prot sweeps the archive for event scripts and FMV triggers; file walks one raw script linearly. For a specific scene's per-scene scripts (LZS-compressed inside the scene's MAN sub-asset), the engine has the direct path:

./legaia-engine man-scripts --scene town01 --disc "/path/to/disc.bin"

10 · Run the browser version locally

The same engine runs in a browser as the static site's play page, sharing the simulation kernels with the native window. That build is not in the clone - site/wasm/ is generated output. Build it once:

scripts/ci/build-wasm.sh
python3 -m http.server -d site

The first takes around nine minutes cold and needs wasm-pack; then open /play.html. Nothing is uploaded: the disc image you pick stays in the tab.

Rebuild after changing anything the page compiles - which is most of the workspace, not just crates/web-viewer. To check whether the bundle you built still matches your sources:

python3 scripts/ci/check-wasm-freshness.py

Worth running before concluding a change did or didn't work in the browser: a stale bundle looks exactly like a fix that had no effect. Background: tooling/shipped-bundle-freshness.md.

See also