At a glance

Crates
legaia-web-play (the play runtime, over legaia-web-minigames, the minigames runtime) and legaia-web-patcher (the ROM patcher) under legaia-web-viewer (the inspectors and the cdylib the site loads): one wasm bundle, four products
Session
legaia_engine_session::BootSession over the page’s audio output - the type the native window runs
Frame
tick_frame() calls the session’s tick in the native frame order and returns the scene a door just walked into
Draw side
Resolved against the same scene resources the host built at scene entry; nothing is decoded twice
Parity
Disc-gated tests pair this host against the native window; a source-derived gate fails when a screen reaches one host only

Module map

engine-session::BootSession the native window's own session web-play - play page: wraps the session runtime load, pad, tick, state play map, player, NPC draws play_dialog reading box play_cutscene / play_fmv prologue, movies play_camera follow + orbit play_menu pause menu play_battle* live fights, 3D, effects play_shop merchant, banners play_sfx / bgm / xa sound play_minigames in-world minigames same bundle, no session minigames web-minigames: standalone playable page field_scene / field_npc web-viewer: assembled maps, NPC pages arts_view / equipment_view web-viewer: viewer pages scene_export web-viewer: .glb export rom_patcher web-patcher: client-side, nothing uploaded
The play runtime is the engine, not a re-implementation of it: it holds the same session type the native window runs, built over a scene host assembled in memory from the visitor’s own disc image. The other surfaces share the format crates and the render kernels but run no session. Four crates build the bundle: web-play holds the play runtime, web-minigames the minigames runtime, web-patcher the ROM patcher, and web-viewer the inspectors; web-viewer depends on web-play and web-patcher, and web-play on web-minigames, never the reverse. wasm-bindgen collects the exports of every crate the bundle links, so which crate an export lives in is invisible to the site’s JavaScript.

Simulation and draw are split on purpose

  • runtime is the simulation: load a disc, enter a field scene, set the pad word, set the camera azimuth so the D-pad remaps camera-relative, tick a frame, read state. It runs the ported field VM, free movement against the walkability grid, the NPC motion VMs and the dialogue runner.
  • play is what the page draws: the assembled map, the lead’s mesh posed from the world’s live pose frame, and the scene’s placed NPCs at their live positions.

The page hands each animation frame’s sim ticks to play_frame_ticks (runtime/frame_ticks.rs), which gives each tick to the owner BootSession::claim_tick names - the field, the pause menu, a shop or name entry - in the order the native window’s sim_tick uses. Menus, shops and name entry therefore step on the sim tick, not the display frame, and the whole frame body is compared state by state against the native window in engine-shell/tests/host_frame_parity.rs.

The map and NPC layers make the native window’s exact resolver calls. NPC clip playback runs in simulation-tick time - one clip player per placed slot, advanced once per tick - so cadence does not depend on the display’s refresh rate and scripted actors perform their cued beats.

What each play module carries

ModuleCovers
play_dialogthe retail dialog reading box
play_cutscene / play_fmvthe opening prologue chain and the movie beats
play_camerathe follow camera and the debug orbit
play_menuthe retail pause menu
play_battle, play_battle_render, play_battle_fxlive battles: rules tick, the 3D scene, effects
play_shopfield merchants, the inn prompt, banners
play_sfx / play_bgm / play_xasound effects, scene music, voice clips
play_minigames, play_fishingthe in-world minigames reached through the scene’s own door warps
boot_title, play_name_entrythe boot title screen and name entry
session_save, cardssession saves and the two memory-card ports
play_dev_menuthe retail developer menu, behind a session-only checkbox that is never persisted
play_hotkeysthe native window’s option / cheat / camera keys, through the shared engine-session::hotkeys table

Keeping two hosts in step

The play page and the native window are two framebuffers over one engine, and the failure that costs most is quiet: a screen is wired natively and the web host drifts behind with nothing in any diff to say so.

Every screen’s geometry is a draw-list builder in engine-ui, and a host has that screen exactly when it calls the builder. So the feature surface can be derived from source, and a gate fails when a builder reaches the native window but not this crate. One-host cases live in a waiver file, each with a reason, validated in both directions so it cannot decay into a stale list.

A screen can be fully wired and connected to nothing. The gate answers “does this host call the builder”; it cannot answer “does this host feed it the same model”. The options screen was drawn on both hosts with the gate green while one of them rebuilt its session from defaults on every open and dropped the result on close.

AreaState against the native window
Seru-trade shop screensclosed: one shared composition
Options screenclosed: edits persist across reloads in localStorage, the browser twin of the native window’s legaia-options.toml
Inn promptclosed
Load / Save rowsclosed at the model: this host browses two memory-card ports, the native window writes save files, both from one summary
In-world minigamesclosed: both hosts enter them from the scene’s door warps
Field music, effects, voiceclosed

The other surfaces

  • Viewer pages - assembled full-scene maps, the field-NPC catalog, the Tactical Arts and equipment viewers, summon and battle-stage previews - read the same format crates the extractor does.
  • NPC conversations on the NPCs page are each actor’s interaction script walked down every arm by the field kernel: pages of text, Yes / No menus and where each option leads, the story-flag and inventory tests that pick a branch, the side effects, and what the next talk runs. Text is decoded from the visitor’s disc in the browser.
  • Scene export builds a textured .glb of a scene in the browser.
  • The ROM patcher runs the randomizer and mod pipeline client-side over the visitor’s disc and hands back a patch file. See ROM patcher.
  • The translation workbench is the in-browser language-pack editor.

See also

Every module family with its exports, the parity tests, the texture-replacement registry and the long tail of helper modules are in docs/subsystems/browser-host.md.