At a glance

Where
Three kingdom scenes map01 / map02 / map03 (Drake / Sebucus / Karisto). Kingdom bundles PROT 0086 / 0245 / 0392; the walk .MAP is the first entry of each scene block. Code: the world-map overlay, loaded into the shared 0x801C0000+ window in two builds (walk, top-view debug).
Game mode
0x03 - the ordinary field-run mode a town uses. The overworld is classified by scene name, not by mode.
Walking
Byte-for-byte the field locomotion against the same walkability grid; the camera is a pitched GTE view at a 6x world scale.
Encounters
A region-keyed roll on every 128-unit tile crossing feeds a 5-state carrier entity that installs the formation and launches the battle in one tick. Record format: encounter.
Entrances
Walk-on tile triggers in the .MAP spawn partition-2 records of the scene MAN (its script-and-data bundle); each names its destination with a 0x3F scene change and carries its own story-flag gates.
Ground
A heightfield from the .MAP's floor nibbles, textured per cell from a multi-page atlas; landmarks are the kingdom bundle's slot-1 mesh pack; the sea shimmers by palette animation from slot 5.
Port
engine-vm::world_map (entity SM), engine-core::world_map (top-view controller), build_walk_heightfield (ground), engine-vm::prim_dispatch (top-view terrain). Browser twin: world overview.
Confidence
Confirmed from disassembly and RAM captures for everything above, the walk camera included: it is the field zone camera, and on all three resident overworld states its live words equal the zone composer's staging descriptor.

How a step becomes a battle

Random encounters are not a separate subsystem. One MAN-placed carrier entity - distinct from the player - runs a five-state machine every frame. The idle state is where the encounter roll lands; the moment the carrier reaches state 1 it installs the monster formation and, in the same tick, falls through to the battle hand-off.

0 Idle roll on tile crossing, or a script advance 1 Activating copy monster ids into the formation cell 2 / 3 Transitioning game mode := 8 (battle next frame) 4 Terminal parked until the battle returns roll hits same tick same tick one carrier entity per scene; the states in the accent boxes run inside a single frame
The carrier's five states. Formation install and battle launch happen in the same tick the carrier reaches state 1.

What each state does, in order:

StateAction
0 IdlePolls the pending-BGM resolver and the pad for interaction. The encounter roll (region roll) or a script advance pushes the carrier to state 1 and points it at an encounter record.
1 ActivatingClears the 4-slot formation cell, copies count monster ids out of the record, raises the player's "encounter active" flag, clears the record pointer, moves to state 2 - and keeps executing.
2 / 3 TransitioningWrites game mode 8 (battle), sets state 4, clears the encounter-active flag.
4 TerminalParked; the battle's return path re-seats the overworld.

The formation cell is the input to the battle-scene loader (battle). Scene and town transitions are not in this machine - they are the field VM's named scene-change op, covered under entrances.

Register-level detail: encounter-record installation and who arms it

The install body at 0x801DA620..0x801DA678 (inside the entity tick, jump table 0x801CEC28 on entity[+0x8A]):

  1. Clear the formation array 0x8007BD0C..0x8007BD0F (slots 3, 2, 1, then 0 - slot 0 in the delay slot of JAL 0x801DE190).
  2. monster_count = entity[+0x94][+3].
  3. Copy entity[+0x94][+4 ..+4+count] into the cell byte-for-byte; clear entity[+0x94], set entity[+0x88] = 0, entity[+0x8A] = 2.
  4. Fall through the case-2/3 arm: _DAT_8007B83C = 8, entity[+0x8A] = 4, clear bit 0x80000 of _DAT_8007C364[+0x10] (the player context, corpus-stable at 0x80083794).

The adjacent byte 0x8007BD11 selects the battle-data PROT entry (0x367 or 0x36D). The random roll FUN_801D9E1C sets +0x88 / +0x8A / +0x94 from the rolled formation index. Scripted arming comes from a family of field-VM op handlers inside the script dispatcher (0x801DEEDC, 0x801DEF08, 0x801DEFA0, 0x801DF038, 0x801DF3FC, 0x801E1C38, 0x801E1F44, 0x801E21C0), each a "trigger encounter on actor X" op sharing one clause:

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

The carrier is reached through the per-entity update-function-pointer dispatch, never by a direct jal, which is what shows it is a MAN-placed entity and not the player. State 0's resolver call (FUN_800243F0) is the per-frame BGM/asset poller, not a location lookup.

Walking the overworld

Everything on field locomotion applies: the same held-pad remap, the same 2-unit stepping, the same per-axis collision against the walkability grid (the kingdom maps carry thousands of wall sub-cells). Three things are specific to the overworld:

  • Region-keyed encounters. Each 128-unit tile crossing selects the first region whose bounding box contains the player; that region's rate depletes a step counter, and a counter at zero rolls a formation from the region's slice and hands it to the carrier. A camera-only map with no region table never rolls.
  • The camera. The same zone camera a town has: the kingdom MAN's camera-region records, composed per region into a pitch-only GTE view with a 6.0x uniform world scale (battle uses 4x), focused on the player's X/Z and eased between regions. During the opening's Rim Elm fly-in the cutscene timeline owns the camera instead: a snap to the aerial shot, then a quadratic-ease-out descent.
  • Camera-relative d-pad. The remap is not trigonometry: the held direction is looked up in an eight-entry compass ring and rotated by a whole number of 45-degree octants, so "screen up" always walks toward the top of the screen.

The port keeps what the player sees identical while placing its camera on a different world axis than retail; the compensating rotation lives in the remap. Rotating only one half of that pair breaks the screen contract, so a projection test and a pad-to-axis test pin both halves.

Deep dive: camera model, axis convention and pinned globals
SymbolMeaningValue / global
Hprojection distance368 (_DAT_8007B6F4)
Sbase world scale matrixDAT_8007BF10 = 24576 x I (6.0x)
Rpitch-only rotation trio_DAT_8007B790
focusplayer world X/Z (negated)_DAT_80089118 / _DAT_80089120
TReye trio, composed per region_DAT_800840B8: (-69, 776, 8875) on map01 near Rim Elm, (-71, 536, 9139) Sebucus, (-86, 406, 11041) Karisto

screen = H * (R * (S * (v - player)) + TR) / Ze. The pose is the field zone camera's: the region record's pitch and eye depth, eye X = -(depth >> 7), eye Y compensated for the terrain height under the player, and the ease between regions the community poll probe's wmcam rows capture. An earlier model treated the Sebucus and Karisto values as two ends of one zoom axis; they are two region records, and that model framed map01 from well above retail's camera.

Axis convention. FUN_800467E8 looks the direction nibble (pad & 0xF000) up in the ring at DAT_800766FC (1000, 3000, 2000, 6000, 4000, C000, 8000, 9000), adds the octant count at gp + 0x2D8, masks & 7, and writes the entry back over the pad bits. The step arms of FUN_801D01B0 then fix the axes: 0x1000 = Z+, 0x4000 = Z-, 0x2000 = X+, 0x8000 = X-. Retail's yaw-0 camera looks down +Z; the port's world_map_camera_mvp puts the eye on +X, so its azimuth-0 Up walks -X. A single-axis sign flip turns the frame from a rotation into a reflection and is caught by the tests named above.

Port symbols. tick_world_map, step_world_map_locomotion, live_world_map_tick, set_world_map_regions (the region roll), World::advance_with_collision, World::world_map_camera_relative_bits.

Where the towns are: entrances and story gates

How does stepping on a tile mean "enter Rim Elm"? The kingdom .MAP holds walk-on tile triggers. Each points at a partition-2 record in the scene MAN, and that record is a small script: it may carry a named scene change (op 0x3F), a one-shot cutscene beat, or both, and it carries its own two story-flag lists - C1 blocks the record if any listed flag is set, C2 requires all of them.

1Tilethe player's 128-unit tile matches a walk-on trigger in the .MAP.MAP triggers 2Recordthe trigger names a partition-2 record in the scene MANMAN P2[n] 3GateC1 / C2 story-flag lists decide whether the record runs at allflag test 4Beatoptional cutscene (the mist-wall force-walks are records with no destination)timeline 5Destinationop 0x3F names the CDNAME scene and the arrival tilescene change

Drake's hub, map01, as the disc lays it out:

DestinationRecordGateNote
town01 (Rim Elm), cave01, vell, vozz, suimon, jouone P2 record eachnonereachable on a fresh arrival
dolk / dolk2P2[1] / P2[2]selector on flag 0x142same trigger and arrival tile; clear = pre-boss, set = post-boss interior
keikoku (the Ravine)P2 portalsC1 = [0x193]drops out of the portal set once vozz sets the flag
mist-wall bandsP2[34..36]C1 = [0x482]force-walk beats, no destination; dispatched as a cutscene timeline

The interior legs (what each town's exits lead to, which deeper rooms are gated) are tabulated on the chapter-1 hub sweep page. NPCs on the map are partition-1 placement records whose dialogue text is inline in the record; talking to one opens that text directly.

The exits that live in the other trigger table

Every scene has two walk-on trigger tables, not one. The first is inside the scene's .MAP; the second is a separate one-sector sidecar file, .PCH, with the same header shape, staged just past the map in memory. The per-tile lookup searches the map's table first and falls through to the sidecar. So a scene can put every one of its doors in a table that is not in its map at all.

Five scenes do exactly that, and it made them look one-way: Uru Mais (uru) and its three dream rooms (urudre1..3), plus jouine, the deepest Drake Castle room. Every one of them has a walk-on exit; it is only ever in the sidecar.

SceneExit recordWalk-on bandTail opLeads to
uruP2[42]tiles (36..39, 5)0x3F named scene changeMAP03 (Karisto overworld)
uruP2[37]tiles (37..39, 44)FMV trigger, movie 7uru2, after MV5.STR
urudre1P2[2]tiles (35..37, 22..24)0x3Fback to uru
urudre2P2[9]tiles (26, 14) and (24, 13)0x3Fmap01
urudre3P2[0]tile (51, 90)0x3Fback to uru
jouineP2[16]tiles (17, 17..19)FMV trigger, movie 8town0e, after MV6.STR

uru is the chain's hub and carries six doors in total - the two above plus one dream entrance per room, at tiles (110..112, 35), (10..12, 98) and (80..82, 99). All six live in the sidecar. jouine is the only one of the five with no named scene change anywhere in its script: its record ends by firing a movie, and it is the movie's dispatcher that picks the next scene.

Three separate decoders read these five as sealed, and each answer was a property of the instrument. A script walk that reads a partition as one stream desyncs inside the first long block of dialogue text and never reaches ops sitting several kilobytes deeper. A tile sweep with a cap stops before the sidecar's rows, which sort after the map's - uru's exit band is the 63rd of its 118 walk-on tiles. And a short "did anything happen?" budget after stepping on a tile cannot outlast records that spend hundreds of frames in explicit waits, or that run a boss fight first.

Confirmed on hardware behaviour: walking four tiles north from Uru Mais's own arrival tile under an emulator probe spawns record 42 and calls the scene-change routine with MAP03, returning into the field VM's 0x3F arm. Since that tile exists only in the sidecar, the same capture is the live proof that the fall-through search is real and not just present in the code.

Deep dive: how the port seeds entities, portals and gates from the disc
  • Placements. Partition-1 records (ManFile::actor_placements): [u8 N][N x 2 locals][model][anim_id][tile_x][tile_z][script]. classify_placements reads the kind off the script: a genuine door warp (base 0x3E, op0 in 100..=106, not extended) is a Portal to scene-type overlay op0 - 100; an inline 0x1F text block or a scripted-battle install (op0 < 100) is an NPC; anything else is Plain. Eleven genuine portals exist in the whole corpus; map01 has none, so its entrances come from the bridge below.
  • Bridge. man_field_scripts::overworld_portal_sites joins .MAP tile trigger to P2 record to 0x3F destination + arrival tile, decoding the op-0x70 conditional pair as ConditionalDest. enter_world_map_scene runs each record through partition2_record_gates / World::p2_record_gates_pass (retail FUN_8003BDE0) and installs an OverworldPortal only when the gate passes.
  • Drain. auto_engage_world_map_portals drives a matching Idle portal to its transition state once per visit; SceneHost::tick drains the WorldMapTransition - an OverworldPortal loads its scene and seats the arrival tile; a door portal resolves through MapIdResolver. SceneHost::dispatch_walk_on_trigger runs in world-map mode too, leaving portal records to the entity SM and spawning beat records as timelines so their C1 one-shot latch holds.
  • NPC talk. tick_world_map opens an NPC's inline dialogue on confirm within one tile (first_inline_dialog_offset, OwnedDialogPanel::from_inline_dialog); the text is the record's structural 0x1F segment pool, never the scene MES and never the 0x3F op.
  • Flag writers. 0x142 is set by plain 51 42 script bytes in rikuroa's streaming-carrier MAN - the self-latching post-victory record P2[50] - and re-asserted on entry by dolk2 P1[0]. Four further records carry the same op as rows of a developer flag menu rather than as the beat, which is why an opcode count credits six writers where two exist; the engine reaches it organically through the boss-stager record (organic_beat_records_disc.rs). 0x482 (mist walls) and 0x225 (the town01 opening one-shot) have no script writer in either the field-VM census or the motion-VM census; they are direct code paths in overlays and remain capture targets (runbook).

Two falsified readings, kept so they are not re-walked: "dolk2 is reached from a dungeon interior" (it is the same hub entrance selected by 0x142), and "0x482 is set in the other7 block" (desynced-walker noise over SJIS text). More on do-not-re-walk.

What you see: the render pipeline

The ground, the landmarks, the rotating horizon and the shimmering sea are drawn by four different mechanisms. The frame starts in the always-resident executable (SCUS) and calls out into the overlay only for the horizon plane and, in the top view, for the terrain renderers.

1Mode tickthe per-mode handler enters the world-map render tick once per frameFUN_80016444 2Five passesthe actor-list iterator walks five linked lists, calling each actor's tickFUN_8002519C 3Render dispatchper actor, a switch on its render mode; case 5 walks the mesh chainFUN_8001ADA4 4Mesh leafeach landmark mesh draws through the per-prim dispatcher; the table-driven renderer is the arm no measured actor selectsFUN_80043390 5Horizonwhen the submode register reads 2, one direct call emits the rotating sky planeFUN_801D7EA0
LayerSourceMechanism
Continent ground (walk view).MAP floor nibbles at +0x4000A smooth heightfield: one quad per object-grid cell, corner heights through the floor LUT (the bilinear ground-height sampler's math).
Ground textureobject record +0x14..+0x18Per cell: 8x8 atlas tile index, PSX texture page (grass / mountain / water / forest are different pages), CLUT word. Matches the record 100% in captures.
Landmarkskingdom bundle slot 1 (Drake 40 / Sebucus 36 / Karisto 56 meshes)Placed by flags & 0x4 records; rendered as ordinary case-5 TMD actors.
Horizon / sky planecos table + three globalsA one-shot emitter behind a self-clearing gate: 224 iterations, about 670 prims, rotating with the camera angle.
Bulk terrain (top view)same slot-1 pack, per cellOrdinary TMD rendering whose per-primitive dispatch is mode-switched to eight overlay-resident renderers that add a distance-cue fog pass (below).
Sea shimmerkingdom bundle slot 5Palette animation - see ambient effects.

Cells with no object record have no ground quad in retail either - their surface is an environment mesh placed over them. A clear-colour hole over such a cell means a missing mesh, not a texturing bug. Slot 4 of the kingdom bundle is not a terrain source and holds no geometry at all; it is the scene's actor animation bank (slot-4 records).

Top-view bulk terrain: the overlay-replaced renderers

The top view (the developers' fly-over) draws the continent as per-cell meshes. The SCUS per-primitive TMD renderer picks its emit function from one of two tables, chosen by a scratchpad flag; with the top-view overlay paged in, the overlay table's eight high-mode slots point at overlay copies of the SCUS renderers with a fog post-process spliced in between the GTE projection and the packet write.

FlagTableRowsLives in
clear0x8007657C4 (alpha 0 / 50 / A0 / F0)SCUS
set0x801F89681 (alpha 0 only; the alpha offset is skipped)world-map overlay

The engine mirrors the dispatch in engine-vm::prim_dispatch; SceneLoadKind::WorldMap selects the overlay variant. mednafen-state prim-dispatch-table decodes both tables from a save state.

Per-slot delta: overlay leaf vs SCUS sibling

Every overlay leaf is the SCUS body plus a per-vertex distance-cue fog block gated on (u8*)(t2-0x2D1) & 0x10: clamp max-Z, shift by (u8*)(t2+0x90), mix the far-Z reference at t2-0x2E0 into the prim cmd, run GTE.dpcs (or dpct + dpcs for textured quads), then index the per-Z RGB LUT at t2-0x2BC three times to add fog tints into packet offsets 8 / 0xC / 0x10. Each leaf ends with j 0x80043580; addiu $a1, $a1, 0xc. Slots 8..11 share the SCUS low-mode dispatchers.

SlotSCUS siblingOverlay leafExtra GTE ops
120x800436580x801F7644dpcs
130x800437680x801F7838dpcs
140x80043B580x801F7F78dpcs
150x80043C6C0x801F8198dpct + dpcs (FT4 / GT4)
160x800438B80x801F7AA4dpcs
170x800439E40x801F7CCCdpcs
180x80043DD40x801F8454dpcs
190x80043F100x801F8690dpct + dpcs (FT4 / GT4)

Three properties hide these from static hunts: the GP0 command byte comes from a descriptor table, not an immediate; an overlay capture that stops at 0x801F0000 clips every leaf; and the horizon emitter is called by a direct jal from SCUS that a sweep of the overlay alone never sees. Capture the overlay with the wide window 0x801C0000..0x801F9000.

Deep dive: per-frame dispatch, the horizon emitter and the pass iterator

Dispatch. Two handlers from the 28-mode table at 0x8007078C reach the render tick: the default handler FUN_80025EEC (FUN_8001698C → FUN_80016444(1)) and the mode-13 handler FUN_80025F2C (FUN_8001698C → overlay entry 0x801CE850 → FUN_80016444(0)). The argument only decides whether the early FUN_8005FB84 block runs. The horizon call is gated on the submode register _DAT_8007BC3C == 2:

80016750  lui   v1, 0x8008
80016754  lw    v1, -0x43c4(v1)        ; _DAT_8007BC3C
80016758  li    v0, 0x2
8001675c  bne   v1, v0, 0x8001676c
80016764  jal   0x801d7ea0             ; horizon emitter

Horizon emitter FUN_801D7EA0: if _DAT_801F351C is set, clear it, advance the angle _DAT_801F3518 += DAT_1F800393 * _DAT_801F3524, and loop 224 times emitting two POLY_FT4 (cmd 0x2C808080, chain tag 0x9000000) plus one small prim per step, vertices projected through the sine LUT at 0x8007B81C with _DAT_801F3520 and its 4/5 as scale moduli. The gate is armed by FUN_801D1344 (reads _DAT_8007BCD0/D4/D8) → FUN_801D8258 (writes the gate + _DAT_801F3520/24/28). The gate lives above 0x801F0000 and survives overlay swaps. 0x801C2B2C and 0x801C9688 are not copies of the wrapper and the emitter: they are those two bodies printed 0xE818 low, and PSX overlays are not relocated.

Pass iterator FUN_8002519C: five linked-list heads at _DAT_8007C34C..+0x20; per node, bit 0x8 of +0x10 takes an early return, otherwise jalr the tick at +0x0C.

OffsetTypeRole
+0x00actor*next (NULL terminates)
+0x0Cfn*tick function
+0x10u32flags: 0x8 early return, 0x200 already-emitted guard, 0x800 free the chain at +0x44
+0x44chain*mesh / prim chain head (count, then pointers)
+0x48u8*move-VM bytecode base
+0x56u8render mode 1..0xB for FUN_8001ADA4
+0x70u16move-VM PC in halfwords

Ticks seen on the overworld: FUN_80021DF4 (move VM step, 8 actors), FUN_8003BC08 (motion VM + the visibility cull FUN_801D79E8, 14 actors), FUN_801E76D4, FUN_801DA51C, FUN_801D1344. The render dispatcher's case 4 splits on actor[+0x9E] (0x4000 → FUN_8002A5A4, 0x2000 → overlay FUN_801CFA48, else FUN_80028158); case 5 walks +0x44 calling FUN_8002735C when the actor's +0x42 is non-zero, FUN_80029888 when that is zero and +0x7A is not, else FUN_80043390 - and across four save states in three game modes every one of 5089 gate hits took the last arm; case 1 re-poses each piece from the scene's animation bank before drawing it (slot-4 records); cases 2/3/6/7/8/B are LOD, particle, billboard and palette-cycle branches. The walk pool DAT_8007C018 settles at 45 entries on map01: five party meshes then the 40-mesh landmark pack, so pack mesh i is pool slot i + 5.

Walk .MAP source. The field-file loader FUN_8001F7C0 resolves the .MAP by PROT entry index (FUN_8003E8A8 over the in-RAM TOC at 0x801C70F0, index from 0x80084540); a live Drake walk reads index 85, the raw uncompressed 0x10000 region at PROT.DAT 0x655800.

Engine port: heightfield, texturing and the slot-4 inspection overlay
  • Scene::walk_heightfield → build_walk_heightfield sweeps the 0x1000 cells, emits one quad per cell with corner Y = -lut[nibble], and bakes per-cell UV + [clut, tpage] (WalkHeightfield::uvs / ::cba_tsb). The heightfield is drawn without the Y-flip the pack meshes get - flipping it sinks every raised cell under its own buildings.
  • Texture rule: u = (id % 8) * 32, v = (id / 8) * 32 from +0x14; page from +0x15 (0x1A grass, 0x0C mountain, 0x1B/0x1C water, 0x0B forest); CLUT from +0x16. Verified with analyze-walk-ground-tiles.py --verify-rule; disc-gated field_ground_surface_disc.rs.
  • Landmarks: Scene::walk_object_placements. Entities draw as kind-coded markers (portals cyan, NPCs green, encounter zones red) and the player as a marker with a facing tick.
  • LEGAIA_WORLDMAP_SLOT4=1 overlays the decoded slot-4 animation bank: one polyline per (clip, part) through that part’s decoded 12-bit translations across its clip (world_map_overlay::translation_path_segments). Slot 4 is an ANM container, not a mesh library - the draw used to call wireframe_segments_3d, which plots raw 16-bit slices straddling the entries’ packed nibble boundaries and is a byte-diffing aid, never geometry. The paths are object-local, so they draw about the world origin until an actor owns them.

History: "single grass page, positional (col%3,row%3), +0x14 unused" was a misread - grass tiles happen to occupy the atlas's top-left 3x3 block; +0x14 is the selector.

Ambient effects: why the ocean shimmers

Nothing moves on the overworld but you, yet the sea glitters. The effect is palette animation: a table in the kingdom bundle's slot 5 tells a walker to copy 16-pixel palette strips onto the water and shoreline palette cells on a fixed cadence, so the same texels cycle through a ring of colours. Two families are easy to confuse:

FamilySourceAnimatesEngine
Slot-5 CLUT-walk tablekingdom bundle slot 5 (type 0x06), 516 bytes, identical across the three kingdomsthe ocean head plus seven shoreline / terrain cellslegaia_asset::clut_walk, WaterAnim::Walk
Script-driven CLUT-cell opsMAN 4C 61 ops (one-shot copy, cross-fade)only the row-498 park-cell one-shots on map01engine-core::clut_fx

The same type-6 table drives ambient walks in twelve carriers, not only kingdoms; towns add an effect tree and the HSV cycler behind jou's pulsating flesh - see field-ambient-fx.md.

Deep dive: table format, cadence arithmetic and the parked strips

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 x [u8 0][u8 hold_vsyncs][u16 0][u16 src_x][u16 src_y]. The asset-type dispatcher (case 6) installs it at DAT_8007B7C8; field init spawns one actor per entry with the accumulator seeded to 100 so all eight fire on the first tick; the SCUS walker (render case 0xB) adds the frame-skip byte DAT_1F800393 (overworld 3, towns 2) each tick and on acc >= hold emits a 16x1 MoveImage, resets to zero, and advances with wrap. Interval = ceil(hold/dt) * dt: the ocean head at fb (0, 506) (18 steps, hold 8) fires every 9 vsyncs, full cycle 162 vsyncs, about 2.7 s.

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; clut_walk::park_strips). map02 / map03 ship only rows {501, 503, 505} and inherit the rest as VRAM residue from the Drake upload, so the engine parks Drake's byte-identical records for the missing rows. The VRAM parity oracle excludes exactly the destination cells (WORLD_MAP_CLUT_CYCLE_CELLS); clut_walk_real and world_map_ocean_clut_live.rs pin it on all three kingdoms. The script family: FUN_801E4C58 (field-VM 0x4C n6 sub-0x61 one-shot copy / fill) and FUN_801E4794 (multi-frame cross-fade); map01 holds exactly eight such ops, all on row 498, frames denominated in vsyncs through the same skip byte.

The developers' tooling: top view and debug menu

The shipped game carries the tooling the designers used to fly over the map: a top-down camera behind a pad combo, and a scrolling dev menu with entries such as MAP CHANGE, ENCOUNT and BGM CALL. Both need a debug flag retail leaves clear, so no player ever reaches them - but the code is all there, and it is the source of the top-view terrain renderer above.

  • The top-view controller is only the debug camera: with the view flag clear it returns immediately. The overworld's per-frame update is the ordinary field chain (gate-arm wrapper → locomotion), the same pair a town runs.
  • Toggle: debug flag set + pad mask 0x4A + held mask 0x40 flips the view flag, snapshots the camera and resets the panel cursor.
  • Top-view controls: d-pad scrolls X/Z by 8 per frame; Circle/Square turn the azimuth by 0x14; R1/L1 change zoom by 4.
  • Dev menu: at least 24 rows; MAP CHANGE and CARD OPTION read CLOSED on a retail flag; CAMERA, ENCOUNT and BGM CALL show live readouts. CAMERA is the follow-camera switch 0x8007B606 (the row is its only writer after new game), drawn OFF, or ON plus the walk-region box’s centre tile; the port’s dev menu carries it on both hosts.
Globals and engine port of the debug tooling
GlobalRole
_DAT_8007B98Cdebug flag (clear on retail)
DAT_801F2B94view flag (walk / top view); DAT_801F2B95 bits 1 / 2 enable the screen-dim pass and a second animation
_DAT_8007B850 / _DAT_8007B874pad mask / held mask
_DAT_80089120 / _DAT_80089118Z scroll (Up / Down) / X scroll (Right / Left)
_DAT_8007B794 / _DAT_8007B6F4azimuth / zoom-height
_DAT_801F35A8..ACcamera snapshot taken on toggle
0x801CF344dev-menu string table; DAT_8007B5F8 encounter-rate readout; _DAT_801F2E90 BGM readout

Port: engine-core::world_map::WorldMapController models the four camera globals and the toggle (tick(pad_input)); engine-vm::world_map_overlay holds the 24-row dev-menu model (DevMenuRow, is_closed, cursor_step, format_fixed_decimal, decode_camera_readout), the escape-timer scheduler, the battle-records model and the equipment stat-comparison preview; engine-ui owns the draw halves (records_screen_draws_for, dev_menu_list_draws_for). legaia-engine play-window --world-map shows the camera state in the HUD.

History: an earlier reading of the top-view controller as "the world-map controller with a normal-walk path" is falsified by its own branch target (the walk branch is the epilogue). It mattered - it implied the overworld needed its own menu-open arm, which left the pause menu unreachable on the overworld in the port. See save screen.

Engine port

ModulePortsNotes
engine-vm::world_mapcarrier entity SMEntityState (Idle / Activating / Transitioning / Terminal), WorldMapEntityHost, step(entity_idx, ctx, host); per-entity roles EncounterZone / Portal / Npc. Serves field-resident carriers too.
engine-core::Worldwalk, regions, portals, NPC talktick_world_map, set_world_map_regions, enter_world_map_scene, auto_engage_world_map_portals; battles return via battle_return_mode.
engine-core::world_maptop-view controllercamera globals + toggle.
build_walk_heightfield / prim_dispatchground, top-view terrainheightfield with baked per-cell texturing; overlay-variant prim table.
legaia_asset::clut_walksea shimmerslot-5 table parser; WaterAnim::Walk in play-window.

How we know

FunctionAddressWhat it provesDump
entity tickFUN_801DA51Cthe 5-state carrier SM; formation install + mode-8 write in one tickoverlay_world_map_801da51c
region rollFUN_801D9E1Cregion-keyed encounter roll per tile crossingworld-map overlay
top-view controllerFUN_801E76D4toggle combo, scroll / azimuth / zoom globals; walk branch is the epilogueoverlay_dialog_801e76d4.txt
dev-menu rendererFUN_801EAD98row labels, CLOSED gating, readoutsoverlay_dialog_801ead98.txt
gate-arm wrapperFUN_801D1344the overworld's real per-frame chain; arms the horizon gate801cfc40.txt window
horizon emitterFUN_801D7EA0224-step cos-projected sky planeworld-map overlay
render tickFUN_80016444submode-2 gated direct call into the overlaySCUS
prim dispatcherFUN_80043390flag-selected table; eight overlay leaves at 0x801F7644..0x801F8690dump_world_map_top_prim_leaves.py
ground-height samplerFUN_80019278the continent is a heightfield from +0x4000 nibblesSCUS
compass remapFUN_800467E8octant table lookup, not trigonometrySCUS
P2 gate testFUN_8003BDE0C1 any-blocks / C2 all-requiredSCUS
CLUT walkerFUN_8001ADA4 case 0xBslot-5 cadence; installed by FUN_8001F05C case 6SCUS
captures-walk pool of 45 entries; walk camera anchors; ocean cadence; texture rule 100%mednafen overworld save states

Full function-level reference: docs/subsystems/world-map.md.

See also