How it works

The world-map code is not in the game's main executable - it lives in overlays, chunks of MIPS code the game streams from the disc into RAM on demand. We traced them with a disassembler (Ghidra) over live RAM captures; the FUN_80xxxxxx names below are the RAM addresses of functions in that trace. The main controller is FUN_801E76D4 (9320 bytes); the per-entity tick is FUN_801DA51C (260 bytes). Sources: overlay_world_map.bin (walk-view) and overlay_world_map_top.bin (top-view debug) mednafen captures.

Two things worth knowing up front. First, the shipped game still carries the developers' tooling: a top-down debug camera (a pad-combo toggle, gated on a debug flag) and a scrolling dev menu with entries like MAP_CHANGE and ENCOUNT - both documented below. Second, encounters are not a separate subsystem: a 5-state per-entity state machine drives them, and the monster-formation install and the battle launch both happen in the same tick the carrier entity reaches its activating state.

Overlay structure

Both variants load into the same address window. The top-view variant starts its first prologue ~0x1400 bytes earlier in the window to fit extra sprite-rendering code.

VariantFirst prologueLoaded when
Normal walk (overlay_world_map)0x801CFC40Standard world-map mode
Top-view debug (overlay_world_map_top)0x801CE850Debug toggle combo (see below)

Both variants share the core field VM (FUN_801DE840), the move-VM overlay extension (FUN_801D362C), and all rendering helpers. The view-mode toggle flag is at DAT_801F2B94. The world-map overlay variants extend past 0x801F0000 - captures use a wide window (0x801C0000..0x801F9000, 228 KB) so the prim-mode dispatch table at 0x801F8968 and its eight overlay-resident emit leaves at 0x801F7644..0x801F8690 are included.

World map controller - FUN_801E76D4

Entry: (ctx_ptr). Three top-level paths based on DAT_801F2B94:

  • Debug top-view toggle - fires when _DAT_8007B98C != 0 (debug flag) AND pad mask _DAT_8007B850 == 0x4A AND held mask _DAT_8007B874 == 0x40. On trigger: DAT_801F2B94 ^= 1, captures current actor camera position into _DAT_801F35A8/AA/AC, clears ctx[+0x54] and ctx[+0x50], calls FUN_80035C10.
  • Top-view camera controls (DAT_801F2B94 != 0) - D-pad adjusts scroll globals; L/R buttons adjust azimuth and zoom:
    • Left/Right → _DAT_80089120 ±= 8 (X scroll)
    • Up/Down → _DAT_80089118 ±= 8 (Z scroll)
    • Circle/Square → _DAT_8007B794 ±= 0x14 (azimuth)
    • R1/L1 → _DAT_8007B6F4 ±= 4 (zoom/height)
  • Normal-walk path (DAT_801F2B94 == 0) - standard per-frame world-map update: field VM tick, actor step, camera follow via the motion VM.

World map entity tick - FUN_801DA51C

Entry: (entity_ptr). A 5-state dispatcher on entity[+0x8A] (jump table at 0x801CEC28). Called once per world-map entity per frame by the entity pool tick loop.

  • State 0 (Idle) - when _DAT_80083808 == 0: calls FUN_800243F0 (BGM/asset resolver) to look up the scene associated with the entity's location; checks _DAT_8007BB38 pad buttons for entity interaction.
  • States 1–4 - Activating, Transitioning, and Terminal states for encounter and location-entry sequences.

Encounter-record installation

The body at 0x801DA620..0x801DA678 populates the global encounter formation cell from a per-encounter record pointed at by entity[+0x94]:

  1. Clear the 4-slot formation array at 0x8007BD0C..0x8007BD0F (slots 3, 2, 1, then 0 - slot 0 is cleared in the delay slot of JAL 0x801DE190).
  2. Read monster_count = entity[+0x94][+0x3].
  3. Copy entity[+0x94][+0x4 .. +0x4 + monster_count] into the formation cell, byte-for-byte.

This copy runs in SM state 1 (entity[+0x8A] == 1). The same invocation clears entity[+0x94], advances entity[+0x8A] to 2, and then falls through the case 2/3 arm, which writes _DAT_8007B83C = 8 (the game-mode handoff that launches the battle), sets entity[+0x8A] = 4, and clears the 0x80000 "encounter active" flag on the player context (_DAT_8007C364[+0x10], which state 1 had raised). So the formation install and the battle launch happen in the same tick the carrier reaches state 1. State 0 reaches state 1 either via the random roll FUN_801D9E1C or - in a 0%-random town like town01 - via a scripted advance from the scene's interaction bytecode.

The carrier entity_ptr is a dedicated field entity, distinct from the player context _DAT_8007C364 (corpus-stable at 0x80083794, which carries no clean +0x8A/+0x94 SM): the routine reads the SM fields off its param_1 but writes the flag onto _DAT_8007C364 separately. The entity-update loop reaches the carrier through per-entity update-function-pointer dispatch (no direct jal), so it is one of the scene's MAN-placed entities, not the player.

The encounter-record format consumed here is documented on the encounter record page. The 4-byte formation cell at 0x8007BD0C is the input to the battle-scene loader (FUN_800520F0); the adjacent byte at 0x8007BD11 is a battle-data PROT-id selector that picks between PROT entries 0x367 and 0x36D.

The pointer at entity[+0x94] is set by field-VM op handlers inside the script-VM dispatcher (FUN_801DE840); see the script-VM page and the op-handler family at 0x801DEEDC / 0x801DEF08 / 0x801DEFA0 / 0x801DF038 / 0x801DF3FC / 0x801E1C38 / 0x801E1F44 / 0x801E21C0. Each handler is a different "trigger encounter on actor X" op; they all share the clause:

sw   <record_ptr>, 0x94(<actor>)
sh   $zero,         0x54(<actor>)
ori  $tmp, $tmp, 0x400      ; raise "encounter armed" flag in actor[+0x10]
sw   $tmp,         0x10(<actor>)

Debug menu renderer - FUN_801EAD98

Entry: (ctx_ptr, x, y, scroll_idx, max_visible). Renders a vertically scrolling menu list for the world map developer menu. String table at 0x801CF344; at least 24 entries (bounds check local_40 > 0x17). Known entries: MAP_CHANGE, CARD_OPTION (both show as CLOSED when _DAT_8007B868 != 0), PLAYER_STATUS, CAMERA, ENCOUNT, OTHER_SETTINGS, BGM_CALL, DEBUG.

Called by FUN_801ECA08 when the debug panel is active (ctx[+0x54] mod-6 dispatch, cases 1 or 3).

Engine port. The renderer-free half of the dev-menu leaves lives in legaia_engine_vm::world_map_overlay: the 24-row model (DevMenuRow + is_closed, incl. the MAP_CHANGE/CARD_OPTION CLOSED gating), cursor_step (swap-wrap), list_body_draws (the phase×gate draw gate), format_fixed_decimal (the zero-padded digit kernel FUN_801EAD98 inlines per numeric readout) and decode_camera_readout; the same module ports the escape-timer scheduler (EscapeTimer, from FUN_801D2EBC), the battle-records data model (records_screen, from FUN_801ED710), and the equipment stat-comparison preview (from FUN_801E5B4C). The draw half - the GPU-packet emitters - lives in engine-ui: records_screen_draws_for (the nine heading rows, the six per-character categories × three columns, the H:MM:SS play clock and the treasure line) and dev_menu_list_draws_for (label column x + 8, 8-px row pitch, the 0x17 clamp), with every offset load-base independent - read straight off the param + N immediates. The per-character portrait/separator icons (FUN_8002C488) remain a UI-icon-atlas sprite seam owned by the host, as with the status page's LV/HP/MP icons.

Render pipeline

The per-frame world-map render dispatches from the SCUS-resident game loop into the overlay-loaded code window. Two chains converge: a per-frame dispatch tick that reads the prim emitter's gate flag, and a one-shot arm path that sets the gate plus a small param block.

Per-frame dispatch (SCUS-resident)

Two SCUS-resident handlers from the 28-mode dispatch table at 0x8007078C reach the world-map render tick:

AddressMode-table roleTick call
FUN_80025EECDefault per-mode handler (used by 13 of 28 modes - not world-map-specific).FUN_8001698CFUN_80016444(1)FUN_80016B6C.
FUN_80025F2CMode 13 (MAPDSIP MODE) - field/world-map display per-frame handler.FUN_8001698Cfunc_0x801CE850 (overlay entry) → FUN_80016444(0).

The a0 arg controls whether FUN_80016444 skips its early FUN_8005FB84 block (Mode 13 skips it; the default handler runs it). Both reach the world-map render branch deeper in the function, so the horizon emitter can fire from any of the 14 modes that route through FUN_80016444 whenever the submode register holds 2.

FUN_80016444 - SCUS world-map render tick (1352 bytes)

Entry: (submode_flag). Iterates the world-map render passes for one frame. The pipeline includes a gated direct call into the overlay-resident POLY_FT4 emitter:

80016750  lui   v1, 0x8008
80016754  lw    v1, -0x43c4(v1)        ; v1 = _DAT_8007BC3C (submode register)
80016758  li    v0, 0x2
8001675c  bne   v1, v0, 0x8001676c     ; skip unless submode == 2
80016764  jal   0x801d7ea0             ; -> overlay-resident emitter

Same 0x8007BC3C register has six SCUS writers (FUN_80016230 - the two-write set/clear path - plus FUN_80025980, FUN_80025DA0, FUN_8001D424); the writer that stores 2 is the entry point for the world-map render branch.

FUN_801D7EA0 - world-map POLY_FT4 batch emitter (832 bytes)

Entry: (). One-shot emitter gated by _DAT_801F351C:

if (_DAT_801F351C != 0) {
    _DAT_801F351C = 0;                   // self-clear gate
    iVar11 = 4;
    local_30 = 0x2C808080;                // POLY_FT4 GP0 cmd + neutral grey
    uVar6 = _DAT_801F3518
          + DAT_1F800393 * _DAT_801F3524; // angle += per-frame-tick * step
    _DAT_801F3518 = uVar6;
    local_3c = _DAT_801F3520;
    local_34 = _DAT_801F3520 / 5;
    local_38 = _DAT_801F3520 - local_34;
    do {
        iVar10 = cos_table[(uVar6 & 0xFFF)];   // 0x8007B81C cos LUT
        // emit 2x POLY_FT4 (chain tag 0x9000000) + 1 small prim
        // (chain tag 0x3000000); vertex coords are cos-rotation-
        // projected with local_3c/local_38 as scale moduli.
        ...
        uVar6 += 0x10;
        iVar11++;
    } while (iVar11 < 0xE4);              // 224 iterations
}

_DAT_8007B81C is the cos lookup table (see the memory map). The function emits ~670 prims per call (2 POLY_FT4 + 1 small per iter across 224 iters). Vertex coordinates project via the cos table, so the rendered output rotates with the camera angle - consistent with a horizon / sky / animated-background plane, not a fixed continent mesh.

The case-5 path of the per-actor render dispatcher FUN_8001ADA4 draws every landmark TMD (castle, towers, bridges, gates) - each world-map actor's actor[+0x44] mesh chain points into Drake's 40-TMD landmark pack (TMD is the game's 3D-mesh format) at PROT entry 0086 slot 1, which the dispatcher walks once per frame through FUN_8002735C (the Legaia TMD renderer - 60 ops on the GTE, the PS1's geometry coprocessor). That accounts for the landmark prims in the GPU pool.

Top-view bulk-terrain render path (overlay-replaced per-prim renderers)

This is the top-view / overview render path (game mode 0x0D), distinct from the walk-view continent (a heightfield, see the engine port). The top-view's bulk terrain prims are not produced by a procedural emitter sibling of FUN_801D7EA0; they come out of ordinary case-5 TMD rendering (of the overview-pool meshes placed per cell) whose per-prim dispatch is mode-switched to overlay-resident renderers when the top-view overlay is paged in. FUN_80043390 (the SCUS-side per-prim TMD renderer at the leaf of the actor-mesh-chain walk) selects one of two function-pointer tables based on _DAT_1F800394 & 1:

FlagTable baseRowsWhere it lives
clear0x8007657C4 (alpha 0/50/A0/F0)SCUS_942.54
set0x801F89681 (alpha 0 only)world-map overlay

The overlay path skips the alpha offset (_DAT_1F800028 is not added on the overlay branch), so only the first row of the overlay table is meaningful. Slots 8..11 of row 0 share the same low-mode dispatchers as SCUS (0x8004409C, 0x8004423C, 0x80044434, 0x800445B0); slots 12..19 carry the eight overlay-resident high-mode renderers at 0x801F7644, 0x801F7838, 0x801F7F78, 0x801F8198, 0x801F7AA4, 0x801F7CCC, 0x801F8454, 0x801F8690. Each is a per-primitive emitter that loads vertex indices from the TMD prim body, looks vertices up in the actor's vertex pool, runs them through the GTE, and emits one GPU prim packet.

Source mesh data is the same kingdom slot-1 TMD pack the landmarks draw from. mednafen-state prim-dispatch-table <save> decodes both tables out of a save state's main RAM and surfaces the eight overlay-resident targets; pass --overlay-targets-only to pipe addresses into a Ghidra dump_funcs.py TARGETS list. The companion subcommand mednafen-state prim-dispatch-survey <save>... compares multiple saves side-by-side and asserts the SCUS table is byte-identical across them.

Per-slot delta vs SCUS sibling

Capstone disassembly (scripts/ghidra-analysis/disasm-overlay-fn.py --batch leaves) plus Ghidra decompilation (ghidra/scripts/dump_world_map_top_prim_leaves.py) confirms every overlay leaf is the SCUS sibling body plus a per-vertex distance-cue fog post-process inserted between the GTE projection and the OT packet write. The fog block is gated on (u8 *)(t2-0x2D1) & 0x10; when set, it computes a clamped max-Z, shifts it by (u8 *)(t2+0x90), mixes a far-Z reference at t2-0x2E0 into the prim cmd, invokes GTE.dpcs (single colour, slots 12-14 / 16-18) or GTE.dpct + dpcs (textured quads, slots 15 / 19), then indexes the per-Z RGB LUT at t2-0x2BC three times to ADD-write fog tints into offsets 8 / 0xC / 0x10 of the OT packet.

SlotSCUS siblingOverlay leafOverlay-only GTE ops
120x80043658 (68 instr)0x801F7644 (125 instr)dpcs
130x80043768 (84 instr)0x801F7838 (155 instr)dpcs
140x80043B58 (69 instr)0x801F7F78 (136 instr)dpcs
150x80043C6C (90 instr)0x801F8198 (175 instr)dpct + dpcs
160x800438B8 (75 instr)0x801F7AA4 (138 instr)dpcs
170x800439E4 (93 instr)0x801F7CCC (171 instr)dpcs
180x80043DD4 (79 instr)0x801F8454 (143 instr)dpcs
190x80043F10 (99 instr)0x801F8690 (182 instr)dpct + dpcs

Slots 15 and 19 are the textured-quad (POLY_FT4 / POLY_GT4) variants that need dpct for the first three vertices plus a final dpcs for the fourth; every other slot needs only dpcs. Each leaf ends with j 0x80043580; addiu $a1, $a1, 0xc - tail call to the SCUS dispatcher continuation plus the loop's per-prim source-pointer advance. The engine port mirrors this dispatch in legaia-engine-vm::prim_dispatch; SceneLoadKind::WorldMap selects the overlay variant.

Slot 4 of each kingdom bundle is not the bulk-terrain source. Its records are a runtime library of object-local 3D meshes (see the slot-4 records format doc) - independent of the continent terrain emit mechanism.

Why static hunts miss the terrain emitters (negative-result audit)

Three properties of the dispatch hide the terrain emitters from the usual static hunts:

  • The GP0 cmd byte is loaded from a per-mode descriptor table (DAT_8007326C on the SCUS side, 0x801F8968 in the overlay), not built with lui/li immediates - so addprim immediate-scanners see neither FUN_8002735C (the landmark TMD renderer) nor the eight overlay leaves. Static addprim hits reduce to the horizon emitter plus five non-terrain SCUS emitters (HUD sprite batches, screen-tint, digit batchers).
  • An overlay capture that stops at 0x801F0000 clips every leaf address out of the analysed binary. The dynamic prim-pool-writers probe confirms top-of-list PC hits at 0x801F7344..0x801F8DBC - exactly the eight high-mode renderer addresses.
  • The horizon emitter is called by direct jal from SCUS; Ghidra's reference manager misses the cross-program call when sweeping the overlay alone (sweep SCUS to surface the caller).

An audit of FUN_80016444's direct jal targets (12 unique targets across 60 call sites) confirms none is a bulk-terrain emitter on its own - the bulk prims come from many small case-5 TMD renders going through the overlay-replaced per-mode renderers:

TargetCallsRole
0x8001a06819Actor-list pass dispatcher (8 of the 19 hit at the function head).
0x8005fb8418Early-return block; skipped when submode == 2.
0x8002519c6Per-frame render-pass iterator (5 lists per frame).
0x8001d1406Stack-swap wrapper → FUN_8001ADA4 (per-actor render dispatcher).
0x800172c01GTE matrix setup helper (FUN_80026988(&local_18, 0x1F8003A8)).
0x800179c01Small helper.
0x800188c81Debug-HUD text renderer (PSX_TEST_PROGRAM string).
0x8001d058248-byte scratchpad / GPU register flush.
0x8002b688/7902 / 2Tiny accessors (60-80 bytes each).
0x800469781Screen-tint fade emitter (gated on gp+0x9d4).
0x801d7ea01Horizon emitter (overlay-resident, single direct call).

Per-frame render-pass iterator - FUN_8002519c

Five times per frame, FUN_80016444 invokes the SCUS-resident actor-list iterator FUN_8002519c (328 bytes) against five linked-list heads at _DAT_8007C34C..._DAT_8007C36C. Each list is one render pass. The iterator walks the chain and per node either takes an early-return path (when bit 0x8 of node[+0x10] is set) or calls the tick function at node[+0xC] via jalr.

Per-actor record layout consumed by the iterator:

OffsetTypeRole
+0x00actor *Next pointer (singly linked list, NULL terminates).
+0x0Cvoid (*)(actor *)Tick function (the entry point jalr calls).
+0x10u32Flags; bit 0x8 selects the early-return path, bit 0x200 is the "already-emitted this frame" guard.
+0x14u32Saved next-pc copy used by the early-return path.
+0x18u16Halfword count exposed at +0x20 for the early-return path.
+0x44chain *Optional prim-chain head; freed via FUN_80017b94 when bit 0x800 is set.
+0x48u8 *Move-VM bytecode base (for actors whose tick is FUN_80021df4).
+0x70u16Move-VM PC in halfword units; the actual byte offset is 2 * actor[+0x70].

Standard tick functions observed in the world-map render passes:

Tick functionWhereRole
FUN_80021DF4 (SCUS)per-frame actor tickSteps the move VM via FUN_80023070(actor). The eight actors in list _DAT_8007C350 use this tick.
FUN_8003BC08 (SCUS)per-actor tickCalls the motion VM (FUN_8003774C), move-buffer setup (FUN_800204F8), and overlay helper FUN_801D79E8. The fourteen actors in list _DAT_8007C354 use this tick.
FUN_801E76D4 (world_map overlay)world-map controllerTop-view debug toggle + camera scroll/azimuth/zoom + dev-menu render.
FUN_801DA51C (world_map overlay)per-entity tick5-state SM on entity[+0x8A].
FUN_801D1344 (world_map overlay)horizon gate-arm wrapperSee the gate-arm chain below.

Per-actor render dispatcher - FUN_8001ADA4

In addition to the five TICK calls into FUN_8002519c, the same frame issues six RENDER calls into the stack-swap wrapper FUN_8001D140, which forwards into the per-actor render dispatcher FUN_8001ADA4 (2456 bytes). The render dispatcher walks the same actor lists but runs a different switch - on actor[+0x56] (render mode 1..0xB):

  • case 4 (multi-target). Dispatches on actor[+0x9e] flags: bit 0x4000FUN_8002A5A4 (SCUS); bit 0x2000FUN_801CFA48 (overlay-resident); else → FUN_80028158 (SCUS - distinct from the 6692-byte motion bytecode VM FUN_80038158).
  • case 5 (full TMD). Iterates the mesh chain at actor[+0x44] (puVar5[0] = count, puVar5[1..n] = mesh pointers) and per entry calls FUN_80043390 (textured TMD), FUN_80029888 (environment-mapped when actor[+0x7a] != 0), or FUN_8002735C (60-GTE Legaia TMD renderer for bone-animated meshes - the landmark emit leaf: each landmark TMD in Drake's 40-mesh kingdom pack passes through here).
  • cases 1, 2, 3, 6, 7, 8, B - distance-LOD / particle / sprite-billboard branches calling per-effect helpers.

The landmark prims originate in FUN_8002735C, whose cmd byte comes from the per-mode descriptor table at DAT_8007326C; the top-view's bulk terrain follows the same dispatch-table pattern via FUN_80043390's overlay-mode jump table at 0x801F8968 and its eight overlay-resident high-mode renderers (the walk-view continent ground is instead a heightfield, see the engine port). Neither shows up in immediate-scanning addprim hunts - see the negative-result audit disclosure above.

Gate-arm chain - FUN_801D1344FUN_801D8258

The one-shot gate _DAT_801F351C is armed by a 40-byte trigger function called from a 1332-byte parameter-prep wrapper:

AddressRole
FUN_801D1344World-map gate-arm wrapper. 1332 bytes; function-pointer-only entry (Ghidra incoming=0). Reads three globals at _DAT_8007BCD0/_D4/_D8 and forwards them to FUN_801D8258 as the scale / step / OT-layer params at PC 0x801D1470: jal 0x801D8258. Same RAM address holds a different function when the dialog overlay is paged in - that variant is the actor frame handler (see the functions reference).
FUN_801D825840-byte gate setter. Writes _DAT_801F351C = 1, then _DAT_801F3520 = param_2, _DAT_801F3524 = param_3, _DAT_801F3528 = param_4 - the inputs the emitter consumes on its next run.
FUN_801C2B2CCode-identical relocation copy of FUN_801D1344 in the 0897 field overlay. Same body, different load address; calls jal 0x801D8258 at PC 0x801C2C58. Active during field-mode entry transitions.

The gate flag _DAT_801F351C is in the persistent 0x801F0000+ region, so it survives overlay swaps. The flag is shared - both the world-map overlay's FUN_801D7EA0 and the 0897 field overlay's FUN_801C9688 read + clear it.

Engine port

Two modules in the clean-room port cover this subsystem:

  • engine-vm::world_map - clean-room port of FUN_801DA51C: EntityState enum (Idle / Activating / Transitioning / Terminal), WorldMapEntityHost trait (encounter / interact / scene-transition callbacks), step(entity_idx, ctx, host). World drives one WorldMapEntityCtx per installed overworld entity each SceneMode::WorldMap tick: the Idle-state encounter latches the configured formation, which the world resolves into a battle through the same formation_table machinery as a field encounter - tagged via battle_return_mode to return to the overworld - while interactions surface as a FieldInteract event. Each entity carries an optional per-entity role (WorldMapEntityConfig): an EncounterZone spawns its own formation, a Portal surfaces a WorldMapTransition with its target map when engaged, and an Npc carries its interaction id plus the inline dialog-text bytes from its placement record's structural 0x1F-lead segment pool (the actual message - not from a 0x3F op, which is the named scene-change).
  • Overworld player movement + region encounters - tick_world_map walks the player actor from the held d-pad (step_world_map_locomotion) and, on each 128-unit tile crossing (live_world_map_tick, mirroring the field live_field_tick), rolls the scene's region-keyed encounter table (set_world_map_regions, the clean-room port of FUN_801D9E1C): the player's tile selects the first region whose AABB contains it, the region's rate depletes a step counter, and a <= 0 counter rolls a formation from the region's slice and flips WorldMap → Battle (returning to the overworld). A camera-only world map (no region table) is unchanged. In walk mode the native play-window camera follows the player; the top-view debug camera keeps the free scroll. While a cutscene timeline that staged op-0x45 camera params owns the overworld - the New-Game opening's map01 leg - the walk/top-view cameras stand down and the shell renders the same cutscene GTE camera the field prologue uses: retail's Rim Elm aerial fly-in is three camera beats in map01's opening record (a snap to the high aerial shot, then a 45 0B .. apply 900 mode-2 quadratic-ease-out descent), confirmed against a per-frame RAM capture of the live camera globals; the marker overlay hides while the timeline runs. The walk-view camera itself is the retail GTE model, RAM-pinned from the overworld resident savestates: screen = H·(R·(S·(v − player)) + TR)/Ze with H = 368 (_DAT_8007B6F4), S = the base matrix DAT_8007BF10 = 24576·I (a 6.0× uniform world scale; the battle sibling holds 4×), a pitch-only rotation trio at _DAT_8007B790, focus = the player's world X/Z (_DAT_80089118/20 hold its negation), and TR from _DAT_800840B8 - two pinned zoom states, (0,536,9139) / (0,406,11041). The retail pitch/TR path between the anchors is not yet pinned; the community poll probe streams the full tuple on the overworld (wmcam rows: rotation trio + H + the TR low halves + view mode, on change), so any captured zoom/rotate session yields the trajectory.
  • Collision + camera-relative movement - the walk overlay's locomotion is byte-for-byte the field FUN_801d01b0 + FUN_801cfe4c against the same _DAT_1f8003ec + 0x4000 walkability grid (the kingdom maps carry thousands of wall sub-cells there), stepped through the shared World::advance_with_collision. World::world_map_camera_relative_bits rotates the held d-pad through the camera azimuth so "screen up" walks toward the top of the screen for any framing (the retail func_0x800467e8 remap). The kingdom pack is drawn Y-flipped (PSX Y-down → renderer Y-up); the world→screen axes come from the real world_map_camera_mvp matrix and are pinned by a disc-free projection test.
  • The overworld axis convention - retail pins it twice, both in the instruction stream. FUN_800467E8 is not a trigonometric rotation: it looks the held direction nibble (pad & 0xF000) up in the eight-entry compass ring at DAT_800766FC (1000, 3000, 2000, 6000, 4000, C000, 8000, 9000), adds the integer octant count at gp + 0x2D8, masks & 7, and writes the entry back over the pad bits - so the remap is a whole number of 45° steps turning from +Z toward +X, and a count of 0 is the identity. FUN_801D01B0's four step arms then fix the axes: 0x1000actor[+0x18] += 2 (Z+), 0x4000 → Z−, 0x2000actor[+0x14] += 2 (X+), 0x8000 → X−. Those are the raw PSX d-pad bit positions, so retail's unrotated overworld d-pad walks Up = world Z+, Right = world X+. The port's frame is offset from that on purpose: retail's yaw-0 walk camera looks down +Z, while world_map_camera_mvp puts the eye on +X, so the port's remap carries a compensating rotation and its azimuth-0 Up walks −X. What the player sees matches; the world-axis assignment does not - and rotating only one half of the remap/camera pair breaks the screen contract. A single-axis sign flip is a different animal (it turns the frame from a reflection into a rotation) and is caught by the projection test plus a pad→move_state end-to-end test of the retail bit-to-axis table.
  • Geometry: the kingdom landmark pack (slot 1) - a map\d\d scene load decodes the kingdom bundle's slot-1 Legaia-TMD pack (Drake 40 / Sebacus 36 / Karisto 56 meshes) and slot-0 TIM atlas straight from the 7-asset descriptor table (SceneLoadKind::WorldMap); only the scene's primary kingdom entry contributes. The DAT_8007C018 pool the tile dispatcher reads is filled by one descriptor-walk (FUN_801D6704FUN_80020118 party meshes [0..4] + FUN_80020224, dispatching via FUN_8001f05c; only cases 0x02/0x09 install). A real map01 walk capture settles it to 45 entries: 5 party meshes + the 40-mesh slot-1 landmark pack (prefix=5: pool slots 0..4 hold the five party meshes, so pack mesh i lands at pool slot i + 5) - so the walk pool is the landmark pack. Parsed live, the 40 meshes are small object-local tile/prop meshes (dx/dz ≤ ~768): the landmark layer (trees, mountains, the castle), not the continent ground. (Walk-view and overview pools are mutually exclusive - 0085's and 0093's slot-0 atlases target the same VRAM pages.)
  • The continent ground is a procedural heightfield, not instanced meshes - confirmed by FUN_80019278 (SCUS, always-resident, no overlay aliasing), the bilinear ground-height sampler: it gates on the object-grid 0x1000 cell bit and interpolates the floor height from the 2×2 block of +0x4000 nibbles (each & 0xf → the 0x1f80035c LUT, weighted by sub-tile position). So +0x4000 is terrain elevation and the 0x1000 continent is a smooth heightfield surface; the .MAP +0x10 field feeds only the sparse placed-landmark layer (FUN_8003A55C, flags & 0x4; +0x10 == 0 for bulk ground cells). The only per-cell terrain emitter is FUN_801F69D8 (the top-view overview renderer, gate 0x2000), which draws per-cell meshes via +0x10 through FUN_80043390 - FUN_80019278 (the height math) is the reliable anchor for the walk-view heightfield geometry.
  • Walk .MAP source (pinned) - the field-file loader FUN_8001f7c0 is dual-mode; on retail (_DAT_8007b868 == 0 && _DAT_8007b8c2 != 0) it resolves the .MAP by PROT entry index via FUN_8003e8a8 (indexing the in-RAM PROT TOC at 0x801c70f0), the index read from the global at 0x80084540. A live Drake walk reads 0x80084540 = 85, so the records+grid is the raw 0x10000 region at PROT.DAT 0x655800 (= toc[87], no compression; 99.7% byte-identical to the live buffer). The per-entry extractor mis-slices it (its 0085_map01.BIN count=46 pack at 0x668000 is the field object/script pack; the real .MAP is under the overlapping manifest entry 83). Parsing the raw region with the walk rules (0x1000 gate, pool = record[+0x10] + prefix) reproduces the live placements exactly.
  • Entities + boot-path seeding - the overworld shares game mode 0x03 with towns, so the engine classifies it by scene (is_world_map_scene); a field-VM scene_transition resolving to a mapNN label routes SceneHost::tick through enter_world_map_scene (region table + world-map mode). On-map entities are the MAN partition-1 records (FUN_8003A1E4, decoded by ManFile::actor_placements / Scene::field_actor_placements): each is [u8 N][N×2 locals][model][anim_id][tile_x][tile_z][script], giving spawn position, model index, animation clip (anim_id = scene-bundle ANM record + 1, installed into actor +0x5C; 0 = none - see the placement-header resolution), and a per-entity field-VM script. classify_placements reads the kind off the script: a genuine warp (the base 0x3E with op0 in 100..=106) → Portal (target map id op0 - 100, i.e. one of the 7 door-warp scene-type overlays); an inline 0x1F dialog-text block (structural, not an opcode) / interact (0x3E, op0 < 100) → NPC; otherwise Plain. The walk is over-approximating and desyncs inside embedded message / SJIS text, so a genuine-warp gate (!extended && op0 in 100..=106) rejects phantoms. Across the PROT corpus exactly 11 genuine portals survive (town01: 14 NPCs; koin1 maps 3/4/5, koin3 map 6, balden map 3, plus one overworld fishing-spot warp each on map02/map03). enter_world_map_scene installs the typed Portal/NPC entities with their spawn positions from disc.

Rendering (walk view)

Heightfield ground

The continent ground renders as a heightfield surface: Scene::walk_heightfieldbuild_walk_heightfield sweeps the 0x1000 cells and emits one quad per cell, each corner's Y from the +0x4000 floor-nibble grid via the floor LUT (the FUN_80019278 math), verified vs the real disc (map01/02/03 build >10k-quad heightfields with genuine elevation). The baked corner height -lut[nibble] is already the world height the placement / actor transforms carry in their un-flipped translation, so the heightfield draws without the mesh Y-flip the pack meshes get - flipping it re-negates the elevation and sinks every raised cell below its own buildings (in town scenes that hid the whole cliff-top core, including Rim Elm's spawn plaza). play-window draws it as the ground with the placed landmarks (Scene::walk_object_placements, the flags & 0x4 slot-1 pack meshes via record[+0x10]+prefix) on top. Placed entities draw as kind-coded upright markers (World::world_map_entity_markers; portals cyan, NPCs green, encounter zones red), the player as a white-yellow marker with a facing tick (World::world_map_player_marker; step_world_map_locomotion records the heading into the actor's render_26). Distinct from the top-view bulk continent (per-cell meshes via FUN_80043390 / MAN 0x7F-sentinel).

Per-cell ground texturing (multi-page atlas)

The walk-view ground is per-cell POLY_FT4 (cmd 0x2C) quads, one 32×32 quad per visible cell, emitted in a row-major world-cell sweep. The texture is selected per cell from a terrain-type-keyed multi-page atlas - grass, mountain, water, and forest cells each sample a different VRAM page - via the cell's object-record +0x14..+0x18 run: +0x14 = 8×8 atlas tile index (u=(id%8)×32, v=(id/8)×32), +0x15 = PSX tpage (the terrain page/type: 0x1A grass, 0x0C mountain, 0x1B/0x1C water, 0x0B forest), +0x16..+0x18 = PSX clut word. Verified by aligning each quad run's UV→tile sequence to the .MAP's +0x14 grid (scripts/ghidra-analysis/analyze-walk-ground-tiles.py --verify-rule): tile/page/clut match the record 100% across mountain + coast captures. build_walk_heightfield bakes the per-cell UV + [clut,tpage] (WalkHeightfield::uvs / ::cba_tsb) so one ground mesh samples the right page per cell.

The gate is the object grid, in towns too. The ground quads share corner vertices, so a capture's ground lattice rebuilds camera-independently and the fitted projection re-projects every cell to sub-pixel residual. Asking per cell whether retail emitted a quad gives a clean split on a Rim Elm field capture: every on-screen 0x1000 cell has one, no on-screen objcell == 0 cell has one, and every recovered quad carries its record's +0x14/+0x15/+0x16. A floor cell with no object record therefore has no ground quad in retail either - its surface is an env mesh placed over it by a +0x10 record. Widening the gate to the collision grid is wrong twice over: it emits quads retail never draws, and - having no record - they sample empty atlas space, decode to 0x0000, and are discarded. A clear-colour hole over such cells means a missing mesh, not a texturing bug. Disc-gated coverage: crates/engine-core/tests/field_ground_surface_disc.rs.

Ocean / water CLUT animation (kingdom-bundle slot 5)

The water tile's CLUT row at fb (0, 506) (CBA 0x7E80) - plus seven more shoreline/terrain shimmer cells - is rewritten by a table-driven walker whose operand source is the kingdom bundle's slot 5 (the type-0x06 slot of PROT 0086/0245/0392): an LZS-compressed 516-byte CLUT-walk animation table, byte-identical across the three kingdoms (parser legaia_asset::clut_walk). Format: [u32 count=8][u32 entry_offsets[8]], then per entry [u8 kind=1][u8 nframes][u16 cumulative_size][u16 dest_x][u16 dest_y] + nframes × 8-byte frames [u8 0][u8 hold_vsyncs][u16 0][u16 src_x][u16 src_y]. The asset-type dispatcher FUN_8001f05c case 6 installs the decoded table at DAT_8007B7C8; field init FUN_801d6704 spawns one actor per entry (FUN_80024cfc, accumulator seeded to 100 so all eight fire on the first game tick); the SCUS walker FUN_8001ada4 case 0xB steps each actor - acc += dt per game tick (DAT_1F800393; overworld 3, towns 2), and on acc >= hold_vsyncs emits a 16×1 MoveImage from the parked strip onto the destination cell, resets acc to zero (live traces show strictly constant intervals), and advances the frame with wrap. Real interval = ceil(hold/dt)*dt vsyncs: the ocean head ((0, 506), 18 steps hold 8 over the row-505 strip with a 128/144 ping-pong ×3 mid-cycle) fires every 9 vsyncs on the overworld, full cycle 162 vsyncs ≈ 2.7 s. play-window runs the same model (WaterAnim::Walk / advance_ocean_animation: per-entry accumulators, CPU-VRAM move_image, re-upload on fire), with the legacy 13-frame ocean-head cycle (legaia_asset::ocean) kept only as a fallback for a bundle without slot 5.

The walk sources park at VRAM rows 498/499/501..505 as raw CLUT-block records in the bundle's slot-0 TIM_LIST (no TIM magic, so plain TIM walkers skip them; clut_walk::park_strips). map01 ships the full six-record set; map02/map03 ship only rows {501, 503, 505} and inherit the kingdom-invariant rest (rows 498/499/502/504) as VRAM residue from the Drake upload - map01 is always the first world map, and resident Sebucus / Karisto captures hold map01's record bytes on those rows byte-exact - so the engine parks the byte-identical Drake records for rows the scene's own bundle doesn't carry. The earlier map01 capture census stays as corroboration: every censused animating column falls inside a slot-5 destination cell (row 506 cols 0..48 incl. the STP near-copies + pure-channel tail; row 508 cols 0..48 incl. the map01-only [32..47] == [0..15] mirror - strip content, not a second writer; row 509's entries 42..43 inside (32, 509); row 500 cols 62..63 inside (48, 500)), and row 507 - which no entry targets - is fully static. The VRAM parity oracle excludes exactly the destination-cell fold (vram_oracle::WORLD_MAP_CLUT_CYCLE_CELLS); the disc-gated clut_walk_real test pins the full entry set against all three bundles, and world_map_ocean_clut_live.rs verifies the cycle live on all three kingdoms.

The row-498 park-cell fades are a separate, script-driven family - event-triggered MAN 4C 61 ops, not part of the slot-5 table: FUN_801E4C58 (field-VM 0x4C n6 sub-0x61, a one-shot 16×1 MoveImage cell copy / flat-colour fill whose coordinates are script operands) and FUN_801E4794 (a multi-frame CLUT cross-fade state machine). map01's field MAN holds exactly eight 4C 61 ops, all on the row-498 strip park row (four frames=0 one-shots copying (112, 499) onto (0/16/32/48, 498) + four 128-vsync cross-fades back; scanner scene_clut_cell_fx, disc-gated map01_clut_fx_disc). The fade's per-tick advance multiplies by the same adaptive frame-skip byte 0x1F800393, so the frames operand is denominated in vsyncs; engine mirror engine-core::clut_fx + World::step_clut_fx, fed by the op4c_n6_sub_61_emitter host hook.

Slot-4 vertex-pool inspection overlay

The per-kingdom object-mesh library (kingdom-bundle slot 4) decodes onto SceneResources::world_map_slot4 for every SceneLoadKind::WorldMap scene (and only those); with LEGAIA_WORLDMAP_SLOT4=1, play-window builds a colour-by-kind LineList from world_map_overlay::wireframe_segments_3d and merges it into the world-map overlay-lines buffer, so the decoded pool is visible in the live 3D view. It is an inspection overlay, not faithful world geometry: it draws raw object-local coordinates + group-polyline topology, whereas retail consumes the pool in place through the cluster-A handler chain - the per-prim GTE emit family rooted at the TMD prim dispatcher FUN_80043390, entered from the world-map renderer at ra 0x801F78D4 - which supplies the per-object placement transform and triangle emission (documented in the world-map overlay format doc); the viewer does not replay that handler chain. Off by default.

Misreadings to avoid
  • "Single 0x1A grass page, positional (col%3,row%3), +0x14 unused" is a misread: grass cells use page 0x1A with +0x14 in the atlas's top-left 3×3 block, so a mod-3 sequence is coincidental; +0x14 IS the tile selector.
  • A placement classifier without the genuine-warp gate mis-classifies geremi (op0=200) and the leftover-JP other7 (op0=175/179) as portals to non-existent maps; the gate (!extended && op0 in 100..=106) rejects these text-desync phantoms.
  • Portals + NPC talk-to - auto_engage_world_map_portals runs each tick (after locomotion, before the SM step) and drives any Portal whose placement tile matches the player's to its transition state, surfacing a WorldMapTransition to its target map (Idle portals only, once per visit). NPCs are talk-to: tick_world_map opens an Npc's inline dialogue (sets World::current_dialog + emits OpenDialog) on a confirm press within one tile, dismissed on the next press. Dialogue text is inline in the record's structural 0x1F-lead segment pool (found by first_inline_dialog_offset, not by a dialog opcode), not the scene MES. OwnedDialogPanel::from_inline_dialog decodes the first 0x1F segment and play-window renders the box (geometry-header layout + multi-segment menu rendering not yet pinned). Not the 0x3F op: that is the named scene-change (it carries a destination scene name and calls the scene-change packet FUN_8001FD44), and the overworld's reachable towns/dungeons are listed as a table of 0x3F ops in the controller script - man_field_scripts::scene_destinations recovers them (map01 → town01/0b/0c, dolk, rikuroa, cave01, vell, vozz, …, all real CDNAME scenes). The real overworld hop is the OverworldPortal config: map01 has no partition-1 Portal placements, so its town/dungeon entrances are seeded (enter_world_map_scene) from the .MAP walk-on tile-trigger → partition-2 record → 0x3F bridge (man_field_scripts::overworld_portal_sites), each carrying its exact CDNAME destination + arrival tile. WorldMapTransition is emitted by the entity SM and drained by SceneHost::tick (sibling of the named-0x3F drain): an OverworldPortal loads its scene_name and seats the arrival tile; a door Portal resolves its target_map through the MapIdResolver. Story-gating is the entrance record's own C1/C2 gate: enter_world_map_scene runs each bridge site's partition-2 record through partition2_record_gates + World::p2_record_gates_pass (FUN_8003BDE0: C1 blocks if ANY listed flag is set, C2 requires ALL) and installs an OverworldPortal only when it passes. Most Drake entrances carry empty gates (unconditional); the Ravine (keikoku) portals carry C1=[0x193], so a fresh continent arrival keeps them reachable and setting 0x193 drops them from the per-visit portal set. The other half is walk-on beat records: a gate-1 tile trigger whose record carries no 0x3F (the Drake mist-wall force-walk bands, map01 P2[34..36], C1=[0x482]) is spawned by SceneHost::dispatch_walk_on_trigger - which runs in world-map mode too, leaving portal records (tested by p2_record_is_portal) to the entity SM and spawning only beats as a cutscene timeline, so their C1 one-shot latch is honored (band force-walks while 0x482 is clear). step_world_map_locomotion stands the player down while such a timeline runs. A finer mechanism lives inside an entrance record: a story-conditional destination where an op-0x70 SysFlag.Test switches the 0x3F target after a beat. map01's dungeon entrance (P2[1]/P2[2]) branches on flag 0x142 - clear → dolk (pre-boss), set → a second 0x3Fdolk2 (post-boss), same trigger + arrival tile; overworld_portal_sites decodes the conditional pair (ConditionalDest) and the seeder resolves it via World::system_flag_test. (This falsifies the earlier "dolk2 is reached from a dungeon interior" reading - no interior lists dolk2; it is the same hub entrance as dolk, chosen by flag 0x142.) Gate-flag setters beyond a scene's bundle MAN: man-scripts --system-flag-census walks the MAN field-VM ops 0x50/0x60/0x70 across EVERY carrier per scene (bundle + the streaming variant MANs); the other disc-resident bytecode writing the same bank is the motion VM FUN_80038158 (op-7/op-8, carrier MAN tail-section 1, swept by --motion-flag-census). A flag absent from BOTH censuses is set by a direct code path (FUN_8003CE08-class call in an overlay). Where the chapter-1 spine flags landed: 549/0x225 (town01 opening one-shot) - writer still unrecovered; the RAM-diff pin stands but neither census finds a disc stream carrying it (the earlier "written by op-7 from its own script bytecode" reading is falsified), so the setter is a direct code path and the spine flag-writer capture harness is the closer. 0x142 (the Caruban beat / dolk-dolk2 switch) - writer pinned: the SETs are plain field-VM 51 42 script bytes in the rikuroa streaming-carrier MAN (extraction 157) - P1[10..12] plus the post-victory record P2[50] (C1 = 0x142 itself, the self-latching one-shot) - re-asserted by dolk2's carrier P1[0..1] and cleared by dolk's bundle P1[26]; the firehose capture caught the write live (ra 0x801E3598, the dispatcher's own 0x5x SET arm) and the resident script heap byte-matches the carrier (the old corpus-negative stood because no census walked the streaming variant MANs). Engine: the whole chain is organic record execution - approaching the rikuroa boss-stager placement P1[3] runs the record through the field VM (World::install_boss_stagers_from_man / run_boss_stager_record; park gate = 0x142, read from the record's own head test), whose 52 89 SETs the transient staged marker 0x289 and whose 3E FF 11 enters the fight; the post-battle field return re-runs the scene-entry script P1[0], whose 72 89 test arm spawns P2[50] through the C1-gated record dispatch - the record's own 51 42 script bytes SET 0x142 (and 62 89 clears the marker), flipping the dolk→dolk2 entrance organically (disc-gated oracle: organic_beat_records_disc.rs). 0x482 (mist walls) - no script writer exists: the earlier "SETs in the other7 block, clears in the edbalden/eddoman epilogue carriers" reading was desynced-walker text noise (full-width SJIS digits / an EXIT label table aliasing the 54 82/64 82 op bytes; falsified per-site by hand disasm, pinned by the census decode-coherence flag in man_variant_carrier_census_disc.rs). The writer is a direct code path - a capture target (write-watch across the post-Zeto Drake-revival beat), like flag 549; the engine leaves the gate as-is meanwhile.
  • Chapter-1 Drake hub sweep - past the covered spine (town01 → map01 → keikoku and the boss leg map01 → rikuroa / dolk → dolk2) the remaining map01 interior legs are cave01, vell, vozz, suimon, jou, each decoded + driven straight from disc bytes (no capture) by chapter1_hub_sweep_oracle.rs: driving town01 → map01 and stepping onto the leg's portal tile loads it in Field mode with its MAN present. Portals / entrance records / MAN partitions: cave01 tile (37,110) P2[5] [1,13,18]; vell (77,97) P2[6] [8,17,13]; vozz (107,91) P2[8] [4,24,20]; suimon (57,61) P2[19] [10,7,3]; jou (95,23) P2[37] [15,8,7]. Every one lists a single onward 0x3F back to map01 (jou also warps to its interior variant jouina). dolk2 (post-boss variant; its real MAN is the streaming carrier extraction 70, partitions [29,73,17]) is a terminal interior: its only 0x3F leads back to map01. Gate census: all five swept entrance records install unconditionally (empty C1/C2); the only C1-gated map01 entrance is keikoku (C1=[0x193]), the only op-0x70 branch is the dolk/dolk2 destination switch on flag 0x142 (a selector in the ungated dolk record), and 0x482 is a separate no-0x3F band record - so no C1/C2 progression gate stands between a fresh Drake arrival and any of the five legs. The “suimon and dolk2 share a MAN” identity - dissolved: both halves were the CDNAME-shifted scene window - dolk2's unshifted window bled two entries into suimon's block and picked up suimon's sidecar copy of its own 2345-byte [10,7,3] MAN (rikuroa/geremi aliased the same way; byte comparison + position law in scene-v12-table § over-read). In the retail frame suimon keeps that MAN (its scripted bundle) and dolk2 resolves its own streaming carrier (extraction 70, [29,73,17]). Scene::load converts CDNAME raw-TOC block ranges to the extraction frame (raw - 2), which also dissolves the historical “a scene's .MAP is two entries below its block” rule - the .MAP is simply the retail block's FIRST entry.
  • Chapter-1 hub depth (vozz + jou → jouina) - one level deeper on the two story-load-bearing legs, decoded + driven by chapter1_hub_depth_oracle.rs. vozz is the Ravine unlock: its 0x3F set is {map01} only (exit P2[10], tiles (60..62, 2)), but the ONLY 0x193 SET on the disc is vozz P1[7] (51 93 at MAN 0xDA6, one-shot-guarded on 0x2AC); its P2[11..=13] are self-latching one-shots (each record's C1 is the very flag its own script SETs - the canonical one-shot beat idiom). The town01 → map01 → vozz → map01 round trip drives clean in-engine. jou's castle door is chapter-gated: the jouina warp P2[5] (tiles (93..95, 97)) sits behind C2=[0x44D], whose only setter is jou's own opener chain P2[2] → P2[3] → P2[4] gated upstream on 0x44B (set by izumi P1[15] / noaru P2[31] - the Noa beat). In-engine the gate behaves: fresh walk-on installs nothing; with 0x44D set the same walk-on spawns the door record. jouina is not terminal (destinations {jou, jouinb}, both ungated). Player-channel handshake (resolved): jou P2[5]'s door cutscene drives the PLAYER channel (A2 F8 06 ExecMove + C3 F8 ... HaltAcquire). The timeline stepper models the handshake (ExecMove arms an in-flight countdown, HaltAcquire parks then steps past by encoded width; see cutscene), so the record reaches its trailing 0x3F and the oracle drives the jou → jouina hop to SceneEntered.
  • Chapter-1 hub breadth (cave01 / vell / suimon + the Drake Castle chain) - the remaining hub legs, one level deep, decoded + driven by the disc-gated chapter1_hub_breadth_oracle.rs. cave01 is a two-mouth pass-through - one named 0x3F destination (map01) carried by TWO exit records (P2[0] gate-0 trigger (8,89), P2[1] gate-1 band (93..94,96)). Nine of 18 P2 records are gated: six self-latch one-shots plus the longest ordered beat chain decoded on a hub leg - P2[13] (C1 0x15E / C2 0x15D) → P2[14] (C1 0x169 / C2 0x15E) → P2[15] (C1 [0x13, 0x142] / C2 0x169; the final beat stops replaying once the Zeto flag sets). Ungated P2[16] SETs the 0x15D entry key. The 0x15E beat is read cross-scene by urudre1 P2[0] (the Uru Mais dream tests the cave beat). vell - single exit P2[10] (band (88..92,7)). P2[11] self-latch C1=[0x2AF] (strictly vell-local Set/Test pair); P2[7] carries C1=[0x63A, 0x7] byte-identical to vozz P2[7]'s gate, and 0x63A has ZERO script sites disc-wide (no writer anywhere in the MAN corpus - open thread). Also carries a gate-4 trigger family (record 53, five scattered tiles) not seen on the other legs. suimon is a pure corridor - all three P2 records are ungated 0x3F exits to map01; story variation only via the shared controller P1[0] testing 0x142. The Drake Castle interior is FOUR scenes deep: jou → jouina → jouinb → jouinc → jouind. jouinb ([19,7,13]) is fully ungated (no jouina-style C1=[0xF] busy-latch): P2[9] back to jouina, P2[10] deeper to jouinc ([43,18,60], lists {jouinb, jouind}). Once jou's 0x44D door is passed the deep castle is open. The oracle's part F drives jou → jouina → jouinb → jouinc end-to-end in one session (the door cutscene completes through the player-channel model) - the deepest driven interior chain in the engine. jouinc/jouind decode is an open thread. Decoder asymmetry (pinned): the partition-1 destination-table scan under-reports doors carried only by P2 records (jouinb's jouina return door) - the reverse of the jou P2[5] blind spot; the P2 walker and the strict portal-site join both see them.
  • engine-core::world_map::WorldMapController - models the camera globals (scroll_x, scroll_z, azimuth, zoom) and the top_view debug toggle flag; exposes tick(pad_input) matching FUN_801E76D4's control logic. Attached to World as world_map_ctrl: Option<WorldMapController> and ticked when SceneMode::WorldMap is active.

The legaia-engine play-window --world-map flag activates WorldMap mode and shows camera state (scroll, azimuth, zoom) in the HUD overlay.

World-overview viewer

The /world-overview/ page in the static site renders each kingdom's landmark layer in real-time WebGL 3D from a disc image. The viewer-side reverse engineering (slot-1 layout heuristics, distance-cue fog approximation, sentinel placement resolver, ocean asset extraction, camera anchors) lives on its own page: world-overview viewer subsystem.

Full reference

The complete function-level reference with Ghidra provenance lives at docs/subsystems/world-map.md in the repo. Function dumps: ghidra/scripts/funcs/overlay_dialog_801e76d4.txt, overlay_dialog_801ead98.txt, and 801cfc40.txt.

See also