At a glance

Where on disc
Slot 4 of the kingdom bundles: PROT 0086 (Drake, map01), 0245 (Sebucus, map02), 0392 (Karisto, map03). Type byte 0x05.
Magic
None. Every sub-body carries the constant marker 0x080C at +4.
Size
15-16 clips per kingdom; 24-32 KB decoded; 8 bytes per (frame, part) entry.
In RAM
Loaded verbatim (LZS-decoded, no fixup) at the type-0x05 buffer pointer _DAT_8007B888, so the base differs per kingdom - see RAM layout.
Parser
legaia_scene_formats::world_map_overlay.
Confidence
Confirmed - container and RAM placement byte-verified, every header and entry field pinned to the instruction that reads it, clip lookup checked against a live map01 state.
Used by
World map, World-overview viewer

What it is

A kingdom's slot 4 is an animation bank: 15-16 self-contained clips, each an 8-byte header plus a table of 8-byte poses laid out frame by frame. An actor on the overworld picks a clip by number, the engine advances a cursor through it, and for each frame it applies one pose to each piece of the actor's model before drawing that piece. The model itself lives elsewhere - slot 4 carries only the motion.

  • Not geometry of any kind. No vertices, no triangles, no coastlines. Slot 4 was read as a vertex pool for a long time; that reading is falsified (see History) and it is why the format looked like it had a missing “command stream” that nobody could find.
  • Not the continent itself. The bulk terrain (~4300 textured quads in the top view) comes from the kingdom's slot-1 TMD pack, drawn through the overlay-mode dispatch table - see world map - bulk terrain.
  • Not a world-map format. The type byte 0x05 is what asset-type labels “MOVE”, and every field scene's type-0x05 section has the same shape: the same 0x080C marker, the same header fields, the same size law. A town's bank is this format with more clips in it. See ANM.
BundlePROT indexCDNAME labelDecoded sizeBodies
Drake0086map013230415
Sebucus0245map022696416
Karisto0392map032444416

Layout

Three nested levels: an outer pack of clips, each clip a header plus a frame-ordered table of poses, each pose eight bytes.

Outer pack

The offsets are absolute byte offsets into the decoded payload - unlike the slot-1 TMD pack next door, whose offsets count words.

Clip header (8 bytes)

FieldMeaning
part_countPieces posed per frame. Must equal the model's object count or the whole draw is skipped
flagsBit 0 enables blending between consecutive frames; no other bit is ever set
frame_countClip length in frames (the high byte is 0 in every clip on the disc)
markerConstant 0x080C - the animation-record marker, never read by the game
rateSub-frame divisor 1 / 2 / 4; only its low byte is read, and only when blending is on (below)

Clip payload

Entry (frame f, part p) sits at +8 + (f × parts + p) × 8, so a clip is always 8 + parts × frames × 8 + 8 bytes. That arithmetic fits every clip in all three kingdoms exactly, and every trailer is zero.

Each entry is one pose

The pose decoder unpacks three 12-bit signed translation components out of the first five bytes - byte 2 carries the high nibbles of X (low nibble) and Y (high nibble), byte 4's low nibble carries the high nibble of Z - sign-extends each, and pushes the vector through the GTE against the piece's rotation matrix. Bytes 5, 6 and 7 are the three rotation angles, each shifted << 4 into the console's 4096-per-turn angle units.

Translations are small - the whole corpus spans roughly -541 .. 384 units - because they are offsets of one piece within a model, not world positions. When blending is on, the decoder also unpacks the next frame's entry for the same piece and interpolates both translation and rotation with the cursor's sub-frame fraction, wrapping to frame 0 at the end of the clip unless the actor is set to hold its last frame.

rate - the sub-frame divisor

The header's +0x06 field is the clip's sub-frame divisor: the cursor advances by ceil(speed × 2 / rate) when blending is on, and by the actor's plain speed when it is off. That is why the field and the blend flag co-vary - every rate = 4 clip sets the flag. Values 1, 2 and 4 are the only ones on the disc.

Clips are shared the way actors are: the three leading rate = 1 clips are byte-identical in all three kingdoms, a trailing cluster is shared across all three, and other clips are shared between kingdom pairs. Degenerate clips (one part, all-zero entries) are empty placeholders.

The one field nothing reads

Every field of the entry is read every frame except the high nibble of byte 4, which is zero in all 22 228 entries across the three kingdom banks and a town's bank - structural padding, not dropped data.

Per-kingdom clip inventory

Drake = 15 clips, Sebucus = 16, Karisto = 16. Clips 0-2 are the shared rate = 1 templates in every kingdom, and an actor selects a clip by its index plus one.

Drake (map01, PROT 0086)

ClipPartsFramesRateBlendEntriesNotes
010201no200shared template
110201no200shared template
210301no300shared template
32302no60half the entries are zero
42202no40drives a live 2-part actor
510302no30025 unique of 30 frames
610262no260
710302no30025 unique of 30 frames
81032no303 identical frames (placeholder)
912302no360shared with Sebucus / Karisto 12
1012302no360shared with Sebucus / Karisto 13; drives a live 12-part actor
1112102no120shared with Sebucus / Karisto 14
12101202no1200the longest clip on any map
1314154yes210drives a live 14-part actor
142302no60drives a live 2-part actor

Sebucus (map02, PROT 0245)

16 clips. Clips 8-11 are the blended rate = 4 group.

Karisto (map03, PROT 0392)

16 clips. Clips 4-9 and 11 are blended at rate = 4; clip 10 is the corpus's one blended rate = 2 clip.

Full dump: asset kingdom-slot <PROT>.BIN --slot 4.

RAM layout

The LZS decoder writes slot 4 straight to its destination - byte-identical to the disc, no fixup - and the destination is the asset dispatcher's type-0x05 buffer pointer _DAT_8007B888, freshly allocated per scene load. That is why the base differs per kingdom, and it is also how a RAM probe should find it: read the pointer rather than hunting for the bytes. Clip 0's entries start 0x40 past the base.

KingdomBundle_DAT_8007B888End (excl.)Bytes
Drakemap01 / 00860x8011A6240x8012245432304
Sebucusmap02 / 02450x80119CE40x8012063826964
Karistomap03 / 03920x80108D840x8010ED0024444

In a live Drake overworld save the pointer reads 0x8011A624 and the 32 304 bytes there match the disc payload exactly - which settles the identity of that address rather than leaving it a coincidence of allocation order.

How it is played

Four plain calls, each reading a field of what it was handed. Only after the last one does the ordinary primitive dispatcher draw the piece.

  1. Install - FUN_8001F05C case 5The scene loader LZS-decodes slot 4 into a fresh buffer and stores the pointer at _DAT_8007B888.
  2. Select + clock - FUN_800204F8The actor tick resolves the actor's clip number to a clip and advances its 1/16-frame cursor.
  3. Draw - FUN_8001B964The animated-actor renderer works out the frame, checks the part count, and walks the model's pieces.
  4. Pose - FUN_8001BE80Decodes one entry into a GTE translation and rotation; the piece is then drawn by the primitive dispatcher.
FUN_8001F05Ccase 5 - install FUN_800204F8select clip, tick cursor FUN_8001B964animated draw FUN_8001BE80decode entry FUN_80043390prim dispatcher DAT_8007C018pool TMD _DAT_8007B888 actor[+0x4C] = clip per part actor[+0x44] mesh chain TMD group pointer
Slot 4 reaches the screen only through the animated draw: the clip poses each piece, but the vertices come from an ordinary model in the global TMD table, and the primitive dispatcher never receives the slot-4 buffer.

The clip lookup is one-based: the actor's clip number indexes the offset table with no +4, so number 1 reads the first offset. An actor with the party flag set draws from the party bank instead, and numbers above 0x3FF from the “MOVE2” bank - neither of which the overworld uses.

  • In place. A read-watchpoint tiled across the Drake window catches only reads (363 of 365 captures); nothing copies the bank anywhere.
  • Six references, image-wide. The buffer pointer is written twice, read once in the actor tick, and read three times in the Baka Fighter minigame overlay. No world-map overlay touches it, and the primitive dispatcher never receives it.
  • A hard structural check. If a clip's part count does not equal the model's object count, the renderer skips the whole draw. On a live Drake overworld save every animated actor satisfies it - 2, 12, 14 and 2 parts against models of exactly those sizes.

What actually draws the pieces

The primitive dispatcher FUN_80043390 is a TMD-style display-list walker: it reads a command word (primitive kind plus count), optionally loads the GTE depth-cue registers, and tail-calls a per-kind handler through a jump table - 0x8007657C in SCUS, or 0x801F8968 while the world-map overlay is paged in. Each handler transforms 3-4 vertices through the GTE and writes one GP0 packet into the ordering table. Its vertex pools are ordinary models from the global TMD table - never slot 4. The per-kind handler table and what each kind draws are on the renderer page.

Handler-bank selection and per-kingdom render load

The dispatcher adds a bank offset to the kind × 4 jump-table index. With no fade argument it uses bank 0; with one it loads the GTE far colour and depth-cue interpolant and switches kinds 12-19 to the depth-cue (fog) handler set. Two tint-word bits raise the bank further. The tests are sequential, so 0x20000000 wins when both are set. The bank is not the blend equation - that is a separate field taken from the tint word's bits 24-25.

fade_flagscmd_flags bitsBankEffect
== 0ignored0 (+0x00)Kinds 12-19 use the plain handler set
!= 0neither high bit1 (+0x50)Kinds 12-19 swap to the depth-cue handler set
!= 00x040000002 (+0xA0)As bank 1, but kinds 18 / 19 swap again
!= 00x200000003 (+0xF0)As bank 2, with its own kind-19 handler; never observed in retail world-map render

Over 19,935 dispatcher entries on Drake, 77% take bank 0 and 23% bank 1; neither high flag bit is ever set. Unobserved in one capture is not unreachable - banks 2 and 3 are real handlers. Exec-breakpoint totals over one warp transition (1800 vsyncs): Drake 71,331 handler hits, Sebucus 90,096, Karisto 13,593 - a measure of per-kingdom render load, not of slot 4. Kind 18 dominates in every kingdom (about 80% of Sebucus's primitives); kind 13 is heavy on Drake (9,465) and nearly absent on Karisto (49).

A second candidate reader at 0x80059DE4 is the generic VRAM LoadImage DMA (FUN_80059BD4) incidentally touching the window - not a slot-4 consumer.

The dispatcher's callers, and where the animation joins them

The primitive dispatcher takes (display_state, cmd_flags, fade_flags): display_state[0] is the vertex pool, [3] gates colour / light modulation, [4] is the command stream. All of those come from a model in the global TMD table.

  1. Top-view dispatcher FUN_801F69D8 (world-map overlay, 2572 bytes). Copies a 0x20-byte camera struct into scratchpad, loops over visible tile cells (±10 padding), dereferences each 0x20-byte object record, applies frustum bounds and visibility flags, projects through the GTE, then calls the dispatcher with colour 0xD0D0D0 (variants for the record's +0x1E flag and the interactive bit +0x12 & 0x800) and a fog cue clamp((screen_z - 0x5000) >> 3, 0, 0x1000).
  2. Per-actor renderer FUN_8001ADA4 (SCUS, 2456 bytes). Walks a linked list of actors and switches on each actor's render mode. Mode 5 draws the actor's model chain as-is. Mode 1 runs an optional screen-space cull and then calls FUN_8001B964, which re-poses every piece from the clip before drawing it - that is the path slot 4 feeds. A third mode, 11, steps a palette-cycling table that happens to live in the same actor field; it is a different format.

The neighbouring TMD table

The same dispatcher draws field characters and per-tile kingdom objects from a global table of TMD pointers, DAT_8007C018. That is a separate path: slot 4 is walked at its resident base and never installed into the table, even though a field-scene RAM snapshot fills the same address range with that scene's TMD pack.

DAT_8007C018 - installer, consumers, party-mesh source
GlobalRole
DAT_8007C018Array of u32 TMD pointers, stride 4; each target is a Legaia TMD (magic 0x80000002) with +0x08 group count and 0x1C-byte group descriptors from +0x0C
DAT_8007B774Install counter (next free index)
DAT_8007BB38Walk counter (last valid index) - every reader gates on it, so entries past it are never content
DAT_8007B824Per-pack count / persistent-base index
DAT_8007B6F8Kingdom-TMD prefix (5 in every snapshot)
FunctionRole
FUN_80026B4CInstaller, called per TMD from the asset dispatcher's TMD-pack case; builds the group-descriptor array at tmd + 0xC and stores the pointer. Ghidra misses the store because an addu sits between the lui+addiu and the sw.
FUN_80021B04, FUN_80024D78SCUS actor allocators - fill actor+0x44 from DAT_8007C018[actor+0x64]; the variant also sets actor+0x10 |= 0x08000000
FUN_801D77F4, FUN_801D8280Overlay allocator and table walker
FUN_801F69D8Top-view dispatcher - indexes [(object_kind + DAT_8007B6F8)] per tile
FUN_8001E890, FUN_8001EBECCap the three party TMDs at 10 groups and patch one equipment-conditional group descriptor per member (disc group 10 or 11)
FUN_801D6704Overlay scene loader: installs PROT 0874 section 0 into [0..4] via FUN_80020118, then the scene's own TMD pack via FUN_80020224 - the same walk every field scene runs

Entries [0..4] are the party character meshes from PROT 0874 section 0 (a 5-TMD pack, LZS at file offset 0x20; slots ship with 12 / 12 / 12 / 3 / 2 groups). A settled dolk field snapshot shows 143 installed entries, [5..142] being that scene's field TMD pack; a mid-warp Sebucus snapshot shows 92 with the install still in flight. Extraction 874 is raw TOC index 0x36C (876), which is what the CDNAME player_data define and the dev-tree path data\field\player.lzs name (numbering space); FUN_8001E890 loads it with li a0,0x36c. Extraction 0876 is a different file (a VAB + TIM list + SEQ stream with no TMDs).

In the port

legaia_scene_formats::world_map_overlay parses a kingdom's slot 4 (parse, the clip header accessors, Slot4Record::transform for the decoded pose). The bank is the same container every scene's type-0x05 section uses, so the generic ANM reader handles it too.

asset kingdom-slot <PROT>.BIN --slot 4
asset player-anm <PROT>.BIN --desc-count 7

The first dumps each clip's parts / frames / rate / blend flag; the second is the generic animation-bank detector, which reports a kingdom bundle exactly as it reports a town.

The older slot4-png / wireframe projections plot raw 16-bit slices that straddle the entries' nibble boundaries - byte-inspection aids, never geometry.

How we know

Every header and entry field is pinned to the instruction that reads it, the resident bank is byte-identical to the disc payload in all three kingdoms, and the clip lookup matches every animated actor on a live map01 state.

Provenance: routines, sites and captures
Function / siteAddressWhat it provesEvidence
Asset dispatcher case 50x8001F38C, store at 0x8001F3A8Slot 4 lands at _DAT_8007B888, the generic type-0x05 bufferDisassembly
Clip selector FUN_800204F8bank pick 0x80020534, lookup 0x80020584One-based clip lookup out of that buffer; cursor advance and wrapDisassembly; live map01 state matches all four animated actors
Animated renderer FUN_8001B964frame math 0x8001BAC0, part check 0x8001BAF0, draw call 0x8001BC84Frame-major entry indexing; the part-count / model-object-count invariantDisassembly; live actor list
Pose decoder FUN_8001BE80translation unpack 0x8001BF04, angles 0x8001C0CCThree packed 12-bit translations plus three byte-scaled angles; the blend pathDisassembly
Reference sweep of the buffer pointer0x8007B888Six references image-wide, none in a render pathfind-gp-relative-refs.py over SCUS + every overlay
Resident basesDrake 0x8011A624, Sebucus 0x80119CE4, Karisto 0x80108D84Byte-identical to the disc payload for every clip; each equals that kingdom's buffer pointerdiff_slot4_ram_vs_disc.py, locate_slot4_base.py, live state read
LZS decoder FUN_8001A55Cwrites at 0x8001A604 etc.Sole writer of the window: decoded verbatim, no transcoderautorun_slot4_loader_hunt.lua
Primitive dispatcher FUN_800433900x80043390; SCUS jump table 0x8007657C, overlay table 0x801F8968Command-word decode and four-bank handler selection - the renderer the posed pieces go throughDisassembly; captures/slot4_dispatcher/
TMD installer FUN_80026B4Cstore at 0x80026BA8How the global model table is filledfind_addr_materializer_dat_8007c018.py
Party-mesh sourcePROT 0874 section 0The table's first five entries byte-match the disc packDrake post-warp RAM dump vs LZS-decoded section

Full capture transcripts and reproduction steps: docs/formats/world-map-overlay.md.

Readings that turned out wrong

Slot 4 is not geometry: neither a vertex pool waiting on a missing command stream nor a coastline wireframe. Both readings are falsified, along with a handful of smaller ones.

The retired hypotheses and what killed each
  • A vertex pool with a missing command stream. The long-running reading: each 8-byte entry a 3D vertex (x, y, z, attr) loaded straight into the geometry coprocessor, with the triangle topology and object placement in a separate stream nobody had located. The stream was never found because it does not exist - the buffer pointer reaches no renderer, the entry's field boundaries fall on nibbles rather than 16-bit words, and the pair of bytes called attr is the Y and Z rotation. It came from reading a primitive handler that genuinely works that way and assuming slot 4 was its pool.
  • Coastline wireframe / dev-menu outline. Rendering every body on every projection plane and topology mode produces no map silhouette in any kingdom - unsurprising, since the plotted values are packed transform fields. A parts × frames heightfield grid fails the same test.
  • “Slot 4 is transcoded into the working buffer.” The 0x801BA000 writes belong to the per-actor procedural stream, and the “window full of models” snapshot was a dolk field scene, not a world map.
  • “Sebucus shows no render reads.” A wrong-base assumption; against the verified base, 171 of 177 reads land in the window - and their return address sits in the pose decoder's caller, which is what identified the reader.
  • “The world-map overlay renderer reads it.” A capture recorded a return address of 0x801F78D4. A MIPS return address is the call site plus eight, so the call is two instructions back - and decoding that word in all 68 images that share the slot-B load address, three hold a call there and the world-map renderer is not one of them. The only one that can be resident during a warp is the summon renderer, calling the per-primitive draw wrapper. So the value resolves, and it still is not evidence of a world-map terrain pass: a read watchpoint fires on an address, and the wrapper is handed whatever pointer the heap gave it.
  • Table entries past the walk counter are stale pointers, not content.
  • PROT 0085 / 0244 / 0391 named the entry an over-read window started in; the bundle is the next entry. See PROT.

Catalogued on do-not-re-walk.

Open questions

  • Which actor plays which clip. The numbers come from each scene's script, so naming a clip “the windmill’s spin” needs a script pass, not more format work.
  • Which caller, if any, selects primitive-handler banks 2 and 3 - candidates are debug render modes, battle reuse of the dispatcher, or cutscenes.
  • Whether the _DAT_8007B824 persistent-slot path is live. FUN_8001F05C case 2 with param_3 == 1 is its sole SCUS store and FUN_8001E1B4 resets the install cursor from it, but no dumped caller passes 1; a write breakpoint on _DAT_8007B824 answers it.

See also