Formation install and who arms the carrier

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.

Walk camera and axis convention

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 an ease between regions. 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, so the projection and the pad-to-axis mapping have to change together.

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.

Portals, gates and NPCs 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.
  • Talk-to. The overworld runs the field overlay's pad controller (FUN_801D01B0), so its action button is the field's interaction probe FUN_801CF9F4: one point 64 units ahead of the player, a ±72 box against every placed actor, the touched placement's record resumed past its spawn section by the dialog SM FUN_80039B7C. tick_world_map runs that path (world_map_record_talk_slot → trigger_field_interact → drive_inline_dialogue) against the placements' live seats. The fishing signboards (map02 P1[7], map03 P1[19]) are talk-to through it: frozen-lake flag 0x378 (map03), rod and lure holdings (4C DC), a yes / no picker, a white fade (34 01) and 48-frame wait (4A 30), then the record's own 3E 64 warp. The simplified-dialogue mode keeps the older path: an NPC entity's inline text, opened on confirm within one tile of its static spawn.
  • 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 through the boss-stager record. 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 (capture 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.

Why the sidecar doors read as sealed

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.

Overlay terrain renderers

The per-primitive dispatcher picks its emit function from one of two tables, chosen by a scratchpad flag; with the world-map overlay paged in, the overlay table's eight depth-cued slots point at overlay copies of the executable's renderers.

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

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.

Each overlay leaf also bends every corner's screen Y by the curvature table entry its own depth indexes, after the back-face test and the ordering-table depth; the executable's four lit handlers never bend, and no overworld mesh reaches them. mednafen-state prim-dispatch-table decodes both tables from a save state.

Per-frame dispatch, horizon and 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.

Sky band FUN_801F73E4 (PROT 0901): the terrain sweep FUN_801F69D8 calls it first, unconditionally, so every overworld frame draws it into the farthest ordering-table bucket and the terrain draws over it. It zeroes the yaw word, rebuilds the view, and projects (0, 0, 10000) from the raw eye position; the result’s screen Y is the camera’s horizon row, and the band’s top edge sits 16 pixels below it. Five 128×128 SPRTs alternate the two tiles of the 8bpp page at (512, 256) (CLUT 0x7A80, the kingdom bundle’s 256-colour TIM), scrolled by ((sx + playerX / 64 + yaw) & 0xFF) - 0xFF and darkened from 0x808080 to 0x404040 once system flag 0x14C is set. At the walk camera’s pitch the terrain hides most of it; it shows where the visible-tile window or a coast leaves the top of the frame open. Both resident overworld states’ packets reproduce exactly; the port draws it on both hosts, depth-tested at the far plane.

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.

Ground heightfield in the port

  • 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. The rule matches the object record on every captured cell.
  • 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 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.

The palette-walk table

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) 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.

Debug tooling globals

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-field::world_map::WorldMapController models the four camera globals and the toggle (tick(pad_input)); engine-dev-menu::dev_menu_leaves 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). The overworld opens the pause menu through the ordinary field arm. See save screen.

In the port

ModulePortsNotes
engine-field::world_map_entitycarrier 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-field::world_maptop-view controllercamera globals + toggle.
build_walk_heightfield / prim_dispatchground, top-view terrainheightfield with baked per-cell texturing; overlay-variant prim table.
legaia_scene_formats::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

See also