How to read this page

A thread is one question the project stopped to ask - why a boss battle freezes with the camera circling, where the party's battle models come from, which routine swaps the music. A thread is settled when it has an answer that can be pointed at: an instruction sequence, a runtime capture, or a measurement taken off the disc. resolved is a claim, not a warranty - which is what the evidence column is for.

Each area below is one table. A row gives the question, its verdict, the evidence grade, and under Why the full answer - addresses, captures and corrections. A thread whose answer outgrew a table cell links to its write-up under Long write-ups in this area. Every area, row and write-up is rendered from docs/reference/re-settled-threads/ when the site is built, so this page cannot state a verdict the docs have since overturned. The areas run from the overworld inwards (world map, battle, field, text, animation, audio, boot, rendering) and end with the project's own instruments.

Source of record
docs/reference/re-settled-threads.md
Row lifecycle
open → settled (here) → falsified if a later reading overturns it
Function names
A FUN_<addr> in a row is described in key functions

What an evidence grade means

Every thread is graded by what its stated evidence rests on. A thread citing more than one kind is graded by the weakest load-bearing claim - the one that breaks the conclusion if it is wrong.

GradeThe thread cites
disassemblyInstructions, addresses, opcode encodings, branch or store sequences read from the MIPS code. The strongest grade.
captureA runtime capture: a save state, an emulator probe, a write watchpoint, a trace, or a measurement taken off the disc.
decompiled-CGhidra's C output, a FUN_x(...) call signature, a Ghidra label, or a claim about store order / count / a boolean operator with no instruction behind it.
inferenceReasoning from surrounding facts, corpus absence, or analogy, with no direct evidence cited.

decompiled-C is the re-audit bucket, not a wrong-answer bucket. It marks a claim nobody has confirmed against instructions. Three shapes carry most of the risk: evidence citing a call signature or a funcs/<addr>.txt dump rather than instructions, any claim about store order or count, and any claim about which boolean operator a predicate uses. The catalogue of C-rendering artifacts is on Ghidra workflow.

A disassembly grade is only as good as the addresses in its cell. A row that names a function and a behaviour but no instruction address reads the same as one that was checked, and is the same bucket in practice. Re-audits of such rows have found a "nothing increments it" beside an add-one, a "neither subtracts HP" beside two HP stores, and a pad test sitting in a hole in a function dump. A checked row carries the addresses that settle it.

World map / kingdom bundles

# Settled threads: World map / kingdom bundles

ThreadAnswerGrade
Is the overworld ground depth-cued, and from which far colour?yes, toward a literal 0x100 per channel
Why

FUN_801F69D8 calls SetFarColor(0x100, 0x100, 0x100) (FUN_8005B7D8) at 0x801F729C just before jal 0x801F89B8; the emitter cues each cell with IR0 = max(SZ1 - 0x5000, 0) >> 3, SZ1 being corner (x1, z0). Scratch 0x1F800398 is the base RGBC, not the far colour. A PCSX-Redux watch saw far colour 0x1000 on all 6000 cue hits, and on keikoku's retail frame 566 of 584 continent cells match the port's engine-field::overworld_ground_cue exactly, all within 3. The emitter tests no 0x1000 cell bit; both hosts draw the cue (world-map.md).

disassembly + capture
Are the overworld decoration cells depth-cued?yes, one IR0 per object toward 0xD0
Why

Before each jal 0x80043390 (0x801F7254) the decoration sweep FUN_801F69D8 forms IR0 = min(max(TRZ - 0x5000, 0) >> 3, 0x1000) from the object origin's camera depth (0x801F7200..0x801F7220) with a1 = 0x00D0D0D0; the dispatcher makes a1 the far colour and parks IR0 at 0x1F800038, which all eight PROT 0901 prim leaves load before their DPCS / DPCT. The landmarks are not this sweep's. Both hosts stage it per draw (engine-field::overworld_ground_cue::decoration_draw_cue; world-map.md).

disassembly
Why do the overworld fog sheets read denser and brighter than retail's?the texture blend's 5-bit truncation
Why

The sheets are additive (ABR 1, texpage 0x0027 at 0x8007322C; every row-473 CLUT entry has STP set) and texture-blended; the GPU writes (t5 * c8) >> 7 per channel at the framebuffer's 5-bit depth, so at the median packet colour 39 a wisp texel of 1..3 of 31 contributes nothing, and a sheet adds about half the light of an unquantised blend. Both hosts' screen-primitive shaders run the truncating product, render-kernels::screen_prim::psx_texture_blend (field-ambient-fx.md). Colour, count, shape, draw order and camera offset match regardless.

disassembly + capture
Is the sparkle burst FUN_801E5338 reachable?no
Why

Its only reference is the tick word of template 0x801F2978, which only FUN_801E5834 materialises, and nothing on the disc references FUN_801E5834 in any form (both reference scans plus a raw jal/j scan). No live actor carries the tick in any of the 98 mednafen states. Filed [unreferenced]; no port.

disassembly + capture
Which camera does the overworld walk use?the field zone camera
Why

The overworld is a mode-0x03 field-run scene. On keikoku_chest_preload, sebucus_overworld_resident and karisto_overworld_resident the live pitch, yaw and eye trio 0x800840B8 equal the zone composer FUN_801DAB90's staging descriptor at 0x801F3580, with H = 368; the region ease is FUN_801DB510 (world-map.md).

capture + disassembly
What does 0x1F800394 bit 0 select?the kingdom overworld
Why

Set on every non-battle kingdom-overworld state in the mednafen library and clear on every other state, overworld battles included. FUN_801D629C (PROT 0897, s4 = 0x1F800314, lw 0x80(s4)) takes an overworld arm on it: a camera-space depth test before the slot pop (jal 0x8003D344 at 0x801D6460, slti 0x4001 at 0x801D6470) and a further -0x28 lift of the particle height (0x801D651C..0x801D653C); map01's system script raises the pool's gate (4C 30) after widening the view window (field-ambient-fx.md).

disassembly + capture
Does the disc carry one 4C EA occurrence or more than one?three - one per kingdom map bundle
Why

Three: the scripted game-over has one clean site in each of map01 / map02 / map03. The count needs text segments and pickers decoded as strides of the stream; a census walk that ends at each record's first 0x1F text segment and starts partition-0 records three bytes late reads one fewer than the op-arm oracle (field-op-census.md).

capture (disc census, asset field-op-census --only '4C EA')
The ninth routine of PROT 0901's draw bandit is not a per-prim leaf
Why

FUN_801F89B8 is the bulk terrain-cell emitter - a hand-written GTE loop that draws one textured quad per map cell. It reads the four signed bytes of the camera's visible tile window at scratchpad 0x1F8003E8..EB and takes max - min on each axis as its two loop counts (0x801F89FC..0x801F8A28), which is what makes that window a draw extent rather than a fog hint, and it indexes the floor-height ladder at 0x1F80035C (0x801F8A7C). jal'd from 0x801F733C in its own image. Row + detail on functions/world-map.md.

disassembly
The world-map camera's sub-9 arm table, and where its horizon gates storesubs 0xA..0xC store in the leaving j's delay slot
Why

Field-VM op 0x4C nibble-4 sub-9 dispatches an arm table spanning 0x801E1480..0x801E162C. Subs 0xA / 0xB / 0xC write the horizon gate in the delay slot of the j that leaves the arm (0x801E1648 / 0x801E1688 / 0x801E16C8), so a scan that stops at the branch names the instruction before; sub 0xD scales _DAT_8008457C >> 12 into 0x8007B910. The bit-24 arm of sub 9 writes two words - ctrl[+0x4A] and _DAT_8007BCAC - not one. See script-vm-menuctrl.md.

disassembly
World-map walk-view continent ground renderresolved
Why
capture
Walk-view decoration draw gate - which grid bit stamps the .MAP record meshes on the overworld?0x2000 alone; no flag or mesh-0 test
Why

The resident slot-B kernel FUN_801F69D8 (PROT 0901, byte-matched against a map01 walk capture) tests only cell & 0x2000 at 0x801F6ECC, skips placed records, and reads +0x10 with no zero test and no flag-0x2 test; Y is the 2x2 corner average. Mode 3 reaches it through FUN_80026CE4, which picks it over the field sibling FUN_801F7088 on _DAT_8007BA90. The big enterable mountains are 0x2000-only cells, so a 0x1000 gate drops exactly them - addresses and the cell census in world-map.md.

disassembly + capture
Walk-view untextured landmark prims - does retail draw the F*/G* colour prims of the slot-1 pack meshes (Rim Elm's hut roofs)?yes, same per-prim dispatch as the textured prims
Why

FUN_80043390 selects the renderer by the group header's flags >> 1 (0x80043614) and skips a group only on a null table entry. Slots 12..=15 (F3 / F4 / G3 / G4) are populated in the SCUS row 0x8007657C and in the world-map overlay row 0x801F8968 (0x801F7644 / 0x801F7838 / 0x801F7F78 / 0x801F8198, cued like the textured leaves). Rim Elm (Drake slot 29) is 24 G3 roof triangles over textured walls; 34 slots across the three packs carry colour prims - world-map.md.

disassembly
DAT_8007C018[45..53] mid-load vertex-pool pointersstructural
Why
disassembly
Field decoration path - does it dispatch the NCC light handlers?yes - the light-source rows, flags 0x10..=0x17; exec breakpoints in cave01
Why
capture
Kingdom slot 4 - per-record semantic + consumerthe world-map scene's type-0x05 ANM animation bank; the "vertex pool + cluster-A stream" reading is falsified
Why
disassembly + capture
The placed-actor mesh resolver on the world mapFUN_80020F88, at spawn time - not in the draw loop
Why

Called from the allocator FUN_80020DE0 at 0x80020F18 and from FUN_80024E08 at 0x80024E60: actor+0x64 = .MAP_record[actor+0x60][+0x10] + DAT_8007B6F8, render mode from rec[+0x12] & 3, then FUN_80024D78 fills the 0x9C-byte chain at actor+0x44 from DAT_8007C018[actor+0x64]. The port's field_objects::pack_mesh_index + FIELD_ACTOR_PACK_BIAS implement it. See world-map.md.

disassembly
MAN sections 2 and 5 - what do _DAT_801C6EA0 / DAT_80073EE0 carry?both are place-name carriers
Why

Section 2's body is the scene display name the on-entry banner draws (and the save screen's location row); section 5, the universal zero-length terminator, leaves its pointer on the world-map location table the kingdom MANs trail - 29 records of region + map x/y + discovery flag + 24-byte name, walked by the label pass that draws each place's name at its map position. Readers: the banner at 0x8003BB44, PROT 0981's label loop at 0x801CF34C (stride 0x20). With the SCUS quick-travel cells that is three carriers of one name, so a rename edits all three. Layout + provenance: place-names.md.

disassembly + capture
Where does the model-pool id space split at 0xF0?in the callers, not in the resolver
Why

FUN_80024E08 resolves a model id against the pool and does no splitting at all. The 0xF0 fork lives at 0x800393B8 and at 0x8003A2DC - the latter inside FUN_8003A1E4's placement installer (0x8003A2CC..0x8003A328) and its op-0x0E sibling - each choosing between DAT_8007B824 and the scene base DAT_8007B6F8 before it calls. A port that documents the split on the resolver names a routine that cannot perform it.

disassembly
Is FUN_80026B4C the only writer of the TMD pointer table?no - PROT 0976 zeroes a slot directly
Why

The registrar is the only routine that installs an entry, but 0x801CF1E0 inside PROT 0976 stores zero into the table without going through it. A liveness rule derived from the registrar alone therefore has one hole; DAT_8007B6F8 is stored with sw and read back with lhu, which is the other thing a pool walk has to respect.

disassembly
What is PROT 0981?the overworld map display, game mode 0x0D
Why

A 0x1000-byte slot-A image at 0x801CE818. Its monster_test label is CDNAME inheritance from the block opening at extraction 0978; the image's own operands are world-map ones throughout - DAT_80073EE0, the kingdom filter uRam8007b970, the camera pair _DAT_80089118 / _DAT_80089120, the eye trio 0x800840B8. Retail reaches it from the L1 edge, with no dev gate: the mode-12 handler FUN_80025DA0 loads it and calls 0x801CF4AC, the mode-13 handler FUN_80025F2C calls the tick 0x801CE850 every frame. One framed function and three leaves. details

disassembly
Which image holds the dev-menu row strings at 0x801CF344?PROT 0897, the field overlay
Why

File offset 0xB2C at base 0x801CE818. The argument is formed by lui/addiu pairs inside the renderer's own body - 0x801EAE44 / 0x801EAE48 and 0x801EB320 / 0x801EB324 - and a map03 state's 0x801CE818..0x801D0000 RAM window is byte-identical to 0897's first 0x17E8 bytes. PROT 0981 aliases the same VA as the other slot-A occupant, and the two are never co-resident, so the address is attributable only by image contents.

disassembly + capture
What writes the overworld camera's vertical-offset control word scene_ctrl[+0x4A]?map01's entry script, through op 4C 49's delta arm
Why

P1[0]'s park loop reaches 4C 49 3C 00 00 00 at +0x1B2 once its CD F8 00 00 7E 7E box test passes; the prologue's 2E 19 has raised _DAT_1F800394 bit 25, which sub-9 tests first (0x801E1488), so it stores 60 at 0x801E14BC and 60 - player[+0x16] into _DAT_8007BCAC at 0x801E14D4. A PCSX-Redux watch across the castle-to-map01 entry sees the reset at 0x8003A0D4 and then that single store (field-ambient-fx.md).

capture + disassembly
What does FUN_800271A8 build?the overworld's screen-Y curvature table, not a depth ramp
Why

FUN_8003AEB0 calls it as (0x28, 0x2AB980) when _DAT_8007B6A8 is set. It fills 0x8007BB08 with a 0x4000-entry quadratic drop, then RTPSes (0, ramp[3n/2], 2000) at H = 0x3C0 and stores SY - 0x78 into the 0x2000-entry i16 table at 0x8007BB04. Its readers are FUN_80043390's overworld leaves and FUN_8003F86C at index (SZ >> 5) + 1, and FUN_8001C394 at (ΣSZ >> 2) >> 5 with no +1; checked entry for entry against capture RAM (renderer.md).

disassembly + capture
Why does one overworld state hold a curvature table six rows up?the map display rebuilt it under the running frame's OFY
Why

The builder stores SY - 0x78 under whatever OFY the GTE holds. FUN_8005B7F8 (SetGeomOffset) has two callers disc-wide: the frame-begin pass (OFY = 114) and FUN_8001DAF8 (120). FUN_800271A8 has three: the MAN installer (0x8003AF98, reached only from the field init right after FUN_8001DAF8(0x140)) and PROT 0981's mode-1 / mode-3 arms (0x801CE958, 0x801CEB88), which run inside a mode-0x0D frame. Every library overworld state reads entry 0 = 0 but sebucus_overworld_resident (-6) (renderer.md).

disassembly + capture
Is the overworld's curvature per sheet or per vertex?per vertex, overlay rows 12..19 only
Why

The world-map leaf at 0x801F7644 bends each corner's SY by the table entry for its own SZ after NCLIP and the ordering-table depth are taken (0x801F7770..0x801F77E4). Only the overlay's rows 12..19 read the table pointer; the SCUS lit rows 8..11 draw flat. A fog sheet takes one entry at its particle's depth (renderer.md).

disassembly
Does any overworld draw reach the lit rows 8..11 the curvature skips?no - the port's bend-everything is inert
Why

The overworld dispatch takes a group's row as its header word >> 17 (srl s5,s7,0x11 at 0x800435A4), i.e. flags >> 1, so a lit row is a flags 0x10..=0x17 group. A census of every TMD a magic sweep finds in the three kingdom bundles' scene entries and in the party pack PROT 0874 counts none; the lit-row groups on the disc sit in field scenes and a few non-overworld entries. So bending every overworld draw matches retail pixel for pixel. details

disassembly
What ink does the map display's pointer draw?its baked reds, averaged: the colour word's ABR never reaches an untextured packet
Why

Retail's table holds the pointer as flat semi packets (0x22 / 0x2A) in the TMD's baked reds one cue step down, with no E1 among them, so they blend on the draw mode the chain left (ABR 0). The untextured leaves read only the dispatcher's ABE word (0x800449B8, 0x80044B94); the ABR word reaches textured leaves alone (world-map).

disassembly + capture

Long write-ups in this area

World-map walk-view continent ground render

Status: resolved - Evidence: capture. Heightfield geometry plus per-cell, terrain-keyed multi-page texturing; shipped in the engine.

The continent ground is a procedural heightfield drawn as one textured quad per cell, not instanced meshes; the slot-1 pack meshes are only the sparse placed landmarks.

  • Height: FUN_80019278 (SCUS, always resident) reads an entity's XZ, gates on the object-grid 0x1000 cell bit and bilinearly interpolates the floor height from the 2x2 block of +0x4000 nibbles (grid[0], [1], [0x80], [0x81], each & 0xF -> DAT_1F80035C[nibble], weighted by the sub-tile position, >> 0xE).
  • Landmarks: pool = record[+0x10] + prefix, resolved 14 of 14 against the live render list via FUN_8001ADA4 case 5 / FUN_80024D78 / FUN_80020F88; spawned by FUN_8003A55C, gated on flags & 0x4 (about six objects, pools 36 / 34 / 11 / 7 / 19 / 21). The bulk 0x1000 cells carry +0x10 == 0.
  • .MAP source: a raw (uncompressed) 0x10000 region at PROT.DAT 0x655800. The loader's retail branch resolves PROT index *(0x80084540) = 0x55 = 85 -> toc[87] = 3243. The per-entry extraction's 0085_map01.BIN (a count-46 pack at 0x668000) is the field object / script pack; the .MAP sits under the overlapping manifest entry 83.
  • Texturing: per-cell POLY_FT4 (cmd 0x2C), one 32x32 quad per visible cell in a row-major world-cell sweep, selected from the cell's object record: +0x14 = 8x8 atlas tile (u = (id % 8) * 32, v = (id / 8) * 32), +0x15 = PSX tpage (0x1A grass, 0x0C mountain, 0x1B / 0x1C water, 0x0B forest), +0x16..+0x18 = PSX clut word. Tile, page and clut match the record 100% across mountain and coast captures (scripts/ghidra-analysis/analyze-walk-ground-tiles.py --verify-rule).
  • Port: legaia_scene_formats::field_objects::build_walk_heightfield / Scene::walk_heightfield - a quad per 0x1000 cell, corner Y from the +0x4000 LUT, per-cell UV and [clut, tpage] baked into WalkHeightfield::uvs / ::cba_tsb; verified against the disc.
  • Not a positional (col % 3, row % 3) pick on a single 0x1A grass page: grass cells' +0x14 values land in the atlas's top-left 3x3 block, which imitates one. The per-cell terrain renderer is overlay-resident (0x801F76xx), so a static SCUS consumer sweep does not see it.
  • Not a combined walk + overview mesh pool: 0085's and 0093's slot-0 atlases target the same VRAM pages, so they are mutually exclusive sets that clobber each other if co-loaded.

Owning page: world-map.md (texturing under Ground texturing).

Field decoration path - does it dispatch the NCC light handlers?

Status: resolved (yes - the light-source rows) - Evidence: capture (cave01_attached_light exec breakpoints and frame) with the dispatcher disassembly.

The field decoration pass FUN_801F7088 emits through the per-prim dispatcher FUN_80043390, and TMD groups with flags 0x10..=0x17 reach its four NCCS / NCCT light handlers.

  • Dispatch: the body is picked by flags >> 1 (0x800435A4); kinds 8..11 are FUN_8004409C / FUN_8004423C / FUN_80044434 / FUN_800445B0, the ROM's only hardware-light code.
  • Frame: cave01's rock columns are all flag-0x11 groups. cave01_attached_light holds the scene-load light (angle trio 0x994 / 0x9CC / -0x62C, back colour 0x202020) and the light matrix 0x1F8003A8 that trio builds, element for element. Retail's frame shows the wall turned from the light at an eighth of its texel and the facing wall above neutral; shading the lit rows with NCCS under those inputs reproduces it to within the image channel's noise, where a neutral 0x80 leaves both walls at their raw texel.
  • Exec count: autorun_w4d_light_kind_hits.lua on that state (no warp) hits kind 8 (FUN_8004409C) 882 times and kind 9 (FUN_8004423C) 200 times in 60 vsyncs, every hit returning to 0x801F78D4, the decoration pass's call into the dispatcher.
  • town01 is consistent: a cold-boot dirty_exec_hot sweep (~46M interpreted hits) finds zero hits in [0x800445B0, 0x80044798), which is the kind-11 body alone, and town01 sets a white back colour (op 4C 8A) under which a lit row draws at or above neutral and looks baked. Its houses do carry lit rows, and a ~31K-hit town01 window shows the kind-11 body hot.
  • Coverage limit: that sweep is the script-locked prologue arrival, effectively one viewpoint; map02 / map03 and free-roam multi-screen towns are unsampled.

Three instruments cannot test this, and each reads as a negative:

  • gte_ring records only RTPS / RTPT (gte_rtp_record, func 0x01 / 0x30) and INTPL (func 0x11), never NCCS / NCCT / DPCS / DPCT (gte.cpp record hooks). A GTE-ring "zero NCC" is vacuous; dirty_exec_hot is the valid liveness probe.
  • fntrace catches only dispatcher round-trips. The SCUS render handlers are natively compiled and directly called, so FUN_80043390 records 0 hits while fntrace_arm all catches ~300k dispatches/s.
  • map01-class world maps dispatch through the replaced table 0x801F8968 to the PROT 0901 leaves (dirty_exec_hot hot at 0x801F6E6C), not the SCUS 0x8004xxxx handlers - a different renderer, not a light-path test.

Owning page: renderer.md.

Kingdom slot 4 - per-record semantic

Status: resolved - Evidence: disassembly + capture.

Slot 4 is the world-map scene's asset-type-0x05 ANM animation bank, structurally identical to every field scene's type-0x05 section.

  • Container: [u32 count][u32 byte_offsets]; each body is one clip - an 8-byte header (marker 0x080C, part_count, a u16 frame count, an interpolation flag, the sub-frame divisor 1 / 2 / 4) then parts * frames 8-byte entries, frame-major: entry(f, p) = body + 8 + (f * part_count + p) * 8 (0x8001BAC0..0x8001BAEC).
  • Entry: three 12-bit signed translations in bytes 0..4 (sign-extended at 0x8001BF44..0x8001BF6C, pushed through GTE MVMVA at 0x8001C0E0) and three 8-bit rotation angles in bytes 5..7 (angle = byte << 4, read at 0x8001C0CC / 0x8001C0E4 / 0x8001C0E8). The only unread field is byte 4's high nibble, zero in all 22,228 entries.
  • Load: FUN_8001F05C case 5 stores the buffer at _DAT_8007B888 (0x8001F3A8). The pointer has six references across SCUS and all 32 extracted overlay images - the store, a reset in FUN_8002541C, one read in FUN_800204F8, three in the Baka Fighter overlay - none in a render path.
  • Clip select: FUN_800204F8 picks one of three banks (0x80020534..0x80020598): party flag actor[+0x10] & 0x01000000 -> _DAT_8007B75C; else actor[+0x5C] < 0x400 -> _DAT_8007B888 (slot 4); else _DAT_8007B840, the type-0x0B "MOVE2" bank. The lookup is 1-based, rec = bank + *(u32*)(bank + (id & 0x3FF) * 4), stored at actor[+0x4C], with the 1/16-frame cursor at actor[+0x68].
  • Draw: the animated renderer FUN_8001B964 (FUN_8001ADA4 render mode 1, table 0x8001042C) walks the frame's parts, refusing unless chain[0] == part_count (0x8001BAF0), and per part calls the pose decoder FUN_8001BE80 (jal at 0x8001BB20) then FUN_80043390 (0x8001BC84).
  • Live: in a map01 field-run state _DAT_8007B888 = 0x8011A624, the 32,304 bytes there are byte-identical to the disc payload, and four animated actors carry +0x5C = 5 / 11 / 14 / 15 with +0x4C = base + offsets[id - 1] and clip part_count = 2 / 12 / 14 / 2, equal to their pool TMD's nobj. The capture's return addresses 0x8001BB28 and 0x8001BC8C are the two jals above.
  • Detector: asset player-anm extracted/PROT/0086_map01.BIN --desc-count 7 reports one player-ANM bundle with record0 marker_1 = 0x080C.
  • Not a GTE vertex pool (i16 x, y, z, attr) walked by a "cluster-A command stream": the field boundaries fall on nibbles, not halfwords, and attr is the Y / Z rotation pair, read every frame. FUN_80044C14, a per-kind prim handler that does read vertices that way, does not own this buffer.
  • ra = 0x801F78D4 in the warp capture is not a slot-4 reader: it is the return of jal 0x80043390 at 0x801F78CC in PROT 0900 (PROT 0901 holds no call there), a draw wrapper handed a TMD group pointer.

Which actor plays which clip is scene data, not format: the ids are scene-script literals in actor[+0x5C].

Owning page: world-map-overlay.md.

DAT_8007C018 liveness rule

Status: resolved (structural) - Evidence: disassembly.

An entry of the TMD pointer table is live only at indices 0 ..= _DAT_8007BB38; there is no per-index semantic, and 45..53 in a small field scene is stale carryover.

  • Registrar: FUN_80026B4C writes DAT_8007C018[cursor] (sw a0,0x0(v1) at 0x80026BA8, v1 = 0x8007C018 + cursor*4) and mirrors the pre-increment cursor into gp+0x820 = _DAT_8007BB38 (0x80026BBC).
  • Walker: FUN_801D8280 loads that counter (0x801D8284) and runs i = 0 ..= counter, stepping the base by 4; entries above the counter are never visited.
  • Counter writers: an all-forms sweep finds exactly one store corpus-wide, and it is gp-relative, so an absolute-only scan finds none.
  • Per-stage reset: FUN_8001E1B4 at 0x8001E3AC.

Source: docs/reference/re-settled-threads/world-map.md

Battle / arts / level-up

# Settled threads: Battle / arts / level-up

ThreadAnswerGrade
Does the action SM's idle request restart a body's idle?no: setup seats the phase, and the request keeps the loop
Why

FUN_801D5854 writes no anim field, and battle setup seats clip 0 at (rand() % frames) << 4 (0x80051574). The engine's first idle request rebuilt the idle a render build had installed, so bodies swayed in step until their first turn and snapped there. Write-up

disassembly + capture
Why did the engine still name Thunder Blast on the frame before a wipe's results?the Done band's list wipe takes the label
Why

The 0x51 teardown resets the element list (FUN_801D99BC at 0x801E6170) before it re-spawns its closes on gliding mode 1 (0x801E6178..0x801E6204), and the label (records 76 / 77, opened by the 0x28 arm's FUN_801D8DE8(0x4C, 0)) is not among them, so it is gone from that pass to the next action's open. The port hides it on the same latch (BattleState::action_hud_wiped, engine-battle-hud::battle_move_name); battle_jou_party_wipe_results_retail reads image 1.000.

disassembly + capture
Why does an Orb's Heal land on other numbers?the flight strikes, then 0x3F's applier heals
Why

The heal's flight rolls FUN_801DD0AC on its victim whatever the cast is (0x801E188C, applied at 0x801E1960), then 0x3F calls FUN_800402F4(0, 0, ..): +200, no popup. A --watch recording reads the Killer Bee 96 -> 46 -> 246. Write-up.

disassembly + capture
Where does Scythe Wind's caster stand when the walk-in arrives?where retail's does, on retail's frame steps; the gap is the frame step
Why

On the cost-column recording from dohaty_nova_v2_pre_scythe_wind, retail's arm 0 lands Nova at (61, -497) and the clip-9 turn reads 2175. On --retail-steps the engine lands her at (62, -497) and turns to 2180; on its own constant step 3 at (48, -529) and 2168. The walk law is the port's; what parts the plain film (.913 against .972 on the steps) is the two step-2 passes retail runs once 0x6E's heavy entry leaves the ring, which is the frame step's thread.

capture
Which earlier actor did a monster's cast walk from?DAT_8007BD00, the gauge panel's last member
Why

FUN_801D8DE8's case 0x52 stores DAT_8007BD10[ctx[+0x13]] - 1 there (0x801D9094), and the action seed's Spirit arm raises the panel (0x801E2FDC). It names the fitted seat on all nine captures that walk from an earlier actor. Write-up.

disassembly + capture
Which pass raised the HUD plaque and readout bar battle_map01_gomboo_steal_mid_cast shows three pixels short of rest?the capture's own action seed
Why

A --watch film of Gomboo's Steal reads glide records 0 / 1 cleared by the seed's table clear (0x801E2CF4) and spawned on that pass, which also stores the yaw rung 0x800 (0x801E2CEC..0x801E2CF8; only the Attack arm overwrites it with 0x200). The capture's counter 0x81C is seven step-3 passes on, both records retired at 15: the screen shows the last in-flight pass. The forty-vsync reading took the Attack rung. The engine frame matches (image 1.000). Owning page: retail-compare.

capture + disassembly
What dims Element Change's ray fan differently from the port's?the capture emulator's dither, not the rays
Why

Rasterised under both rules, the still's ordering table places the capture on PCSX-Redux's side: the fans read (165, 94, 39) in the buffer, (162, 93, 39) under the emulator's rule, (145, 75, 30) under the console's, and each fan's builder output and dispatcher calls match the engine's. With the image child on that rule the fans read within 2 per cent of retail's and the state .945; the rest is the crystal's outline and the rays' rand() deal. Owning page: retail-compare.

capture
What backs Aluru's creature off through arms 15..18?arm 14 stores the running clip's root speed
Why

PROT 0916's arm-14 pass writes -16 into the creature's running clip entry (sh v0,0xc(v1) at 0x801F79A8), and a negative root speed steps every pass with no range test. A --watch of the creature's +0x38 reads the anim tick's root-motion store (0x80047DC0), -24 a step-3 pass, z -2307 to -3075. Port: World::apply_aluru_seat_writes. Owning page: cast-module.

disassembly + capture
What takes Death Game's move label off the screen?the module destroys the label's window
Why

Arm 2's face pass calls FUN_800319A8(0x21) (li a0,0x21; jal 0x800319A8, 0x801F7F34..0x801F7F40 in PROT 0957), the destroy-window routine, and 0x21 is the window id FUN_801D8DE8 opens the label under for (0x4C, 0): placement record 76's +0x00 byte (0x80077330). PROT 0966 (Evil Seru Magic) does the same on arm 10's pass (0x801F7058). battle_jou_puera_v3_death_game_mid_cast's recording shows the label through arm 1 and none from the faces on. Port: ModuleCamState::move_label_closed (cast-module).

disassembly + capture
What holds Xain's rate byte at 4 through Terio Punch?the commit of the punch's own clip
Why

PROT 0953's arm 0 stores +0x21D = 1 (0x801F6CA0) beside its stage of clip 0xA with the +0x1DC bump, and Xain's anim tick commits that clip on the same pass; the commit lifts any rate but 8 to 4 (0x8004B080..0x8004B090). A --watch 0x800ED481 recording of the punch from battle_tunnelc_xain_bull_charge_mid_cast reads both stores on one vsync, 1 from 0x801F6CA0 and 4 from 0x8004B090 with the old 1 in v1, and later commits leave 4. The cursor's 8 a step-4 pass is rate 4's. The port's commit already makes the lift. Owning page: cast-module.

disassembly + capture
Why does battle_vell_nighto_49_strike's headless camera stand its yaw 51 short of retail's?the gap no longer reproduces; two units are left
Why

Under LEGAIA_RC_CAM_TRACE the headless seed's yaw counter now reads 588, retail's, and the ground snap's strike-heading target 2805 against retail's endpoint 2803 (it read 2752); the glide aligned on it reads retail's live 2794 and the camera channel 1.000. Nighto's facing still stands 1911 against retail's 1930, which the strike-heading law turns into those two units. retail-compare

capture
Why does battle_geremi_ogre_strike's replay reach its gate on the idle?it reaches it on the walk; the film's residue was two harness seats
Why

The replay now gates on the walk (clip 1), but the capture's clip seeds stand each body on the displayed frame, so the film's Ogre walked from cursor 72 where the state holds 96, and the replay's pick swung 09 where retail's queue holds 07 07. The film driver now seats the state's own cursor and queue at the gate: every segment runs retail's length, .920 to .983 (battle_korb2_medusa_strike .718 to .959). No law changes. cast-sequence oracle

capture
Why does a full party's summon run step 2 into the flash where retail runs 3?it no longer does; the 0x32 drive hold counted passes
Why

Recordings of battle_kor_spoon_strike and battle_tower_swordie_strike with the cost column read 0x28..0x32 at 540..650 and the close-up's first two passes over 0x1FF; retail drops to 2 twenty-two vsyncs into 0x34, the engine twenty-one ticks in, its camera on retail's to the unit. 0x32's drive wait ran eight passes (24 vsyncs at step 3) against retail's 15; a CD read is wall-clock, so it now counts vsyncs. battle-action

capture
Why do Caruban's two swings land their hits where retail's do not?a blocked swing's seed word, which the burst reading cannot see
Why

The deal read hit words off spark-burst frames, so Flap's blocked event drew on the replay's own stream. A --hit-trace recording of battle_rugi_caruban_3_strike logs the seed at the head of the strike kernel's hit body (0x801EC5A8) for all four hits, blocked ones included; dealt those words (--strike-deal), the engine's block verdicts are retail's and the film's cues match 12 of 12 (.696 and film .834 on the burst words, 1.000 and .850 traced). No law changes (oracle).

capture
What draws the speed lines behind an art's banner?a static ray fan; the capture's miss was its seat, not a rand() deal
Why

The effect-script table arm's code 45 (record 0x801F5628, library mesh 13; codes 10, 46, 47 likewise), seated by FUN_801DEA50 on the actor's facing plus 0x800. Its program sets a colour, a lane, a level rate and a wait and halts: nothing moves it, and both ordering tables of minigame_muscle_dome_retail_skeleton_hyper_arts hold the same eleven POLY_GT3 pairs under two camera depths. Seated on the RAM's pose the frame reads .874 to .980. retail-compare

capture
Why do the engine's hit-spark parts retire sooner than retail's on battle_vell_nighto_49_strike?they do not; the engine drew each burst a frame ahead from its seat
Why

The list walk reads next before a node's tick and the allocator appends at the tail, so the twelve children a tail trigger's 0x17 seats are not reached on their seat pass: they draw frame 0 at the parent's point and tick a pass later. The engine drew frame 1 there. Retail's two extra listed passes draw nothing (0x80022BB4). A --retail-parts film and the engine's fxparts.csv now agree pass for pass. functions/battle.md

disassembly + capture
Why does battle_vell_nighto_49_strike's film run its yaw counter slow from 0x20 on?the frame's draw load raises retail's step
Why

The probe's cost column rises from 392 to 579 as the special clip's two after-image ghosts (+36 each) and the four hits' spark parts (about 2 a part) come up, so retail's step goes 2 to 3 and ctx[+0x6DA] climbs 4 a pass. The engine adds both loads to the ring's push (World::battle_frame_load_cost) and reaches step 3 there: the film reads .728 against .729 on retail's own steps. a frame's draw load.

capture
How long do the Done band's closing glides hold the next round prompt?a pass at most in the ordinary flow
Why

The menu SM's gate runs every pass ahead of the action SM (0x80047014 / 0x80047048), and the glides snap within a pass of the 0xFF arm; the pass they snap on is gated. autorun_round_boundary_hold.lua: at step 2 (party_basic_attack_vs_gobu_gobu) that is the 0xFF pass and 0x14 holds nothing; at step 3 dropping to 2 (chitei2_kemaro_v2_pre_lava_breath) it is 0x14's first. The port's fixed four passes are now three plus the gate. battle-hud.

disassembly + capture
Why does Rogue Thunder's hit take about twice retail's HP in the cast-sequence film?the film stood the party out of the state's Spirit stance; the roll is retail's
Why

The rugi_rogue_pre_rogue_* states hold the party at +0x1DE == 4 and the engine child ran with no guard. FUN_801DD4B0(0x580, ..) (0x801F74C0..0x801F74CC, PROT 0964) doubles the defender roll on the stance, the bonus arm rebuilds the attacker at def + 0x580 + rand % 0x161 and the finisher halves: 704..880 a victim. Retail's Thunder, Flame and Wind hits and the guarded engine's all land there. The driver now stands the probe's guard= seats. cast-sequence-oracle.md

disassembly + capture
Does the port grant the summons worn gear teaches?it does now; it had no code for it
Why

FUN_80042558's grant scan (0x80042714..0x80042890) and revoke scan (0x80042894..0x80042990) run in crates/retail-exec against CharacterRecord::sync_equipment_summons, byte-equal over whole records. retail-kernel-differential.md.

disassembly + capture
Where does the battle HUD read a zeroed maximum MP?the seat's own +0x152, which the port now carries
Why

Battle init copies the record's maximum into +0x152 (FUN_80053CB8, 0x80053E50..0x80053E58; a monster's from its record +0x10, FUN_80054CB0 0x8005518C), and from then on every in-battle reader takes the seat's: the MP tint (FUN_80046A20 0x80046B74, FUN_801D8DE8), the item applier's clamp and the validator through their actor pointer tables. The dumped corpus stores it only there, in Lapis Wave (0x801F6D1C) and in Fatal Decision; nothing restores it in the fight or copies it back to the record. battle_deene_lapis_strike reads 0/0 on every seat on both sides. battle.md

disassembly + capture
Do level-ups draw rand()?eight a level, on the one stream; the port drew none until the stream was lent to the tracker
Why

The eight jal 0x80056798 of FUN_801E9504; the differential holds the record and the draw count equal on one stream. level-up.md.

disassembly + capture
Why does Aluru's arm 19 read as a white ramp running twice retail's?the wash ended early, no ramp is wrong
Why

The kill of the fade parked at ctx[+0x102C] and its white fall sit behind word > 3 * scalar << 6 (0x801F86AC) in the tail arms 0x12 and 0x13 share; fired on the arm's first pass, they took the wash away and the frame fell to the night stage. On the pass alone, with the verdict's draw held on retail's seed (--rand-at 0x34:0), arm 19 tracks retail. cast-module.md

disassembly + capture
What drew the lightning arcs past Aluru's blast in the engine?the port's emitter ran on a hidden body
Why

Effect-0x10 sparks from the burning-body emitter, shed by the dead victim, which keeps the +0x21F = 2 of arm 0x12 (0x801F8684) after the Done arm hides it. The emitter sits in the pose decode, which only the draw callback FUN_80048A08 reaches, and the draw tick FUN_800480D8 calls it only for a body it draws (0x800482E0 / 0x800482F8). The port now takes the draw plan's verdict, and the film holds no arcs. battle-action-helpers.md

disassembly + capture
Why do Aluru's and Nighto's cast-sequence cues read .33 and .40?the verdict's draw, not the cues
Why

Both draw a kill verdict in arm 0. Retail's seed word gives Nighto 22235 % 8 = 3, no kill, and the engine's stream 30808 % 8 = 0, a kill, whose death clip sounds the extra cues. Aluru's differs the other way, and --rand-at 0x34:0 puts it on retail's kill path with cue score 1.00. Nighto's draw sits later in that tick than the hold reaches.

capture
Why do Vera's and Spoon's shots miss retail's yaw by 34?the oracle's seed, not the cast
Why

Those arms read 0x800 - caster +0x46, and a group-target cast does not turn the caster, so the shot keeps the facing 34 the fight left in the state. The cast-sequence seed leaves it 0. Seeding 34 gives yaw 2014 on both sides and leaves both casts' scores where they were.

capture
Why did Zenoir's first hit sound 0x200, 0x16, 0x16?the wrong stage on the pre-hit
Why

Arm 8's hit stages +0x1EF, else +0x1F0, with no knockdown fork (0x801F77B4..0x801F77D4). The port ran the shared pre-hit, whose fork took the knockdown entry on a monster with a get-up. Retail's recording shows clip 2, then arm 9's sb zero,0x1da leaves its cue 0x21C.

disassembly + capture
What were the pale quads Spikefish's arms 5..8 drew in the engine?the port's seat, not a part
Why

The port seated PROT 0925's whole record set at the band's entry, so arm 7's five additive shells (0x801F7DB0..0x801F7EE0) drew from arm 1 on. parts.csv showed them live from the first module tick. The records are seated per arm (cast-module.md).

disassembly + capture
What does a four-slot event list read?a byte the fight's heap placement picks, which the disc decides
Why

The tick reads entry[0x10 + ((entry + 0x13) & 0xFF)] in RAM, and the block address follows a fixed law: ids load upward (0x800543EC..0x800549F0), each block takes block[+0x08] bytes (0x80054668) from the top of the free node, and the first ends at 0x800B9A50 with one member seated, 0xA8 lower per further member. The +0x88 relocation (0x8005473C) puts 0x80 under k = 0x1A. 145 placements and 22 reads in 86 retail states match (monster-animation.md).

disassembly + capture
Why does a measured juggle window run past 32 * beat / (speed * rate)?the window counts passes
Why

The commit zeroes the cursor (sh zero,0x68(s5) at 0x8004B068), so no fraction carries. The tick advances it (step * speed * rate) >> 1 a pass (0x80047730..0x80047764, step = DAT_1F800393) and latches +0x1F7 after the commit (0x80047E1C..0x80047E54), so the byte drops on the first pass boundary at or past the formula and a step-3 pass overshoots in vsyncs (battle-formulas.md).

disassembly
What does the anim commit's read of the juggle byte +0x1F7 decide?where the re-staged clip's sound-cue track starts
Why

FUN_8004AD80 stores 0 to the cue cursor +0x1F6 on every commit (delay slot 0x8004AFD0) and 1 (0x8004B05C) when the staged entry is the playing one (bne 0x8004AFCC), +0x1F7 is up (0x8004AFD4), and the entry is +0x1EF / +0x1F0, or +0x1F3 on a party seat (0x8004B010) or with first cue entry[+0x56] >= 0x64 (0x8004B048..0x8004B050). A juggled re-hit replays its reaction without the first cue. It is not a counter or guard window, and it reads the juggle counter's own byte. Port clip_ladder::recommit_cue_cursor (battle-formulas.md).

disassembly
What does FUN_801EFE44 do at an action seed?it moves the combatants: a formation recentre
Why

Called from state 0x0C for every category but Run (jal at 0x801E2D44). It squashes an axis span over 0x800, boxes the formation - for an Attack only its target +0x1DD - and subtracts the box's centroid from every included slot's +0x34 / +0x38, so a melee opens with its target on the stage origin. The Cort capture reads Vahn (0, -812) -> (0, -1625) and Cort (0, 813) -> (0, 0) across the seed pass. Empty party seats are moved and boxed too. Port battle_action::seed_formation_recentre (battle-action-helpers.md).

disassembly + capture
Do a melee's bystanders move out of the attacker's lane?yes - the make-way step
Why

State 0x14 (0x801E30CC..0x801E3198) gives each slot that is neither attacker nor target a heading a quarter turn off the attack line, on its own side, into ctx[+0x6E6 + slot*2], and seeds the countdown ctx[+0x6D6] with the melee monster's size class; the SM's shared tail (0x801E6824..0x801E6934) steps them (lut * step) >> 9 while it drains. Port battle_action::arm_make_way / make_way_step (battle-action-helpers.md).

disassembly + capture
How often does the battle separation pass push a close pair?once a battle frame, by a fixed amount
Why

FUN_80046A20 calls the driver FUN_80051078 at 0x80047050, right after jal 0x801E295C and under its gate (ctx[+6] and 0x8007BD71 both 0xFF, 0x80047024..0x80047044); the driver runs every ordered pair of seven slots with actor +0x04 set. The kernel FUN_80050BB8 pushes when |dx*sin>>12| + |dz*cos>>12| < (r_a + r_b) / 6 (0x80050BBC..0x80050CC8), by sin >> 10 / cos >> 10 on the live pair +0x34 / +0x38 (0x80050CD0..0x80050D24). Neither reads DAT_1F800393. Party radius: the stager's 0x280, 0x400 on Terra (0x8005151C..0x80051538).

disassembly
What do the monster spell entries' +0x04 / +0x08 descriptors hold?two clip-morph lanes over the monster's own mesh
Why

The battle draw's FUN_800495C8 (jal at 0x80048A7C) reads both lanes off the playing entry (0x800495FC): keys +0x01..=+0x04 give a trapezoid weight over the clip phase (0x80049634..0x80049704), +0x08 counts [object, first_vertex, count, deltas] records it blends in place through FUN_8005B038 (0x800497DC) after saving the authored vertices; FUN_80049858 restores them, lane +0x08 first. All 313 disc lanes parse with every run inside its object. Both hosts draw it through World::battle_morphed_tmd. monster-animation.md.

disassembly + capture
Is the per-character ability bitfield at record +0xF4 / +0xF8 catalogued?yes - the 64 passive indices
Why

The two words are one 64-bit field: the stat aggregator FUN_80042558 turns each equipped item's passive index into bit index of +0xF4 (sltiu < 0x40 at 0x80042664, sllv / or / sw at 0x80042684..0x8004268C) over all three character records with no membership test (0x80042D8C), so an absent member's party-wide passive still reaches the global mask; and all 64 indices are tabulated with their names, scopes and readers on accessory-passive-table.md. The bits named on battle-formulas.md are entries of that table.

disassembly
Does the fight against monster 0xAF (Tetsu) seat and flag as retail does?yes
Why

Tetsu's only row, town0d row 4, carries header byte 1, so ctx+0x287 = 4, Run is refused and the roll is skipped. Battle init still raises 0x200: its 0xAF arm (0x800519DC..0x80051A04) runs straight-line after the seat loop, ungated by the row. The lone seat is (0, 800) from either seat family - counts 1 and 2 are authored identical in both, and every solo library battle reads it. The engine enters the row this way (tetsu_row_is_flagged_and_forbids_the_ra_seru). No state inside the fight exists; the tutorial spar is monster 0x4F with the word 0.

disassembly
Do party statuses survive a battle?no, except in an arena leg
Why

The exit's party loop clears +0x16E unless _DAT_8007BAC0 & 0x100 (0x80046E90..0x80046EB0), and floors HP 0 to 1 on every exit. The controller node runs before the actor nodes, so the party ticks copy the cleared word to record +0x12E (0x80048040 / 0x80047680), which battle init reloads (0x80051718). That the list walk finishes in the exit frame is inference; all 38 non-battle library states read +0x12E as 0. Port battle_formulas::battle_exit_party_reset.

disassembly
What is the loss window 0x42?the result window's twin
Why

FUN_801D8DE8 ids index the SCUS placement table 0x80076C10 (jump table 0x801CEB68 at id - 0xA); records 0x41 and 0x42 are byte-identical, a framed 288x42 box landing at (16,160). The loss string is the defeat buffer ctx+0x129 (FUN_801D84C0): the lead's name plus the suffix at 0x801F4C94, or the team string 0x801F4C78 with its name escape patched. Both hosts draw it through engine-battle-hud::battle_defeat_banner (battle.md).

disassembly
Does a monster's one-shot clip tween into its queued clip?yes, and the step it tweens across moves the actor
Why

On the stream's last frame the next entry is frame 0 of the queued clip when HP +0x14C is non-zero and +0x1DA < 0x10, with the committed entry's +0x0E added to the Z delta unless +0x228 is set (0x80049A7C..0x80049BD0, 0x80049D64). The natural end (frame >= frames) moves +0x34/+0x38 by that +0x0E along the facing (0x80047A68..0x80047B2C); the event-path cut moves it pro-rated (0x80047950..0x80047A28). Port World::battle_tween_target, MonsterAnimPlayer::set_tween_target, motion::end_root_step.

disassembly
Which keyframe does FUN_8004998C's re-blend retry rewrite?the next frame's, in a scratch journal
Why

The counter gp+0xA10 is zeroed per part (0x80049D6C) and gated > 0xC00 (0x80049FD8); the retry rebuilds the next frame's triple as (x+0x800, -(y+0x800), z+0x800) in the journal 0x801C9060, never in the stream. The battle unwrap guards on > 0x800, the field helper on >= 0x800. On PROT 867, 103650 of 9448740 blended part samples take it; whole-frame samples are unchanged. Port engine-battle-vm::battle_pose_blend, live on both hosts.

disassembly + capture
Which arts directions does Rot disable?Left 0x08, Right 0x10, Up and Down 0x20
Why

0x801D1E60..0x801D1F7C, each refused press answered with cue 0x23. The ring refuses Attack at 0x38 (0x801D1560) and Magic under Curse (0x801D1434). Port rot_blocks / ring_arm_refused.

disassembly
What does a special battle do at the wipe check?the whole party counts down when the leader is fully rotted
Why

With _DAT_8007BAC0 non-zero, 0x801E6578..0x801E65AC counts the whole party down when the leader's +0x16E & 0x38 == 0x38. The three Rot limbs accumulate: the applier ORs each new limb in at 0x801E1734..0x801E1740. Port battle_formulas::special_battle_wipe.

disassembly
What does Run do in an arena leg?the leader acts first and the leg reports a run
Why

Flow 0xFE's tail (0x801D3228..0x801D328C) stores ctx+0x274 = 0 and the leg outcome 0x80084448 = 4. First monsters 0xAF and 0x3D..=0x3F are exempt - exactly the two 0x200 fights. Port special_battle_run_forfeit, a leader-first dispatch.

disassembly
Does a special battle open the result window?no
Why

FUN_8004E568 skips FUN_801D8DE8(0x41) at 0x8004F614 and FUN_801D8DE8(0x42) at 0x8004F8F0 when the word is non-zero. Port VictorySequence::window_opened, which gates the spoils panel both hosts draw.

disassembly
Who writes _DAT_8007BAC0?eight stores; none between the formation roll and the results
Why

A find-gp-relative-refs.py sweep finds eight stores, including boot FUN_8001D424 (0x8001D528) and the minigame exit FUN_80026018 (0x80026098). None runs between the formation roll's raise (0x8005205C) and FUN_8004E568, so the Rim Elm ambush pays nothing (battle-formulas.md). No state reaches that victory, so it is not capture-confirmed.

disassembly
What does the special-battle word _DAT_8007BAC0 suppress, and who makes a fight unescapable?spoils, absorb, spell XP and monster flee; the formation row's header byte
Why

Whole-word readers: FUN_8004E568 zeroes gold (0x8004F0AC) and EXP (0x8004F274) and skips the drop (0x8004F480); FUN_8004AD80 skips the steal (0x8004B48C); FUN_801DDB30 skips spell XP (0x801DE450); FUN_801E91E8 answers an absorb as known (0x801E9224); FUN_801E9FD4 drops a granted monster flee (0x801EA994, after the roll). Op 3E FF writes no escape flag (0x801E070C..0x801E0788, PROT 0897); ctx+0x287 is the row's record[+0] (via 0x8007BD60 bit 7). The Rim Elm ambush row carries 0 (escapable; engine: rim_elm_ambush_disc).

disassembly
Why does the Tetsu spar never open on a back attack?the battle-stage id skips the formation roll
Why

Its 3E FF row (town01 row 4) carries header byte 0, but FUN_80051D84 also skips the roll on DAT_8007B64A at 0x80051DB8, which the tutorial arm sets.

disassembly
Why do three-on-one formations sit +13 in Z?the per-round recentre FUN_801DB318
Why

The battle flow runs FUN_801DB318 at every round start (0x801D0EE4) and on the ring's first cancel (0x801D11E0): it squashes an axis wider than 0x800 and subtracts the centroid ((max + min) as u32) >> 1, which is -13 for z = -825 ..= 800. Balanced formations read their authored rows. Engine World::normalize_battle_formation (battle.md).

capture + disassembly
What does a downed party member play?knockdown, then entry 7, then entry 8
Why

The tag-4 commit at HP 0 stages entry 7 for a party seat (0x8004BEA8..0x8004BEE0), the tag-7 commit stages 8 (0x8004BF18..0x8004BF24), and the tag-8 commit raises +0x1DC bit 3 (0x8004BF34..0x8004BF4C); entry 8 - root speed 0, the downed kneel - re-commits at each natural end. Three states read +0x1D9 = +0x1DA = 8 with +0x1DC = 8. Engine world::battle::clip_ladder (battle.md).

capture + disassembly
What does FUN_8004DC68 do?the near-camera ghost pass
Why

Every battle frame (jal at 0x80047124) it sets +0x8 |= 0x83000000 (semi-transparent, blend 3) on bodies within dist/4 of a point on the camera's view axis and ghosts a caster's side during states 0x28..0x2E; a RAM replica matches 277 of 279 seated slots. Engine camera_ghost_pass, drawn on both hosts (battle.md).

disassembly + capture
Who forbids the Ra-Seru chip, and where is it read?bit 0x200 of _DAT_8007BAC0: two raisers, two readers
Why

Battle init clears a word of exactly 0x200 and raises the bit for first monster 0xAF (0x800519C0..0x80051A04); the formation roll raises it for 0x3D..0x3F on map 0x0C / 0x15 (0x8005200C..0x8005205C). The ring crosses the chip out at 0x801D12DC and refuses its arm at 0x801D1448; no other load of the word tests 0x200 (battle.md).

disassembly
What do the Baka Fighter impact pair's templates spawn?a sprite-arm flip-book and two stage meshes
Why

Templates A (0x801DB8FC / 0x801DB960) are op-0x23 sprite-arm quads (FUN_8002A5A4) stepping a 16-cell flip-book; B (0x801DBBA4 / 0x801DBBD4) are stage TMDs 1 / 2 (gp[+0x754] zeroed at 0x801CF1B0), faded through +0x78 from 0x1000 under an additive colour word. Port engine-minigames::baka_impact_fx (minigame-baka-fighter.md).

disassembly
What does FUN_801F0450's tail do?the Auto command's art insertion
Why

0x801F0B4C..0x801F1274 walks the character's art-animation bank (0xD0 stride) from record rand() % 5 + 0xB and splices learned arts' arrow strings over the end of the queue the spend loop wrote, paying out of a local copy of the Spirit gauge actor[+0x170] - nothing is stored back. The per-arrow cost is halved under record +0xF8 & 0x800 (0x801F0D00), the routine's only test of that bit. The pool arm and its tail run only for a slot whose ctx[+0x266 + slot] Auto flag is set (0x801F0704) (battle-action.md).

disassembly
Where does the target cursor's name plaque sit?placement record 0x29, centred on x = 0xE8
Why

FUN_801D5854's target arm (0x801D5B08..0x801D5BAC) measures the target's name (jal 0x80035F04) and seats the box at x = 0xE8 - w/2, pulled back to 0x130 - w past 0x130, row 162; the live seat starts at max(x + 0x80, 0x148) and slides in. On party_basic_attack_vs_gobu_gobu "Gobu Gobu" measures 55 and rests at (205, 162) (battle-action.md).

disassembly + capture
Which battle limbs draw dark, and how?a party seat's Rot limbs, per mesh object
Why

FUN_80048A08 reloads the colour word +0x74 and blend +0x78 for each object (0x80048BEC..0x80048C00) and, for a party seat (+0x5A < 3), overrides both when the seated actor's +0x16E carries a Rot bit and the object falls in that bit's range from the five-byte row at 0x80077998 + (0x8007BD10[seat] - 1) * 5: bit 0x08 objects row[0]..=row[1], 0x10 row[2]..=row[3], 0x20 from row[4] up (renderer.md).

disassembly
Does the tag-0x67 streak ribbon have a caller outside the move-FX dispatcher?yes, the per-clip pass of FUN_8004CE2C
Why

Gala's tag-0x67 arm (0x8004D1E8..0x8004D248) calls FUN_801E1D98 with the target's seat vector +0x3C (addiu a0,s1,0x3c at 0x8004D220) and the literal trail id 0xC on every frame the clip cursor sits in 0xB0..=0xF0 (battle-action.md).

disassembly
What is ctx[+0x25]?the round's skip count
Why

Actors whose turn the round passes over without acting: 0x801DAB84 clears it (sb zero,0x25 in the delay slot of jal FUN_801DABA4) and the dead-slot sweep bumps it (0x801DAC2C). Every other sb ...,0x25(...) in PROT 0898 stores a GPU-packet u / v byte; the round counter is +0x28A. The action SM's round-end bound subtracts it (battle-action.md).

disassembly
What does 0x801F696C gate?the strike loop's per-frame swing drift
Why

FUN_801EED1C clears it at its head (0x801EED88); the Miracle arm (sw at 0x801EF5B8, a j delay slot), the Super tail match (0x801EFBD4) and the head of FUN_801F0450's auto-fill arm (0x801F0518, not its art tail) set it. Its one reader, lw at 0x801E3840, arms the drift that moves the actor and its target along their facings each frame when the committed clip carries a header byte and the latched clip is outside 0x10..=0x1A (battle-action.md).

disassembly
How is the command-phase commit log laid out?records 0x2B + 3n, seated off the name's width
Why

FUN_801D388C's commit arms land name, command and target through FUN_801D5718, seat them at x = 16, name_w + 0x20, name_w + 0x60, and scroll rows 170 / 146, 170 / 146, 170, 194; an all-target commit logs record 0x3D / 0x3E. Settled at 0x801D3E38..0x801D3E48 (n * 3 + 0x2B), the three jals 0x801D3E4C / 5C / 6C, the seats 0x801D3E8C / EA8 / EC4, the rows 0x801D4540..0x801D4548 and the all-target records 0x801D4418 / 0x801D4438. party_basic_attack_vs_gobu_gobu holds the one-row case (battle.md).

disassembly + capture
What does the 0x6E commit-confirm screen gate, and where does Reselect land?party-wide, after the last able member; one step back onto the last able member
Why

Every commit site stores 0x6E instead of 0x28 once FUN_801DB81C equals the party count, for every party size and behind no option; with nobody able, the prompt's Begin stores it directly (0x801D10A0). Reselect is FUN_801D388C(0x21) -> FUN_801D32BC(1) (0x801D4750), and the dispatcher refunds the landing member's Item (0x801D30BC); the party_basic_attack_vs_gobu_gobu display list holds the two 64 x 20 plates at (84, 82) / (172, 82). Both hosts stage it (battle.md).

disassembly + capture
What does FUN_801DABA4's first loop do to a dead actor?zero the key, clamp Spirit, count the skip, refund the Item
Why

For a slot with +0x14C == 0 and an unspent key: key zeroed (0x801DABF8), Spirit +0x170 clamped to 100, ctx[+0x25] bumped, and a staged Item (+0x1DE == 1) refunded through FUN_800421D4(+0x1DF, 1) (0x801DAC44..0x801DAC5C). Its tie list is biased: a seat that raises the maximum is entered twice, so the first seat above 0 to reach the top key wins 2 / (ties + 2) (0x801DAC7C..0x801DAD60).

disassembly
What ends Koru's four turns, and how long is the strip up?Koru's own AI arm; the command phase
Why

No code compares ctx[+0x28A] to a bound. The AI switch at 0x801CF1CC sends entry 178 to 0x801EB52C, whose round switch (sltiu v0,v1,5 at 0x801EB540, table 0x801CF49C) casts 0xA2..0xA5 on rounds 0-3 and the finisher 0xA1 on round 4. The strip is text actor 1 on the gp+0x148 list, drained by FUN_800355F0 at 0x801D0EB4 and through FUN_801D99BC (0x801D9A24) when a round plays out.

disassembly
What does battle state 0x5A do?the arts target cursor
Why

Left / Right walk +0x1DD through FUN_801D8D00 (0x801D21D4..0x801D2268) after the arts entry; retail does not pre-pick the target (arts-command-gauge.md).

disassembly
Does the arts-shout selector remember a last pick per character?no - one byte party-wide
Why

FUN_8004C140 re-rolls while the draw equals gp+0xA4A = 0x8007BD62 (beq back to the rand call at 0x8004C3F8), stores the pick there, then forces channel 0xC when formation id gp+0x9F4 is 0x4F (0x8004C400..0x8004C414). A one-entry pool whose channel is the last pick would spin.

disassembly
What does the dome hub's first visit draw, and in what order?course card over title art; last emitted paints first
Why

Arms 4 / 5 call the course card FUN_801D042C (six corner draws from records 5 + course and 8), arm 0x14 raises nothing on a first visit, and FUN_801D1610 is a subtractive POLY_G4 (tpage 0x46, ABR 2). Every hub emitter links at OT slot 3 through the LIFO FUN_8003D2C4, so the last packet emitted is drawn first, and retail clears record 4's semi byte before each face (sb zero,0x176b at 0x801CFAF8) (minigame-muscle-dome.md).

disassembly
What triggers retail's in-battle steal?the killing blow, once per strike chain
Why

Not a command and not a hit: FUN_8004AD80's death-spoils arm (0x8004B29C..0x8004B65C) runs when a monster's knockdown clip ends at HP 0, and sets the latch ctx[+0x27] at 0x8004B3E4 before testing the killer, so an action's first kill spends its attempt. The action SM re-arms it at every strike chain's exit (sb zero, 0x16(s5) at 0x801E3A84, s5 = ctx + 0x11). The gates, the roll (doubled by Items Up), the caption record and the table's two readers are on steal-table.md.

disassembly + capture
Does the same arm do anything else?it returns a thief's loot
Why

When a monster that stole dies, the item goes back to the bag with a "took" (0x80077A38) or "recovered" (0x80077A4C) caption, or "recovered all" (0x80077A70) when ctx[+0x18] already reads 0x5B. PROT 0941 fills the stolen band at 0x801C8FE0.

disassembly
How does the command ring step back to an earlier member?cancel with a non-zero step counter
Why

The ring's cancel tests ctx[+0x1F] at 0x801D11B4 before the direction arms: zero returns to Begin / Run, otherwise FUN_801D388C(0x10) calls FUN_801D32BC(1) at 0x801D4010 and refunds an Item commit (0x801D12AC). Case 0x21 is the Reselect on the 0x6E commit-confirm screen (0x801D3040..0x801D3088).

disassembly
What do the first-visit dome hub arms draw?course card, title art, backdrop ramp
Why

Arms 4 / 5 draw the course card FUN_801D042C over the title art; arm 2 raises the backdrop level by 4 * dt while the intro card fades; arm 6 drains it and kicks the battle load (0x801CF9E4, 0x801CFC48). The ROUND banner FUN_801D02F0 is drawn by arm 0x15 only.

disassembly + capture
How many writers does ctx[+0x28B] have?five
Why

The four SCUS raises plus the tick's own clear, sb zero, 0x28B(v0) at 0x801E263C in FUN_801E2524 (PROT 0898). Only the MIRACLE raise (0x8004B7D0) and the side-array raise (0x8004B840..0x8004B868) fire the fixed cue; the HYPER default matches the art byte actor[+0x1DF + ctx[+0x15]] against 0x1C..0x1E and coin-flips through jal 0x80056798 at 0x8004B91C.

disassembly
Which stat does each damage wrapper read?801DD4B0 INT, 801DD6B4 ATK
Why

FUN_801DD4B0 reads +0x168 (lhu at 0x801DD52C, 57C, 594, 608) and FUN_801DD6B4 reads +0x158 (0x801DD738). FUN_801E7320's +3 is a constant (addiu a0,v1,0x3 at 0x801E73B8), and its modulus is the seated monster count ctx[+1] (lbu 0x1(v1) / div at 0x801E7380..0x801E7388), not the actor table's length.

disassembly
What actually breaks a rebuilt PROT 0874 container at battle load?a truncated pack
Why

FUN_8001E890 does check integrity in its own frame, on one arm of three: with gp+0x6AC at 2 it reads the raw container back out of VRAM (0x8005842C, four rects from (0x180, 0)), re-sums every word at 0x8001E9C4..0x8001E9F4 against the boot sum at gp+0x6B8, and reloads on a mismatch. That guard is on the decompress source; the register-only arm (bne v1, v0 at 0x8001E974) skips it, so the gate's writers stay the reason nothing walks a clobbered pack. The "header size word" and the "decoded length" are one descriptor field, so a rebuild cannot vary one alone. character-mesh.md.

disassembly + capture
What is FUN_801F44A0?the floating damage-number ring push
Why

(value_i16, seat_u8): value at ctx[+0x83C + cursor*4], seat at ctx[+0x318 + cursor*2], ctx[+0x85C + cursor*4] = 0, then the cursor ctx[+0x262] bumps and folds mod 8, ctx[+0x273] counting pushes. FUN_801E09F8 inlines the block at 0x801E1898..0x801E1918; PROT 0955's Kiss of Death calls it to show its literal 1. Not the Point Card clamp.

disassembly
Which entry does the battle loader's VDF registration walk?raw TOC 874 = extraction 872, vdf
Why

It reads raw entries 873+874 (etmd+vdf) in one contiguous transfer, keeps the second half's base in _DAT_8007B878, and walks a flat [u32 count][u32 offsets[count]] pack there, handing each base + offsets[i] to FUN_8001FBCC at 0x80052584. Its four constants 0x368..0x36B are raw TOC 872..875 = extraction 870..873, the whole befect_data block. The player-character pack is extraction 874 = raw 0x36C, a different entry. Body in battle.md.

disassembly
What makes a Ra-Seru chip render?resolved
Why

Three gates in order, and only the third selects a mark: the member's own chip byte ctx[+0x25F+member], then +0x16E & 0x1000, then the special-battle word 0x8007BAC0 & 0x200. Three emitters draw the marks - FUN_801DBC30 ((0, 96), 64x16, CLUT 0x7704, the red X), FUN_801DBD04 ((80, 96), 32x24, CLUT 0x770B, the Rot stamp, over the Attack chip under +0x16E & 0x38 == 0x38) and FUN_801DBEC4 ((120, 96), 64x16, CLUT 0x7700, the Curse plate, over the Ra-Seru chip under +0x16E & 0x1000) - all POLY_FT4, tag 0x09000000, code 0x2C808080, tpage 7, each early-out on ctx+0x6CE. See minigame-muscle-dome.md.

disassembly
Does the port draw retail's Rot / Curse marks over refused arms?yes, on both hosts
Why

The ring stamps Rot on the Attack chip under +0x16E & 0x38 == 0x38 (FUN_801DBD04(0xA0, 0x42)) and lays the Curse plate on the Magic chip under +0x16E & 0x1000 (FUN_801DBEC4(0xF8, 0x42)), 0x801D1314..0x801D1360; the arts entry stamps each rotted direction with FUN_801DBDDC(x, y, cost) (0x801D1DA8..0x801D1E54). The texels are the etim page's blue "Rot" and "Curse". Both hosts draw them from engine-battle-vm::battle_party_panel. No library state carries a Rot or Curse party member, so no retail frame pins the draw. See arts-command-gauge.md.

disassembly
What restricts a special battle's commands (0x8007BAC0)?resolved
Why

Bit 0x100 bars Item, 0x200 bars magic, course = ((word - 1) & 0xFF) >> 4. Two writer families: SCUS battle init keys on the first enemy monster id (0x800519DC; 0x8005200C under mode 0xC/0x15), and the arena seeds the word itself: FUN_801CEA6C stores 0x101 / 0x111 / 0x321 over a zero at 0x801CEBA0 / 0x801CEBB4 / 0x801CEBC8 on story flags 0x536 / 0x537 / 0x538, last match winning - so a seeded visit bars Item on every course and magic on the top one. 13 stores over 84 images, 5 clearing. Five readers gate on non-zero; 0x100 also gates the award arms 0x801E7978 / 0x801E7B40.

disassembly
What does a cast cost?MP, not AP
Why

The cast path 0x801D1408..0x801D1528 writes the action queue +0x1DF[0], sets +0x1DE = 2 and +0x1E7 = 9 and enters phase 0x46; it spends no AP. The cost is the spell table's DAT_800754C8 + id*12 + 3, discounted by the character record's +0xF4 ability bits 0x20 / 0x10. See battle-formulas.md.

disassembly
0x801E6218 - the multi-cast sweep the port was said to be missingthe latch's fall-through tail, ported; nothing else enters it
Why

It is the tail of the once-per-action teardown latch, entered only by falling through from sb v0,0x6(s5) at 0x801E6214; a five-form reference sweep over 84 images finds nothing else that reaches it. Its multi-cast part is a widget unload loop (jal 0x801D8DE8 at 0x801E62CC / 0x801E62F0, pair (id, id - 4)), not a separate sweep, and is ported (done::done_band_capture_and_banner_sweep, battle_value_readout). See battle-action-exit-gates.md.

disassembly
Which routine sets battle status bit 0x400?resolved
Why

PROT 0955's Kiss of Death miss arm, ori v0,v0,0x400 at 0x801F8CFC followed by sh v0,0x16e(s0) (the status appliers are tabulated in battle-formulas.md). The hit arm sets the victim to exactly 1 HP and clears 0x0F80 with no wrapper.

disassembly
PROT 0955's six no-damage tick bodiesresolved
Why

White Shield multiplies the defence pair by 3/2 idempotently from 0x801C9348[seat-3]; Power Charge adds x >> 2 to ATK capped at 999 (sltiu 0x3E8 at 0x801F74D4); Melt Spray takes 20% off ten halfwords and underflows x in {0, 1} to 0xFFFF (only 2 of its 10 floor tests read a live 32-bit register - 0x801F83E0, 0x801F8504 - the other eight re-lhu the store, same outcome); Void Accessories rolls one of three Goods slots off record[+0x19B+slot], refunds through FUN_800421D4 and rebuilds the bitfield with FUN_80042558; Kiss of Death is the row above; Terror Scream steals only the turn. Table in cast-module.md.

disassembly
Which of the capture bodies sharing a module image seat records nobody has placed?each body names its own; PROT 0955's stager seats the two that draw
Why

Kiss of Death seats its lips and burst per arm; White Shield's arm 0 seats 0x801F93D4 and Power Charge's 0x801F972C, each a twenty-byte record whose op 0x20 calls the cell's stager FUN_801F9370 (arm 0 0x801F93EC, the shield; arm 1 0x801F9744, the aura); Melt Spray makes no spawn call. PROT 0951's Chaos Flare seats two runs of seven per arm. details

disassembly + capture
What texture do Kiss of Death's lips draw from in the Lip King fight?the caster's own readef pages, streamed on its turn
Why

The lips record samples tpage 0x88, CLUT 0x7A00: the side-band page (512, 0) the turn pick FUN_801DABA4 has the applier stream for the acting monster's group (3 * record[+0x1C]). The engine wrote the formation's groups once in seat order, so in [0x21, 0x44] the page held the second seat's dev placeholder ("BACK READ"); the port now streams the group on every monster turn. details

capture + disassembly
What draws Terio Punch's arm-8 starfield?record 0x801F799C on the side-band page, over a stage the byte 3 keeps dark
Why

A camera-space sprite at z = 0x1000 on tpage 0x88, the stars Xain's group streams to (512, 0) (both VRAMs agree). The stage stayed lit because arm 8's sb zero,0x278 (0x801F75E4), which sits behind its gates, fired at the arm's entry. details

capture + disassembly
Which slot-B images write the actor stat block?eight, not 0955's four
Why

Eight of the 64 images write +0x150..+0x16D: 0940 (0x801F78B8), 0942 (0x801F7D34), 0943 (0x801F6A04 - 0x801F69D8 there is the 0xB5 body's head table, not the writer), 0945 (0x801F69F8), 0954 (0x801F6A58), 0955's own cells, and 0925 / 0956 which touch +0x16C only. See cast-module.md.

disassembly
How many capture trampolines are there, and what keys a tick body?21 trampolines / 48 arms; the key is a pair
Why

The band holds 21 trampolines carrying 48 (action id -> body) arms over 32 distinct bodies. A body VA is not a key: 0x801F69D8 is a tick body in six different modules, so the seam is keyed (entry, body). See cast-module.md.

disassembly
What does the AI companion pick read?it watches the wrong actor by design
Why

FUN_801EED1C's character-id-4 arm (0x801EEE40..0x801EEE48) reads actor_table[0] (+0x14C / +0x14E / +0x16E at 0x801EEE58, E8C, EC8) and writes actor_table[1] (lw 0x4(s0)). Its physical leg redraws rand() % ctx[+1] + 3 until the seat is alive (0x801EEF2C..0x801EEF98); a monster record with +0x1E == 2 yields a single 0x0E (0x801EEFC8..0x801EEFD4, store at 0x801EF02C), otherwise two draws of rand() % 2 + 0x0C (0x801EEFD8..0x801EF024). See battle-action.md.

disassembly
What are the battle applier's 132 selectors?116 of them are the epilogue
Why

Jump table 0x80014FA0 holds 132 slots over 15 distinct targets; 116 point at 0x800421A8, the shared epilogue, so there is no "stat-up / status-clear / queue-end / item slot" family above 0x0E. The one body above 0x0E is slot 0x82 at 0x800421A0, the Incense window top-up FUN_80046870 (port top_up_cooldown; not a brightness ramp). Decoded arms: 0x08, 0x0A, 0x0B..0x0D, 0x0E. See battle-formulas.md.

disassembly
How does a learned skill enter the displayed list?ordered insert, not head insert
Why

Both list writers insert in ascending order: the party-slot learned-Arts list at +0x74D (selector 0x0B) and the displayed-skill list at +0x186 (arm 0x80041FB4). A head insert would have reversed both displays.

disassembly
FUN_80043264 - how many equipment slots does it scan?three, not eight
Why

The counter starts at li v1,0x5 (0x80043284) and runs while slti v1,0x8, so it walks char +0x19B..+0x19D - the three accessory ("Goods") slots of the +0x196..0x19D block. Row on functions/battle.md.

disassembly
What does FUN_801E09F8's hit arm write?resolved
Why

The +0x1DC writes are ORs, not stores (|= 4 at 0x801E19D8, |= 1 at 0x801E1A18); the face store carries no +0x800, so the victim turns toward the attacker; and the reaction pick has three legs. Ported as effect_child_hit.

disassembly
Which routine applies a player-Seru module's magnitude?the tick body, not the stager
Why

The 0x801CF4EC arms are ctx+0x279 phase machines of 3396..7260 bytes, and the actor+0x14C write lives in them - in PROT 0910's case one call deeper still, in the applier FUN_801F81DC its tick reaches three times. The per-entry "data stager" is a different routine in the same image and carries no magnitude. Bodies in cast-module.md.

disassembly
Which player-Seru modules heal, and by what formula?three: Vera and Orb in the base band, Spoon in the evolved one
Why

PROT 0905 (Vera) restores record[+0x729 + slot] * 0x20 + 0xE0, clamped to the +0x14C/+0x14E pair, skipped at HP == 0 or +0x16E & 4, and pushed to the popup accumulator as a negated value at 0x801F7D0C. PROT 0911 (Orb) restores (magic_level << 6) + 0x1C0 over the party row actor_table[0..ctx[+0]]. Both unlock a status cleanse at level >= 3 (tier chosen through 0x801F6960). The input is the per-magic level byte (ids +0x705, levels +0x729 off 0x80084140, matched on actor[+0x1DF]). PROT 0919 (Spoon) restores (level << 7) + 0x380 over party seats 0..3. Everything else in the base band subtracts.

disassembly
How does the battle AP gauge fill, and what does a level-9 heal do to it?+8 per action, +32 for Spirit instead, damage taken, and a level-9 light heal doubles it
Why

Actor +0x170. State 0x50 of FUN_801E295C (0x801E5D60..) subtracts the art cost, then adds the accumulator +0x224 = 8 (0x20 for a Spirit action), capped at 100; the defender of a hit gains max(1, damage * 100 / maxHP); and cure tier 4 of Vera / Orb / Spoon (sll at 0x801F7F2C / 0x801F7E18 / 0x801F839C) doubles each cured seat's gauge under the same cap. The attacker's own hit credits it nothing. First reported by the_rabidsquirel (retail save-state testing; every writer).

disassembly
How many phase stores does a player-Seru body make?3..10, counting both store forms
Why

Per module: 0903 4, 0904 6, 0905 4, 0906 8, 0907 10, 0908 6, 0909 6, 0910 5, 0911 3, 0912 6, 0913 5; eight of the eleven write the terminal 0xFF through a pre-formed pointer. 0x801F69D8 is the tick arm for six of the eleven (0903 / 0904 / 0905 / 0908 / 0911 / 0912). A census keyed on the literal 0x279 displacement misses every store through a saved register and reads PROT 0908 as zero (falsified).

disassembly
What is actor +0x1DC at the cast band's reaction sites?flag bits on the victim, a restage bump on the caster
Why

On the victim it is a bitfield: PROT 0903 and 0904 ori bits 4 and 1 into it (0x801F7548 / 0x801F755C, 0x801F7E38 / 0x801F7E50), and PROT 0906 and 0908 store 5 and 1 (0x801F7438; 0x801F7D28, 0x801F7D54, 0x801F7F58). On the caster the same modules bump it (addiu 1, the restage trigger) at 0x801F7364..0x801F7374 (0903), 0x801F71AC..0x801F71C0 (0904), 0x801F6F50..0x801F6F60 (0906) and 0x801F77D8..0x801F77F4, 0x801F79EC..0x801F7A04 (0908). "Nothing increments it" is falsified.

disassembly
What does PROT 0907 (Nighto) roll?a kill roll and a resist roll, and the boss case is forced
Why

0x801F8534 is rand() % 8 - zero kills, anything else confuses - and 0x801F853C is rand() % (0x13 - level) >= 9 for the resist. The resist is forced, not rolled, when ctx[+0x287] is non-zero and the victim's monster record 0x801C9348[seat-3][+0x20] is set (0x801F6BF0..0x801F6C24), with an extra forced resist on rand() % 3 == 0 for character index 3 alone. Gaza 2 reproduces it live: ctx[+0x287] = 4, record +0x20 = 1, boss untouched.

disassembly + capture
Which band routine allocates a battle seat?PROT 0940's 0x50 / 0xAE, and only that one
Why

It claims actor_table[ctx[+1] + 3], copies the monster-record pointer into 0x801C9348[seat], and seeds +0x16C = 0, +0x1DE = 2, +0x1DF = 0x50, +0x1DD = 9 plus an HP copy. The 0xAE arm then coin-flips one of HP 1 / MP 0 / ATK 1 / AGL 0x20 onto either the clone or the caster - the "nine stores in two passes" shape is two mutually exclusive branches, not one pass.

disassembly
What does PROT 0941's 0x51 (enemy Steal) take?a bag slot or an item off the steal table; no damage
Why

Against a party victim it rejection-samples the 256-slot bag at 0x80085958 (rand() % 0x100, up to 0x400 draws), flooring the slot against *(0x8007B5EA) while DAT_8007BD10[1] == 4, then calls FUN_80042310(id, 1). That gate is lbu v1, 1($s5) at 0x801F77E8, $s5 formed as 0x8007BD10 at 0x801F77B0: seat 1 holding roster character 4, the split-bag condition - not a context field, since PROT 0941 makes no +0x11 access at all. Against a monster victim it rolls rand() % 100 against 0x80077828 + id*2, the same table the player's Steal reads. The stolen id lands at 0x801C8FE0 + (seat-3)*4, the message pointer at 0x800774AC.

disassembly
What does ctx[+0] count in a cast module?the party, not the whole actor table
Why

0x8004B3F0 walks DAT_8007BD10's party ids to form it, and ctx[+1] is the monster count. The +0x1DD group codes read against it: below 7 is one seat, 8 sweeps 0..ctx[+0], anything else sweeps 3..3+ctx[+1] (PROT 0956 folds codes below 3 into the 8 case and skips the caster).

disassembly
What escapes PROT 0906's phase-5 dispatch hole?the stager's arm 2
Why

The tick's arm 4 advances the phase byte into a slot its own table does not serve, and retail parks there busy until the stager runs: 0x801F783C..0x801F7858 is ctx[+0x279] += 1, reached as arm 2 of the seven-word head table at 0x801F69D8 (7788, 780C, 783C, 785C, 7920, 7964, 79A8).

disassembly
What is PROT 0904's arm 12?the swept quantity is an angle, not a radius
Why

ctx[+0x6D8] accumulates frame_delta * 8 and the arm advances when it passes 0x1000 - a full 12-bit turn - so what moves per tick is the direction of a ±0x30 cone, not the radius of a ring. Seats 3..=6 are tested against it by bearing (FUN_80019B28); a seat is skipped while (|d| - 0x30) < 0xFB1 unsigned, which admits both ends of the circle, and +0x1D9 is what stops a seat being hit twice as the cone comes round. Settled at 0x801F7AC8..0x801F7AF0 (the add), 0x801F7EF0 (slti 0x1000), 0x801F7E80..0x801F7E88 (seats), 0x801F7CF0..0x801F7D14 (the two gates). Port engine-cast::seru_magic_ticks.

disassembly
What is PROT 0962's arrival predicate?it arrives when the test reads zero
Why

bne v0,zero,<hold> at 0x801F7C0C and 0x801F7D08 holds the approach while FUN_8004E2F0 returns non-zero, so a non-zero return is not yet there. FUN_80050BB8 on that path is the separation nudge, not the approach step.

disassembly
Is ctx[+0x287] the counter-attack byte?no - it is the scripted-fight flag, derived at battle init
Why

FUN_800513F0 computes it as (DAT_8007BD60 >> 5) & 4, i.e. bit 0x80 of the per-battle flags that FUN_801DA51C raises for a formation row with a non-zero record[+0]; the counter-attack byte is +0x288. Its value space is {0, 4} (lbu at 0x800513F4, srl 5 / andi 4 at 0x80051430 / 34, store at 0x80051444), and 9 of 96 battle states read 4. All three FUN_801E295C reads gate on it, so two audio-duck arms and the attack-return arm are unreachable in an unscripted fight.

disassembly + capture
What gates each player-Seru arm's dwell?measured per arm - and it is not one constant
Why

Driving casts from pre-cast states gives per-arm dwell in module ticks for PROT 0903 / 0904 / 0905 / 0907 / 0908 / 0910 / 0911 and 0959. PROT 0907 holds 8 of its 16 arms identical across two fights and moves the other 7, so a single per-arm frame constant is the wrong model. ctx[+0x6D8] seeds 120 and drains one per tick on the player half and holds a constant 20 on the capture half. Tables in cast-module.md.

capture
Do the Seru side-effect debuffs and the encounter boost profiles reproduce live?yes, both, at measured magnitudes
Why

A level-9 Seru debuff takes a monster's ATK from 17 to 14 on both halves - the documented 20%. Both boost profiles reproduce off a live fight: a random encounter's [84, 17, 15, 14, 10, 30] becomes 17 / 25 / 24 / 12, and Gaza 2's 288 / 222 / 220 becomes 360 / 444 / 247. Vera at level 3 restores 320 and Orb 640, matching their closed forms exactly; PROT 0908's splash reproduces 522 -> 130 (/4) and 541 -> 405 (*3/4).

capture
What does the dome tally screen draw?six rows, and the HP accumulator sits between lanes 2 and 3
Why

The rows are [lane0 pending, lane1 pending, lane2 pending, the HP accumulator 0x801D1AC8, lane3 pending, the running tally 0x80084440], at per-lane brightnesses [0, 1, 2, 0, 3, 3] - four steps, not six. The six rows draw through FUN_801D1308 -> FUN_801D050C with the brightness argument unclamped; the 0x100 -> 0xFF clamp at 0x801D08FC belongs to FUN_801D08EC, the corner-anchored sibling, which no tally row calls. FUN_801D1184 re-forms the 0x801D base into a different register between the product and the store (0x801D11E0 / 0x801D11F0), so 0x801D1ACC is the round lane.

disassembly
What does the dome arena seed 0x8007BAC0 with before any story flag matches?1
Why

sw $s2 at 0x801CEB8C, with $s2 loaded 1 forty-three instructions and three jals earlier. That is course 0 with no bans - a seeded word, not an untouched one. The 0x101 / 0x111 / 0x321 values, and therefore the claim that every seed carries 0x100, belong to the three flagged arms only.

disassembly
Is 0x801F69D8 a routine in PROT 0943?no - it is a head table
Why

The five-word table at the image base is dispatched by the body at 0x801F6A04: sltiu v0,v1,5 at 0x801F6A70 bounds the index, addiu v0,v0,0x69d8 at 0x801F6A80 forms the entry, jr v0 at 0x801F6A94 takes it. The body runs 0x801F6A04..0x801F6EF4 and the MP-pair stores at 0x801F6D08 / 0x801F6D1C are inside it, so citing 0x801F69D8 as the writer names the table that points at the writer.

disassembly
Is the monster record's +0x20 a per-monster death-immunity byte?no - a double-width texture-page flag
Why

lbu a2,0x20(v0) at 0x801F1D0C hands it to FUN_80055468, which widens the model's VRAM rect from 0x20 to 0x40 halfwords at 0x800554E0..0x800554F4; 37 of 186 records set it. PROT 0907 / 0908 / 0916 read the same byte as a "big model" resist proxy under the scripted-fight flag; it is not an immunity list (falsified).

disassembly
Where does a slot-B module image's highest spawn record end?its own move-VM program bounds it
Why

Walk the opcode widths to a terminator and round the end up to 4, because the records are word-aligned. HALT (arm 0x800232BC..0x800232CC: ori v0,v0,0x8 into actor +0x10) outranks an armed 0x19 / 0x1B idle loop, the walk chains [header][program] rather than assuming one record per pointer, and where it dies with no terminator the last maximal 0x09 0x0FFF WAIT it stepped over bounds the record. Every image that has a highest record bounds it; the residue misses below or lands on the end and never terminates above it. See slot-b-module-layout.md.

disassembly + capture (disc walk)
What are Enemy Steal's acceptance tests, and what does the consume touch?a third test on shop price, and a window-bounded consume
Why

Beyond the roll and the occupied-slot test, the sampled item is accepted only when its +2 shop price is non-zero, so the quest/found-only ids - 96 of the 256 - are unstealable. The consume FUN_80042310 reads gp+0x2D2 / gp+0x2D4 as its first two instructions and scans only [start, end), returning the 0x100 sentinel when it finds nothing; a steal outside the active window banners a success and removes nothing. gp is 0x8007B318 live, so gp[+0x2D2] is 0x8007B5EA.

disassembly + capture
How large is the item bag, and what bounds a reader?256 slots behind an active window
Why

One 256-slot array at 0x80085958, reached through five SCUS helpers that each check the window pair gp[+0x2D2] (start) / gp[+0x2D4] (end), with gp[+0x2D6] the count. FUN_8004313C is the sole writer of that pair: a page flag at 0x80084594, a fourth-bank flag test and the party-id byte at 0x80084598 choose start 0 or 0x80 against end 0x100, which is the "split bag" a lone character sees. A real three-member memory-card block carries items up to index 159, so a 72-slot bound drops 88 of them - that figure is a cheat page's display page, not a capacity.

disassembly
Do the two move-0x36 / 0x37 damage wrappers differ from the shared kernel?the respect wrapper does not, the bypass one does
Why

Driving both against the same seat, FUN_801DD4B0 (move 0x36, the defence-respecting wrapper) reproduces the shared kernel exactly - 251 against 251 - so nothing in a capture distinguishes it; FUN_801DD6B4 (move 0x37, the bypass) returns 972 where the kernel returns 311. Exactly one capture-class id per wrapper class carries a move-power record.

capture
Does the spell record's +0x00 class byte reach the action seed?yes - it moves the fold by 21 frames
Why

The action seeder's Magic arm compares the byte against 0x14, and flipping it moves the damage fold 21 frames in a driven cast.

capture
What gates each capture-class arm's dwell?for all fourteen trampoline arms; the drain is per arm
Why

The countdown is drawn down by a per-arm multiplier of the scratchpad frame byte at 0x1F800393 - its product with 0x1F80037D, twice it, or once it; no single product fits every arm (arms that read as constants 4 and 8 are 1x and 2x a byte reading 4). All fourteen are measured, one fight each (PROT 0943's 0xAB twice), reading the single jal 0x801F2160 site in PROT 0898 as one module tick; the last two need a caster with enough spell entries. Three arms park rather than gate and PROT 0950's arm 6 has no gate. Tables in cast-module.md.

capture + disassembly
Is the battle loader's model-pack pointer *(gp+0x6BC) sane at battle time?no - the battle load reuses the block
Why

Across the catalogued state population the pointer names the same heap block in 97 of 98 states; it is sane in 37 of 37 field states and garbage in 61 of 61 battle states. The unclamped registrar inside FUN_8001E890 - lw a0,0x6BC(gp) at 0x8001EAFC, count off +0x00 at 0x8001EB10, jal 0x80026B4C per entry at 0x8001EB4C - is reached on all three arms of the gp+0x6AC fork, so nothing in its own frame keeps it away from such a block. The gate's writers do: over six measured routes it is never entered over one (see the registrar-entry row).

capture
How does the character-pack loader size its section buffers?from the container header, so a rebuild cannot change the decoded size
Why

FUN_8001ED60 takes the section-0 and section-1 buffer sizes from the container header words held at gp+0x69C and gp+0x6C8, and the LZS decode is length-driven by the descriptor. A header-byte-exact rebuild whose decoded size differs therefore gets a truncated pack rather than a larger one. The battle loader itself reads neither: *0x8007B878 is the vdf pack and gp+0xA8C the etmd pack.

disassembly
Is FUN_801F6B24 one walk or two?two dispatchers picked by one word
Why

beqz _DAT_8007BAC0 at 0x801F6BA8 selects between a 19-arm field-restore table at 0x801F6AD8 (sltiu 0x13) and a 12-arm int.tim panel-still table at 0x801F6AA8 (sltiu 0xC). Both start at phase 2 because SCUS's FUN_80025358 owns states 0 and 1. An ordinary battle teardown takes the field-restore table - four 64x256 uploads at x = 384 / 448 / 512 / 576, one hit each - and the panel-still table records zero. Counts are residency-gated: an ungated breakpoint at the same slot-B VA also fires on whatever else occupies the slot.

disassembly
What are PROT 1221 and 1222?the party's ringside reaction pair
Why

Two headerless BGR555 stills, 0x28000 bytes each, uploaded as four 320x64 bands to VRAM (384, 0); one is the cheering party and the other the dejected one, which is what the loader's index rule encodes - the variant is chosen from the lead character's live HP (u16 0x8008480E < u16 0x80084824 >> 1). See ringside-still.md.

capture + disassembly
Where does a retail value-readout quad put its far corner?inclusively
Why

Retail's own readout quads name the last texel, not one past it: 31 against 31, 47 against 47, 55 against 55 over the sampled frames. An exclusive corner draws every quad one texel wide; with the inclusive corner the digit gap is 0.

capture
Is the model-pack registrar ever entered while *(gp+0x6BC) holds a reused block?no - six routes, five entries, count 5 every time
Why

Breakpointing FUN_8001E890's entry, its gate, the registrar at 0x8001EAFC and every jal 0x80026B4C, and write-watching both words, over a door warp, a boss fight resolving to the field, a field walk into an encounter, a cold boot into NEW GAME and a cold boot through CONTINUE into a card load: the routine is entered five times, always over 0x8014D53C, and the registrar reads count 5 every time. The field-to-battle route enters it zero times. State 1 (register only) occurs solely with the field pack intact; states 0 and 2 always have the file read and the decompress between the gate and the registrar.

capture
What is FUN_8001E890's gate?the load-state word gp+0x6AC, not the game mode
Why

lw v1,0x6ac(gp) at 0x8001E900 forks three ways: 0 read the entry, decompress and register; 2 re-sum, decompress and register; 1 register only. Twelve writers keep the register-only arm away from a reused buffer - FUN_80016230 zeroes the word on every mode step outside 2/3 (0x800163B4), PROT 0978's post-battle restore writes 0 then 2, and the core reset, the minigame warp and the field overlay write 0. Not 0x8007B83C: that is gp+0x524, the game mode (falsified).

disassembly
Why do PROT 0943's 0x40 and PROT 0944's 0x53 fault before their first tick?they do not - the fault is SCUS walking the caster
Why

Both stage clip 0x0B on the caster (sb 0x0B,0x1DA(s1) at 0x801F6FBC / 0x801F758C), and the anim commit FUN_8004AD80 resolves a staged clip by indexing the monster record's spell-entry offset array with it (0x8004AF08..0x8004AF18). Gobu Gobu's array holds ten entries, so index 0x0B reads the record's name text as a pointer, an unmapped read. Forced on a twelve-entry caster both casts complete with zero unmapped accesses, walking arms 0..4 in 1, 9, 40, 8 and 32 ticks. No monster record's magic slots name either id, so both are casts retail never performs.

capture + disassembly
What is battle context +0x276?the side-band applier's stage byte
Why

Its writers are FUN_801DABA4 (0x801DAD6C), FUN_801E295C (0x801E49F4) and the applier SM FUN_801F12D0; no tutorial routine writes it. Both CD-XA cue arms test it, and the summon modules poll it before raising their own head cue, so it is open by construction at the moment a cast wants it. It is not a tutorial flag; feeding it from one silences the melee sting in those battles.

disassembly
What does FUN_8003DE7C(1) wait on?a data stream read in flight, not the last voice clip
Why

On the retail path (0x8007BA70 == 0, 0x8007B8C2 != 0) the poll skips its gp+0x91C countdown (beq at 0x8003DE90, 0x8003DF04) and answers busy only while the stream queue holds a read (gp+0x988, 0x8003DF70..0x8003DF78, then CdSync(1) and gp+0x928). The XA starter sets neither, so a voice inside a sounding clip replaces it. Every PCSX-Redux cast film reads 0x8007BA70 = 0 and the countdown at 120 through playing clips; retail-exec runs the routine to the same answer. The countdown reading is falsified. Engine World::battle_stream_read_in_flight.

disassembly + capture
What does the dance count-in banner draw?a sprite record, 160 x 32
Why

Record 0 of the 20-byte HUD table at 0x801D46CC - texel seat (0x48, 0x90), texel cell 0xA0 x 0x20, CBA 0x7D0A - seated by FUN_801D2F38, which halves the cell at the caller's 0x1000 unit scale ((w * scale) >> 13 at 0x801D3180..0x801D319C) and centres the result on the seat. Reading +0x0A as half-extents doubles it to 320 x 64. It is not text, and its animator samples once per three vsyncs.

disassembly
Does any item reach the Point Card strike (effect class 14)?yes - item 0xFE, by id rather than by descriptor
Why

Arm 14 at 0x8004209C takes min(_DAT_800845B4, 9999) off the Point Card bank and applies it as battle damage. No descriptor carries class 14, but action state 0x3E tests actor[+0x1DF] == 0xFE at 0x801E4080 and stores selector 0x0E into actor[+0x1E8] at 0x801E40C0, ahead of the state-0x3F applier call. item-effect-table.md

disassembly
What does an arts book write, and to whom?the class picks the character, the tier is the art id
Why

FUN_800402F4's arm at 0x80041FB4 turns the class into a roster slot with addiu v1,v1,-0xb at 0x80041FC0, so classes 11/12/13 are Vahn / Noa / Gala and the ally the player picked is never read; the tier byte is stored verbatim as the learned id by sb s6,0x74e(a0) at 0x80042064, into the sorted position the loop above it opens. The nine book records carry Fire and Thunder I/II/III = 3/2/1 and Wind = 5/4/1, which is a per-character art-id space and not a book level. item-effect-table.md

disassembly
Does retail bound the sprite-stack append its fog sheets use?no bounds check
Why

FUN_8001FA68 is an eight-instruction append whose store sits in the jr ra delay slot; its caller loads a capacity into a2 (lh a2,2(a0) at 0x8003F7FC) that the callee never reads. Port cutscene::sprite_stack_push; u16_list_append::list_append_u16 is the same address.

disassembly
Which battle-action state owns the only arm that reaches FUN_801F3990?state 0x3D, and an item is the door
Why

One reference disc-wide, the jal at 0x801E3E04, inside action-SM table slot 0x3D (base 0x801CED44, the arm's word at 0x801CEE38) - the Spirit / Item wait state, entered only from 0x3C, whose sb v0, 7(v1) at 0x801E3B60 is unconditional: the arm's one branch, sltiu v0, v0, 3 at 0x801E3B28, rejoins at 0x801E3B40, above it. The Magic arm 0x801E2EB0 reaches 0x3C only for a class byte < 0x14 and a spell id < 0x65 (sltiu at 0x801E2EF4), which the player Seru block cannot satisfy. details

disassembly + capture
What writes the slot-B stage selector _DAT_8007B64A?the field entity tick, off system flag 0x19
Why

A gp-relative sweep finds 14 accesses - 7 stores, 5 loads - where an absolute-address scan finds none. The value writer is the field entity tick FUN_801DA51C: it clears the byte at 0x801DA69C (in the delay slot of a jal 0x8003CE64) and raises 1 at 0x801DA6A8 when system flag 0x19 is set, which the consumer then reads back. 1 pages extraction 967, the battle tutorial; 0 skips the load (beqz at 0x80052688). Battle latches 3 at 0x801E6D2C, right after FUN_8003EC70(0x4A, 0) pages 969 directly. Zeroing sites: SCUS 0x80046E74 / 0x800557AC / 0x80056114, field 0x801DA69C, and both stage images at 0x801F7120.

disassembly
Which extraction entry does the slot-B pager's argument name?selector + 966
Why

FUN_8003EC70 computes a0 + 0x381 in raw TOC space, and raw is extraction + 2 (measured across every TOC entry), so the pager's own index is extraction a0 + 895. The one site that hands it the stage selector, 0x8005269C, passes selector + 0x47, so selector n pages extraction n + 966: 1 -> 967, 2 -> 968, 3 -> 969, and 0 skips the load. The same algebra reproduces the band's other two call sites, +0x28 -> 935 + i and -0x79 -> 903..913.

disassembly + capture
What do PROT 0968 and 0969 do?the two boss-stage modules, one function each
Why

Neither is a cast module: none of the three PROT 0898 entry tables names them, and only the pager loads them. Each is one function over its whole image, a phase machine on ctx[+0x289] driving the first monster seat. 0968 (FUN_801F69F4, seven phases behind the table at 0x801F69D8) is the arrival staging - camera walk, cue 0x20A, banner, then a hand-back clearing the stage id at 0x801F7120. 0969 (FUN_801F69D8, four phases, no head table) is the form transition - cue 0x20B, the seat's HP +0x14C = 1, a two-position camera shake, then FUN_8003ED04(0). details

disassembly
What is the != 4 term in the battle selectable scans?a seat, not an action state
Why

DAT_8007BD10 is the per-slot roster character id: FUN_801DA34C indexes it and subtracts one to reach a character record (0x801DA37C..0x801DA3B0). 4 is the AI-companion seat, so the scans are excluding a seat the player does not command. It is not an action-state byte, and no "removed / done" state 4 exists (falsified).

disassembly
What is the auto-command string pair at record +0x1A7 / +0x1B7?a staged queue and its backup
Why

Gated on DAT_8007BD04; the band is chosen by sltu(+0x156, +0x154). The primary leg falls back to +0x77F when its head is empty; the secondary zero-fills with no fallback (beq at 0x801DA4CC into 0x801DA51C). The write-back overwrites exactly one band and is guarded on +0x14C != 0 and +0x1DE == 3. The 0x5C8 save-window delta corroborates the pair: 0x76F - 0x5C8 = 0x1A7, and the patcher's performed-Super mask +0x75D lands at record +0x195.

disassembly
Is FUN_801D0748 the Muscle Dome's match SM?no - it is the round SM every battle runs
Why

It has exactly one jal across SCUS_942.54, every based overlay image and every raw PROT entry: 0x80047014, inside the SCUS battle frame driver FUN_80046A20, with no test in front of it and the fall-through rejoining at 0x8004701C. Three non-dome battle states enter it 350 / 313 / 255 times over 700 vsyncs each, every entry with ra = 0x8004701C. The dome is one of its callers' contexts; see minigame-muscle-dome.md.

disassembly + capture
What draws the dome panel still at VRAM (384, 0)?FUN_801D00F8 in the contest hub PROT 0977
Why

Two POLY_FT4 quads, tpage 0x106 over screen (0,-20)-(192,220) and 0x109 over (192,-20)-(320,220), sampling (384, 0)..(704, 240); *(0x801D1A7C) is broadcast into all three colour lanes, so it is a fade level and the call is skipped at zero. The emitter never materialises 384 - a textured primitive addresses VRAM through the packed tpage page index, 384 / 64 = 6 - so no 0x180 literal exists to find. Geometry in ringside-still.md.

disassembly + capture
What raises the still's fork _DAT_801D1AE0?the arena init, on re-entry
Why

Not the match teardown: FUN_801CEA6C forks on the arena word _DAT_8007BAC0 and stores 0 to the latch at 0x801CEB54 on a first entry (op 0x3E leaves the word clear) and 1 at 0x801CEC04 on every later one. A 3600-vsync walk into the arena enters the emitter 79 times and takes the six-tile arm every time. The natural arm: the checkpoint RAM of a three-visit dome run reads latch 1, hub arm 0x0A and level 8 at the second and third visits, with both still packets (0x2C080808) in the primitive pool. ringside-still.md

disassembly + capture
Who raises the Arts banner selector ctx[+0x28B]?SCUS FUN_8004AD80, in sequence, not as alternatives
Why

Every sb ...,0x28b on the disc is in FUN_8004AD80: the raises at 0x8004B774 (3), 0x8004B80C and 0x8004B87C (2), all in the actor[+0x1DA] == 0x1A SpecialStarter arm - and 0x8004ADDC adds 4 to a live one for the matching seat. 0x8004B774 falls through to 0x8004B7D8, whose read of the side array 0x801F6990 (0x8004B804) is stored over the 3. The four positions are 1 NEW (build-starter mark), 2 HYPER (default), 3 MIRACLE (ctx[+0x28D + slot]), 4 SUPER (super-starter mark). battle-action.md

disassembly
Can a RAM clobber of the resident PROT 0874 container trip FUN_8001E890's checksum?no - the sum is over VRAM
Why

Forcing gp+0x6AC = 2 reaches the sum arm; a genuine mismatch enters 0x8001EA08 and self-heals in about 45 vsyncs (clear the gate, j 0x8001E900, CD re-read, gp+0x6AC := 1 at 0x8001EB0C). Four XOR'd words of the resident container left the sum byte-identical (0x7BF74962), because it is taken over a StoreImage (0x8005842C) read-back of VRAM. Only VRAM at (0x180 + 0x40i, 0) or the boot sum at gp+0x6B8 can break it. Probe scripts/pcsx-redux/autorun_w5a_0874_clobber.lua.

capture
What does FUN_801D84C0 build?the four battle-result messages, not party panel labels
Why

Its two arms copy (FUN_8003CA78) and append (FUN_8003CAC4) pool strings at 0x801F4C38..0x801F4CC4 in PROT 0898 into ctx+0xA9 / +0x129 / +0x159 / +0x189: a victory line with its spoils sentence, a defeat line, and the two escape outcomes. FUN_8003CBF8(buf, 0xC1, 1) (0x801D86A4 and siblings) locates the 0xC1 name escape - it is not a width measure - and every patch writes the first seat's id, so a lone lead is named and a party is the lead's team. live-audit-triage.md

disassembly
Where are the steal-result captions composed?in SCUS FUN_8004AD80
Why

The three jal FUN_8003CB54 sites on the disc are all in FUN_8004AD80 (0x8004B2F8, 0x8004B338, 0x8004B60C), each after a FUN_8003CA78 copy of a template formed at 0x80077A38 / 0x80077A4C / 0x80077A64; FUN_8003CB54 appends the 0xC2 escape pair. The caption is shown through FUN_801D8DE8(0x5B). Port engine-battle::battle_steal, run from World::tick_battle_animations (steal-table.md).

disassembly
What is FUN_801D32BC?the command window's member cursor, not a turn order
Why

Six jal sites in PROT 0898: the round reset 0x801D8910, FUN_801D388C's case 0x10 (back, 0x801D4010), 0x11 (forward, 0x801D4128), 0x21 (back, 0x801D4750) and its tail pair 0x801D5690 / 0x801D56A0. Initiative is the execution order; the command order is the slot scan. Port engine-core::world::battle::member_step (the backward step) and engine-battle-menus::battle_input.

disassembly
At what level does a re-entered hub draw the ringside still?*(0x801D1A7C) across six hub arms
Why

The re-entry init seeds hub arm 0x0A at 0x801CEE2C and zeroes the level at 0x801CECD0. Arm 0x0A raises it 4 dt to 0x80; 0x0B lowers it 2 dt to 0x40 while tally lane 0 sits at its clamp; 0x0C holds; 0x14 raises it 4 dt to 0x80 on the latch-1 arm only; 0x15 holds under the ROUND banner; 0x16 drains it 2 dt to 0. Dispatch is the hub's 51-entry table at 0x801CE990. ringside-still.md

disassembly
What does the victory drop roll do?at most one item per battle
Why

FUN_8004E568 0x8004F3D8..0x8004F5A0: for every enemy seat, rand() % 100 against record +0x49, plus 30 when a living member has Items Up (+0x6C0 & 0x20000); the last winning seat's +0x48 decides, even a zero one, and a seat whose actor has +0x227 set is skipped. _DAT_8007BAC0 != 0 skips the loop. Unless a seat reads 100 or Items Up applies, a rand() & 3 must then be 0 (0x8004F584), and nothing is granted at 99 held (battle-formulas.md).

disassembly
How does the victory pose pick a party slot?a rejection loop
Why

0x801E66A4..0x801E6724 repeats rand() % party_count until the slot is eligible; a uniform pick among eligible slots spends the same draws on a different slot.

disassembly
What is FUN_801E91E8?the already-learned-Seru check of the killing-blow absorb
Why

0898 0x801E91E8..0x801E92D8: the 1-based position of Seru id in the acting character's learned-spell list (0x80084140 + char * 0x414 + 0x704, compared as id - 0x80), answering 1 when the slot is >= 3, carries no Ra-Seru (ctx[+0x25F + slot]) or _DAT_8007BAC0 is set. Its one caller is the absorb in FUN_801EC3E4 (battle.md).

disassembly
What does ctx[+0x269] hold?the Seru the killing blow absorbed
Why

The Done band reads it at 0x801E6224 and teaches it through FUN_801E92DC at 0x801E6234 in the same action - the absorb's grant, not a route into the capture cinematic 0x68..0x6B (battle.md).

disassembly
What is state 0x3E's class-5 arm?the gauge-extension item's presentation, not a Spirit arm
Why

0x801E3E80..0x801E4018, taken when the committed effect class is 5 (the item class that extends the gauge for one battle): raises HUD elements 0x0F / 0x52, draws (rand() % 2) * 2 for the camera variant (jal 0x80056798 at 0x801E3F2C), stages min(0x120, base * 7 / 5 + 8) and the actor's spirit +8, or +10 with ability bit 0x200, capped at 100.

disassembly
Which monsters take the 0x19 short step?every record without a tag-0x20 action
Why

The approach runs a tag search over the monster's installed action table (FUN_80050E2C with 0x20 at 0x801E3260..0x801E3268, the 0xFF miss falling back to tag 1 at 0x801E327C..0x801E32B0); 180 of the 186 records carry no tag-0x20 entry and take 0x19, and the six that do also carry 0x21 (monster 73 at entries 10 / 11) (battle-action.md).

disassembly + disc census
What is action tag 0x22?a knockout taunt
Why

0x801E5594..0x801E5658: a monster whose attack left its target at zero HP stages its tag-0x22 entry (+0x1DC |= 2) while a party member still stands, so the wiping blow plays none; 166 of 186 records carry no 0x22 entry (battle-action.md).

disassembly
What does selector 8 (Antidote) clear?both poison bits, and it skips a dead target
Why

0x80041BCC / 0x80041C04 in FUN_800402F4's selector-8 arm: the status word is masked with 0xFFFC and a target at zero HP is skipped. Port engine-battle-vm::battle_action::effect_selector.

disassembly
How often does battle action state 0x00 run?every round
Why

Begin sets the flow word to 0xFE (0x801D31AC), and that arm writes ctx[7] = 0 (0x801D3224), so state 0x00 - the formation arm and FUN_801F0450's pool arm with it - re-runs at each round's start. FUN_801DB318 is flow cases 0 and 2 (0x801D0EE4), so it too runs per round.

disassembly
When does the commit log glide off the ring?on a member leaving or returning, never at round start
Why

The flow SM's tail table at 0x801CE948 runs FUN_801D5778's copy loops for steps 5, 7, 9, 0x2A and 0x30 (slide out, mode 0) and 0x2B, 0x31 and 8 (slide in, mode 1); Begin (0x24 / 0x29) just exits. The glide is linear in FUN_801D9BBC over ctx[+0x1C], set to 0x10 at 0x801D8904. Engine: battle_commit_log::LogLaunch.

disassembly
How does the battle-intro banner name a repeated monster?drop the last character, then append * N
Why

For a repeated species the label loses its final character (the instance letter) and strcat (0x801D9E60) appends the rodata string at 0x801CECA8, giving Killer Bee * 3. The banner is skipped when the first monster id is 0xB5 (0x801D0DF0). Engine: battle_hud::battle_intro_names.

disassembly
What is Swordie's slash routine FUN_801F81DC?a four-slot progress timer, not a one-shot hit
Why

Each slash keeps a progress word at 0x801F8D9C + 4*i that grows by rate * speed per call and lands its hit when it reaches speed << 6; arm 9 starts slash i once ctx[+0x6D8] passes (i + 2) * speed * 16. At four vsyncs per tick the timers reproduce the measured dwell (arm 7 sixty-five ticks, arm 9 twenty-five). Engine: seru_magic_ticks::swordie_slash_step.

disassembly + capture
Does anything read the second 8x8 percentage block after the element-affinity matrix (PROT 0898 0x801F5428)?no; dead data in USA, PAL and Japanese builds
Why

Both address scans over every image (--prot) find no reference of any form to any of its 64 bytes. The matrix base 0x801F53E8 is formed at eight lui sites, including the Seru side-effect stager FUN_801F3D3C at 0x801F3E4C, each indexing atk * 8 + def unchecked; no character, monster or summon cast element exceeds 7, so no index reaches the block (battle-formulas.md).

disassembly
Encounter MAN sub-section layoutheader shape corrected
Why
disassembly
Battle-intro tile shatter - the side-face shade pagea resident field asset, not a transition upload
Why
capture
How long does a field-to-battle transition run (DAT_801D2458)?132 frames; 252 for the swirl
Why
disassembly
Which stage-dome objects does the battle backdrop draw?drop index 1, not "keep index 0"
Why

The registration edits the object list rather than truncating it: each backdrop actor owns a private 0x9c part table at +0x44 (allocated at 0x80021184), and 0x80051ad4..0x80051bac applies one count -= 1 plus one entry[i] = entry[i+1] shift from index 1 to each. Object 1 is dropped and everything else kept, gated on _DAT_8007b64b == 0. Indistinguishable from "draw object 0" on the two-object shells; on the seven four-object domes it keeps sky, mountains and the ground ring. See battle.md.

disassembly + capture
Battle ground grid depth cue - the far colourcaptured at the draw; port fogs the grid
Why
capture
Which element the screen-element +0x0E kind pair selects, per valueit is a table index, not a style enum
Why
disassembly + capture
The element-badge palette selectorthe badge record's own palette byte
Why
disassembly + capture
The status-element badge sheet 0x18..=0x20nine 48x16 word tags; ladder assignment independently confirmed
Why
disassembly + capture
Does the battle ground grid roll per-cell randomness?no - a four-entry table walk
Why

No. func_0x801d02c0 builds sixteen literal UV words into scratchpad 0x1f800034 (0x801d0304..0x801d03a0) and the emit loop reads group n for quad n, advancing 0x10 each time. They decode to four fixed 32x32 sub-tiles of the (192..=255)^2 window walked in sub_row * 2 + sub_col order, copied into the packet verbatim - no roll, no corner mirror. The grid origin also carries an extra -0x200 bias on z, and pass 1's cull is a view-z bracket with no screen-space term (that is a separate pass-2 test). See battle.md.

disassembly
Endless camera orbit (Gaza 2 softlock) - the 0x19 attack-approach parkcaught live; root-caused; disc fix shipped
Why
capture + disassembly
Which FUN_801D5854 case-6 arm a running fight takes, and where ctx[+0x6DA] is seededresolved
Why
capture + disassembly
Which framing the Done band (0x50 / 0x51) takes, and the idle-orbit rate at the Begin/Run promptresolved
Why
capture + disassembly
0x19 fallback approach drive - which anim-driver field does summon staging leave stale?pinned + causally reproduced on the parked save
Why
capture + disassembly
Super / Miracle Arts trigger chainall 15 Supers live-executed
Why
disassembly + capture
Xain "Bloody Horns"/"Terio Punch" ignore elemental guards (community mystery)resolved
Why

Not an element drop - a resist-ladder bypass. Capture-class modules (PROT 944..966) pick one of two damage wrappers: FUN_801DD4B0 (fifth finisher argument 0: sw zero,0x10(sp) at 0x801DD67C, resist ladder runs) or FUN_801DD6B4 (1 at 0x801DD82C; the finisher's bne a3,zero at 0x801DDBD4 skips the party-defender jewel/guard block). BH (952, 0x801F7948) / TP (953, 0x801F7410) use the bypass wrapper; enemy ESM (966, 0x801F8610, 0x801F8F08) uses the respecting one (hence Cort reads as Dark). See battle-formulas.md and spell-table.md.

disassembly + capture
Which call applies Astral Slash's damage?the melee kernel, on the staged clip's four beats
Why

PROT 952's 0xB8 tick 0x801F6A0C has no wrapper jal and no HP store; arm 2 stages clip 0x0A on the caster (0x801F6D88) and parks the strike cursor (ctx[+0x15] = 0xFF, 0x801F6E78). FUN_80047430 calls FUN_801EC3E4 on the playing entry every frame (0x800478A0); Gaza's (165) entry 0x0A carries power bytes 0D 0E 0F 10 on beats 14 15 16 17, so four melee hits accumulate and land on the last. Port: fold skipped, total lands on the beat (cast-module.md).

disassembly + disc data
Is a plain monster swing elemental?yes - the melee kernel's guard ladder keys on the attacker's record element
Why

FUN_801EC3E4's inlined ladder (0x801ED5CC..0x801EDA00) runs for a party defender and a monster attacker and reads 0x801C9348[attacker - 3] + 0x1D in every arm: a matching elemental guard halves the excess over Defense, All Guard takes 3/4 unless the element is 7. This is why a Luminous Jewel halves Astral Slash. The port had passed element 7 for every swing (battle-formulas.md).

disassembly
First boss trigger → Battleresolved
Why

The scripted-battle arm is the field-VM op 3E FF <formation_row> (battle.md): Zeto = garmel P2[12] row 9 (lone 0x4B), Caruban = rikuroa stager P1[3] row 17 (lone 0x49, World::run_boss_stager_record). DAT_8007b7fc is writer-less across SCUS_942.54 + every static overlay (absolute, gp-relative and materialisation sweep); readers pin it as the debug forced-battle formation id - battle init FUN_80055b6c (0x80055BF8..0x80055C24) → FUN_8005567c seeds the formation cells DAT_8007BD0C+ from it (sb at 0x80055698..0x800556A8), and FUN_80046A20 routes a nonzero value to its mode-0 debug-menu exit (0x80046DDC). Retail never sets it.

disassembly
How an enemy's signature cast picks the spell id it announcesresolved
Why

FUN_801E9FD4's round-counter arm, 0x801EB7C0..0x801EB81C (PROT 0898 file 0x1CFF0..0x1CFFC): the round byte ctx[+0x28A] goes through the 0xAAAAAAAB reciprocal, the arm returns unless % 3 == 2, then stores that remainder to actor[+0x1DE] (action category), loads the seat's byte from the formation cell 0x8007BD0C + seat, and writes actor[+0x1DF] = monster_id - 0x29 - the literal 0x2442FFD7, with sb v0,0x1DF(s4) in the next j's delay slot. Formation id 162 announces spell 0x79, 163 0x7A, 164 0x7B, in the party spell table's own id space. See randomizer.md.

disassembly
How a Delilas sibling's signature move stages its clipswalk order capture-pinned
Why

The modules drive actor[+0x1DA] as an archive entry index; per-frame capture of natural duel playouts orders the walks. Lu (0960): 0x0E -> 0x0C -> 0x0D, closing 0x0F, all literals. Che (0959): 0x0A -> 0x0B. Gi (0958): 0x0A -> 0x0B -> 0x0C -> 0x0A -> 0x0B, closing 0x0D - the addiu v0,v0,-2 at 0x801F839C is a mid-cast rewind, not a stray step. Sites + walks: monster-animation.md; anatomy: cast-module.md.

capture
Is the player battle idle channel-delta encoded, like the readef "ME" bodies?no - raw packed inline in record[0]
Why

Each action entry in a player battle file's record[0] carries [u8 parts][u8 frames] at +0xAC then parts * frames raw 9-byte TRS records - the monster archive's own packing, no codec between disc and RAM. Entries tile the block: Vahn's slot 0 is 15 parts x 9 frames ending at 0x5CD, slot 1 opens at 0x5D0, and so on down the table (Noa's rig is 16 parts to Vahn's and Gala's 15). battle_party_pose_live byte-matches the disc stream against live PSX RAM at record0_base + stream_off. So an idle rewritten at its exact retail length leaves every later entry offset valid and needs no relocation.

capture
Enemy-ally charm battle softlockboth tracks fixed
Why

The state-0x5A victory arm's party-slot assumption OOB-indexes the win-pose roster DAT_8007BD10 (via 0x801E6770) when a living charmed ally is the acting actor at monster-wipe victory - the FUN_801E7320 reroll theory is falsified (re-do-not-re-walk.md). Fixed on both tracks: engine victory_pose_fixup/charm_widen, and the disc-side legaia_patcher::charm_fix guard - a single-word detour at the 0x801E6690 keep-branch into a SCUS dead-space liveness guard. Full chain + port: battle.md.

disassembly
Battle-actor +0x16E bit 0x400 applier (guard-disabling status)one setter, in a cast image
Why

The only setter is PROT 0955's Kiss of Death miss arm (ori v0,v0,0x400 at 0x801F8CFC over the lhu +0x16E at 0x801F8CF4, stored at 0x801F8D04) - a slot-B cast image, so a sweep of SCUS_942.54 and the static overlays never sees it. A sweep of SCUS_942.54 + the static overlays finds only clears (cure FUN_8004CE2C, waker FUN_801F45A4, item cures, the on-hit strip, battle-exit); the generic appliers FUN_801EC3E4 / FUN_801E09F8 never reach 0x400. Port: FLAG_KISS_OF_DEATH_MARK in kiss_of_death_tick. Writer inventory: battle.md.

disassembly
Who calls the battle on-screen test FUN_8005126C?exhaustive negative
Why
disassembly
What spawns the battle XA seek selector FUN_8004DA00?resolved
Why

Nothing calls it - it is the +0x08 tick of the static actor template at 0x800767F4, and the battle scene-loader FUN_800513F0 spawns that record into the system actor pool at 0x80051D3C as its last act before returning. The selector is therefore a per-frame pass resident for the whole battle. It plays nothing: each arm ends in FUN_8003EAE4, a CdlSeekL with no read (0x8003EB68), parking the drive on the voice's file; the gate at 0x8004DA50 passes on ctx[+0x276] == 0. The port (legaia_engine_audio::battle_voice) is REPLACED-BY ahead-of-time clip decode. The tick is +0x08 from 0x800767F4.

disassembly
Effect-VM pass-1 "state token algebra" (FUN_801E0088)+ ported
Why
capture
Seru-magic summon visual (e.g. Tail Fire)player visual; wired
Why
capture
Why a disc-booted port fight never showed an enemy specialthe boot catalog lacked the disc's monster-special ids
Why

The picker rolls Gimard's +0x21 Tail Fire (0x27, read off the battle_gimard_tail_fire_a RAM) on half its turns, and take_monster_turn discards a spell its catalog lacks: SpellCatalog::vanilla() has no 0x27, and placeholder entries collide with real ids (0x26 at 9 MP over Thunderbolt's 18). The boot catalog fills the block below 0x81 from the disc table; see battle-action.md.

capture
The party cast trigger FUN_801DBF9C - outcome producer or params stager?a params stager; every Seru id takes the summon arm
Why
disassembly
Player-summon presentation - label, readout, hide, flashes, creature seatported behind the band's own seams
Why
capture + disassembly
Fade-actor hold word -1 - "no hold" or "hold until killed"?hold until the actor is killed
Why
disassembly
summon.dat / readef.DAT side-band streamingentries + format
Why
disassembly
Monster steal item (Evil God Icon)resolved
Why
capture
Battle face-stamp issuing siteresolved
Why
capture
Per-spell magic power / multipliermechanism + roll ported
Why
disassembly
Arts command sequence - independent sourceresolved
Why

The SCUS arts-name table (DAT_80075EC4) glyph string is byte-exact ground truth for every art's directional command; legaia_art::ArtsOracle exposes it, and disc-gated contract tests validate both the best-effort PROT 0x05C4 parse_record command-decode and the curated gamedata directions/ap columns against it (one documented walkthrough error: Hyper Elbow).

capture
Weapon-specialty arm width (off-class widens the Arms command)resolved
Why

Not a runtime favored-class comparison. The arm command's AP cost is a per-(character, weapon) byte in the player battle file, at the weapon section's swing record (section[+0x04]) +0x74 (favored 0x1E / off-class 0x2A / far 0x36); LZS-decoded and copied verbatim into the runtime gauge (DAT_801C9360[char][0x0C]+0x74) at battle load by FUN_800557B8, read by gauge builder FUN_801D388C case 9. Byte-validated across all three player files; randomized by legaia_patcher::weapon_specialty. See docs/subsystems/arts-command-gauge.md.

capture
Stat growth-rate sourcevalidated + wired; core + opt-in jitter
Why
capture
Character-record HP/MP/AP pair order (+0x104..0x110) is (max, cur)relabeled throughout
Why
disassembly
Monster stat-record archive sourceresolved
Why
capture
Monster mesh + texture poolresolved
Why
capture
Terra slot-3 / story-flag overlapresolved
Why
capture
Battle party mesh pack other5 = PROT 1204 (battle form; Baka Fighter reuses it)empirical
Why
capture
MP-cost ability-bit priority (half vs quarter)re-read against the instructions
Why
disassembly
Scripted Tetsu encounter → Battle (v0.1 oracle Battle leg)resolved
Why

All three inputs derive from disc bytes: the formation-row selection is the standard scripted-battle op 3E FF 04 in P1[10] (same case-0x3E install arm as Zeto/Caruban; row 4 = lone Tetsu), the sparring-partner reposition is P1[10]'s 4C 51 15 0E 07 22 NpcRun→tile (21,14) = RIM_ELM_SPARRING_CARRIER_TUTORIAL_POS exactly, and the spar Yes/No is a MES-embedded option picker (0x29 open + N×2 signed relative-jump table, handler FUN_80038050; port legaia_mes::Picker::jump_target + InlineDialogueRunner::last_choice), not a field-VM opcode. details ↓

capture
Battle stage backdrop: which scene_tmd_stream a scene fights inresolved
Why

A scene bundle carries one stage stream per sub-area, and the battle's is not uniformly the block's first - map01 uses bundle slot 5 (entry 88), Rim Elm town01 slot 6 (entry 7). Engine ProtIndex::battle_stage_entry_for_scene. details ↓

capture
Battle stage backdrop: is the authored half completed, and howtwo actors; per-stage transform; object 1 dropped
Why

FUN_800513F0 allocates two actors from one shell TMD (0x80051A7C..0x80051AC0, ctx+0x106C / +0x1070), both drawn. Copy B takes a half turn (0x80051BC0) unless the stage is on the DAT_80078B50 table (0x80051BCC..0x80051C18), which mirrors it in X instead (0x80051CC4..0x80051CE4). Live in 15 battle saves: both pointers distinct, the split matching the table every time. Not "drawn once, so nothing completes it" (re-do-not-re-walk.md). details ↓.

capture + disassembly
Battle-stage overlay band (+0x47)resolved
Why

FUN_800520F0 pages a per-stage slot-B overlay via FUN_8003EC70(_DAT_8007B64A + 0x47) (0x8005267C..0x800526A0), skipped when the id is 0 (which every catalogued battle but the Tetsu tutorial reads). Engine engine-scene::overlay_loader::battle_stage_overlay_entry. details ↓

disassembly
Battle-intro tutorial boxes (Tetsu sparring fight)machine pinned, ported and wired
Why
disassembly (machine exclusivity byte-anchored; prompt pool shared with 0968)
What arms the battle-stage id 1 (sparring tutorial)resolved
Why

Not the formation, scene or monster: a one-shot system-flag arm. FUN_801DA51C's battle-entry tail defaults the stage-id byte to 0 in a delay slot, tests flag 0x19, and on a set flag writes 1 and clears the flag (0x801DA67C..0x801DA6B0). The disc's only setter is town01's Tetsu record (50 19, two ops before its 3E FF battle-entry op). Port battle_tutorial::TUTORIAL_ARM_FLAG. details ↓

disassembly + disc bytes
Mid-battle second _DAT_8007B64A stage-id writer (the 0x801fd514 phantom)coordinate re-keyed
Why

FUN_801E6968's tail arm (the Lost Grail Final Heal sweep's epilogue; sb at 0x801E6D2C, battle-SM cleanup state 0x50): formation head still 0xB5 (Cort) + first monster seat dead → stage id 3 (entry 969, the form-transition module), slot-B page-in issued same-frame. Port engine-battle::battle_stage_module::boss_transition_stage_id via World::battle_stage_id. 0x801fd514 is a phantom print off a base-tag-less dump; see re-do-not-re-walk.md. details ↓

disassembly
"No party seated" vs "party dead" in the wipe scanport-only state; guarded
Why

Retail's 0x5A wipe scan (0898 0x801E6510..0x801E664C) walks the actor table for exactly the seated count at *(0x8007BD24) - the beq at 0x801E6524 falls straight into the wipe compare on a zero count, and retail has no guard, only a count that is never zero after battle load. "No party seated" is a port-only state (unguarded, it reads as a party wipe on the first end-of-action). BattleActionHost::slot_seated gates PartyWipe on party_seated > 0 (MonsterWipe still resolves), and the pad ladders seed the retail New Game roster. Disc-free pin: unseeded_battle_wipe_guard.rs. details ↓

disassembly + capture
Battle command-flow byte ctx[+0x06]resolved
Why

The other battle SM - FUN_801D0748, the menu half, distinct from the action SM's ctx[+0x07] and overlapping its value space. Its dispatch is a compare tree (0x801D0C84..0x801D0DC8) whose selection band is regular decimal tens 30..120 (turn prompt / category menu / escape / item / magic / arts entry / target / target confirm / commit / attack-mode), which is what identifies it as the tutorial hook table's key: the nine live hook slots are that band minus the magic window. Engine mirror engine-battle-menus::battle_flow. details ↓

disassembly
Action-SM state 0xFF treated as battle end by the portpath was live-reachable; port fixed
Why
disassembly
Spine flag 0x142 (Caruban beat / dolk-dolk2 switch) writerdisc writers + engine port + oracle
Why
capture
Spine flag 0x482 (Drake mist-wall) writerwriter-less; "direct code path" presumption falsified
Why
capture
CDNAME scene-window frame (raw = extraction + 2) in Scene::loadengine converts; misattributions corrected
Why

A raw-TOC define used as an extraction index lands two entries late, dropping each block's first two retail entries and bleeding in the next block's; Scene::load converts. In the right frame the .MAP is the retail block's FIRST entry (not "two below"); "suimon == dolk2 MAN" and "rikuroa MAN = [18,70,20]" are next-block sidecars under the wrong label; "urudre1 tests 0x15E" and "0x63A has no writer" are false; "0x1BE = rikuroa Zeto gate" is geremi's arrival one-shot. Head blocks (defines 0/1, inside the TOC header rows) keep legacy windows. See cdname.md.

capture
Motion-VM (FUN_80038158) bytecode carrier + flag censuscarrier pinned; spine flags negative
Why

The second motion VM's bytecode source is MAN tail-section 1 (installer FUN_8003A9D4; parser legaia_scene_formats::man_motion; layout + op table in motion-vm.md). Disc-wide op-7/op-8 census (--motion-flag-census): overworld walking-band choreography + one town0b clear; 0x142/0x482/0x1BE and 549 appear in NO stream - the "549 set by op-7 bytecode" carrier claim is falsified. Anchor test motion_flag_census_disc.rs.

capture
Debug-menu "STR trigger teleports + sets flags" mechanismno per-FMV event table; dev-menu tools explain it
Why
disassembly
Spawned-record player-channel (0xF8) ExecMove/HaltAcquire handshakeretail machine traced; the port's model diverges
Why
disassembly
Equipment stat-bonus table - slot modelslot model + passives
Why

The stat-bonus table (DAT_80074F68, 8-byte stride) is decoded from FUN_801CF650/FUN_801CF5D0 (legaia_game_tables::equip_stats): +0=INT, +1=ATK, +2=UDF, +3=LDF, +4=SPD (not AGL / evasion). Five lbu/add pairs at 0x801CF6C0..0x801CF72C; note the asymmetry that rules out a linearised-C reading - equip+0 lands on the last accumulator, out of sequence with +1..+4. AGL takes no equipment add at all. The four +7 categories are Legaia's four weapon/armour slots (body/head/footwear exact by name; none of the 77 accessories appear in this table). Wired: DiscEquipInfo gates EquipSession's per-character list.

disassembly
Flag 0x63A - the vell/vozz P2[7] gate with NO script writerscript writers exist; the "no writer" premise was the CDNAME +2 skew
Why
capture
cave01 P2[16] (the 0x15D entry-key setter) - what spawns itslot-counted spawn chain
Why
capture
Drake Castle deep interiors (jouinc/jouind) depth decodedoor-choreography families, not story gates
Why
capture
scene_destinations P1-table scan misses P2-only door namesP2 pass folded in
Why

The P2-only class is the town/dungeon exit door (a P2 door-choreography record): town01→map01 (Rim Elm's overworld exit; the P1 pass alone sees zero town01 destinations), retockin→retona, geremi→map02/tower - 13 scenes / 14 destinations disc-wide. jouinb→jouina is not in the class: it is P1-visible (the over-walk resyncs across that record). Merged kernel legaia_scene_formats::man_edit::scene_destinations (P1 pass as prefix + clean-gated P2 pass, (name, index) dedupe); the engine delegates to it; disc pins scene_destinations_p2_disc.rs.

capture
0x4C 0x51 byte +3 = [bit7 special-model | facing nibble] vs the glide-speed interim depth & 7 readingfacing wins; the two readings were two different ops
Why
disassembly
How an NPC's facing changes after spawn - snap vs ramp, and which writer winstwo laws; order-of-execution priority
Why
disassembly
dolk2/rikuroa MAN source (the "v12-embedded MAN" was an over-read)streaming carrier
Why

Their own base+3 bundles are the MAN-less count=4 form [1,2,6,0x14]; the "embedded MAN at 0x1000" inside their SceneV12Table entries is an over-read onto the next scene's bundle (suimon's / geremi's; scene-v12-table.md § over-read). Retail sources their partition scripts from the block's standalone data_field_streaming entry's type-3 chunk (dolk2 ext 70 [29,73,17], rikuroa ext 157 [13,29,64]; live script-heap byte-match at the Caruban beat). Engine: field_man_payload streaming fallback (streaming_man_payloads) + retail-frame Scene::load windows; pins v12_bundle_man_disc.rs.

capture
kor-family op-0x49 flag window [0x138..0x13F] - what the 8 flags gateUru Mais warp-pad destination memory
Why
disassembly
Attack-band damage pacing - who calls FUN_801EC3E4, what paces the hits, when the total landsthe anim tick calls it every frame; the clip's +0x10..+0x13 beats fire the hits; one HP write per combo
Why
disassembly
The battle-intro enemy-name banner - which placement record raises itnone does - the composer places it itself
Why
disassembly + capture
What does the battle overlay's private PRNG FUN_801D0290 feed?nothing in the battle model - it shapes one effect ribbon
Why

All five draws sit inside FUN_801CFA48, the lightning effect-ribbon emitter (the 0x2000 arm of FUN_8001ADA4's multi-target case; PROT 0973's dev harness labels it THERNDER1): inner / outer half-widths, a 1-in-8 heading kink, the per-segment turn and the advance length. The state word 0x801F6950 is re-seeded on every call at 0x801CFC18 from param[+0x1C] >> 2, so it is a shape hash, not a stream. 0x801CFB94 is a branch label inside that routine, not a function (the name came from a slot-A VA collision with a real entry in PROT 0970).

disassembly
Is 0x801F6950 read across overlays (the overlay_0897_* dumps)?no - three machine references on the disc, all in PROT 0898
Why

The field overlay's content ends at 0x801F3818, below the word. Three of the four overlay_0897_* hits are the address appearing as an instruction address in a mis-based print; the fourth is the seed store re-keyed by 0x167E8 into FUN_801CFA48.

disassembly
What are the halfwords of a 0x80076C10 screen-element record?a two-seat pair over shared width / height / string
Why

Seat A (+0x00 id, +0x02 x, +0x04 y, +0x0E kind) and seat B (+0x01, +0x0A, +0x0C, +0x0F), shared +0x06 width, +0x08 height, +0x14 content string (measured by FUN_80035F04, the rendered-width kernel - not an animation descriptor). FUN_801D8DE8 has one spawn arm per seat (0x801D92E8 / 0x801D935C, by mode & 1) and glides toward the other seat. Movers: FUN_801D5718 land, FUN_801D5778 launch (seat B pushed -0x140), FUN_801D57E8 clone. +0x10 is 13 on the framed rows and unread; +0x12 is zero in all 103 records. Which seat is parked is per record.

disassembly
How is a slot-B cast / summon module entered?two link-time tables in PROT 0898; row = extraction - 903
Why

The 32-slot cast-tick table 0x801CF4EC behind FUN_801F1ED4 (jal per arm, key actor[+0x1DF] - 0x81; called from the battle SM at 0x801E4B1C / 0x801E4C7C / 0x801E4CA8), and the 64-word move-VM entry table 0x801F6734 copied into gp[+0x714] (0x801E44C8 / 0x801E4630) and called by move-VM op 0x20 (jalr at SCUS 0x80023764). Both give PROT extraction = 903 + row, verified 13/13 and 12/12 against the extracted images. Most slot-B images carry no internal jal, so a call-graph walk attributes no dump to them. See cast-module.md.

disassembly
How is a capture-class cast module's tick entered?a third link-time table in PROT 0898, keyed on the spell record's sub-id
Why

FUN_801F2160 (0898 file +0x23948, sole caller 0x801E50C8 in the battle SM) reads caster[+0x1DF], indexes the static spell table 0x800754C8 + id * 12, takes the record's +1 capture-class sub-id (sltiu 0x20) and jumps through 32 arms at 0x801CF56C; arm i is a hard-coded jal into PROT 935 + i. Most arms land on a per-spell trampoline (88..204 bytes) that re-reads caster[+0x1DF] and calls one body per spell id. It is not keyed id - 0x81 like the cast-tick table.

disassembly
Is the whole slot-B band mapped?all 64 entries carry a static-overlays.toml row; 61 are dumped, and 0915 / 0926 / 0935 wait only on their extraction images
Why

Function extents recovered by frame matching (prologue → the first jr ra whose delay slot restores the same frame); every 0x801F6734 row and every arm of both tick tables lands on a recovered head in its image and no other. The band holds 196 framed functions (1..8 per image), 25 images with internal jals. The extraction test's pointer-resolution gate counts only references landing inside another mapped image, so references that legitimately leave an image (0898's data, the post-image .bss) do not hold PROT 0915 / 0926 / 0935 back.

disassembly
The slot machine's cabinet emittera mesh, not a packet: PROT 1200 descriptor 1
Why

PROT 1200 carries three descriptors - TIM_LIST, a 2160-byte untextured Legaia TMD (65 verts, 76 prims, baked greys / dark reds / navy) and a MOVE table. The slot init FUN_801CEC94 loads the entry (FUN_8003EB98(0x4B2, ..) at 0x801CEE2C), FUN_80020224(0) at 0x801CEE44 dispatches all three, and FUN_80020DE0(0x801D3618, ..) at 0x801CEEA8 spawns the actor bound to model slot 0. Its extents enclose the paylines, dot matrix, reels and pedestals. See minigame-slot-machine.md.

disassembly
The slot machine's in-game BGMhost-scene authored, not the overlay's
Why

koin1 (PROT 543, Sol Tower's minigame floor, whose MAN carries the 0x3E warps op0 = 103 / 104 / 105) picks the track in its entry script by spawn bbox: 2018 (M16, the casino floor), 2024 (the bar), 2045 otherwise (op-0x35 words in P1[0], decoded MAN 0x22C..0x30B); balden (the Vidna cabinet) plays 2058 / 2010.

capture (disc read of the scene MANs; script data, not code)
Which payline does a forced slot stop land on?one of the five, chosen per spin by rand % 5
Why

FUN_801d2440 reads 0x801D3630[DAT_801d4134 * 16 + reel * 4] (0x801D2444..0x801D2464) and stops a hit at raw row R at R + 3 + word (0x801D24F4..0x801D2520); the display's +0x10 bias puts the target on the top, middle or bottom row for word 0 / 21 / 22, and the five rows are the three horizontals and both diagonals. DAT_801d4134 is rand % 5 (0x801D25A0..0x801D25D0). The search window is raw cur + 1 ..= cur + depth. Port land_row; minigame-slot-machine.md.

disassembly
The Muscle Dome's Auto arm - which routine picks the commands?none on the screen; the Auto flag makes FUN_801F0450 rebuild the queue every round
Why

FUN_801DA34C (called at 0x801D15C8 on the phase-0x28 Attack confirm) reloads the record's 16-byte command string (+0x1A7 / +0x1B7), which FUN_801DA59C writes back and the review screen shows. The swings come from elsewhere: the Auto pick raises the per-fighter flag ctx[+0x266 + seat] (0x801D17D0 / 0x801D164C), and FUN_801F0450 rebuilds that fighter's queue (sb at 0x801F0B04) at action state 0x00, which runs every round (Begin sets flow 0xFE, whose arm writes ctx[7] = 0 at 0x801D3224). Engine: World::run_auto_attack_pool_arms.

disassembly
The dome's three UI cue ids (0x21 / 0x22 / 0x23)accept / highlight moved / refused-or-back
Why

37 jal FUN_8004FCC8 sites in FUN_801D0748 (15 / 7 / 15), not 34. Pinned by content: every cancel-mask (*(0x800846D4)) press is 0x23, every confirm-mask (*(0x800846D0)) press 0x21, and the pre-pass 0x22 sites fire only when the pressed bit differs from ctx+0x880. Arm boundaries come from the compare chain 0x801D0C84..0x801D0DCC.

disassembly
The arena backdrop's object-1 dust decaltrimmed by the SCUS battle loader unless _DAT_8007B64B is set
Why

FUN_800513F0 spawns two backdrop actors and, when _DAT_8007B64B is zero (0x80051ABC), decrements both part counts and shifts each list down from index 1 (0x80051AD4..0x80051BAC). One writer on the disc, FUN_801D9E1C at 0x801DA0AC. The arena leaves it clear: autorun_w4d_dome_decal_flag.lua reads 0x00 at the test 0x80051ACC on the contest's one loader entry and logs no write to the byte (minigame-muscle-dome.md).

disassembly + capture
FUN_801F2410 / FUN_801F2E10 - dome routines or shared?neither is the dome's
Why

801F2410 is the cast colour wash - screen-wide POLY_G4 bands onto *(0x1F8003A0) tinted ctx[+0x27E..+0x280] * ctx[+0x27A] / 255 in mode 0, a textured one-line POLY_FT4 backdrop in modes 1 and 2, called only from the cast dispatchers' epilogues (0x801F2144, 0x801F23F4). 801F2E10 has no reference in SCUS or any overlay; its 11 callers are all in slot-B module PROT 0909.

disassembly
What is per-character record +0x131, the second byte the new-game seed inits to 1?nothing - it is write-only
Why

sb $v0, 0x6f9($s0) at 0x800561C8 is its only writer on the disc; an opcode-decoding sweep for lb / lbu / sb at displacement 0x131 and at the four slots' block-relative 0x6f9 / 0xb0d / 0xf21 / 0x1335 over SCUS + all 80 based overlay images finds no reader, and no wider access spans +0x130..+0x131. Nor is +0x9C a rank counter: it is the Monsters Defeated word (save-record.md).

disassembly
What does item-effect flag bit 0x40 mean?the descriptor's target side - set = the enemy party
Why

Read once, at 0x801D18E0 in FUN_801D0748 (PROT 0898), forked with andi 0x20 at 0x801D18E8 into phases 0x5B / 0x5D / 0x64 / 0x66 (one enemy / all enemies / one ally / all allies); the spell table's +2 byte runs the same ladder at 0x801D1C50 / 0x801D1C58. The five carriers are the Point Card and the two summon-flute pairs. FUN_801D0F1C never reads the bit (see the falsified row).

disassembly
What is the arts-name table's +4 halfword (DAT_80075EC4)?an authored tier constant with no reader
Why

Eight sites materialise the table base; a delta-tracked walk of every load off them reaches only +0 / +1 / +2 / +8 / +0xC / +0x10, and a five-form sweep of 0x80075EC8 finds no reference. The values are a per-art tier ladder keyed to list position (60000 Miracle, 50000..30000 the elemental arts, 20000, 15000..5000 by input count, 1 terminator) - neither AP nor input count.

disassembly
What is the byte at actor[+0x22C] + 0x80 the SFX-cue router folds into the category?the actor's display object's sound-bank category, values {0, 7, 8} - not an element byte
Why
disassembly
What moves the evolved-Cort battle off flow ctx[+0x06] = 0x0C?the boss stage module hands the flow back after its own intro countdown; no input involved
Why
capture + disassembly
Is _DAT_8007B64B clear during a Muscle Dome contest?yes - the dust decal is trimmed
Why

FUN_800513F0 entered once (ra = 0x80046F7C) with the byte 0x00; at 0x80051ACC the trim arm ran, and a write watch logged zero writes over 1800 vsyncs (koin1, modes 0x03 → 0x18 → 0x19 → 0x14 → 0x15). The field handoff FUN_801D9E1C never runs on the arena path. Probe autorun_w4d_dome_decal_flag.lua.

capture
Do the renderer's light-capable prim kinds 8..11 ever execute?negative (no live light path)
Why

Zero hits on 0x8004409C / 0x8004423C / 0x80044434 / 0x800445B0 over 1111 map03 overworld frames, 347 + 470 field frames (map02, ropeway) and ~1160 battle frames. The control had to change with the scene: a kingdom overworld never enters the SCUS prim-dispatch family at all (13 handlers armed, zero hits) - it renders through PROT 0901's eight replacements, all of which fired in the same run. Probe autorun_w4d_light_kind_hits.lua; renderer.md.

capture
What are PROT 1221 / 1222 (other5 / other6)?int.tim / int2.tim - the Muscle Dome's two ringside panel stills
Why
disassembly
What do the higher readef groups' aux slots carry?no new kind - textures, the four ME archives, or a never-staged actor record
Why

FUN_801F12D0 is an 8-stage machine (jump table 0x801CF4CC): stage 2 uploads slot base to CLUT row 488 / page x = 512, stage 4 uploads base+1 to row 490 / x = 640, gated at 0x801F1500 / 0x801F150C on base >= 0x42 || 0x0C <= base <= 0x36. That gate partitions the file exactly: textures inside it, the four ME archives below it, and in the three excluded groups (base 0x39 / 0x3C / 0x3F, actions 0x14..0x16) an actor record byte-identical to its base+2 twin, which the applier never stages. See summon-readef.md.

disassembly
Is cutscene_str (PROT 0970)'s 123 KB code gap un-dumped code?a zero-filled reservation
Why

0x801D1878..0x801F1A00 is 32,793 zero words of 32,866 - a .bss-shaped hole. The image's real un-dumped code is about 1.3 KB. Recorded in disc-coverage.md.

disassembly
Can a scene TMD pack member be grown in place?nothing outside the pack holds a byte offset into it - rebuild is the only cost
Why
disassembly + capture
Is any of an enemy signature cast's choreography data-driven?partially - the spawn layer is data in the art path's own record format; the lift and camera are module code
Why
disassembly
How many damage-clamp shapes does the slot-B cast band use?three, and one module picks per hit
Why

Over the 83 wrapper jal words in the 64 images, 79 of them inside a frame-matched function of the image carrying them: 70 take shape A (cap live HP, sltu, kills), seven shape C (cap HP - 1, sltu - neither kills nor heals), two shape B (cap HP - 1, signed slt - the two AoE stagers). Every shape-C site is a tick body or a body a tick calls. PROT 0910's applier picks its cap from its own slash counter at 0x801F8DAC - HP - 1 on slashes 1..3, live HP on slash 4 - so kill capability there is per hit, not per module. Image by image in cast-module.md.

disassembly
Where does a capture-class cast get its damage constant?baked into the module image, not read from a table
Why

Each capture-class module carries its own power constant as an immediate ahead of its wrapper call (read here for PROT 0966: li a0,0x100 at 0x801F8EFC; the other modules' values are the owning page's), so there is no per-spell power row to patch and no table to randomise - editing the number means editing that image. Per-module values in cast-module.md.

disassembly
Which slot-B images touch more than one seat?two stager routines, and both subtract HP
Why

PROT 0927's FUN_801F85A8 sweeps the enemy row actor_table[3 ..] to ctx[+1] (0x801F8718..0x801F87E0) and PROT 0966's FUN_801F8D64 the party row actor_table[0 ..] to ctx[+0] (0x801F8FB4..0x801F8FC0) - not the whole table. Each rolls a wrapper (0x801F8758, 0x801F8F08), caps the roll at HP - 1 with a signed slt (0x801F878C, 0x801F8F34) and subtracts it (sh 0x14c at 0x801F87C4, 0x801F8F6C). A sweep wounds and cannot kill; the kill is the same image's tick. "Neither subtracts HP" is falsified. See cast-module.md.

disassembly
How do the 0x801CF56C capture-class arms reach a spell body?through a per-image trampoline in six of them
Why

Arm i is a hard-coded jal into PROT 935 + i, and in six images the landing site is a trampoline of 88..204 bytes that re-reads caster[+0x1DF] and dispatches one body per spell id - PROT 0955 is the extreme case, a 20-word jump table serving six spells from one cell. Two power constants that looked missing are register reuse: PROT 0918 carries 0x12 at 0x801F8798 and PROT 0949 carries 0xC0 at 0x801F72F8. See cast-module.md.

disassembly
What calls FUN_801E91E8, and what reads its result?one jal, and half the result is unconsumed
Why

Its single call site is 0x801EE2C0 inside FUN_801EC3E4 (PROT 0898), and the routine contains zero data words. The unconsumed half of what it returns is ctx[+0x269] - staged and never read on the capture leg. Row in functions/battle.md.

disassembly
Does the battle tutorial's wait state draw anything?negative (kind 0xD draws nothing)
Why

The tutorial's wait record is widget kind 0xD at 0x801F75F0, and FUN_80031D00 dispatches on the kind alone - kind 0xD has no draw arm. The pause between tutorial beats is a timer with no picture.

disassembly
What are placement-record +0x0E/+0x0F and +0x10?+0x10 is the widget kind, +0x0E/+0x0F the frame style
Why

+0x10 reaches FUN_8003541C as a1 from 0x801D8E8C and lands on node +0x1C, the kind the widget dispatcher switches on; +0x0E/+0x0F land on node +0x1D and thence gp+0x14C, which FUN_8002C69C reads to pick a frame style from {0x31, 0x33, 0x34, 0x35}. No image reads +0x11..+0x13. Detail in battle-action.md.

disassembly
What is the battle attack angle ctx[+0x6D2]?a bounded framing term, zeroed after the first hit
Why

It holds [0, 0x800]: term 0 frames the strike face-on and the from-behind case contributes atk / 32. The battle SM zeroes it at 0x801EC888 once the first hit lands, so it shapes the approach and not the exchange.

disassembly
Which spells are 0x27 and 0x81?0x27 Tail Fire, 0x81 Gimard / Burning Attack
Why

The two ids live in different spaces. 0x27 is the enemy special Tail Fire; 0x81 is the first player Seru-magic id, whose loader-B call FUN_8003EC70(0x81 - 0x79) (jal at 0x801E463C, addiu a0,a0,-0x79 in its delay slot) pages extraction PROT 903: the loader adds raw 0x381 (0x8003ECC4), and 8 + 0x381 is raw 905.

disassembly
Where is the Spirit AP-halving flag?character record +0xF8, bit 0x800
Why

It is accessory passive 0x2B (AP Used Down) in the persistent per-character ability bitfield, tested by the queue builder FUN_801EED1C at 0x801EF364 and by the status panel FUN_801D33D8 at 0x801D4520. Not a per-battle actor bit (falsified).

disassembly
What is battle ctx[+0xD]?a four-value ladder whose arms compose like two bits
Why

FUN_801D5854 compares the byte against 1, 2 and 3 (0x801D6510..0x801D6540, again at 0x801D6698..0x801D66C4 and 0x801D689C): 1 and 3 add 0x800 of camera yaw; 2 and 3 add 0x80 of pitch and store TR.y = 0x400 - a literal, not a 0x100 drop (li v1,0x400 / sh at 0x801D6564 / 68). No arm tests a bit, so a value above 3 changes nothing. Nothing in any arm writes a Z angle. See battle-action.md.

disassembly
Does an Attack x2 consume the Super Art starter?no - the starter survives
Why

The compare is bne v0,a1 with a1 = 1 at 0x801E3A4C: the build loop marks an accepted art's starter 1 at 0x801EF788 and the Super tail-replace FUN_801EF9E4 marks 4 at 0x801EFBA8, so a doubled ordinary attack tests against the wrong constant and leaves the Super starter standing. See battle-action.md.

disassembly
What are battle ctx[+0x17], ctx[+0x18] and ctx[+0x19]?a once-per-action teardown latch, a HUD element id, and a per-battle Spirit latch
Why

ctx[+0x17] is the action teardown's once-per-action latch and ctx[+0x18] the id of the HUD element that action raised, both at 0x801E2F50. ctx[+0x19] is read at exactly one site, 0x801E61CC, which unloads HUD elements 0x0F / 0x52 - so it latches "Spirit was used this battle" and nothing else.

disassembly
What is battle ctx[+0x270]?FUN_801D5854's second camera ramp
Why

Written over 0x801D5960..0x801D59B8 and read by one consumer, case 8's death re-frame. The clamp is sb-truncated, so values above a byte wrap rather than saturate - which is the behaviour to port, not the intent.

disassembly
What does the dome's contest restore (FUN_801D0ED8) do?one-shot per contest, and it leaves the accessories alone
Why

The caller FUN_801CEA6C tests _DAT_8007BAC0 at 0x801CEB58 and jumps past the jal 0x801D0ED8 at 0x801CEBF0 when it is already non-zero, so a contest refills once when it opens and a leg boundary never does. It refills party slot 0's HP / MP / SP to their maxima (+0x104 / +0x108 / +0x10C), and its stripped arm zeroes only +0x196 / +0x197 / +0x198 / +0x19A - the Seru-lock byte +0x199 and the accessories survive. Detail in functions/minigames-debug.md.

disassembly
Do the dome's hub screens run on frame counts?no - they are fade / hold / fade envelopes
Why

DAT_801D1A80 and its siblings are 0..0x80 brightness levels, not durations (slti 0x81 / li 0x80 at 0x801CF944..0x801CF958), and the two card holds end on pad & 0xF4 (0x801CFBE0, 0x801CFFE4) rather than on a timer. The tally arm tests the same mask a third time (0x801CFD60..0x801CFD7C) to latch the fast roll DAT_801D1AB4. A frame-count port ends the screens at the wrong instants and cannot be ended early by a button. See minigame-muscle-dome.md.

disassembly
Which dome chip seats does the AP-cost re-centring apply to?both x seats, +0x2 and +0xA
Why

The term is (cost - 30) * K[slot] / 2 with K = DAT_8007B650 = [2, 1, 1, 0] and truncate-toward-zero halving, applied to each of the record's two seat-x halfwords; anchors are 176 / 216 / 216 / 256 from 0x80076BBC. K makes the arm chip grow away from the D-pad glyph between the pair.

disassembly
What advances the victory counter gp[+0x9EA] the win-pose mouth flap clocks on?the battle tick, by the frame step
Why

The battle tick FUN_80046A20 adds the frame step DAT_1F800393 on entry, every battle frame and ungated (lhu v0,0x9EA(gp) / addu / sh at 0x80046A60..0x80046A7C); the win-pose stage zeroes it (sh zero,0x9EA(gp) at 0x8004EF18) and the stamp pass reads it halved (0x8004C87C..0x8004C884). It counts vsyncs, so the halved value is a 30 Hz clock; the port's per-vsync +1 mirror matches it. battle-data-pack.md

disassembly
Why are Noa's battle-file sections 2 and 3 swapped against Vahn's and Gala's?the file follows her record's equip bytes
Why

The battle-file loader matches section k against record byte +0x196 + k (FUN_80052770 case 4: lbu v1,0x75E(v0) at 0x800529D0, 0x80084140 + (char - 1) * 0x414 + k). Noa's record keeps her Ra-Seru in byte 2 and her weapon in byte 3, as the SCUS halfword tables say: weapon byte 0x8007B42C = 2, 3, 2, Ra-Seru byte 0x8007B424 = 3, 2, 3. battle-data-pack.md

disassembly
What do the record's +0x00 and +0x04 hold across a captured level-up?cumulative XP and the next threshold; one level, not four
Why

FUN_801E9504 compares the cumulative XP at +0x00 with the threshold (lw v0,0x5C8(s7) / sltu at 0x801E9F68..0x801E9F70, looping while it still reaches it), stores the next threshold to +0x04 (sw a2,0x5CC(v0) at 0x801E9FA0) and zeroes it at level 99 (0x801E9698). The captured 0x16D -> 0x2DA is Vahn's L3 to L4 threshold beside a level byte 2 -> 3, so the +0x00 step 0x4F -> 0x73 is 36 XP, not a four-level jump. battle-round-loop.md

disassembly
Which capture bodies still replay their clips ahead of the arms?none: every stage site sits in its arm
Why

Each sb ...,0x1DA on a capture body's caster is placed in the arm that runs it (caster_stages::CAPTURE_CASTER_STAGES): Hyper Crush 0x0D / 0x0B and Hyper Lightning 0x0D / 0x0C on arms 0 and 2, Chaos Flare 8 and Melt Spray 7 on arm 2, Power Charge 0x0A on arm 0, Bloody Horns 9 / 0x0A on its director's arms 2 and 3, and Genocidal Cannon 8 on arm 2 (0x801F6EC8, behind the countdown gate bgtz at 0x801F6DB4, table word 2 0x801F6D00). No row replays at the band's head. cast-module.

disassembly
What is left of Koru's Ultra Charge film?the module band's rand() deal
Why

nilboa_koru_pre_ultra_charge reads .811, .809 on --retail-steps and .931 on --rand-track (phase 1 .799 to .930): the frame pacing that once took a share no longer does, and seated on retail's words each pass the cast matches. Harness seats only (cast-sequence oracle).

capture
Why does Plasma Strike's victim burn through arm 5?the module band's rand() deal
Why

Lu's charge clips resolve seven hit events through arm 5 on both sides; retail's block roll grants all seven, the engine's own stream four, so three connect and burn the victim. Seated on retail's words each pass (--retail-steps --rand-track), nivora_duel_pre_plasma_strike matches all 24 cues (.917 on either seat alone) and arm 5 reads .905 against .813: the block rolls draw the same words and land the same verdicts, so the roll is the deal, not a rule. Harness seats only (cast-sequence oracle).

capture
Why do Thunder Storm's ribbons fall short of retail's centre?the arm's armed wash: its frames build on each other
Why

Arm 4 arms the wash with 0x202020 every pass, the high half set by lui a0,0x20 in the delay slot of the switch's branch into the arm (0x801F6AD4), which the presentation decoder missed, so the engine cleared each frame. With the wash armed the farthest bucket holds the subtractive quad, and each pass's ribbons land on the passes before: arm 4 reads .838, .835 and .839 on the three Gilium films against .58. cast-module

disassembly + capture
What sets the white pulses' phase in Barra's arm 7?the pass count: the cloud cycle steps once a pass
Why

The emitter 0x801F8B18 calls stager arm 1 once a pass, and arm 1 seats the next of four cloud records by the word 0x801F9364 ((w + 1) & 3, 0x801F83DC..0x801F848C), so where a pulse falls is the number of passes since arm 6's pass seated the emitter. Not rand(): on --retail-steps the Gobu Gobu film's (0x8F) arm 7 reads .995, its pulses pair for pair, against .75 on the engine's own steps. What is left plain is the engine's step cadence through the arm (cast module).

disassembly + capture
What brightens Barra's arm 7 every ten to twelve vsyncs?a cloud of the four-record cycle reaching the lens
Why

The four records 0x801F8B40..0x801F8BE8 differ only in their texture band, each a quarter of the cloud page drawn as a 0x800-wide sprite flying at the lens for 31 vsyncs. One is seated a pass in turn, so the frame whitens once every four passes: twelve vsyncs at step 3, irregular because retail's arm-7 frames mix steps 3 and 4. The same arm's sky was a separate defect: its scanline sample origin ctx[+0x285] / +0x286 slides with the words 0x801F9360 / 0x801F9362, which the port now carries (cast module).

disassembly + capture
Why does Theeder's arm 11 flash read a step ahead once a fade takes its spawning frame's step, and why does Gizam's arm 9 fall with the 0x32 hold?neither does now; what is left is not the flash
Why

On the Gobu Gobu films the frame luma tracks retail's pair for pair through both arms: Theeder's arm 11 within 0.6 on average (at most 3) plain and 0.4 on --retail-steps, Gizam's arm 9 within 0.4 (at most 1.6) on both. Theeder's arm reads .916 / .907 on the two seats, its residue the creature's sway and the spark lines over the flash's first frames; Gizam's reads .97.

capture
Where do Gimard's arm-11 flame puffs spawn from?from the walk's own pose, two a pass, as retail's
Why

The burning-body emitter sheds two puffs a pass at the creature's pose of that pass. On --retail-steps (LEGAIA_DIAG_CAST_PARTS against a --pose-objects recording) the port's arm-11 spawns match retail's in number (32), in pool count (10 live), in anim cadence and in the walk's cursor and live pair per pass, and their mean position per quarter of the walk lies within one walk step (111 units) of retail's. The earlier 70..110 offset counted pool rows, not spawns. Puff by puff they part by the jitter, a rand() deal (battle-action-helpers).

capture
Why do five enemy casts lose their cues under the console CD timing?the module bands' rand() deal and one pass on PCSX-Redux's lattice; no cue test reads the step
Why

Engine films under LEGAIA_BATTLE_CD_TIMING=console against the PCSX-Redux recordings: Glare keeps its cue, and with retail's rand() words seated Kiss of Death (--rand-at 0x70:4) plays 3 of 3, Plasma Strike (--rand-track) .917 as on the default timing, and Doomsday (--rand-track) 11 of 11. Aluru Cannon keeps every cue, one 7 ticks off retail's. The table.

capture
What sheds the rest of Element Strike's burn?the golem's two after-image ghosts, while its strike clip runs to its natural end
Why

The party seats' selector retires when the tint decay arm eases their word back to neutral, 16 battle frames into arm 7, in retail as in the port. Seat 7 keeps +0x21F = 1 and its clip 1 (committed id 0x11) 265 vsyncs into the arm, so its ghosts shed fire; k = 4's bare sb zero,0x1da (0x801F8378) waits for the clip's end, which the port had committed at once. PROT 0964.

disassembly + capture

Long write-ups in this area

The action SM's idle request keeps a playing idle

Grade: disassembly + capture. The port answers the action SM's FUN_801D5854(seat, 6..9) calls with an idle request on the body (World::apply_battle_pose). Retail's routine is the battle camera: across overlay_battle_action_801d5854.txt its stores go to its own frame and the battle context's framing words, none to the actor's +0x1D9..+0x1DC or the node's cursor +0x68. A request therefore never moves a body's idle.

The idle's phase is the loader's. FUN_800513F0 draws one rand() a seated body and stores (rand() % frames) << 4 into the node's +0x68 (jal 0x80056798 at 0x80051544, sh v1,0x68(s1) at 0x80051574; the monster twin from 0x800518D0). The engine held the draw until the body's first idle request, and the install it found carried no idle marker. That was the render build's player at keyframe 0, or the scene host's. So every body swayed in step from the battle's first frame until its own first turn, then jumped to the drawn phase. World::seat_battle_setup_idle now seats clip 0 at the draw when the loader installs the clips, and the SM's requests keep that loop.

The idle's phase also decides when a cast starts. A clip staged under neither +0x1DC stage bit commits only from the anim tick's natural-end path (FUN_80047430 to FUN_8004AD80 at 0x80047B54), so a Spirit or Magic band waits on its caster's idle loop. A restart at the pre-arm had re-based that loop at keyframe 0, which held the commit for most of a cycle. With the loop kept, the wait is what is left of the cycle. Per-tick traces of the pad drives, run on both trees, show this as their first divergence. Noa's opening Magic commits clip 0x11 eight ticks sooner (state 0x48 ends at tick 133 against 141). The heals' Spirit band 0x3D commits clip 9 two ticks sooner, and everything after moves with it.

On the image channel the battle corpus moves both ways. battle_uru2_nurga_heal_mid_cast rises from .973 to .999 and battle_rugi_puera_v2_strike from .989 to 1.000. Eleven pad-driven rows fall on that shifted history, battle_tunnelc_xain_bloody_horns_mid_cast the most (.855 to .797).

A pinned replay opens its fight with no setup draws, so its opening turn's idle phase is its own, and retail's is in no RAM the capture holds: the commit it waits on lies far more than sixteen passes back.

On battle_bylon_theeder_v2_thunderbolt_mid_cast the shift did not move the frame through the cast itself: it tipped the harness's undrift search onto a round eight units of residual nearer, which moved Gala's seeded ground and with it the box the displayed walk starts from. The search now keeps a round only when it is UNDRIFT_MIN nearer, and the still reads .999 (retail-compare).

The super_queue_replace_* drives had gated the Attack arm's seed pass on ctx[+0x87C]. That word counts from the acting seat's last idle re-commit (0x8004BF50..0x8004BF78), so the drives now meet the pass on its yaw rung. A caster idling in the capture band's entry 0x6E is seated on the capture's cursor, as in 0x6F..=0x71 (retail-compare).

An Orb's Heal strikes the ally it heals

Grade: disassembly + capture. A monster's heal or buff (a spell record class under 0x14) runs the action SM's Spirit / Item band, and two sites land it.

The clip's effect script seeds a homing flight, and the flight's hit arm in FUN_801E09F8 does not ask what the cast is: it rolls FUN_801DD0AC on the row the caster's +0x1DF maps to (lbu a0,-0x1(v1) / jal 0x801DD0AC at 0x801E1888..0x801E188C) and takes the roll off the victim through the safe applier (sh v0,0x14c(v1) at 0x801E1960), pushing it onto the readout ring. Heal's map entry is 0, the table's all-zero row, so the roll is the stat terms alone.

Then 0x3F calls FUN_800402F4 with the spell record's pair, (0, 0, ..) for Heal: the static heal table's 200, stored at 0x800408AC with no popup.

A --watch 0x800ED684 recording of the Orb's Heal on its Killer Bee (ropeway_orb_pre_heal, target seat 4) reads both stores: 96 -> 46 from 0x801E1960 (ra 0x801E1894) in 0x3E, then 246 from 0x800408AC (ra 0x801E413C); the frames show the 50 popup and DAMAGE 50, and no heal number. The port folded the catalog's heal at 0x50 with a popup and never struck. Port: World::land_spirit_band_flight_hit and the monster arm of World::fold_battle_item_action; the film reads .843 to .951.

The earlier actor of a monster's walk is DAT_8007BD00

Grade: disassembly + capture. FUN_801D8DE8's case 0x52 (the spirit-gauge panel, entry 0x48 of the jump table at 0x801CEB68) stores DAT_8007BD10[ctx[+0x13]] - 1 to DAT_8007BD00 (sb v0,-0x4300(v1) at 0x801D9094), the acting member's character index. The action seed's Spirit arm raises the panel (li a0,0x52; jal 0x801D8DE8 at 0x801E2FDC), so the byte names the last party member to act on a Spirit, the framing a monster's case-6 walk sets out from.

Over the ten captures whose walk start was read off the frame (DISPLAYED_WALK_PINS), the byte names the fitted seat on all nine that walk from an earlier actor; the tenth, battle_rugi_barra_windcutter_mid_cast, walks off the prompt and reads no seat. No other RAM byte, as a seat, seat plus one, bit or actor pointer, agrees with the fits on more than eight. The harness reads it (RetailBattle::gauge_actor); the pins keep the style and a measured yaw, and the ten stills score as before.

The battle hit tint - what a landing hit writes and how it reaches the pixel

Status: resolved. Evidence: capture (the triple, its ease rate and the pixel law read off two consecutive save states; every write and gate also in disassembly).

A landing hit stamps a three-word tint triple on the struck actor, selected by the acting record's +0x7A (party melee / arts) or the move-power +0x0A (monster specials). The presentation SM eases it back, and the draw stages the words as GTE far colour + IR0, so the texel is modulated, not replaced.

  • Stamp. FUN_801EC3E4 0x801EE3D4..0x801EE43C: +0x04 = 0x801F53D4[sel - 1], +0x21F = sel, +0x0C = 0x1000, with sel = record[+0x7A], a beq zero past the arm and sltiu v0,v0,0x6 bounding it. No exit precedes the arm, so every connecting swing (a Stone-absorbed one included) reaches it.
  • The bound is a route, not a table guard. Six archive entries carry 6, and 6 is the tint-less Curse arm (0x801EE690, a 1-in-4 +0x16E |= 0x1000 roll). Pin: battle_afterimage_gate_real.rs.
  • Other stampers. FUN_801E09F8 0x801E15AC..0x801E15EC is the monster-special twin off move_power[+0x0A], at each arm's impact phase (ctx[+0x24E + i] == 3). The clip-0x18 arms of FUN_8004CE2C stamp the same three words (0x8004D1D4..0x8004D1E4, 0x8004D28C..0x8004D29C).
  • Ease. FUN_80050120 arm 0 (0x800501A4..0x80050210): the word eases to 0x20080200 at 1 * dt * 8 lane units per frame, then +0x0C drains by dt << 5, then +0x21F clears. The jump table at 0x8001532C routes states 1 / 3 / 4 / 6..=10 to fixed colours with +0x0C = 0x1000, 2 to the capture fade, 5 to a hold.
  • Capture. battle_gimard_tail_fire_b -> _a hold Vahn at +0x21F = 1, +0x0C = 0x1000 with the red lane at 0x35F then 0x31F - eight frames of the ease. battle_melee_hit_spark (Vahn's Somersault landing on Gimard) holds Gimard at the neutral word, +0x0C = 0, +0x21F = 0: a selector-0 record tints nothing.
  • Disc census. Every player-file basic entry and every Somersault-class art is selector 0; only five Vahn art records (selector 1) and two Gala records (selector 2) tint their target.
  • Pixel. FUN_8004A908 packs the lanes into the render node's +0x74 and copies +0x0C into +0x78; FUN_80048A08 stages them as far colour + IR0 (gp[0x9D8] / gp[0x9DC]). The struck Vahn reads red 160..248 over green / blue 8..80 across his texture - modulation, not a flat fill.

Port: engine-battle-vm::battle_formulas::tint_sm_step (the SM), World::arm_impact_tint at the three hit seats, MonsterAnimation::impact_class carrying +0x7A; both hosts stage IR0 = blend / 0x1000 toward the unpacked lanes (battle_impact_fx::tint_ir0).

Owning page: battle.md.

Battle end - the results sequencer's timeline, and who strikes the pose

Status: resolved. Evidence: disassembly + capture.

FUN_8004E568 runs every frame the battle-end signal is 0xFE, the battle exits on ctx[+0x6CE] >= 0x43, and the acting slot ctx[+0x13] strikes the pose.

  • FUN_80046A20 stops stepping the action SM once DAT_8007BD71 == 0xFE (0x80047040) and calls FUN_8004E568 every frame (0x800470D0..0x800470E8); the exit test is slti v0,v0,0x43 on ctx[+0x6CE] (0x80046DAC).
  • _DAT_8007BD2C doubles as the sequencer's phase word (jump table 0x800152FC): 0 -> 2 -> 4 -> 5 through two CD loads for a victory; a wipe's 5 lands on the annihilated arm directly.
  • The results frame stages the pose into +0x1DA of the seat ctx[+0x13] names. The action SM's victory arm writes that seat last: FUN_801E295C's s5 is ctx + 0x11 (0x801E2994), so its sb v1,0x2(s5) stores (0x801E66EC, 0x801E6740, 0x801E6760) are ctx[+0x13] stores. An earlier reading searched for 0x13( offsets only, missed them, and concluded the leader poses.
  • Timeline on rim_elm_gimard_victory (PCSX-Redux poll, N = 1): signal v322, results v402, exit fade v657, exit v723.
  • The three-member noa_levelup_banner state reads ctx[+0x13] == 0 with seat 0 carrying pose 0x14 while Noa levelled: Vahn landed that fight's last action.

Port: world::battle::victory. Owning page: battle.md.

What a normal party attack sounds like

Status: resolved. Evidence: disassembly throughout; the live gate by capture (N = 4, probe autorun_w4d_melee_grunt_gate.lua).

An ordinary swing emits neither cue. The XA30 grunt belongs to a strike that commits the defender's +0x1F3 reaction; the 0x10C sting plays only when the effect handle _DAT_8007BD84 is non-null.

  • The fork (FUN_801EC3E4, on _DAT_8007BD84). Zero takes FUN_8003D53C(0x1D, chan, dur) at 0x801EEB44 (per-character (0,0x26) / (4,0x2E) / (6,0x1A) off DAT_8007BD10[seat]) and the re-read at 0x801EEB60 skips the cue. Non-zero branches over the grunt (0x801EEAC8) into FUN_8004FE5C(0x10C, seat) at 0x801EEBE8, whose voice leg is further gated on FUN_8003DE7C(1) == 0 (0x8004FE9C). The grunt's seat is s6 (0x801EEA70), the sting's s4.
  • The gate ahead of both. 0x801EEA88 / 0x801EEAA0 require s7 != 0 and s7 == actor[s4][+0x1F3], s7 being the staged pose byte committed to +0x1DA at 0x801EEC6C; only one of the fourteen definitions reaching the compare loads +0x1F3. The +0x1F3 reaction sits behind sltu s0,s1 at 0x801EC878, whose threshold is not pinned.
  • Third gate. 0x801EEAB8 slti v0,v0,0x2 reads _DAT_8007BC20, the executable's own xa_flag debug counter (XA-drive state: FUN_80016B6C prints it at 0x80016EB8..0x80016EC0, FUN_8004DA00 zeroes it on five arms) - not a character level.
  • _DAT_8007BD84 is an effect-instance handle, not a mode word, and the same cell the damage finisher reads as the enemy-defender halve. An all-forms sweep (the gp form 0xA6C(gp) yields zero) over SCUS, all 1233 PROT entries and the overlay images finds exactly three stores: 0x8004D658 and 0x80056080, both sw zero (battle start, round reset), and one non-zero at PROT 0940 file +0xCA0 = 0x801F7678, the Cort "Mystic Shield" stager saving the FUN_80021B04 handle it spawned. FUN_8004CE2C dereferences it (0x8004D548; +0x56 / +0x72 written, +0x10 read) and consume-releases it at 0x8004D658 alongside cue 0x10D. An ordinary swing pages in no capture module, so the cell reads null.
  • Banks (disc demux). XA27 is an eight-channel stereo sting bank, XA30 a ten-channel mono grunt bank.
  • Live. All four captured party swings skip the gate: s7 comes from the +0x1EF / +0x1F0 / +0x1F1 reaction-pose loads, never +0x1F3, and neither FUN_8003D53C nor FUN_8004FE5C is called.

Port: World::fire_melee_impact_cue + read_battle_xa_clip_bank.

FUN_8003EAE4 is a seek plus bookkeeping - and the driver is not untraced

Status: resolved. Evidence: disassembly.

FUN_8003EAE4 seeks and raises gp+0x908; a lone call streams nothing. The CD-callback sequencer it does not arm is FUN_8003D764.

  • What it does. Cancels any in-flight read, positions the drive on the clip file's record (FUN_8005C160(2, 0x801C6ED8 + slot*8, 0x8007BC10) - the argument is the record pointer, not the slot index), issues CD command 0x15 (li a0,0x15; jal 0x8005C034 at 0x8003EB68), and stores 1 at gp+0x908 / gp+0x910 and the slot at gp+0x890.
  • The driver. FUN_8003D764 (dumped; functions/script-vms.md) dispatches solely on the state ring gp+0x928, whose only writer is FUN_8003D53C (0x8003D6F4, 0x8003D724), which also registers the callback. FUN_8003EAE4 never writes gp+0x928; it reads it as an entry gate that makes the whole routine a no-op while a clip is armed.
  • Its three cells. Only gp+0x908 has a reader (a "streamed clip busy" level); gp+0x910 and gp+0x890 are write-only across SCUS_942.54 and all 1233 PROT entries.
Formation species limit - what the battle setup does with 3 distinct monster ids

Status: resolved. Evidence: capture (write watchpoints + cell readback + heap walk; the rebuild loop also read in disassembly).

A formation [a,b,c] of three distinct species loads as [c,c,a] on half of all rolls and verbatim (three streamed blocks) on the other half.

  • The loop. FUN_80055B6C (0x80055C80..0x80055D2C) counts copies of cells[0] and holds "the other species" in a single register, then - behind a 50% coin flip (FUN_80056798() & 1) - rebuilds the cells as [other x n, first x m]. For two distinct species that is an exact multiset swap (the species-order variety shuffle); retail authoring never exceeds two.
  • Three species. The third overwrites the register: the middle id vanishes and the last is duplicated.
  • Capture. autorun_formation_cell_writers.lua (write PCs 0x80055D14/18): installs of [133,151,94] / [94,133,151] / [151,133,94] / [32,34,14] all read back [c2,c2,c0] at battle main, seat 1 sharing seat 0's record pointer, in both the map03 and rikuroa contexts.
  • Verbatim side. All three distinct blocks stream, which turns an over-budget trio into a probabilistic battle-load hang (freeze captures: the heap-budget section of battle.md).
  • Randomizer. The unconditional battle-load safety pass enforce_species_limits (crates/patcher/src/encounter.rs) caps every random formation at 2 distinct species and at the disc's authored heap-cost maximum.
Attack-band damage pacing - the hit-event model

Status: resolved. Evidence: disassembly (FUN_801EC3E4 head 0x801EC41C..0x801EC494, apply arm 0x801EE984..0x801EEA78, epilogue 0x801EECDC..0x801EECE8; FUN_80047430 call sites 0x800478A0 / 0x80047BF0 and the bit-1 cut 0x80047900..0x80047948; FUN_8004AD80 0x8004B064) + capture (two PCSX-Redux hit-event timelines, a plain Somersault and a Tri-Somersault Super).

The anim tick calls the damage kernel every frame a battle clip plays, the clip's +0x10..+0x13 beats fire the hits, and live HP moves once per combo.

  • Who calls it. The strike loop of the action SM (FUN_801E295C state 0x1E) stages one byte into +0x1DA and sets +0x1DC bit 1; it never calls a damage kernel. The kernel runs from the anim tick with the cursor frame in a2.
  • Head guard (is this frame a hit). ctx[7] != 0x5A; the committed entry's byte 0 in 0x0C..=0x1F; the per-clip hit index +0x1F4 < 4; entry[+0x10 + idx] != 0; frame + 1 >= entry[+0x10 + idx].
  • An admitted hit resolves with entry[idx] as its power byte, adds the damage to the target's combo word +0x0 and its HP-bar word +0x10 (0x801EDB40 / 0x801EDB58) and bumps +0x1F4; every commit zeroes the index.
  • The HP write (s2 = 0 mode). The hit that lands after the strike loop has parked the cursor at 0xFF (ctx[+0x15], 0x801EE9A4) and is its clip's last listed beat (entry[+0x11 + idx] == 0 || idx == 3) subtracts the whole accumulator from +0x14C and zeroes it (0x801EEA10..0x801EEA74).
  • The other two s2 arms (0x801EDEE4..0x801EE130, monster-attacker copy 0x801EE790..0x801EE980). s2 != 0: a look-ahead over every remaining hit of the action that finds none able to connect with the target's +0x1E size class applies the total at once. s2 = 0xFF: the War God Icon's Attack x2 (ability bit 0x0D) with ctx[+0x16] < 2 withholds it so the pair lands as one.
  • Bit-1 cut. The tick commits the next staged byte once entry[+0x10] + 2 < frame with entry[+0x76] == 0, which chains one swing into the next mid-clip; art records carry +0x76 = 1 and play to their natural end.
  • Loop window. The loop-window arm re-zeroes +0x1F4 on every rewind for a party slot on 0x11 with a latched id >= 0x2B (0x80047840..0x80047878), so a windowed Hyper / Super clip re-fires its hits per cycle.

Captures (N = 2, scripts/pcsx-redux/autorun_hit_event_timeline.lua over party_basic_attack_vs_gobu_gobu and battle_vahn_tri_somersault_super):

  • Plain three-arrow Somersault. Swing 0F (p0 = 0x18, e0 = 7, lock 0) commits and hits at frame 6; swing 0E (p0 = 0x13) commits 8 vsyncs later at frame 10 (the bit-1 cut) and hits at frame 6; the 0x19 starter (slot 0x10, e0 = 0) plays with no events; 0x27 installs at slot 0x11 (e0 = 5, lock 1) and hits at frame 4 while the SM sits in 0x20, and the target's HP falls 76 -> 23 in that one frame.
  • Tri-Somersault Super. Seven hits across 0F, the two-hit 0x1F (p = [0x17, 0x17], e = [8, 19]), 0E (p0 = 0x1D), the 0x1A SpecialStarter (slot 0x11, loop window [13, 14] x 5, solo byte 1) and three 0x2B clips; HP lands 3542 -> 2318 on the last one.
  • Probe caveat. *(actor + 0x22C) alternates between two draw nodes on consecutive vsyncs, so a per-vsync cursor sample can read the node the tick did not advance (three frames behind); the in-sync stream satisfies the head guard on every hit.
  • Port timeline (legaia-engine play-window --battle 4, RUST_LOG=legaia_engine_core=debug, N = 2 runs) reads the same shape. A two-swing Auto queue commits 0C, hits at frame 6 of its e0 = 7 entry and accumulates, commits 0D at the boundary and lands the total on its hit. A typed ↑↓↑ becomes 0F 0E 1A 27: both swings hit at their beats, the starter parks [13, 14] x 5 with no hit, and the Somersault entry (p0 = 0x18, e0 = 5, lock 1) hits at frame 4 and lands the total once.

Port: legaia_engine_battle_vm::battle_action::hit_event (the head), World::tick_battle_hit_events / land_melee_hit / apply_combo_total (engine-core). Owning page: battle-action.md.

The battle HUD's per-phase surfaces are the sub-draw script table

Status: resolved. Evidence: disassembly, corroborated by the handle lists of sixteen states.

Which readouts a battle frame shows (roster card, full-width pill, plaque, AP plate, target plaque, move name) is not computed: it is a table of per-step element lists.

  • The menu SM FUN_801D0748 runs FUN_801D388C(step) on every ctx[+0x06] edge (0x801D0EE4, 0x801D109C, 0x801D13F0, 0x801D14C8, 0x801D1658, ...).
  • step indexes PTR_DAT_801F4D34: fifty [count][anim][panel] + count x (placement record, mode) records in the battle overlay's rodata, walked at 0x801D4BA4..0x801D4CBC. anim = 1 hard-resets the handle list first, so the pairs are the whole screen.
  • FUN_801D8DE8(record, mode) takes the record straight into 0x80076C10 + id * 0x18. Mode bit 0 picks the spawn seat (+0x02/+0x04 or +0x0A/+0x0C) and the glide runs to the other; bit 1 suppresses the glide (0x801D92E0..0x801D93DC).
  • The action SM's openers are the 0x0C seed (0x801E2F24: bar for a party target byte +0x1DD, panels for 8) and the Item / Spirit pre-arm 0x3C (0x801E3DA0); every close is in the 0x51 band (0x801E6170..0x801E6364).
  • The item window's 0x64 "target strip" is record 7 again (step 0x12: 07/0, 34/1), and its packet-pinned pens are the ring bar's seat for seat - the port draws the bar there and the item window keeps only its breadcrumbs.
  • Each mednafen state's ctx[+0x1074] walk agrees element for element; the disc-gated crates/engine-scene/tests/battle_hud_subdraw_disc.rs holds the port's constants to the table's bytes.

The resulting rule (card at the round prompt and while a window is browsed, pill in the ring and the target steps, nothing during a party attack on a monster, the target's pill during a monster cast) is on the owning page: battle.md.

The ring's element chip reads - or the Ra-Seru, and the gate is the equipment byte

Status: resolved. Evidence: disassembly, corroborated across twenty-nine states.

  • The chip. FUN_801D8DE8's case for record 10 (0x801D8EC8..0x801D8F2C) writes the chip's string pointer as 0x801F4B9E + char_id * 10 (the run Meta / Terra / Ozma at indices 1..=3, a lone - at index 4) when ctx[+0x25F + member] is set, and the - entry when it is clear.
  • The gate's one writer is the party battle-actor init FUN_80053CB8 (0x800541D0..0x80054270): lbu v0,0x761(v0) off the 0x80084140 display alias, i.e. the live character record's +0x199, the Ra-Seru slot of the eight equipment bytes at +0x196 - or +0x760 (+0x198) on the arm character id 2 (Noa) takes.
  • States. In every catalogued state the three gate bytes equal (+0x199 != 0) for the seated members: 0 in the sparring fight, 1 for Vahn from the Meta capture on, 0 for Noa beside Terra, all 1 at the level-99 Rage state.

Port: engine-battle-hud::battle_magic_chip.

The combo counter's glide is placement record 80's

Status: resolved. Evidence: capture (the live glide record); the stepper by disassembly.

  • The HIT / TOTAL / DAMAGE cluster hangs off screen-element record 80: seat A (328, 170), seat B (168, 170), opened at mode 0 and closed in the 0x51 band (FUN_801D8DE8(0x50, 1) at 0x801E6360, gated on the damage finisher's _DAT_8007BD14).
  • battle_melee_hit_spark carries the glide record live at ctx[+0x11B4 + slot * 0xC]: total 0x10, elapsed 0x0C, start (328, 168), target (168, 168), handle at x = 208 - the +40 every packet of the cluster shows against the settled seats of player_steal_skeleton_banner.
  • FUN_801D9BBC steps it linearly (start + (target - start) * elapsed / total, snap on arrival), so the cluster slides 160 px in over sixteen frames.

Port: engine-battle-vm::battle_value_readout::combo_slide.

The chrome kind byte is an index into the widget-class table

Status: resolved. Evidence: disassembly + capture.

A screen-element record's +0x0E byte is a record index into the widget-class table at 0x800732A4; the record is the sprite the element is framed in.

  • Dispatcher. FUN_8002C69C (ghidra/scripts/funcs/8002c69c.txt, the POLY_FT4 / SPRT emitter) reads the kind out of gp+0x14C, multiplies by 0x0C (sll/addu/sll at 0x8002C7A0..0x8002C7AC) and adds 0x800732A4.
  • Exact join. Every kind byte, high and low, on all 103 initialised placement records names a real widget record.
  • It is a pair. +0x0E and +0x0F are both indices; they are equal on all but five records, where the pair reads (gold, blue).
  • Chain field. Widget +0x02 is a signed hop (lb v1, 0x2(s7) at 0x8002FF00, added into the index and re-entered at 0x8002C780 unless zero), which is why one kind draws a whole readout. Walking the 0x2B chain against the bar's own pen (16, 192) reproduces (80, 194), (136, 188), (192, 194), (240, 188) - the four seats the packet walk measures.
KindWidget recordWhat it is
0x01(192, 0) 16x20, sub-palette 4, class 3blue plate body - the command chips
0x02(192, 64) 16x20, sub-palette 12, class 3carved-gold plate body - the name plaque
0x03 / 0x04 / 0x44class 0, tile-set 0the rectangular gold 9-slice window
0x07chain 0x07 → 0x08 → 0x09a roster panel: HP row, MP row, 102x48 plate
0x2Bchain 0x2B → 0x2C → 0x2D → 0x2E → 0x2Fthe active-actor bar's two label / separator pairs, then its plate
0x33 / 0x34 / 0x35the panel chain plus the status markerthe three roster panels, one kind per party slot

The panel placement records (6, 78, 79) carry kind 0x07; 0x33 / 0x34 / 0x35 are the sibling kinds that add the level / status marker, special-cased at the head of FUN_8002C69C into FUN_8002C2E4(slot).

Parser legaia_ui_formats::ui_widgets, disc oracle crates/ui-formats/tests/ui_widgets_real.rs. Owning page (layout, classes, chains, palette decode): battle.md.

The element-badge palette selector

Status: resolved. Evidence: disassembly + capture.

The selector is the badge record's own palette byte.

  • Each badge is a widget record (0x8B..=0x92, 20 x 12 at a 32-texel pitch from u = 6 on row v = 192) whose +0x03 palette byte is 0x40 + index.
  • Bit 6 of that byte switches both draw routines onto a second CLUT decode, fb_y = 498 + ((b & 0x3F) >> 2), fb_x = 896 + (b & 3) * 16 - a 4-wide by 2-tall block of sub-palettes (low two bits pick the column, the next two the row). The decode is ((((b & 0x3F) >> 2) + 0x1F2) << 6) + 0x38 + (b & 3) at 0x8002C5E0..0x8002C604 in FUN_8002C488, and the same sequence at each of FUN_8002C69C's packet sites (0x8002C8B8, 0x8002C97C, ...):
badge i -> palette 0x40 + i -> CLUT ( 896 + (i % 4) * 16 , 498 + i / 4 )
  • That reproduces all four captured pairs from the disc alone: u = 6 with (896, 498), 38 with (912, 498), 166 with (912, 499), 230 with (944, 499).
  • The v = 208 "winged" strip is a separate set of eight records (0x94..=0x9B), 28 x 12 from u = 2, on the second CLUT block (0x48 + index, rows 500 / 501). Record 0x9B reads v = 192, so the eighth wide badge samples the square-framed art on the plain row while its seven siblings sample the winged row; the winged eighth badge exists in VRAM and no record selects it.
The status-element badge sheet (0x18..=0x20)

Status: resolved. Evidence: disassembly + capture.

The nine ids FUN_8002C2E4's exclusive ladder emits are widget records 0x18..=0x20: 48x16 cells in a two-column block on the resident system-UI sheet (VRAM page (896, 256)), each on its own row-511 sub-palette, drawn by FUN_8002C488 at the caller's seat with no bias. The ladder is the li a2 chain 0x8002C39C..0x8002C464 into one jal 0x8002C488 at 0x8002C468, tested in this order: HP +0x6CE == 0 (0x20), then status +0x6F6 bits 0x4, 0x400, 0x800, 0x380, 0x78, 0x1000, 0x2, 0x1; the first hit wins. The decoded art is a word tag, not an icon, which confirms the ladder's per-bit assignment independently of the accessory-guard derivation (accessory-passive-table.md).

SpriteMaskCellSub-paletteReads
0x180x0001(0, 48)9Venom
0x190x0002(48, 48)10Toxic
0x1A0x0004(48, 80)16Stone
0x1B0x0078(48, 112)14Rot
0x1C0x0380(0, 96)17Rage
0x1D0x0400(0, 64)11Numb
0x1E0x0800(0, 80)15Sleep
0x1F0x1000(48, 64)13Curse
0x20HP == 0(48, 96)18Faint
  • The zero-HP arm reading Faint shows the KO test and the bit ladder are one selector over one sheet.
  • The block's tenth cell ((0, 112)) is other art; there is no tenth badge.
  • Row 511's sub-palette strip is wider than the sixteen the chrome plates use: these badges alone reach index 18, i.e. VRAM x 288.
Battle-intro tile shatter - the side-face shade page

Status: resolved. Evidence: capture.

The shade page is a resident field asset, not a transition upload.

  • The page. The 4bpp page at VRAM (448, 0) under the shatter's four semi-transparent side faces is the top-left 64 x 64 texel corner of legaia_actor_formats::field_char_textures entry 0 (PROT 0874 §2), a 256 x 256 4bpp TIM declared at (448, 0), uploaded at field init and resident for the whole field session.
  • CLUT and blend. clut 0x7641 = (16, 473), CLUT index 1 of the same entry's 16-CLUT block (a 256 x 1 black-to-bright, STP-set ramp on row 473). tpage 0x0027 carries ABR mode 1, so the side faces add the ramped texels over their opaque siblings.
  • Capture. scripts/pcsx-redux/autorun_tile_shatter_page.lua walks karisto_sol_pre_encounter into a random encounter, exec-breaks on the style-2 tick FUN_801D0D24, saves states on shatter frames 1 / 8 / 24 and logs every LoadImage / MoveImage rect. The (448, 0) rect and the row-473 CLUT are byte-identical to the pack entry before the encounter, mid-shatter and across two field scenes; no upload touches them in the transition window.
  • Emitter runtime inputs (same capture). The per-tile view matrix at scratch 0x1F8003C8 is identity rotation with zero translation from the second shatter frame on; frame one still holds the field camera's value, so retail's first frame draws no tiles (_DAT_8007B6CC, the "not the first frame" flag, is that signal). The FT4 near cutoff 0x1F80037E reads 0x10; ZSF4 is 0x400, so OT depth is the plain four-corner SZ average.

Owning page (full spec + engine wiring): cutscene.md.

Battle-intro transition length - DAT_801D2458

Status: resolved. Evidence: disassembly.

132 display frames for every style, 252 for the swirl.

  • The intro overlay's init stores 0x84 unconditionally (addiu v0,zero,0x84 at 0x801CED14, sw v0,0x2458(v1) at 0x801CED2C), two instructions before the sltiu v0,a0,0x5 style switch on DAT_801D2460.
  • One arm overrides it: jump-table slot 4 (the swirl; table 0x801CE840, body 0x801CEFEC) re-stores 0xFC at 0x801CEFF4 / 0x801CEFFC.
  • Read off PROT 0979's instruction words at load base 0x801CE818; the decompiled FUN_801ce8cc reaches past that dump's window, so the C alone does not settle it.
  • It is the denominator of the whole transition (fade leads, ready bits at - 0x1E / - 6, shatter records held until delay < elapsed * 0x3C with delay = rand() % 5000): the grid needs ~84 frames merely to finish starting, and a shorter window leaves part of it at its seeded pose throughout.

Port: render-kernels::battle_intro_styles::intro_duration_frames. Owning page: cutscene.md.

Battle ground grid depth cue - the far colour

Status: resolved. Evidence: capture (control registers read at the grid draw).

The grid's far colour is the backdrop's staged far colour; there is no separate grid base.

  • The emitter. func_0x801d02c0 runs DPCS per projected lattice vertex with IR0 = SZ >> 2 loaded bare (srl + mtc2, so no saturation - past SZ = 0x4000 the blend extrapolates until the DPCS output clamp bounds it) and contains zero ctc2: the far colour it consumes is whatever the control file holds on entry. A save-state read therefore cannot attribute the value to the grid pass (snapshots read (0, 0, 0) or (4096, 4096, 4096) depending on what ran last).
  • The probe. scripts/pcsx-redux/autorun_grid_far_colour.lua sets exec breakpoints on the emitter entry and its first DPCS site (0x801d061c) and dumps control regs 21-23 at the draw.
  • The law. Every battle hit shows FC = the backdrop far-colour staging word at 0x8007BB48, times 16 into the 28.4 control registers.
  • Settled values. (0x40, 0x40, 0x40) on ordinary stages (two town01 battles, stage ids 0x15 / 0x0C) and (0xFE, 0xFE, 0xFE) on an overworld battle (stage id 0x55, on the 13-id DAT_80078C1C outdoor table at SCUS file 0x6941C). Both are the neutral base 0x808080 through FUN_80050120's two derivation arms: >> 1 indoor, (c - 0x010101) * 2 outdoor.
  • Intro ramp. The battle-intro fade ramps the staged word up from near-black at +0x020202 per frame for ~28 frames before it settles; a Queen Bee sample reading (6, 6, 6) is frame 1 of that ramp, not a per-stage ambience.
  • Other constants. DQA = -64 / DQB = 320 << 16 at every hit. A field state never enters the emitter: the aliased field-overlay code at the same VA idles at the white (4096)^3 field FC.

Port: legaia_engine_battle_vm::battle_ground_grid carries the laws (grid_ir0, grid_far_colour, OutdoorCueTable, the ramp constants), with the SCUS table pinned by crates/engine-battle-vm/tests/battle_grid_cue_scus_real.rs. engine-render stages a per-draw DrawCue (retail sets the DPCS inputs per drawn object) and the play-window grid draws under the SZ >> 2 ramp toward the per-stage far colour. The browser play page draws the same grid from the same table: play_battle_ground_cue_json hands the page the engine-resolved far colour and the page renderer attaches it as a per-draw cue.

Who calls the battle on-screen test FUN_8005126C?

Status: resolved - exhaustive negative. Evidence: disassembly.

Nobody calls it, and the same holds for two of its neighbours; there is no draw-pass consumer of its verdict.

  • The sweep. A five-form sweep of SCUS_942.54, all statically based overlay images and the raw bytes of every extracted PROT.DAT entry finds no reference to 0x8005126C: no literal address word (so no dispatch table and no actor template), no jal, no j, no PC-relative branch, no lui+addiu materialisation (address-reference-scan.md).
  • Same result for the passive-name draw FUN_80035274 and the angle tween FUN_80050D40. Each follows a clean jr ra epilogue whose delay slot closes the previous frame, so they are entry points rather than interior labels. Two of the three open frames of their own; FUN_80050D40 is a frameless leaf.
  • FUN_80025054 is the same finding one level up: it is a template tick, and the template record 0x80070614 that would install it is what nothing materialises. Its table was swept whole (a record reached as base + index is not a materialisation pair): the head 0x800705FC is named once, in the field overlay, and handed straight to the allocator as one record rather than indexed.
  • Positive controls the scan reproduces: the template word for FUN_8004DA00 at 0x800767FC, the 21 jal sites of the billboard projector FUN_800195A8, an intra-function branch target inside FUN_8005126C itself, and the menu overlay's sub-screen pointer table at 0x801E4F40.
  • Limits. An LZS-compressed PROT entry would hide a reference (overlay code is stored raw, so this does not affect the images callers live in), and an address assembled in more than two instructions is not a lui+addiu pair.
  • The one positive. FUN_8004DA00's spawner is the battle scene-loader FUN_800513F0.

Port: battle_on_screen stays inert on purpose. The three rows are unreachable retail code, settled under the ignore list's unreferenced section (worklist-classification.md). Write-ups: battle.md § Unreferenced SCUS entry points. The same sweep over every disclosed-inert port anchor separates rows waiting on wiring from rows waiting on nothing; the closed list of the latter, SCUS and overlay alike, is on address-reference-scan.md.

Action-SM state 0xFF treated as battle end by the port

Status: resolved. Evidence: disassembly (retail half); the port path is live-reachable and fixed.

Retail 0xFF is the round boundary, not the battle's end: its only writer is the non-wipe arm of the 0x5A end-of-action gate (li v0,0xff / sb v0,0x7(a2) at 0x801E67DC / 0x801E67E4, the only 0xFF store to ctx[+0x07] in FUN_801E295C), and wipes signal through DAT_8007BD71 = 0xFE (sb v0,-0x428f(v1) at 0x801E6674) without writing a state byte. A port that maps 0xFF to battle_end(BattleEndCause::MonsterWipe) grants a spurious victory (loot and XP) after one round with both sides standing.

  • Why a live battle reaches it. The counter is the action context's turn cursor ctx[+0x1A] (BattleActionCtx::turn_cursor), not a field of the actor record; Begin seeds it from the formation-advantage byte and never from ctx.queued_action. The 0x5A gate's non-wipe arm bumps it and compares against party_alive + monsters_alive (0x801E679C..0x801E67CC: ctx[+0x1A] + 1 against ctx[+0x00] + ctx[+0x01] - ctx[+0x25]), so the threshold is reached once each living combatant has acted - the ordinary end of a round.
  • Dispatch. The gate runs whenever a driver leaves the SM parked at EndOfAction across a tick (after a folded monster spell cast and after a Sleep / Stone skipped turn), so the boundary is reached in the first round of a battle that leaves both sides standing.
  • Port. The state is ActionState::RoundEnd: its handler clears every actor's acted counter and hands control back through EndOfAction (the state the arming driver keys the next turn on). The retail 0xFF body (ctx[+0x28A] round bump, FUN_801F45A4 settle) runs host-side in engine-core's live loop. battle_end(..) fires only from the paths that raise retail's 0xFE signal: the 0x5A wipe arms and the escape teardown 0x66.
  • Regressions. engine-vm full_round_with_both_sides_alive_does_not_end_the_battle; engine-core round_boundary_state_is_not_a_spurious_victory.

Owning page: battle-action.md.

Endless camera orbit - the 0x19 attack-approach park

Status: resolved; a one-word disc fix ships as legaia-patcher --approach-softlock-fix. Evidence: capture (the fingerprinted scenario battle_gaza2_park_0x19, caught from ordinary play under the poll-only hunter autorun_gaza2_park_hunter.lua; interpreter replay autorun_gaza2_range_wedge.lua; RAM-table read of the parked save) + disassembly (overlay_battle_action_801e295c.txt, 0x801E31F4..0x801E32DC).

The community-reported "endless camera orbit" (Gaza rematch; JP exhibit too) is the battle-action SM parked in state 0x19 while the idle camera azimuth sweep (FUN_801D0748) keeps orbiting. The orbit is a symptom.

  • The park. State 0x14 (attack approach setup), finding the target out of range, looks up the walk animation (action tag 0x20) in the acting monster's table via FUN_80050E2C. A table without one - bosses generally; Gaza's 12 tags read [00 01 02 03 04 05 0B 0E 13 0C 23 23] - takes the fallback: stage the tag-1 "Move" float loop and drop into 0x19, the range re-poll, whose arm has no movement code and no timeout (its not-in-range edge only bumps ctx[+0x6D4], read solely by the arms-resolver roll).
  • Why it usually works. The fallback normally still approaches during 0x19 (~19 units/vsync, driven by the staged Move clip's playback, not the SM). In the caught parks the drive dies ~12 vsyncs in (anim pair back to 0/0), frozen beyond reach.
  • Trigger. A summon immediately followed by the boss's melee (scenario battle_gaza2_park_0x19_summon_melee); the stale field is actor +0x1DC bit 2 (details ↓). The disc fix is indifferent to it.
  • Stale sentinels. The +0x1DD == 8 targets on the idle party actors are stale all-target sentinels; the round is stuck on the boss's action alone.
  • The sibling 0x51 HP-settle park is a fully decoded mechanism that remains injection-only: a three-capture retail campaign (twelve Lost-Grail revives, no harness HP writes) measured out both of its candidate generators (re-do-not-re-walk.md), and the 0x19 class explains the community exhibits without any HP desync. Whether any retail sequence can produce a 0x51 park is unproven either way; nothing observed requires it.

Owning page (anatomy, fix, engine-port note): battle-action.md.

The in-fight action framing and the yaw-counter ladder

Status: resolved. Evidence: capture + disassembly.

Of FUN_801D5854 case 6's two arms, the 0x801D64C4 arm films every ordinary action - party and monster alike - for the whole of a running fight.

  • The fork byte DAT_8007BD71 (0x801D5CEC..0x801D5CF4) is the battle-end signal and reads 0xFF until a wipe or an escape. Not a party / monster split: re-do-not-re-walk.md.
  • The pose. pitch 0, yaw = (ctx[+0x6DA] − actor[+0x46]) & 0xFFF, TR = (0, 0x500, ctx[+0x6D0]) (the depth prescaled by FUN_801D829C), focus the negated actor[+0x34/+0x38] pair with the height left at zero; then the ctx[+0xD] style tweaks (1/3 add a half turn; 2/3 set TR.y = 0x400 and add 0x80 of pitch) and the character-4 override.
  • Yaw-base seeds (ctx[+0x6DA], each read off the instruction): sh zero,0x4(s7) in the round-begin arm (0x801E2B40); li 0x800 in the 0x0C seed arm (0x801E2CF8); li 0x200 beside the ctx[7] = 0x14 store (0x801E2F20); and FUN_8004E13C's (rand() % 2) << 11 + 0x280 (0x8004E288..0x8004E2B0) under a three-way gate - argument 2, ctx[+0x243] != 2, ctx[+0x13] < 3 - whose argument is the committed clip's header byte +0x87 from FUN_8004AD80 (0x8004BE18..0x8004BE2C).
  • Per-frame advance. The prologue adds max(1, 4 × frame_step / 3) (0x801E29E4..0x801E2A24). battle_melee_hit_spark reads ctx[+0x6DA] = 0x298 mid-art: 0x280 plus 24 frames of it.
  • Captures. Three PCSX-Redux .sstate captures parked in ctx[7] == 0x19 with Gaza (seat 3) acting: TR (0, 1280, 5324) = prescale(0xD00) with ctx[+0x6D0] = 0xD00 in all three; focus (−433, 0, −291) / (785, 0, −39) / (0, 0, −1490) against Gaza's +0x34/+0x38 of (433, 291) / (−785, 39) / (0, 1490); the ctx[+0xD] == 2 capture at pitch 0x80 over TR.y = 0x400; and in each the live yaw eight units behind (ctx[+0x6DA] − actor[+0x46]) & 0xFFF (1657 vs 1665, 1412 vs 1420, 574 vs 582) - the per-pass re-arm chasing a counter that moves.

Port: legaia_engine_battle_vm::battle_cam_script - ActionFraming::battle_over names the fork byte, action_framing drops the focus height, and BattleCamera::observe_action_state applies the seed ladder on the action-state edges. The swing-clip commit is stood in by the edge into 0x1E, since the engine's animation player does not expose the clip header byte. Both hosts feed the same action_state.

The Done band framing and the two orbit writers

Status: resolved. Evidence: capture + disassembly.

After an action resolves, FUN_801E295C sits in 0x50 / 0x51 for the ctx[+0x6D8] tail (0x3C display frames by default). The camera there is on a per-action framing chosen by category, not the far framing; the Begin/Run prompt orbits at −2 yaw units per display frame (−4 per two-frame camera step).

  • The selection. Both Done arms read actor[+0x1DE] before the framing call (0x801E5E90..0x801E5EF4 in 0x50, 0x801E5FC0..0x801E6018 in 0x51), re-armed every pass:
ConditionFraming
category 5 (Run)none; runs the yaw orbit
category 3 (Attack)li a1,0x8
a party slot whose target's live HP +0x14C is zeroli a1,0x8
anything elseli a1,0x6
  • The far framing returns with the next round's prompt. Between two actors the last shot stands: 0x5A re-arms the category's framing (0x801E6428..0x801E6470) and the setup states arm nothing.
  • Done-band capture. zora_glare_petrify_post (mednafen, ctx[7] == 0x51, slot 3 acting after a spell, ctx[+0x6D0] = 0xC00, ctx[+0x6DA] = 1966, Zora at (649, −47) facing 3297) reads pitch 0, yaw 2735, TR (0, 1275, 4820), focus the negated (624, 0, −40): case 6's in-fight pose (0, 0x500, 4915) on the caster's seat with the tween one step short, and yaw (1966 − 3297) & 0xFFF = 2765 being chased.
  • A round's first actor. evil_medallion_rage_battle (ctx[7] == 0x0A, flow byte 0xFF) reads pitch 32, TR (0, 1280, 7920), focus at the origin - case 9 over ±825 seats, the prompt's framing, with no earlier actor in the round.
  • A later actor. kor_ghost_knight_pre_v_windhash (ctx[7] == 0x0A, ctx[+0x13] = 1 with a spent key) reads pitch 128, TR (0, 1024, 3276), focus Noa's seat: her Spirit's case 6, landed.
  • Orbit writers. scripts/pcsx-redux/autorun_battle_cam_orbit.lua on battle_gaza2_prompt (240 vsyncs, ctx[+6] == 0x1E, ctx[7] == 0x00) with Exec breakpoints on both sh v0,0x2(a0) stores: the SM's at 0x801E2A6C never fires; the battle tick's at 0x801D07CC fires once per tick and the yaw drops 2 × DAT_1F800393 each time (−6 per 3 vsyncs under the interpreter's step = 3, i.e. −2 per vsync).

Port: legaia_engine_battle_vm::battle_cam_script::done_band_phase over DoneBandInputs (category / party seat / dead target), filled by both hosts; DONE_STATES is split from ACTION_END_STATES; the orbit rate is ORBIT_STEP = 4 per camera step.

The summon-then-melee park trigger - the stale field is +0x1DC bit 2

Status: resolved. Evidence: disassembly (the driver pair FUN_80047430 / FUN_8004AD80 in ghidra/scripts/funcs/80047430.txt / 8004ad80.txt; the flinch staging at 0x80042124..0x80042170 in 800402f4.txt; the SM's 0x14 fallback stores at 0x801E32B0 / 0x801E32D4 in overlay_battle_action_801e295c.txt) + capture (causal control / experiment replay on the parked save battle_gaza2_park_0x19_summon_melee, probe scripts/pcsx-redux/autorun_gaza2_stale_flag_repro.lua with write-watchpoints on +0x1DA / +0x1D9 / +0x1DC logging writer PCs).

The field the summon staging leaves stale is the battle actor's anim event-flag byte +0x1DC, bit 2 (mask 0x4), the stage-idle-at-clip-end flag - not a frame cursor or a clip-length latch.

  1. The summon's hit stages Gaza's light flinch with +0x1DC |= 4|1 (exit-to-idle + commit-now, FUN_800402F4); the flag is normally consumed at the flinch's natural end.
  2. The boss's melee follows immediately: state 0x14's walk-less fallback stages the Move clip with |= 1 first.
  3. The tick's event-path commit clears only bits 0-1 (andi 0xFC; the natural-end path clears 0-2, andi 0xF8), so the Move clip installs with bit 2 set.
  4. At the Move cycle's first natural end (5 frames, rate 2, speed scale 8 → ~12 vsyncs) the tick sees bit 2 and stages idle over the queued clip (sb zero,0x1da at 0x80047B44) instead of re-looping: pair 0/0, idle per-tick speed 0, and 0x19 re-polls forever.
  5. Control / experiment. With the flag clear the bounce (state → 0x14) loops the clip across its natural end (pair stays 1/1) and arrives ~21 vsyncs later; re-arming bit 2 first reproduces the kill write from 0x80047B44 at exactly the first natural end, 12 vsyncs after engage, position frozen thereafter.
  6. Visibility. The park save reads +0x1DC == 0 because the killing commit consumed the flag; it is only visible in flight.

Owning pages: battle-action.md (mechanism); monster-animation.md (driver + flag byte).

Super / Miracle Arts trigger chain

Status: resolved - matcher, tables, builder chain and runtime effect all pinned. Evidence: disassembly + capture.

  • Preseed. FUN_801DA34C copies the saved chain from the char record +0x76F / +0x77F verbatim (lbu +0x76F → sb +0x1DF): the char-record chain uses the queue-space encoding directly (0x0C/0x0D/0x0E/0x0F = L/R/D/U, 0x1A starter, 0x1B..0x32 art constants).
  • Builder. FUN_801EED1C (battle overlay 0898, ActionSeed state 0x0C) rewrites arrow runs to art constants and applies the Miracle replacement inline, then delegates the Super find → tail-replace to FUN_801EF9E4. Miracle-before-Super is structural.
  • Super tables. Keyed on (actor slot, char index): find cells [len][bytes] at 0x801F6524 + char*65 + row*13, replace at 0x801F65E8 + char*80 + row*16, first match wins.
  • Queue. Exactly 16 bytes, actor[+0x1DF..+0x1EE]; +0x1EF.. is neighbouring data.
  • Verification. The resident find / replace tables match the modeled crates/art/src/{miracle,super_art}.rs byte-exact, and all 15 Supers are live-executed: the applier-entry injection probe scripts/pcsx-redux/autorun_super_art_queue_inject.lua breakpoints FUN_801EF9E4, writes the target Super's find bytes into the queue, retargets the char-index register, and reads the tail-replaced queue back at the return site - 15/15 byte-exact, with the two hand-driven combos (Noa's Miracle, Vahn's Tri-Somersault) as positive controls. One post-applier library state per character is re-checked by crates/parity/tests/super_art_queue_replace.rs.

Owning page: battle-action.md.

Super Art connectors and physical inputs - the tokenizer keeps the leading arrows

Status: resolved. Evidence: disassembly (FUN_801EED1C 0x801EF2EC..0x801EF858) + capture (reproduces the in-the-wild Tri-Somersault queue byte-exact) + an independent table (fourteen of fifteen walkthrough inputs agree).

The 0F / 0E "connector" directions in a Super's find pattern are the leading arrows of the arts themselves: the queue builder does not compact a matched arrow run down to 19 <art>.

  • Normalisation loop. On a full match it writes the starter over the run's last arrow (sb v1,0x1df(v0) at 0x801EF6F8, v1 = FUN_801EFBFC's verdict + 0x18), shifts the tail up one slot (0x801EF708..0x801EF750) and inserts the constant after it (0x801EF7A0). The leading arrows stay.
  • Walk order. The outer walk runs tail-first (s8 from 15 down, 0x801EF848) and restarts at s8 + 1 after every match, so runs overlap.
  • Examples. ↑↓↑ alone tokenizes to 0F 0E 19 27. Tri-Somersault's 19 27 0F 19 1F 0E 19 27 is what the seven-arrow input ↑↓↑↑↑↓↑ tokenizes to (Somersault 0..2, Cyclone 1..4, Somersault 4..6); the capture's leading 0F 0E are Somersault's own first two arrows. Laying the three arts end to end tokenizes to four arts and never triggers.
  • Derived inputs. Every retail Super's pattern derives to a unique shortest input, all 7..=9 arrows (legaia_art::tokenize::derive_super_input, searched over the chain arts' directions). The curated walkthrough table agrees on fourteen; the fifteenth (Dragon Fangs, printed as six arrows in the walkthrough) drops one and never performs Swan Driver, so the curated entry carries the seven-arrow form.
  • Downstream. --show-super-arts draws each Super Art's real arrow string: sixty bytes for all fifteen, against the ~330 a concatenation of chain strings costs.

Port: legaia_art::tokenize. Owning page (table + citations): art-data.md.

The runtime art-record chase - the +4 and the - 0x10 grid origin

Status: resolved - record(c) = *(*(DAT_801C9360[char]) + 0x58) + 4 + (c - 0x10) * 0xD0, with +0x10 the name field and +0x24 the power bytes.

  • The chase (0x8004B6FC..0x8004B718): lui/addiu builds DAT_801C9360, lw the per-character block, lw +0x58 the array pointer, addiu s0,v0,0x4.
  • What s0 is the base of. FUN_8004AD80's own three uses of s0 each multiply the action constant by 0xD0 with the identical sll 1 / addu / sll 2 / addu / sll 4 chain and then subtract a constant:
SiteExpressionResolves toConsumed as
0x8004BBE8..0x8004BC10s0 + 0xD0*c - 0xCF0record + 0x10the art's display-name pointer (sw into 0x8007634C/0x80076344)
0x8004BC60..0x8004BC80s0 + 0xD0*c - 0xCDCrecord + 0x24the per-strike power bytes
0x8004BCA0..0x8004BCC4s0 + 0xD0*c - 0xCF6record + 0x0Aa byte handed to FUN_8002B28C
  • 0xD0 * 0x10 = 0xD00, so each constant is 0xD00 - field_offset: the array is indexed from constant 0x10 and s0 is the base of record 0x10 itself.
  • The +0x10 name and +0x24 power offsets are the same ones super_art_power edits off the decoded record0, so the runtime base and the file-side art_block_base are one address in two spaces.
  • --show-super-arts chases the name through this chain; its planner re-proves it per disc by requiring all fifteen Super Art records to carry their own names at +0x10 before writing anything.
Character-record HP/MP/AP pair order

Status: resolved. Evidence: disassembly.

+0x104 / +0x108 / +0x10C are the effective maxima; +0x106 / +0x10A / +0x10E are the currents.

  • Clamp triple. The stat aggregator's closing sequence at 0x80042CE4 is lhu v1,0x104(s0) / lhu v0,0x106(s0) / sltu / sh v1,0x106(s0), repeated identically for 0x108/0x10a and 0x10c/0x10e: it clamps the second halfword of each pair to the first.
  • Cap ladder (0x80042C0C..0x80042C50) is per-field, not a flat 999: +0x104 → 9999, +0x108 → 999, +0x10C → 100, +0x110 → 280, then 999 for five more. A 100-cap on +0x10C is the AP maximum, which corroborates the pair order.
  • Walk-regen FUN_801D0B90 bumps +0x106, clamping at +0x104.
  • The aggregator rewrites +0x104 per frame from base stats plus %-passives; a per-frame recompute cannot be current HP.
  • GameShark "Infinite HP" codes write +0x106 at every character stride - they pin the current.
  • A fresh save cannot distinguish the orders, because cur == max at seed.

Port: legaia_save::HpMpSp and every consumer carry the (max, cur) order; the status AP gauge reads the AP current at +0x10E.

Effect-VM pass-1 "state token algebra" (FUN_801E0088)

Status: resolved + ported. Evidence: capture.

The "state" bytes are 5.3 fixed-point wait counters, not opcodes: two countdown-driven cursor walks (master spawn cadence over 14-byte pack1 records; child anim / motion over 6-byte pack0 frames).

Port: Pool::tick_retail executes the algebra operator-for-operator (pass 2 = Pool::child_billboards), disc-verified over all 33 efect.dat scripts. The engine's live path runs it: engine-core::World::tick_effects sweeps tick_retail per retail frame and active_effect_sprites maps child_billboards one-for-one; dev debug spawns live outside the pool.

Owning page: effect-vm.md.

Battle face-stamp issuing site

Status: resolved. Evidence: capture.

The facial-texel overwrite is the per-frame facial animator FUN_8004C7B4, called from the render-node update with the clip's frame cursor (Terra skipped).

  • Action-entry facial tracks at +0x8C (eyes) / +0x98 (mouth) select frames from static per-character SCUS tables, stamped by MoveImage every frame.
  • Pinned live across a battle entry (karisto_sol_pre_encounter + the MoveImage trace probe).
  • FUN_8004CCD4 is not a stamp: it is the equipment mesh-variant swap (same caller + guards, re-run per ghost by the arts trail renderer), driven by the entry's third track at +0xA4; retail windows Noa-only.

Owning page: battle-data-pack.md § Facial animation tracks + § Equipment-variant track.

Spine flag 0x482 (Drake mist-wall) writer

Status: resolved - writer-less. Evidence: capture.

No writer ever fires: the map01 P2[34..36] C1 spawn-block never latches, and wall despawn is not flag-driven.

  • Byte write-watch on 0x800857E8 (autorun_flag_writer_watch.lua) across the whole post-Zeto beat (battle exit, mist-clear FMV, map01 arrival): the only write to the byte is the SET helper re-latching 0x484 (store FUN_8003CE08+0x28, ra 0x801E3598); 0x482 never flips.
  • Every catalogued state through the Karisto era holds it clear (neighbours 0x484..0x487 at 0x0F), and all 37 census sites stay DESYNCED.
  • Only an engine-side C1-latch-on-fire (pad-walk into a wall) could give the flag a role.
Flag 0x63A - the vell/vozz P2[7] gate with NO script writer

Status: resolved - script writers exist. Evidence: capture.

  • Under the retail-frame scene windows the census shows eight clean sites: Set / Clear pairs in the rikuroa post-Caruban variant MAN (PROT 0157 P2[29]/[30], op 0x56/0x66), rikuroa2 (PROT 0122 variant), retockin (PROT 0281 P2[7]/[8]) and edretoin (PROT 0800 P2[7]/[8]).
  • These are late-game beats, so the vell/vozz C1=[0x63A, 0x7] spawn-block passes for the whole first visit.
  • Retail states corroborate: 0x63A reads clear through the Karisto era while its bank byte already holds 0x0C (0x63C/0x63D set).
  • The byte for 0x63A is 0x80085758 + (0x63A >> 3) = 0x8008581F (not 0x800858C7).
Spine flag 0x142 (Caruban beat / dolk-dolk2 switch) writer

Status: resolved (disc writers + engine port + oracle). Evidence: capture.

The story writers are two records, not the six an arm count reports.

  • Writers. rikuroa's post-victory P2[50] in the streaming variant MAN (PROT 0157), whose own C1 gate is 0x142 itself (the self-latching one-shot shape), and dolk2's carrier P1[0], which re-asserts it.
  • Not writers. The other four clean 51 42 / 61 42 arms are developer flag-menu rows: rikuroa P1[10..12] is a nine-flag Set ladder with its mirrored Clear ladder, and dolk2 P1[1] / dolk P1[26] sit in the same shape. An opcode census cannot tell a beat from a menu row; both decode clean.
  • Live. Firehose-caught (ra 0x801E3598), and the resident script heap byte-matches the carrier. The census must walk the streaming carriers to see it; pins man_variant_carrier_census_disc.rs.

Port: the engine sets it organically - rikuroa P2[50] executes from its own script bytes on the Battle-to-Field edge (organic_beat_records_disc.rs).

Drake Castle deep interiors (jouinc/jouind) depth decode

Status: resolved - door-choreography families, not story gates. Evidence: capture.

  • jouinc's 58-record C1=[0x00F] P2 family is a busy-mutex door family: each record SETs 0x00F first and CLEARs it last, so the C1 gate is a mutual-exclusion lock, and the bodies are per-door walk-through choreography.
  • jouind P2[10..13]'s 0x4BE..0x4C2 band is per-visit door / lift state, cleared by jouina P1[0] on entry - not a later-chapter revisit gate pair.
  • jouinb P2[6..8] is the interior beat band (0x44E..0x450 latches plus the jouinb-local 0x461 state flag).
  • The decode depends on the disassembler's whole-nibble operand widths for 0x4C nibbles 9/A/C/D/F.

Owning page: script-vm.md § door-choreography record families.

cave01 P2[16] spawner - the slot-counted interact chain

Status: resolved. Evidence: capture.

The ungated 0x15D setter P2[16] is spawned by P2[12] once a slot counter reaches 8, and each creature interaction bumps the counter. PROT cites are the extraction frame (cave01 = PROT extraction 38).

RecordRoleBytes / offsets
P2[16] (global 0x1E)sets 0x15D, ungated51 5D at body +0x22, MAN 0x3C10
P2[12] (global 0x1A)bumps slot 0, gates and spawns P2[16]opens 4C CB 00 01 00 (slot 0 += 1); gate 4E 00 50 08 00 06 00 at +0x15; 44 1E at body +0x1C, MAN 0x35B9
P1[3..7]the five creature-interact scripts; each spawns P2[12] once per interaction44 1A at the first-interact branch tail: P1[3] +0x2CC = MAN 0x1CE4, siblings at 0x1FCC / 0x22B6 / 0x259E / 0x2888
P1[2]lead-NPC ladder record testing 0x15E/0x15D/…/0x157; zeroes slot 04C CA 00 00 00 at +0x0C
  • The gate is an op-0x4E sub-5 slot-table compare: while slot 0x801C6460[0] < 8 it skips forward past the spawn (to the 0x166→0x167→0x168 progressive counter at +0x20); at 8 it falls into the 44 1E.
  • Why the count reaches 8. The per-NPC talked latches 0x161..0x165 are re-cleared inside the interact scripts (P1[3] +0x82..+0x8A), so interactions repeat.
  • Every op-0x4E sub-op 0..9 is a compare: the 0x4E details.
NPC dynamic facing - two laws and an execution order

Status: resolved. Evidence: disassembly.

How a facing changes after spawn (the spawn heading itself: below).

  • Walking snaps. Every walk kernel - the 0x47 tail in FUN_8003774C and the directional / wander steps in FUN_80038158 - quantises the frame's step to the eight-entry compass LUT at 0x80073F04 (entry[i] = i * 0x200, 0 = -Z) and writes +0x26 outright. Retail has no walk-turn interpolation.
  • Rotate ops ramp. The four dedicated rotate ops (0x38 / 0x4C, 0x04 / 0x0D) step arc * speed / frames_remaining off the live heading over a budget the op carries, with an exact snap on the terminal frame.
  • Priority is execution order, not a field. FUN_8003BC08 runs the dialog SM, then FUN_8003774C, then FUN_80038158, then the anim consumer; an actor running both a scripted leg and an ambient stream ends the frame facing wherever the ambient stream put it.
  • Case bodies. Op 0x38's case body is 0x800379FC; only 0x4C lives at 0x80037DE0. The jump table at 0x80010EE0 settles all 22 slots.
  • The LUT is eight entries of 0x200, not sixteen of 0x100.
  • 0x4C sub-modes 0x85 / 0x8E / 0x8F all take one arm; 0x8F alone forces the direction.
  • +0x16 is the terrain-conform angle sampled from the scene grid by FUN_80019278, not a facing. The yaw is always +0x26.
  • Live. A cold-boot town01 sample off the static recompilation reads every field actor's +0x26: all on-field headings are multiples of 0x200 with all eight points present, the only exceptions being actors parked on the (0x7F, 0x7F) sentinel tile.

Owning pages: field-locomotion.md + motion-vm.md.

0x4C 0x51 byte +3 reconcile - facing wins; no motion-bytecode synthesis

Status: resolved. Evidence: disassembly.

  • 4C 51 case-1 (dispatcher overlay_0897_801de840.txt, case 5 sub 1) consumes byte +3 only as [bit7 -> actor render flag 0x1000000 (special model) | low nibble -> +0x26 = heading LUT 0x80073F04[b & 0xF]]. The op carries no speed operand: byte +4 is the move-anim id written to +0x5C (consumed by the anim-stream stepper FUN_800204F8); non-player targets also get the +0x8C/+0x8D current-tile bookkeeping, and the trailing FUN_801D81E0 is an active-list relink (the unlink/relink pair FUN_800204A4 / FUN_80020454), not a bytecode builder.
  • The depth & 7 base-step selector belongs to the walk-kernel op 0x47's own third operand: FUN_8003774C case 0x47 computes 4 << (b & 7) (per-frame step 0x80 * dt / that) with the high nibble an approach-mode selector; ops 0x37/0x41 encode their base step as (op0 >> 5 & 4) | (op1 >> 6) of their own two operand bytes (ghidra/scripts/funcs/8003774c.txt).
  • There is no motion-bytecode synthesis step: the field-VM yield-class ops 0x37/0x41/0x47 (and 0x38 with a nonzero duration) park the current instruction pointer at actor +0x94, zero the progress cursor +0x54 and set actor flag 0x400 (dispatcher cases 0x37/0x41, 0x38, 0x47), and FUN_8003774C interprets the record bytes in place - it even resolves the field VM's 0x80 extended-target convention (0xF8 player / 0xFB world-map entity / placement id vs actor +0x50).

Port: placement_glide_speed derives the base step from the real 0x37/0x41/0x47 yield operands (placement_yield_step) and the tail-section-1 wander ops (placement_wander_step), with the facing-nibble reading kept only as a documented last-resort heuristic; 4C 51 byte +3 sets facing + the special-model flag only (placement_initial_facing). Owning page: field-locomotion.md § NPC initial facing / § NPC glide speed.

kor-family op-0x49 flag window [0x138..0x13F] - Uru Mais warp-pad picker

Status: resolved. Evidence: disassembly.

Each flag is one destination row of the Uru Mais dream-shrine teleport-pad picker.

  • Pad records (kor P2[17..20], kor3 P2[9..12], kor4 P2[4..7]; extraction PROT 483/492/501) clear the whole window, pre-set their own row (kor pads -> 0x138..0x13B, kor3 -> 0x13C/0x13D, kor4 -> 0x13E/0x13F), run the FUN_801EF014 picker, then dispatch an 8-way 0x71 test ladder in which each arm clears 0x612, fades, stops the BGM and executes a named 0x3F SceneChange (kor P2[17] body +0x8D..+0x1C6):
rowsdestination
0..3KOR entries (0x0E,0x35) / (0x1E,0x35) / (0x2E,0x35) / (0x3E,0x35)
4..5KOR3 entries (0x70,0x25) / (0x0D,0x36)
6..7KOR4 entries (0x27,0x27) / (0x1E,0x3E)
  • Picker behaviour. State 0 cursors to the pre-set bit (= "you are here") and clears the window; confirming a different row sets base + selection; picking the current row or cancelling sets nothing, so the test ladder falls through to the stay-put arm (+0x1C9: clear 0x136/0x137, fade back, park).
  • Widget semantics (ghidra/scripts/funcs/801ef014.txt): descriptor +2 default = first visible row, +3 rows = visible row count. The paired descriptors are the full 8-row menu (selected by state flag 0x136) vs the rows-4..7 chambers-only menu (0x137; kor3/kor4 carry per-pad record pairs, one per variant).
  • 0x137 and the kor pads. The kor P2[17..20] records set 0x136 exclusively and never 0x137; neither flag should be read as reachable from any pad.
  • Menu pixel height rows * 16 reaches the renderer as a height. The sll v0,v0,0x4 at 0x801EF160 stores to 0x801F2B98 + 0x196 = record 14, field +0xE, of the 0x1C-stride window-descriptor array (stride from FUN_801E9B3C's sll 3 / subu / sll 2); the picker's command list at 0x801F3304 is 0001 000E, window id 14; and FUN_80032434 moves +0xE into the live window's +0x10 (0x80032484 → 0x800325D4), the field the menu-side creator FUN_800326AC fills from its descriptor's h.
  • Cells. System-UI plates from the widget table at SCUS 0x800732A4: record 0x4F is the "B1F" plate, 0x50..0x56 the digits 1..7, 0x57 the "F" suffix, 0x58..0x60 the same set marked in yellow. The legend is the Cross / Circle icons (0x37 / 0x38) and the field-overlay strings at 0x801CF0B8 / 0x801CF0C4 (records checked against a mednafen state's VRAM).

Port: engine-field::field_submode_flag_window hosts slot 0x23 on the field path, drawn on both hosts.

Encounter MAN sub-section layout

Status: resolved

FUN_8003AEB0 is fully decoded: the MAN header chains six sections, and the per-scene encounter section is read straight from disc bytes.

  • Header: +0x22, +0x24, +0x26 are signed-16 record counts of 3-byte records (assembled from lbu pairs then sll 16 / sra 16 at 0x8003B04C..0x8003B098); +0x28 is a u24 (0x8003B108..0x8003B120). They are not four signed-16 section offsets.
  • Region table (section 3): the count-prefixed array of 18-byte records at _DAT_801c6ea4 + 0x4. byte[0] = kind selector; bytes[1..4] = tile-space box [minX, minZ, maxX, maxZ] queried by FUN_801dba20(tileX, tileZ) with tile = (player_pos - 0x40) >> 7; bytes[5..17] = a per-region camera preset (three mode-keyed splits on byte[5] >> 4 into the camera globals 0x8007B607..0x8007B627, consumed by the camera-param builder FUN_801dab90, the arrival handler FUN_801dbec4 and camera-config FUN_801dbc20).
  • Mask-kind records side-copy bytes[1..4] to scratchpad 0x1F8003E8..EB (mirrors 0x801F2778..84): the visible tile window.
  • Port: legaia_engine_battle::encounter_man::scene_encounter_from_man builds per-scene EncounterTables for the standalone towns and the kingdom-bundle scenes (the count = 6 MAN form resolves through find_bundle); legaia_engine_field::field_regions::zone_query (FUN_801dba20, with the FUN_80017fbc .MAP region scan + FUN_800180ec attribute refresh) drives World::refresh_field_regions per tile crossing.
  • Outside this thread: the world-overview actor-placement section consumed by FUN_8003A1E4 (world-overview threads).

Owning page: encounter.md.

Seru-magic summon visual (e.g. Tail Fire)

Status: resolved - player visual wired; enemy "Fire Tail" characterized

A player Seru-magic summon renders as its namesake battle_data creature through the ordinary rigid TRS-keyframe battle draw. The per-spell slot-B overlay is a spawn stager of move-VM scene-graph records, not the creature's draw path; PROT 0900 is a screen-effect + top-view-grid overlay that does not run during a summon. The enemy "Fire Tail" boss move is a single live move-VM part-actor.

Dispatch and stager assignment

  • Battle SM FUN_801E295C state 0x29 resolves spell id 0x81..0x8b via PTR_801f6734[id-0x81] + FUN_8003EC70(id-0x79); FUN_8003EC70(param) loads extraction entry param + 0x37F (prot.md § In-RAM TOC), so 0x81..=0x8b → PROT 903..=913 (Gimard 0x81 → 0903, spell 0x83 → 0905). Overlays timeshare the slot-B buffer at link base 0x801F69D8 (*DAT_80010390).
  • Capture: one mid-cast state per spell (the gimard_summon_* + <seru>_summon_mid_cast scenarios in scripts/scenarios.toml) holds the loader-B current-id 0x8007BC4C at exactly spell_id - 0x79 for all eleven ids, 0x81 Gimard → 903 through 0x8B Nova → 913.
  • Stager blocks (legaia_overlay_images::summon_overlay): PLAYER_SUMMON_STAGER_PROT 0903..=0913; EVOLVED_SUMMON_STAGER_PROT 0914..=0923 (0x8c..=0x95, same (id - 0x81) + 903 run; 8 of 10 legs capture-pinned, 0x90 / 0x91 by arithmetic); HIGH_SUMMON_STAGER_PROT 0927..=0934; ENEMY_BOSS_STAGER_PROT = the six Cort stagers 0938 / 0940 / 0944 / 0961 / 0962 / 0966. Ordinary-enemy casts bind the same way: the Delilas brothers → 0958 / 0959 / 0960, Zeto → 0946 (enemy_stager_binding).
  • Entry 0907 (Nighto) heads with the ASCII title Hell's Music + a normal MIPS prologue. The title is the attack's display name (SCUS spell-table string Hell's Music|Kill or confuse enemy.; summon.dat lists it among the attack-name records, parallel to Gimard's Burning Attack). Not a dance-minigame dual use: the dance overlay (0980) has zero slot-B loader callsites in any form (jal / tail-call / pointer-word / lui+addiu); its only loader-reaching call is the SCUS FUN_80025BA0 wrapper (ids 5 / 6 → the 0900 / 0901 move-FX pair) and its music is sequenced BGM via the sound streaming loader.

Stager records

  • A stager spawns each part through the SCUS part-stager FUN_80021B04 (a1 = world pos, a2 = record pointer, a3 = 0x1000): actor[+0x48] = record move-buffer base, actor[+0x70] = 2 (PC), then jal FUN_80023070 ticks the move VM on record+4. High and enemy stagers spawn mostly through the pool wrapper FUN_80050ED4 (→ FUN_80021B04, pool DAT_801C90F0).
  • Record: [i16 model_sel][u16 reserved][move-VM bytecode @+4], variable length, passed by absolute pointer (lui 0x8020 / addiu). model_sel dispatch in FUN_80021B04: negative → transform / pivot node (dominant; the mesh is bound by the move-VM anim-bank ops); 0x4000 / 0x4001 → render-mode node +0x5A = 3 / 5; otherwise DAT_8007C018[model_sel + gp[0x754]]. The base gp[0x754] is one per-battle value, party_count + 2 (see Per-spell magic power).
  • PROT 0905: 22 FUN_80021B04 sites → 17 part records, all transform nodes, at file 0x180C..0x1E00 (runtime 0x801F81E4..). The addresses resolve only under link base 0x801F69D8; under 0x801F0000 they fall past the 0x5800 file. Parser legaia_overlay_images::summon_overlay::parse scans the spawn calls (direct + pool wrapper); disc-gated summon_overlay_real, summon_overlay_block, enemy_stager_real; CLI asset summon-overlay.
  • Trim before parsing. A stager's extraction .BIN over-reads past the next entry's start LBA; the real footprint is (next_start_lba - start_lba) * 0x800 (unique_content_len), a boundary the Cort mid-cast saves pin byte-exactly against the slot-B resident image. Trimmed, record first words across the whole corpus are only -1 / small library indices / 0x4000. Untrimmed reads show 0x1000 / 0x8000-class "sentinels" and extra mesh records that are neighbouring stagers' offsets dereferenced in the wrong file window.
  • Mesh-bearing records are rare. Gimard's stager (0903) is a pure transform rig (every record -1), so its draw list is legitimately empty; Nighto (0x85 → 0907) carries one mesh record. summon_scene_real drives Gimard for the tick path and Nighto for the draw path.
  • 0x4000 render-mode records live in five stagers, all player casts: Palma 0928 (4), Mule 0929, Jedo 0931, and the evolved casts 0916 (0x8e, 4) and 0921 (0x93, 6).
  • No catalogued state holds a live 0x4000 node: the Cort enemy states' pooled part-actors all carry -1 records (+0x56 = 4 / +0x5A = 2 after move-VM rebinding), and the three Sim-Seru player-cast states hold no live stager part at all (zero RAM references to any stager record while the stager is byte-resident at slot B). An enemy casting a Sim-Seru creature would seat one (crates/parity/tests/summon_render_mode_node.rs). The node's behaviour is decoded statically (next block).

Part render modes (FUN_80021DF4)

  • The per-part tick FUN_80021DF4 dispatches +0x5A into six modes: 2 / 6 parameter / colour tween; 3 (the 0x4000 node) moving particle (FUN_80019D50); 4 VRAM-blit beam (LoadImage / MoveImage / StoreImage = 0x8005842C / 0x80058490 / 0x800583C8); 5 (the 0x4001 node) a 3D positional sound emitter (range / volume + SE trigger, not a visual node); 7 matrix transform + billboard; otherwise transform pivot. Reference: move-vm.md § Part render-tail.
  • Port: the classification engine-effects::summon::RenderMode (from_model_sel, // PORT: FUN_80021B04), consumed by SummonScene::special_render_nodes / part_draws to split the audio-only node off the mesh draw path (tests render_mode_classifies_only_the_sentinel_nodes, special_render_nodes_are_split_from_the_mesh_draw_list); the move-VM call gate is move_vm::actor_tick. FUN_80021DF4's per-mode GP0 / SPU / VRAM emit paths are documented, not ported.
  • 239 field-resident prescript render-mode nodes (a resident-overworld mednafen read) are the non-summon validation source for the integration.

PROT 0900 - the slot-B screen-effect + top-view-grid overlay

  • Residency: file 0x0640..0x2660 is byte-resident at 0x801F7018..0x801F9038 in battle_gimard_tail_fire_a (pin: 0x801F8000 ↔ file 0x1628); function bodies are instruction-identical to the dance / Baka Fighter dumps.
  • FUN_801F811C is the screen-mask (iris) widget handler, not a part transform: its four tweened channels are the edges of a screen rect and its four quads are the black border bands (GP0 0x28 flat quads). Mid-tween the latched current values do not move - each frame re-interpolates from the fixed start, latching only at +0x9C == +0x9E.
  • It is kind 1 of a four-kind 2D widget family (scripted sprite FUN_801F7A9C, mask FUN_801F811C, image panel FUN_801F849C, letterbox FUN_801F8A34) controlled from field-VM sub-ops inside FUN_801DE840; the ten ending scenes drive it through field-VM op 0x43. Handler descriptors, control APIs, call sites and channel offsets: move-vm.md § screen-effect widget family.
  • Port: engine-effects::screen_fx (mask / sprite / panel / letterbox + the 4-mode FUN_801DE4C8 interpolator; disc-gated screen_fx_disc).
  • Apparent references to these handlers from stagers 0910..0915 are VA aliasing: in-file FUN_80021B04 part records at coincident addresses under the shared slot-B base. summon::apply_translation_update keeps the tween shape as an interpreted glide; the faithful port of the routine is screen_fx::MaskWidget.
  • The matrix code in PROT 0900 is the top-view grid-instance renderer FUN_801F7088 plus a parallel second-cluster sibling (RotMatrixX/Y/Z x6, GTE MVMVA). Per grid cell: TR = R_cam * cell_pos + TR_cam and R = R_base * Rx(rec+8) * Ry(rec+0xa) * Rz(rec+0xc), with R_base from the camera Euler _DAT_8007B790/2/4, per-axis skipped by record flags 0x80 / 0x100 / 0x200; model DAT_8007C018[rec+0x10 + base@0x8007B6F8] into cluster-A FUN_80043390. It is not the summon or move-FX path (0 calls during a player summon, below).

The player summon is a battle creature

  • PCSX-Redux capture of a player Gimard "Burning Attack" cast (Vahn solo; scenarios gimard_summon_start / gimard_summon_visible / gimard_burning_attack), exec-breakpoint counts across all three phases: FUN_801F7088 = 0; move VM FUN_80023070 = 2-3; part-stager FUN_80021B04 = 1; battle per-actor draw FUN_80048A08 = 35-64 per frame. Draw path: FUN_80048A08 → per-object rigid-TRS keyframe decoder FUN_8004998C → cluster-A FUN_80043390, each object's Euler composed by RotMatrixX/Y/Z (ported in crates/engine-vm/src/anim_vm.rs). Probes autorun_summon_rotation.lua + autorun_summon_path_reconcile.lua; RAM dumps under captures/summon_rotation/.
  • Creature identity, from the fingerprint-verified frame-0 RAM of gimard_summon_visible (8aa0...): battle actor table DAT_801C9370 has slot 0 = Vahn (HP 196) casting 0x81, slot 3 = a Gobu Gobu enemy (HP 76, 13 parts / ~10 actions; actor 0x8008350C, +0x5a = 3, 13-group mesh table at +0x44, archive 0x800B2694 at +0x4C, +0x88 self-ptr → +0x8C, 13x18 = battle_data id 4 action 0), and a distinct 11-part / 2-action entity whose idle (0x800BBB20, 11x40) byte-matches battle_data id 10 "Gimard" action 0.
  • Spell → creature map 0x81..=0x8b (by name; the "$2" / "$3" enemy variants excluded): Gimard 0x81 → 10, Theeder 0x82 → 25, Vera 0x83 → 28, Gizam 0x84 → 55, Nighto 0x85 → 49, Zenoir 0x86 → 64, Viguro 0x87 → 74, Swordie 0x88 → 86, Orb 0x89 → 83, Freed 0x8a → 92, Nova 0x8b → 95 (legaia_engine_effects::summon::summon_creature_id, disc-gated summon_creature_map_real).
  • Evolved block 0x8c..=0x95, pinned by mesh identity (each summon.dat group's actor-record TMD byte-matches an archive record by longest common prefix, 8-17 KB each, for all of 0x81..=0x95): Gola Gola 0x8c → 98, Mushura 0x8d → 101, Aluru 0x8e → 80, Barra 0x8f → 141, Kemaro 0x90 → 144, Spoon 0x91 → 147, Slippery 0x92 → 150, Iota 0x93 → 153, Puera 0x94 → 156, Gilium 0x95 → 159. Map legaia_actor_formats::summon_creatures::SUMMON_CREATURES, disc-gated summon_creature_tmd_map_real.
  • High block 0x99..=0xA0 (Juggernaut / Palma / Mule / Horn / Jedo / Meta / Terra / Ozma) matches no archive record: those summons carry a bespoke mesh in the summon.dat group's raw part-pool slot (the same oracle asserts the non-match).

Enemy "Fire Tail"

  • In the two mid-cast frames battle_gimard_tail_fire_a/_b (disc + library gated firetail_movefx_liveness) the slot-B occupant is PROT 0900 itself (loader-B id 5), not a per-spell stager, and its widget family is dormant: an effect-actor-list walk finds zero live widgets.
  • The live effect is one move-VM part-actor in pool DAT_801C90F0, ticked by the SCUS actor tick FUN_80021DF4 (→ FUN_80023070; bound at actor[+0xC]). Its record (actor[+0x48]) lives in battle-overlay (0898) resident data at 0x801F5xxx, below the slot-B base; model_sel reads -1 (transform node) / 5 (library mesh).

Assets and engine

  • The CLUT band is byte-identical across the two animation-distinct frames (motion is geometric, not palette cycling). Flame texture = PROT 870 (three 64x256 4bpp TIMs → battle VRAM (320/384/448, 0), CLUT rows 474..476); flame mesh = PROT 871 (etmd.dat, 30-TMD pack) at DAT_8007C018[26].
  • Flame-atlas loader FUN_80020050 (SCUS): uploads PROT entry 0x366 into VRAM twice via FUN_8001fc00 (→ FUN_8003e8a8), VRAM region set up by FUN_80017888 / FUN_8001e54c (param 0xf000); gated on _DAT_8007b868 == 0 (the gate FUN_801dbe9c reads); independent of the FUN_800520F0 battle-bundle path (which pulls 0x367..0x36d).
  • Engine assets: PROT 871 → World::global_tmd_pool[3..=32]; the flame atlas uploads on battle entry and the static flame renders with the row-478 CLUT (GIMARD_TAIL_FIRE_MODEL_INDEX = 26).
  • Summon render: a cast (spell_id in 0x81..=0x8b) calls World::request_summon_spawn from both cast paths (World::fold_battle_event on BattleEvent::SpellAnimTrigger, and cast_spell_on_slots); the host drains World::take_pending_summon_spawn and spawn_summon_creature seats the creature, drawn through monster_archive::battle_render_mesh + MonsterAnimPlayer + tmd_to_vram_mesh_posed_rot (mesh, texture and animation all from PROT 867).
  • Battle-actor animation pipeline: legaia_actor_formats::monster_archive::idle_animation (action 0, the +0x8c 9-byte TRS stream) → legaia_engine_battle::battle_anim::MonsterAnimPlayer (8.8 fixed-point loop cursor producing a legaia_anm::PoseFrame) → legaia_tmd::mesh::tmd_to_vram_mesh_posed_rot (R*v + T, Rz*Ry*Rx). enter_battle_render attaches the clip per actor and World::tick_battle_animations advances it; disc-gated battle_anim_real (monster 1 = 28 frames x 15 parts).
  • Stager scene-graph: engine_effects::summon::SummonScene seeds one move-VM ActorState per parsed part (PC = 2 → record+4) and ticks it through the move VM (World::spawn_summon / tick_summon / active_summon_part_draws; summon::summon_stager_prot_entry maps id → entry; play-window G debug-spawns the Gimard stager). Every Gimard part runs without an unimplemented opcode (disc-gated summon_scene_real). It is the stager-record driver and the model for move-VM part effects, not the creature draw.
The party cast trigger is a params stager

Status: resolved

FUN_801DBF9C(party, spell_id) runs at the end of state 0x29's wait for a party caster and only writes the caster's action-parameter stream; no store touches HP, MP or a target. The outcome is the streamed module's (overlay_battle_action_801dbf9c.txt).

  • Two arms on sltiu v0,a1,0x25. At or above 0x25: actor[+0x1E0] = 9, +0x1E1 = 0x12, +0x1E2 = 0xFF, return (0x801DC064..0x801DC09C). Below: index 0x801F4E64 + id - 1 selects an 8-byte anim-pair list at 0x801F4EDC, copied into +0x1E0.. up to the 0xFF terminator.
  • Every player Seru id is >= 0x25, so every player cast - healing included - takes the summon arm. State 0x29 reads the sub-route back (lbu v1,0x1e0(s3); li v0,0x9; bne at 0x801E45EC).
  • The staged 0x12 is a cast-effect id, not a clip: the argument the summon band hands FUN_801DC0A0 each frame while the caster's +0x1D9 stays 9 (capture gimard_summon_start).
  • Port: BattleHostImpl::spell_anim_trigger (engine-core, // PORT: FUN_801DBF9C) arms the stager on the summon arm; the < 0x25 pair lists are read off the disc into BattleState::spell_anim_pairs.
Player-summon presentation

Status: resolved

Read off the capture corpus and the summon band's disassembly:

  • No spell-name label for a party caster. State 0x28's label block is skipped for an acting id < 3 (sltiu v0,v0,0x3; bne v0,zero,0x801e4460 at 0x801E43D8); the mednafen *_summon_mid_cast display crops show the acting-actor plaque, the caster close-up or the flash's white and the additive burst, and no label. The party readout follows the hide: absent in every 0x33 / 0x34 crop and in gola_gola (0x35, all seats hidden), back under the caster in vera (0x35, stager phase 3, the caster's +0x21C cleared while the other seats stay 0xFF).
  • The hide. 0x34 zeroes the prim word and sets +0x21C = 0xFF on every party seat and every living monster (0x801E4B30..0x801E4B6C); the PCSX gimard_summon_visible / _burning_attack states read exactly that on slots 0..3 while the creature at slot 7 stays drawn; 0x36 restores.
  • The creature seat. Slot 7 at x=185, z=-2272 (caster 82, -542) with the caster's facing on the idle clip at stager phase 6, then z=-1606 on clip 1 at phase 11: it walks in from behind the party toward the target.
  • The two flashes. Flash-in at 0x33 (additive, delay 0x14, ramp 0x14 black → white, hold -1, id 1) plus cue 0x63; flash-out at 0x34 (additive, ramp 0x78 white → black, hold 1). The visible state carries the flash-out actor finished (+0x10 & 8, hold counted below 0).
  • The duck. _DAT_8007B910 reads 161 against level 215 in both mid-cast states - the 75/100 floor of 0x35, exactly.

Port: the summon band (engine-battle-vm::battle_action::summon), the stager and the hide / fade seams (engine-core::world::battle::cast_band), both hosts' draw gates and fade_prim composition.

The fade actor's hold word

Status: resolved. Evidence: disassembly, re-read against the listing.

A hold of -1 means hold the landed colour until the actor is killed (FUN_80020C14, 80020c14.txt).

  • a1 = actor + 0x7C, so the start delay is +0x98, the hold +0x9A and the duration +0x9C. While the delay is positive the tick returns -1 (0x80020C28..0x80020C54).
  • After the duration goes negative the tick sets actor[+0x62] |= 0x100 (0x80020C88), then lh v0,0x1e(a1); bltz v0,0x80020cd4 (0x80020C90..0x80020C98): a negative hold skips the countdown and the tick goes on ramping and drawing.
  • A non-negative hold counts down by the frame step and, once it is no longer positive (bgtz at 0x80020CB4), sets actor[+0x10] |= 8 (finished, 0x80020CC4) and returns -1 (draw nothing).
  • Consequences: the escape white-out persists until the battle unloads; the summon flash-in persists until 0x34 kills it.
  • Port: engine-system::fade::FadeState::step.
summon.dat / readef.DAT side-band streaming

Status: resolved (entries + format)

The two 0x10800-slot battle streaming files are extraction PROT 893 (summon.dat) and 894 (readef.DAT), both decoded. Parser legaia_actor_formats::summon_readef, disc-gated summon_readef_real.

  • Entries. FUN_800558FC ignores its path string in retail (_DAT_8007B8C2 != 0, verified live) and consumes its 4th argument as a raw-TOC index: summon.dat = 0x37F, readef.DAT = 0x380 (raw - 2 = extraction, the same offset as the overlay loaders' param + 0x381). The footprints divide into exactly 103 / 78 slots of 0x10800. In battle_gimard_tail_fire_a the stream buffer at *0x8007BD74 equals entry 894 slot 1, and slot 0's CLUT row / texture page match VRAM (0,488) / (512,0) byte-for-byte.
  • Slot selection. FUN_801E295C case 0x32 maps action id → base slot: 3*(id-1) for id < 0x9A, else 4*id + 0x63; bit 7 selects the file. The applier FUN_801F12D0 streams slots base..base+3 (readef groups stop after base+1 unless base == 0x36) and uploads CLUT rows + texture pages; FUN_801F19EC installs the final slot as the summon creature (via FUN_80055468). Summon group 0 (spell 0x81) carries the "Burning Attack" record.
  • Per-turn seed. FUN_801DABA4 seeds the group base per turn (party 3*(char-1); enemy 3 * monster_record[+0x1C]); the battle-end arms request 3*char+2 directly.
  • Aux slots. The eight aux slots of readef groups 0..3 (slots 3c+1 / 3c+2, c = Vahn / Noa / Gala / Terra) are the player art-animation "ME" stream archives, consumed by FUN_8002B28C out of the *0x8007BD74 buffer (parser legaia_actor_formats::me_archive); the main-vs-base pick is per battle phase (turn staging vs battle-end win-pose staging). Loader FUN_801F17F8 (raw TOC 0x380, slot * 0x10800) has a single staging jal at 0x801F17A0; the cast band originates no request of its own. Higher groups' aux slots are textures, the four ME archives, or a never-staged actor record (the aux-slot row in this area's table). The ME read is gated to party seats 0..2, and a sweep of DAT_8007BD10 over the state corpus finds char 4 at seat 1 in one battle, so group 3's archives are reachable.
  • Unenumerated: the full actor+0x1DF id ↔ enemy-special mapping (the map[actor+0x1df] 128-byte band). The Tail Fire capture is consistent with action id 1 → readef group 0.
  • CDNAME number space. #define numbers are raw-TOC indices, a uniform -2 to extraction: every byte-pinned loader constant for a dev-named file equals the same-named define (summon.dat / readef.DAT 0x37F / 0x380 = bat_back_dat 895/896), and semantic scoring over decidable blocks is 217/225 at -2 vs 209/225 at 0 (scripts/asset-investigation/cdname_shift_analysis.py). legaia_prot::cdname::block_for_extraction_index gives the retail-space name; the identities and exceptions are tabulated on the owning page.

Owning pages: summon-readef.md, battle-data-pack.md § "ME" stream archives, cdname.md § numbering space.

Monster steal item (Evil God Icon)

Status: resolved - static SCUS table DAT_80077828

What the player steals with the Evil God Icon equipped comes from a static SCUS_942.54 table, not from the PROT 867 monster record.

  • Table DAT_80077828 (file offset 0x68028), indexed by 1-based monster id: entry at DAT_80077828 + id*2, a 2-byte [steal_chance_pct, steal_item_id] pair. Chance first, item second - the reverse of the monster record's [item, chance] drop fields.
  • Capture: a live player-steal RAM read of Skeleton (id 13) gives 1e 8a = 30% Incense, matching the on-screen banner; the table is byte-exact against the complete published steal table (item and chance) across every resolvable monster id.
  • Not in the record, disc-measured: for the 185 monster ids both populated in PROT 867 and stealable in the SCUS table, no offset carries the pair in either field order - not in the 13,030,964 bytes of LZS-decoded monster blocks (every offset, full block length), nor in the 15,155,200 raw bytes of the 0x14000 slots. Best agreement in any layer is 2/185 for [chance,item] and 2/185 for [item,chance].
  • Single-byte offset 0x48 scores 31/185, but 0x48 is drop_item, and steal and drop draw from the same 39-item consumable pool; none of the 31 also agree on chance at 0x49. The best non-drop offset is 7/185.
  • Monster ids 187..190 are stealable in the SCUS table but have no archive slot (PROT 867 is 194 slots of 0x14000, 186 populated).
  • Parser legaia_game_tables::steal_table; randomizer legaia_patcher::steal. enemies.toml steal is ground-truth labelling; the SCUS table is authoritative.

Owning page: steal-table.md.

Per-spell magic power / multiplier

Status: resolved - mechanism decoded, kernels ported and wired

A summon's "power" is caster / summon battle-state-derived, not a static per-spell scalar (SCUS spell-table +5..+8 are zero). Damage runs the three-stage chain FUN_801dd0ac roll → FUN_801dd864 scale → FUN_801ddb30 finish; the per-move power table at 0x801F4F5C belongs to monster special attacks, not to magic or party arts.

Where the magnitude is applied

  • Each module's tick body applies it, not its stager: the actor+0x14c write lives in the ctx+0x279 phase machine the 0x801CF4EC table arms, a different routine in the same image. PROT 0903, 0910 and 0913 are damage modules; per-module classification (PROT 0909 included) is on cast-module.md.
  • Not the 7-entry jump table FUN_801f2d68 reads (jr *(0x801F69D8 + state*4)): it resolves to PROT 0900 file offset 0, five staggered entry points into one per-frame routine that lerps move-VM anim banks (FUN_8003ce9c / ce64 / ceb8) and emits GPU packets into scratchpad 0x1F800314 - zero mult / div, no actor+0x14c write, no power read.
  • Damage bodies call FUN_801dd0ac (a0 = per-summon move-type const 0x10..0x12, a1 = a baked 7 on the player half, a2 = target slot), clamp in one of three shapes, accumulate the popup at actor+0x10, then subtract.
  • Two bodies heal, each a closed form over the caster's per-magic level byte (the 32-slot search matching actor[+0x1DF] against the id list at +0x705, reading the parallel byte at +0x729): PROT 0905 (Vera) restores record[+0x729 + slot] * 0x20 + 0xe0, clamped to the +0x14C / +0x14E pair, skipped at HP == 0 or +0x16E & 4, pushed to +0x10 as a negated popup at 0x801F7D0C; PROT 0911 (Orb) restores (level << 6) + 0x1c0 over the party row. Both unlock a status cleanse at level >= 3. Neither is (power_byte << 5) + 0xe0.

The roll (FUN_801dd0ac, overlay_battle_action_801dd0ac.txt)

  • Summon path (attacker slot 7, the summon creature's seat in DAT_801C9370): roll = rand % (INT@+0x168 + 1) + HP@+0x14c + DAT_801C9370[ctx+0x13]_INT * 2, the HP and INT being the creature's and the second INT the caster's; returns roll - defender_mitigation. Re-read against the listing: bne a1,7 at 0x801DD104, lhu 0x168(s1) + div + mfhi at 0x801DD114..0x801DD148, lhu 0x14C(s1) unshifted at 0x801DD16C, the caster's lhu 0x168 doubled at 0x801DD170..0x801DD17C, and a store of 2 to 0x80084448 on the way (0x801DD178). The non-summon path is different in kind: two draws, rand % ((power >> 2) + 1) + rand % ((INT >> 1) + 1) + (HP >> 8) + power + INT * 2 (0x801DD18C..0x801DD25C).
  • Non-summon path reads the 26-byte-stride move-power table at 0x801F4F5C (static battle-overlay data, PROT 0898; parser legaia_game_tables::move_power) through the 128-byte id → index map at 0x801F4E63 (param_1 = map[actor[+0x1df]]).
  • The whole 26-byte record is decoded on move-power.md: +0 power, +0x0a impact-effect selector, +0x0b trail texture page, +0x0d sound cue, +0x12 / +0x16 effect-id lists, and +0x0c an unused C / E / G designer tag with no runtime reader.
  • The move-id space is the spell-table id space, and the map covers 44 ids: idx 0x10..=0x2b = the named monster special attacks (0x25..=0x74), idx 0x01..=0x0f = unnamed internal enemy-attack tiers (0x04..=0x07 / 0x12..=0x1F). The basic-attack / art bands 0x08..=0x11 and 0x16..=0x18 are unmapped (live capture: Vahn's Somersault 0x0F is unmapped and would roll against zero-power record 0). Party arts take damage from the per-strike art-record power byte (art_strike.rs); apply_basic_attack uses the flat art_strike_damage_default for a no-art generic hit.

Scale and finish

  • FUN_801dd864: 8x8 element-affinity matrix 0x801F53E8 (PROT 0898 file 0x26BD0, parser legaia_game_tables::element_affinity) + status bits + the summon magic-power tail roll += roll*(power-1)>>3. Orientation is matrix[attacker][defender]; values are a +/-4% nudge (diagonal 96, opposite pairs 104, default 100), not a x0 / x2 weakness table.
  • Per-character element table 0x801F5480: Vahn = fire, Noa = wind, Gala = thunder, Terra = wind. The enemy element is read record-direct: lbu ...,0x1d(record) with record = 0x801C9348[slot-3] (the per-enemy record-pointer table, not a copied live-actor field), i.e. MonsterRecord::element (+0x1D) - the same record the victory-spoils path reads +0x44/+0x46/+0x48 from.
  • Per-caster summon power-percent table 0x801F5468 (index (char_id-1)*8 + summon_element; PROT 0898 file 0x26C50, ElementAffinity::summon_power): own element 100, opposed 40, Gala dark 60.
  • FUN_801ddb30: equipment elemental-resistance halving, guard halve, rand%9+8 no-damage floor, summon power-% scale, 9999 cap, spirit gauge, MP drain, per-element stat debuffs.

Port

  • Kernels in legaia_engine_battle_vm::battle_formulas: summon_attacker_roll / summon_defender_roll / summon_predamage / the apply_* helpers / heal_summon_amount / damage_finish / spirit_gauge_fill. The finisher's state-mutating tail (damage-popup accumulator, per-seat readout table, MP drain, per-element stat-debuff switch) lives in the live battle context.
  • Monster special attacks: the table loads onto World::tables.move_power (engine-battle::move_power::MovePowerCatalog) and cast_spell_on_slots overrides a damaging monster cast's magnitude with arts_physical_predamage_lazy seeded by the move's +0 power (World::enemy_move_predamage: INT from battle.accuracy, defense terms from battle.defense_split). The attacker x2 + defender x1 rand() draws are taken up front and the bonus pair lazily, only when the bonus arm fires, so the RNG cursor advances by exactly three or five draws in FUN_801dd0ac's call order.
  • Player summon roll: World::player_summon_predamage - summon-body HP / INT from the namesake battle_data creature record, caster INT from battle.accuracy, the caster magic-power byte from the character record's spell list (+0x13D ids / +0x161 levels, the FUN_801dd864 search), then the FUN_801ddb30 finisher with the 0x801F5468 percent. Modelling limits: the slot-7 actor's HP at roll time is the creature record's spawn HP (a damaged mid-battle summon is not modelled), and status / guard default to none.
  • Affinity: the monster path scales by matrix[enemy_element][party_member_element] (World::enemy_affinity_pct, applied inside the roll before the conditional bonus-arm threshold, as retail orders scale → bonus); the player Seru-magic path scales by matrix[summon-creature element][target element] (World::cast_affinity_pct; attacker element from World::summon_attacker_element, defender from World::battle_slot_element), post-roll with the RNG untouched.
  • All of these are gated on the tables being installed, so a disc-free battle reproduces the no-table baseline bit-identically (magnitude + RNG stream).

Move-FX spawn records (the +0x12 / +0x16 lists)

  • Auxiliary tables: EffectAuxTables for the 0x801F6324 prototype-pointer and 0x801F6418 SFX tables; parse_impact_effect_table for the +0x0a selector's 0x801F53D4 entries, which are packed u32 config words, not pointers.
  • Each 0x801F6324 entry is an overlay VA to a variable-length move-VM scene-graph record in the summon-part format (+0x00 i16 model_sel, +0x02 u16 reserved, +0x04 bytecode), packed, not a fixed 0x20 stride; spawned by FUN_80050ed4 → FUN_80021B04. List bytes with the high bit (0x80) route to the 2D efect.dat pool instead (FUN_801dfdf0 → EffectCatalog, ported as spawn_by_ui_id).
  • Model-library base gp[0x754] (global 0x8007BA6C): 0 with no battle effect-model library resident, else party_count + 2 - 3 for the 1-member training party, 5 for the 3-member party (two fixed pool slots + the live party meshes precede the library). One per-battle value drives both move-FX and summon-part spawns; model_sel is library-relative. Evidence: PCSX-Redux exec-bp on FUN_80021B04 (probe autorun_summon_model_base; chain FUN_801e09f8 → FUN_80050ed4 → FUN_80021B04, ra = 0x80050F08, a3 = 0x1000, prototype table 0x801F6324 + effect-list id 0x22 in registers) plus 0x8007BA6C against the party count 0x80084594 across the mednafen corpus; pinned by crates/mednafen/tests/summon_model_base.rs.
  • Engine: World::spawn_move_fx parses the records (MoveFx via MovePowerCatalog::fx_for_move_id), stages them as a SummonScene at the library base (PROT 0871 registered at a fixed DAT_8007C018[3..]) and ticks them through the move VM (tick_move_fx / active_move_fx_part_draws; play-window H debug-spawn). The part draw transform is the engine's anim-bank-derived interpretation; the retail one is the FUN_80021DF4 render tail.
  • Presentation fields: trail texpage (+0x0b → 0x7700 + id) on World::active_move_fx_trail_texpage(); sound cue (+0x0d) via World::take_pending_move_fx_cue(), resolved by legaia_engine_audio::classify_cue → CueDispatch (the FUN_8004fcc8 dispatch decode) in engine-session::battle_fx::spawn_pending_move_fx, which both play hosts enqueue. The voice arm's third argument is a read span, not a pitch: FUN_8003D53C range-checks it against 0x2A31 at 0x8003D5C8 and touches no pitch register.
  • SFX bank: the cue's program / tone (static DAT_8006F198 table, sfx-table.md) index the per-scene music VAB the BGM sequencer already has open (FUN_80065034 reads the libsnd current-bank globals; byte-identical to the disc music_01 VAB for that scene) - SfxBank::play_one_shot(spu, scene_vab), no separate bank.
  • Afterimage: the streak pass FUN_801e1ab0 is legaia_render_kernels::afterimage::build_afterimage_quad (jittered semi-transparent POLY_FT4: per-corner rand wobble, brightness band, UV / CLUT / texpage layout). Its corner projector FUN_800195a8 (camera-coupled GTE billboard: view-space MVMVA centre, +/-half-size corner fan-out, rotation + translation reset, RTPT x3 + RTPS; detail) is legaia_render_kernels::billboard::project_billboard, with the FUN_801e1ab0 call shape (+0x120 Y push, dynamic half-width state+0x6c6 - 0x200, half-height 0x100) as afterimage::project_streak_corners. The RotMatrix* sin / cos LUT is trunc(4096*sin) (disc-gated gte_sin_lut_real).

Owning pages: battle-formulas.md, move-power.md, cast-module.md.

Stat growth-rate source

Status: resolved + validated + wired (core + opt-in jitter)

The per-character stat-grant source is static SCUS_942.54 tables read by the level-up applier FUN_801E9504.

  • Parameter block DAT_80076918: per character (stride 0x3C), 8 contiguous 6-byte sub-records {u16 start, u16 max, u8 jitter, u8 row}. start = base stat (Gala matches the new-game template on all 8); row selects one of 3 curves at DAT_800769CC.
  • Per-level gain = max(1, (max-start) * curve[row][level-1] / D + rand() % (2*jitter+1) - jitter), then caps. D = 0x24C0, the curve normalizer (each curve sums to 0x24C0, so growth accumulates to exactly max-start by L99), for every stat but AGL, whose block divides by 0x18C0 (its own 0xA57EB503 magic at 0x801E993C); AGL meets its 0x118 cap well before L99.
  • Capture: a single-level step (Noa L2 → L3, the noa_levelup_* saves) has all 8 deltas within the core +/- jitter band. Multi-level corpus observations (noa/gala_4_level_jump) are unreliable for this check. Not the "Seru struct +0x74".
  • Parser legaia_game_tables::level_up_tables::GrowthTables::{char_params,level_gain_core} (disc-gated).
  • Engine: StatGain carries HP / MP + the six battle stats; LevelUpTracker::with_growth_tables + BootSession install per-character curves from the user's SCUS, and apply_to_record grows the record-side window then mirrors to live (disc-gated boot test pins Noa's L2 → L3 core). LevelUpTracker::with_level_up_jitter(seed) drives a PSX BIOS-rand LCG (BiosRand), one rand() per stat per level on the unfloored core before the max(1, ...) floor; it is off by default so determinism oracles stay bit-identical (bit-exactness needs the runtime BIOS-rand seed).
  • The slots-1/2 XP-threshold correction is documented on the owning page.

Owning page: level-up.md.

Monster stat-record archive source

Status: resolved

The monster archive is PROT entry 0867_battle_data (extended footprint; the 15.9 MB archive lives in the entry's trailing-gap sectors). Retail-semantically it is the monster_data block: the define monster_data 869 names extraction entry 867 under the raw-TOC -2 correction (cdname.md).

  • FUN_800542C8 streams per-monster 0x14000 LZS slots at (id-1)*0x14000, each [u32 dec_size][LZS], decoding to a block whose head is the FUN_80054CB0 stat record.
  • Record: name @0x00; battle-model TMD offset @0x04; HP @0x0C; MP @0x10; stat u16s @0x0E/0x12/0x14/0x16/0x18/0x1A (ATK / UDF / LDF / INT / SPD / AGL); rewards (XP / drop) inline at @0x44..0x49; magic count @0x4A; spell-ptr array @0x4C.
  • Capture: live-battle PCSX-Redux watchpoint (autorun_monster_record_source.lua) - relative seek (id-1)*40 sectors + disc_read CdlLOC → PROT.DAT 0x38AF000 = entry 867; three records match live actor stats byte-for-byte.
  • Parser legaia_actor_formats::monster_archive; bridge legaia_engine_battle::monster_catalog::catalog_from_monster_archive, wired into enter_field_scene.
Monster mesh + texture pool

Status: resolved

The monster's 3D battle model is a Legaia TMD embedded in each PROT 867 archive block at the offset in stat record +0x04; its texture / CLUT pool is at record +0x08.

  • The TMD is installed at battle-actor +0x230; the 0x1C-stride records FUN_80049858 / FUN_800495C8 walk are its object table. 186 of 194 slots parse cleanly.
  • Pool layout (from the battle loader FUN_80055468): a 0x1E0-byte region of fifteen 16-colour CLUTs, then a 4bpp page (always 256 rows tall, 128 or 256 texels wide; palette = cba & 0x3F). Byte-exact against pool sizes.
  • The on-disc CBA / TSB are nominal defaults the loader relocates per slot, so the raw pool does not appear verbatim in a battle VRAM dump; the loader layout is the ground truth.
  • Parser legaia_actor_formats::monster_archive::{mesh, MonsterMesh::texture}; CLI --obj + --texture-png; WASM monster_mesh_* + monster_texture_* accessors drive the enemy-table site page's per-row WebGL viewer.
Terra slot-3 / story-flag overlap

Status: resolved

The character-record base is game+0x3C8 (live RAM 0x80084708), with the display name at record offset +0x2A7; 0x66F is the name field, not the header size. Terra's record needs no special case.

  • Six in-game RAM captures: mid-game stats at record+0x104 / +0x11C read back the expected per-character HP / MP for all four slots.
  • The four-slot array runs into the global region: slot 3's tail (record offset >= +0x2BC = game+0x12C0) aliases the story-flag bitmap and inventory. Terra's meaningful fields (name, live stats, RecordStats) sit before that boundary. Terra is the New Game template's fourth roster entry (HP 400) but never a savable battle-party member, so the aliasing is benign.
  • Code: RETAIL_CHAR_RECORD_HEADER_SIZE = 0x3C8; legaia_save::CharacterRecord::name() / set_name() at NAME_OFFSET (+0x2A7); Party::from_retail_sc_block reads a populated save's stats from the right fields (checked by synthesising an SC block from a live RAM dump and comparing the parsed HP).
Battle party meshes = assembled from the player battle files (PROT 1204 = Baka Fighter / default-equipment sibling)

Status: resolved (static chain + byte-verified)

A main-game battle renders each party member from a merged TMD assembled at battle setup out of that character's player battle file (data\battle\PLAYER<n>, extraction 0863..0866), one section per equipment slot selected by the equipped item ids (char record +0x196..+0x19A). Textures and the palette come from the same file. PROT 1204 is the Baka Fighter / default-equipment sibling, not the runtime source, and battle does not reuse the field pack 0874 section 0.

Assembly chain

  • FUN_80052770 case 4 (section select: an equipment-id-matched entry or the id == 0 separator = unequipped default) → FUN_80052FA0 (assembler, blob at ctx+0x50) → FUN_800536BC x5 (object splice; nobj += section_nobj, bone-id byte per object, surplus objects tagged = equipment visual meshes) → FUN_80053898 (retag 200 / 201 / 100+, attach bones at blob+nobj, sort) → FUN_800513F0 registers blob+0x18 into DAT_8007C018[slot].
  • nobj 15 → 17 is the weapon + Ra-Seru sections' extra objects (not FUN_8001EBEC, which only toggles a pose transform).
  • Byte-verified in the full-party Gobu Gobu save: DAT_8007C018[0] = ctx+0x50+0x18 exactly, nobj = 17, bone bytes [0..14,200,201], attach [5,8], and all 17 vertex pools found in PLAYER1's sections with equipment-selective matches. Only 12 of Vahn's 17 pools match 1204 (the default-equipment geometry, byte-shared); the 5 equipped-variant objects (Hunter Clothes body x2, Survival Knife piece + extra, the Ra-Seru piece) appear only in the player-file sections.
  • Baka Fighter loads PROT 1204 (overlay_baka_fighter loads data\field\other5.lzs + PROT 1205 / 1206, debug "OTHER5 %d %d"): the same characters with default equipment. Field-pack distinctness: battle_char_pack_real::battle_pack_is_distinct_from_field_pack.

Install into DAT_8007C018[0..=2]

  • Static SCUS, through the generic registrar FUN_80026B4C (store 0x80026BA8), from two battle state handlers reached by indirect dispatch (so a static cross-reference on 0x8007C018 finds no writer): FUN_800513F0 (lead / active actors - tmd_register(*(actor+0x50)+0x18, 0) in a while<3 loop over the active-actor table 0x801C9360, right after the FUN_80052FA0 palette decode) and FUN_800542C8 (additional members - loop bounded by *(rec+0x4a), tmd_register(*(*rec+4), 0)).
  • The battle loader FUN_800520F0 registers PROT 0x36a (etmd.dat) into the effect window DAT_8007C018[3..], not the party slots.
  • Capture: write-watchpoint on DAT_8007C018[0..2] across the auto-starting Queen Bee field → battle transition (autorun_battle_party_mesh_install.lua): all three installs fire at game_mode 0x15, and the pointers byte-match the battle form (Vahn → 0x80165F48). Dumps funcs/800513f0.txt / 800542c8.txt; trace in funcs/8002541c.txt / 800198e0.txt / 800520f0.txt.

Player-file load

  • "data\battle\PLAYER1" is a dev-tree label, not an ISO9660 file: the retail open FUN_800608f0 is a trap stub, so FUN_800558fc always takes its debug branch → FUN_8003e8a8(char+0x360) reads toc[idx+2] (in-RAM PROT TOC 0x801C70F0) as a sector offset into PROT.DAT.
  • Raw 0x361..0x364 (Vahn / Noa / Gala / Terra) = PROT.DAT 0x36E8000 / 0x3791000 / 0x3828800 / 0x3897800 → extraction 0863..0866 (raw index - 2). Extraction 0861 / 0862 are 1-sector stubs whose over-read tail begins Vahn's file 0x1000 in, so a match against the "0861 window" is the same bytes.

Textures and relocation

  • Texels upload from the player files' per-section texture pools at the static rect table 0x800775B8 (FUN_80052FA0 → FUN_80053B9C LoadImage front-end): >= 99.6% band reproduction against clean full-party battles. The 1204 atlases match only 73-98%; the shortfall is the equipped-variant texels. See battle-data-pack.md § Texture-pool VRAM placement.
  • At battle entry every prim's TSB + CBA is rewritten into a packed per-slot runtime band; the CBA column is preserved and a character's two disc rows collapse to one runtime row (one 256-colour palette per character):
CharDisc pages, rowsRuntime pages, row
Vahn(640,0) / (704,0), rows 490 / 491(512,256) / (576,256), row 481
Noa(640,256) / (704,256), rows 492 / 493(640,256) / (704,256), row 482
Gala(512,0) / (576,0), rows 494 / 495(768,256) / (832,256), row 483
  • Disc sub-CLUT use: Vahn 0,1,4,5 / 0,1,7,8; Noa 0,1,2,5,6,7 / 0,3,4,8; the two auxiliary 1204 meshes use row 496 page (448,256) and row 497 page (512,256). The disc TSB / CBA are the authoring layout Baka Fighter uses directly; walked as-is in a normal battle the mesh renders incoherently. Evidence: the runtime TMD dumped from a clean battle save (flags=1, absolute pointers; convert p → p - base - 12) renders the correct characters from the save's VRAM.
  • The texpage → CLUT-row table 0x8007BEC0 (32 x u16, written by FUN_800198E0 as table[texpage & 0x1f] = clut_row) is the scene renderer's; rows 490..497 hold scene environment palette shared by a scene's field and battle modes, not party palette.

Palette

  • A battle-allocated resident block per character, DMA'd to rows 481 / 482 / 483: contiguous at 0x800ebee8 / 0x800ec0c8 / 0x800ec2a8 (Vahn / Noa / Gala) in a clean full-party save, stride 0x1E0 = 15 x 16-colour sub-CLUTs, one per disc mesh object. It sits at arena_base + 0x4048; the work arena is zeroed at load by the sw $zero loop at SCUS 0x80055F14 (base = *(0x8007BD3C), 0x1e8d words).
  • Character-intrinsic and produced at battle load: absent in name-entry / front-of-Tetsu / load-initiating saves, present as a single copy once the battle is up, byte-identical between the Tetsu and Drake fights.
  • Assembler FUN_80053B9C (per-colour store sh a0, 0x894(v0) at 0x80053C6C; write-watchpoint autorun_battle_palette_writer.lua, clean Tetsu fight): copies a source CLUT struct [u16 base][u16 count][BGR555] to dst = arena + slot*0x1E0 + (base+idx)*2, OR-ing 0xFFFF8000 (STP / bit 15) onto every non-zero colour. Source pointer s0 = *(*(0x801C92F0)+8) + per-char-off, a transient 0x800Dxxxx buffer filled by the LZS decoder FUN_8001A55C (write-watchpoint on the struct header 0x800D6C98).
  • So the disc form is LZS-compressed and bit-15-clear (0x1D40...) while the runtime form is bit-15-set (0x9D40...): the palette exists nowhere verbatim, compressed or not (lzs-decode find over every PROT entry, SCUS and init_data; 6372 strict TIMs; LZS output windows to 24 KB).
  • Source records: the player file is self-describing relative to record[0] (header offsets, [id, running_a, size] descriptor entries, id == 0 = section separator), with 5 sub-records scattered on disc at sec_base = rec0 + align_up(recbase - rec0, 0x2000) (a 0x1000 alignment fits Vahn and Noa but lands Gala's subs on a zero-padded 0x7000 block). The 0x2000 stride seen in RAM is only the loader's staging buffer. Layout on the owning page.
  • Decode: FUN_8001A55C's first argument is an output-byte budget (decremented per literal and per match-copied byte; loop while budget > 0). One psx_lzs::decompress(stream, budget) per record, with record[0] + the 5 staged sub-records decoded into one work buffer as FUN_80052FA0 does, reproduces Vahn's palette byte-exact in all 3 bands: base=0x00 = record[0]'s CLUT B, base=0x40 = sub#0's trailing CLUT, base=0x70 = sub#4's trailing CLUT. A budget-less decode runs into the next record (29/32 with 3 diffs).
  • Noa = PROT 0864, Gala = PROT 0865, by matching each record0 CLUT against full-party battle VRAM: Noa → row 482 98%, Gala → row 483 100% (the 1-2% misses are equipment patches in late-game captures). Gala's bands at 0x00 / 0x30 / 0x50 / 0x80 cover all mesh columns. Party order from the names at 0x80084708 + n*0x414 + 0x2A7. Probe autorun_clut_decode_capture.lua captured the 5 sub-record streams.
  • Distinctness: only 10 of Vahn's 130 battle-novel colours (0 of Noa's / Gala's) are in any field-pack CLUT; 146 of Vahn's 256 runtime colours are in no CLUT the 1204 pack ships (bundled row-492 CLUT vs retail row 492 = 0/256).
  • Parser legaia_actor_formats::battle_char_palette (find_record0 + parse_record + collect_palette: record0 CLUT A / B + each separator's id=0 trailing CLUT + the final record, filtered to the columns the mesh samples; STP bit set on upload); disc-gated battle_char_palette_real (byte-exact against extraction 0863 with record0 at file offset 0) and noa_gala_collected_palettes_cover_mesh_columns.

Port

  • Assembly legaia_actor_formats::battle_char_assembly; palette legaia_actor_formats::battle_char_palette; PROT 1204 legaia_actor_formats::battle_char_pack. The web viewer's battle_char_mesh_cba_tsb returns the nominal disc CBA, which is the right pairing for the 1204 authoring-layout (Baka form) render.

GPU upload path and battle-init reads

  • Scene TIM upload: FUN_800520F0 (battle loader) → FUN_800198E0 (per-TIM uploader) → FUN_800583C8 (PsyQ LoadImage) → FUN_8005A1C0 (GPU-queue enqueue, op-type 8 = FUN_80059BD4 via handler table 0x80078D0C) → ring 0x801C9590 → FUN_8005A4A0 (once-per-frame flush) → FUN_80059BD4 (GP0 0xA0 / DMA2; a0 = RECT{x,y,w,h}, a1 = src_ptr). See functions.md.
  • Battle-init disc reads are party-independent (PCSX-Redux, Vahn-only vs full-party: byte-identical raw-TOC index set; raw → extraction is -2): monster 0x365 → 867; conditional stream + etim + etmd 0x367/8/9 → 869 / 870 / 871; efect 0x36B → 873; readef 0x380 → 894; overlay 0x384 → 898; 0x37A → 888; music raw 1016; field-scene re-read 0x5A → 88. The player files stream earlier and no character-CLUT read fires at battle entry.
  • Scene-side facts from the same captures: a map01 battle uploads CLUT rows 488 / 490 / 495..499 and character image pages 512 / 576 / 640 / 704 / 768 / 832 / 864 / 960 at y=0 through FUN_80059BD4; the row-490 source is the resident field-scene buffer (0x800E9690). map01 / map02 sec0 carry row 490 as a flag-0x80000008 256x1 TIM, whose reserved high bit makes parse_strict reject it. Per-scene row-49x 16x1 CLUTs (35 scenes incl. town01) are field-actor palettes.
  • Not the party palette source: scene-bundle sec0 CLUTs (0400_doman rows 488-492, 0061_dolk, PROT 1200 other4 rows 490-494 are field-form / other-pack palettes), the 1204 bundled CLUTs (1204 / 1205 / 1206 are uncompressed copies of the same authoring defaults), or a town0c scene-bundle LZS stream at 0x23430 (that write is the scene bundle decompressing into the shared work arena: 0x800ebee8 held 0x7965481F, not the Vahn palette 0x409d...).
  • Capture tooling: autorun_clut_upload_hook.lua and autorun_clut_upload_watch_live.lua (per-upload (rect, src) capture), autorun_clut_uploader_pc.lua (read-watchpoint that pinned FUN_80059BD4), autorun_find_clut_decode.lua (LZS-output scanner), autorun_battle_char_clut_source.lua (disc-read logger) + map_clut_disc_reads.py.

Owning pages: battle-data-pack.md, character-mesh.md § Battle form.

MP-cost ability-bit priority (half vs quarter)

Status: resolved. Evidence: disassembly, re-read against the listing.

Half wins over quarter, and both subtract a right-shifted copy rather than floor-divide (overlay_battle_action_801e295c.txt). The bits are the caster record's +0xF4 word (lw v1,0x6bc(v0) off 0x80084140, 0x801E4560 / 0x801E3D04); the shift sits in each branch's delay slot (srl v0,s0,0x1 at 0x801E4570, srl v0,s0,0x2 at 0x801E457C), so the no-bit path's beq to 0x801E4584 jumps the one subu s0,s0,v0 at 0x801E4580.

  • The block is inlined twice, byte-identical: 0x801E4568 in state 0x28 (right after that state's capture-archive jal 0x8003EC70 at 0x801E44EC) and 0x801E3D0C in state 0x3C (right after that state's Pomander +0x1DF == 0xFE case at 0x801E3C4C).
  • Priority: andi 0x20; bne <half> then andi 0x10; beq <none> = if (bits & 0x20) {half} else if (bits & 0x10) {quarter}.
  • Formula: half = cost - (cost>>1) (rounds up on odd costs); "MP-quarter" = cost - (cost>>2) = pay 3/4, not cost/4.
  • Port: battle_formulas::mp_cost_after_ability_bits, shared by all three cast paths (the two SM blocks + cast_spell_on_slots); MpCostModifier::from_ability_flags. MP cost consumes no RNG.
Scripted Tetsu encounter → Battle (v0.1 oracle Battle leg)

Status: mostly

The opening Tetsu fight is launched by a dedicated MAN-placed field entity (the sparring partner), dialogue-driven, and the engine reaches Battle from a new-game cold boot by walking to the partner, talking and accepting. One step is staged by the test rather than played: the cold boot skips the opening sequence's carrier reposition.

  • Launch (FUN_801DA51C + corpus RAM): on reaching SM state 1 the carrier copies its entity[+0x94] formation into cell 0x8007BD0C and, via the case 2/3 fall-through, writes _DAT_8007B83C = 8 (the battle handoff). It is not scene-entry-driven and not an inline arm op: an opcode-aware walk of town01's partition-1 scripts finds zero [1][0x4F] arm sites.
  • Formation install: the standard field-VM scripted-battle op 3E FF 04 in P1[10] at record offset +0x7F7 (MAN body 0x01B67) points actor[+0x94] at town01 MAN formation index 4 = the lone monster archive id 0x4F (Tetsu), EncounterRecord::rim_elm_training(). Same case-0x3E direct-install arm as garmel's Zeto (3E FF 09) and rikuroa's Caruban (3E FF 11). It sits in the post-"Come at me!" branch (WaitFrames 16 + flag sets ahead of it; the adjacent Test 0x227 / JmpRel targets land on op boundaries). Test rim_elm_sparring_carrier.rs::town01_p1_10_carries_the_tetsu_3e_ff_04_install.
  • Carrier placement: town01 P1 placement at tile (76, 65), model 0x6A - its post-tutorial village spot, in a sub-area not walk-reachable from the spawn (BFS: 2855 reachable sub-cells, carrier not among them).
  • Opening reposition: P1[10] (start 0x01370) carries, twice, at record offsets +0x1D / +0x28 (MAN body 0x0138D / 0x01398), the op 4C 51 15 0E 07 22 = MenuCtrl nibble-5 NpcRun { x_enc: 21, z_enc: 14, depth: 7, move_id: 0x22 } (field_disasm MenuCtrlKind::Nibble5NpcRun). Tile (21,14) → world (21*128+64, 14*128+64) = (2752, 1856) = RIM_ELM_SPARRING_CARRIER_TUTORIAL_POS; P1[10] is the unique record NpcRun-ing there. The two identical ops are the story-flag two-branch scene-entry prologue. The dialogue-accept capture's actor[+0x90] resolves to the (76,65) / 0x6A record. Not op 0x23 MOVE_TO: its only hits are false decodes in the desyncing dialog region.
  • Frames: the runtime actor frame equals the MAN placement frame - FUN_8003A1E4 spawns at tile*128 + 0x40 via FUN_80024C88 with no anchor, and the player cold-spawn 0xA40 is tile 20*128 + 0x40.
  • Talk and Yes / No: talking is a button press, not a field-VM opcode (op 0x3E with op0 < 100 is the scripted-battle install). The Yes / No is an MES-embedded option picker inside the NPC's inline 0x1F dialog segment: a 0x29 menu-open followed by an N*2-byte signed relative-jump table (handler FUN_80038050, the FUN_80039B7C dialog-SM family), with new_pc = (open + 1 + index*2) + i16_LE(entry[index]). There is no read-and-compare opcode, which is why these records desync under linear disassembly. Port legaia_mes::Picker::jump_target + InlineDialogue::last_choice (crates/engine-field/src/inline_dialogue.rs).
  • Engine:
    • Carriers derive from the scene MAN (man_field_scripts::derive_field_carriers + World::install_field_carriers_from_man) and tick through tick_field_carriers.
    • World::tick_field_interaction_probe (FUN_801cf9f4) runs the DAT_801f2254 facing probe: a radius-64 compass point ahead of the player's facing, box-tested at +/-72 against the talkable NPCs' placement positions (World::npcs.positions). On the action button it talks to the match and turns the player toward it.
    • Talking arms the engage (World::carriers.slots → World::carriers.pending_engage); accepting the prompt (the 0x4C n5 sub-4 dialog dismiss) engages it.
    • World::nav_step_toward walks a BFS route over the real collision grid.
  • Tests: training_battle.rs (formation index, carrier SM, dialogue-accept, training_reaches_battle_via_interaction_probe); v0_1_playthrough.rs::v0_1_battle_leg_reaches_battle_from_new_game (BootSession::begin_new_game seeds Vahn at 180 HP - the new-game state is retail's pre-fight state) and v0_1_battle_leg_walk_talk_accept (walk → talk → accept, no teleport; it places the carrier at RIM_ELM_SPARRING_CARRIER_TUTORIAL_POS first, a ~6-tile reachable hop from the spawn).
The battle-intro enemy-name banner

Status: resolved - the question had a false premise.

No placement record raises it: the flow-0x0A composer FUN_801D9D3C (sole reference in the corpus: the jal at 0x801D0DFC) lays the labels out itself and hands each to the text-actor spawner FUN_8003541C with immediate geometry.

  • One label per distinct monster group: id = group index 0..=3, class 0, kind 3, pen (laid-out x, 48), box measured width x 12.
  • The only placement-table field the intro touches is record 67's +0x14 string-pointer cell (the back-attack / pre-emptive line), drawn under id 4 at pen (16, 12), 288 wide, as literals. Record 67 proper is opened only afterwards, by the post-intro sub-draw, under its +0x01 id 0x2B. The disc w = 0 width overwrite belongs to record 68 (spawned at the measured name width), not to the intro.
  • Capture (scripts/pcsx-redux/autorun_battle_intro_banner.lua, breakpointing the spawner and the teardown sweep FUN_800355F0, walking the text-actor list at gp[+0x148]): an ambush raises Queen Bee at (176, 48) 55 wide, Killer Bee * 3 at (78, 48) 79 wide and Ambushed! at (16, 12) 288 wide, all from $ra inside the composer, held for 120 frames; an ordinary encounter raises two labels for 90 frames and no line. Nothing glides - the composer calls neither FUN_801D8DE8 nor the glide FUN_801DB7B0.
  • One monster suppresses the banner: monster-slot-0 id 0xB5 (evolved Cort) skips the composer and parks ctx[+0x06] = 0x0C, which the flow ladder at 0x801D0C84 has no arm for; the boss stage module moves it on (see The evolved-Cort flow park).

Owning page: battle.md (full law, naming rule and seat arithmetic).

What a port owes the slot-B module band

Status: resolved as a per-address verdict. Evidence: disassembly

Of the 65 catalogued addresses in PROT 0903..0966:

CountWhat
58the module's 0x801F6734 move-VM spawn-stager entry
6cast tick bodies reached from the module's own 0x801CF56C trampoline
1a framed routine nothing references

By content:

  • 45 are pure spawn choreography - an arm switch whose arms only call FUN_80021B04 / FUN_80050ED4 / FUN_801DFDF0 / FUN_80024E80 with a module-resident record and a scale literal
    • expressible as the spawn-record data layer.
  • 13 carry game logic: two stagers apply damage (PROT 0927 through FUN_801DD0AC(0x12, 7, seat), PROT 0966 through FUN_801DD4B0(0x100, ..), each with the HP +0x14C clamp); four more write actor or ctx state.
  • 7 are empty (jr ra; nop).

Owning page: cast-module.md (per-address table).

readef groups 19..21 and monster record +0x1C

Status: resolved-negative - nothing names them. Evidence: disassembly + disc census

Monster record +0x1C is the readef animation-group index, and no shipped record selects groups 19..21.

  • Reader: the initiative scheduler FUN_801DABA4 reads it record-direct (lbu v1,0x1c(v0) at 0x801DB098 / 0x801DB0C8) and seeds the streaming applier's base slot with 3 * group.
  • Census over the 186 populated records: the byte stays in 0..=25 and never takes 1, 2, 5, 12, 19, 20, 21 or 23; group 0 is the default.
  • Second reader: the AI spell picker FUN_801E9FD4 (0x801EBB90) compares the first enemy seat's byte against 0x17; no shipped record satisfies it.
  • Groups 19..21's duplicated actor records are therefore unreachable through the enemy path (slots 58 / 59 / 71 and 64 / 65 / 68 are byte-identical).
  • Parser field readef_group in legaia_actor_formats::monster_archive.

Owning page: summon-readef.md.

The dome panel-still arming

Status: resolved - a generic battle-end teardown, not a dome condition. Evidence: disassembly

  • ctx[+0xC] = 1 is written at 0x800474CC in the per-frame anim-node tick FUN_80047430, for an enemy node (node[+0x5A] >= 3) under gp[+0xA48] & 0x80 and gp[+0x9F4] != 0xB5, together with node[+0x10] |= 8.
  • ctx[+0xC] is a three-value teardown machine: 1 frees the actor table and writes 2; 2 ticks FUN_80025358, which stages PROT 0978 into the freed space - from three sites (0x8004E65C escape, 0x8004F82C victory tail, 0x80056428 in FUN_80056208).
  • ctx[+0x7] == 0x67 is only the successful-escape hold written by case 0x66 of FUN_801E295C.
  • Open: what draws the still.

Owning page: minigame-muscle-dome.md.

The Miracle marker is an equipment byte

Status: resolved - there is no input recognizer. Evidence: disassembly (+ capture for the card corroboration)

  • ctx[+0x25F + slot] has exactly one store in the dump corpus: sb v1,0x25f(v0) at 0x80054270 inside the SCUS party battle-actor seeding routine FUN_80053CB8.
  • The value is the acting character's Ra-Seru equipment byte: record-relative +0x199, or +0x198 for roster character id 2 (beq v0,a3 at 0x800541E4; the weapon-index table _DAT_8007B42C = 2, 3, 2 names the other member of the pair).
  • The marker gates all four special art records at 0x801EF4C8, not only the Miracle.
  • Memory cards: the byte is small and per-character banded, zero for a member not yet bonded.
Two battle-context bytes read wrong

Status: resolved. Evidence: disassembly

  • ctx[+0x26] is the level-up banner's UI element id (0x65). Its reader 0x801E61B4 passes the byte as FUN_801D8DE8's element-id argument in a run of sibling unloads with literal ids; writers 0x801E723C (the only assignment) and 0x801E6D3C (an increment); cleared at 0x801E2CFC. It drives the Done band's 0x50 seed override (0x96 for 0x3C) and the 0x51 banner skip (59 unskippable frames, then a press).
  • ctx[+0xD] is the per-action battle-camera angle variant 0..=3: a four-way switch at 0x801D6510 / 0x801D6698 / 0x801D689C inside FUN_801D5854, seeded rand() % 4 at ActionSeed and narrowed per category from jump table 0x801CF144.
Stone and Curse in the SCUS effect applier

Status: resolved. Evidence: disassembly

  • FUN_800402F4's first-level jump table at 0x80014FA0 (132 entries, guard sltiu 0x84) sends class 9 to the Stone arm and class 10 to Curse.
  • Stone makes three stores after the accuracy roll: +0x16E |= 4; a refund of the target's reserved item through FUN_800421D4, gated on +0x1DE == 1 && +0x16C != 0 (+0x16C is the initiative key, not a cooldown); then +0x1DE = 0. Curse sets its bit only.
  • No item-effect record (0x800752C0, 130 records) carries class 9 or 10, so both arms are reachable only from the streamed capture-class modules.
Attack x2, the swing class and the apply-mode arms

Status: resolved. Evidence: disassembly + measurement

  • ctx[+0x16] has two writers, both in the strike loop and both keyed on the War God Icon (record +0xF4 & 0x2000):
    • the end-of-stream refill at 0x801E3A20..0x801E3A64 runs only while the counter is zero (bnez at 0x801E3A18), lifts it to 1, rewinds the strike cursor and rewrites every marked queue slot to 0x19;
    • the stage site's tail bumps it at 0x801E37AC..0x801E37BC, only when it is already non-zero (beqz at 0x801E37B4), so the second pass's first stage lifts it to 2 - the value that ends the damage kernel's carry arm (battle-action.md).
  • Monster record +0x1E is the limb-vs-height swing class, read through 0x801C9348 by FUN_801EED1C (class 2 gets one low swing) and by FUN_801EC3E4 twice: its head miss gate (0x801EC488..0x801EC554: a party hit of the wrong class does nothing but raise ctx[+0x263]) and its apply-mode look-ahead (class 2 connects only with power bytes 0x01..=0x10, class 3 only with 0x11..=0x15).
  • Across the archive's 186 records the class reads 0 x127, 1 x1, 2 x52, 3 x6, so both arms are ordinary play. Both kernel copies gate everything on a monster target.
How a cast reaches its slot-B module

Status: resolved. Evidence: disassembly

Both PROT 0898 tick dispatchers key the same 64-entry band.

DispatcherKeyTable → PROT
FUN_801F1ED4queued action id0x801CF4EC row id - 0x81 = PROT 903 + row
FUN_801F2160spell record's +0x01 byte0x801CF56C row sub = PROT 935 + sub
  • FUN_801F2160 has a single call site, 0x801E50C8, in battle phase 0x70's hold.
  • Data half: the spawn records the modules pass FUN_80050ED4 / FUN_80021B04 are recovered with the shared summon_overlay reader into an engine pool (legaia_overlay_images::cast_effect_pool), staged at both retail seams.
  • Code half: module tick bodies are ported in engine-vm (cast_module_ticks, cast_arm_ticks, seru_magic_ticks, cast_module_camera); which modules' bodies and camera arms remain is listed per module on cast-module.md.
What an art body costs in AP

Status: resolved - computed, not disc data. Evidence: disassembly

FUN_801EED1C derives it:

  • multiplier 11 / 10 / 6 by the builder's visit ordinal (0, 1..3, >= 4) - not the arts-grid display index;
  • halved before the multiply under the actor's 0x800 flag (srl at 0x801EF378);
  • times the art's command count;
  • accrued into actor[+0x224] and spent from Spirit actor[+0x170] at 0x801E5D74.

Port: engine-battle::ap_gauge::art_spirit_cost.

Tutorial prompt machine exclusivity

Status: resolved - the machine is byte-exclusive to 0967; the prompt pool is shared with 0968. Evidence: disassembly

The sparring-tutorial prompts are resident in stage overlay 967, so the battle SM alone never emits them.

  • Machine: FUN_801F6B70 is entry 967 file +0x198 (0x801F69D8 + 0x198 reproduces the VA), a 91-entry jump-table hook on ctx[+0x06] with nine live slots, each switching on ctx[+0x28A].
  • Exclusivity: five needles from 48 to 2316 bytes - one a 116-byte window free of lui / j / jal, which a relinked copy could not evade - return one physical copy across every PROT entry, SCUS and DMY.DAT (967 is stored raw).
  • Pool: entry 0968 carries a byte-identical 852-byte prefix of the prompt pool at the same offset 0xCAC, inside a 0x5D8-byte run the two entries share; 0968 has its own 7-entry dispatcher and no copy of the machine.
  • Port: engine-battle::battle_tutorial.

details ↓

The _DAT_8007BA78 census is closed

Status: resolved. Evidence: disassembly

A sweep in every reference form over SCUS, all 1233 PROT entries and the overlay images finds every reference to _DAT_8007BA78:

  • two stores: 0x801E30F4 = PROT 0897 +0x148DC (the 4C E2 op) and 0x801DDCE8 = PROT 0899 +0xF4D0 (the title tick);
  • four loads, all in PROT 0970;
  • one literal-word reference at PROT 0971 +0x1238 = 0x801CFA50, the debug menu's editable-globals pointer table - the static witness for dev-menu editing in the corpus states;
  • zero gp-relative 0x760(gp) references (the form an absolute-only scan cannot see).

Limits: a raw scan cannot see code inside an LZS section. A "PROT 0896 carries the same span shifted by 0x9000" reading is an entry-size over-read artifact.

Owning page: cutscene.md.

The 0xF8 halt-acquire handshake

Status: resolved - retail machine traced; the port's model diverges. Evidence: disassembly

0xF8 resolves to the player object _DAT_8007C364 (FUN_8003C83C: li v0,0xf8 / lw v0,-0x3c9c(v0); inlined again at 0x800377A0 and 0x80037E04).

  • ExecMove arms nothing: 0x801DE998 writes only +0x5C / +0x5E / +0x56 and calls FUN_800204F8.
  • The halt-acquire creates the wait object: 0x801DF384 sets +0x94 and ORs 0x400 into +0x10 (plus the caller's when the target is the player, at 0x801DF404); 0x801DF5AC jal 0x801d25ec spawns the glide actor and the release helper 0x801D5D60, which polls andi v0,v0,8 at 0x801D5DB4 and clears the halt at 0x801D5DD4 / 0x801D5DFC.
  • The record parks at the next cross-context op, in the prologue busy gate 0x801DE90C..0x801DE944, whose unadvanced PC the run loop reads as "stop" (0x8003CFF0). The acquire itself advances 9 bytes (11 for sub-A/B), 0 on failure.
  • No backward resume PC: the operand +3 / +5 halfwords are FUN_801D25EC tween arguments.

Owning page: cutscene.md.

Growing a scene TMD pack member

Status: resolved - rebuild is the only cost. Evidence: disassembly (the walk) + capture (the disc-wide offset census)

Every external reference to a pack member is an index, so a member can grow if the pack is rebuilt.

  • The mesh pool is the descriptor walk's registration order (FUN_8001F05C → FUN_80026B4C(buf + offsets[i] * 4): lw a0,0x4(s1) / sll a0,a0,0x2 / jal 0x80026B4C with addu a0,s2,a0 in its delay slot, 0x8001F2AC..0x8001F2BC, looping to the pack's count word at 0x8001F2D4); placements name a pool slot; scene ANM records name a record number; the bundle's descriptors hold offsets into the bundle entry, not the separately streamed pack.
  • Disc-wide, each of PROT 0639's members 106 / 107 / 108 word offsets occurs exactly once (in the pack's own table), the byte-offset form zero times, and the declared length 347476 zero times outside its chunk header.

Owning page: man-relocation.md.

What of a signature cast is data

Status: resolved (partially). Evidence: disassembly

A signature cast's spawn records are data; its lift is code.

  • At all 15 / 41 / 24 jal FUN_80050ED4 sites in PROT 0958 / 0959 / 0960 the record pointer a2 is a module-resident constant and a3 a scale literal; the records are the summon part-record shape the art path's 0x801F6324 prototypes use.
  • Moving them into the art path is blocked by residency and capacity: the prototype table's 61 entries are all populated, the module blocks sit above the slot-B base while the prototypes sit below it, and the battle overlay has 247 bytes of zero slack.
  • The lift is sb into +0x1DA (16 / 6 / 8 sites) from the modules' phase arms, which no record can express.

Owning page: cast-module.md.

Op 0x23 MOVE_TO keys on ctx identity

Status: resolved. Evidence: disassembly

The player arm is chosen by ctx pointer identity, not by the player-class bit.

  • 0x801DEC7C bne s5,v0 compares the executing ctx against _DAT_8007C364 (0x8007C348 + 0x1C); only that identity reaches the camera re-centre func_0x80017EC8 (0x801DEC84..0x801DECA8).
  • Every other ctx takes 0x801DECAC's facing + movement-init arm on its own actor.
  • A spawned partition-2 record inherits the 0x1000000 player-class bit, so keying on the bit teleports the player to wherever the record seats its own actor (keikoku's arrival record, once op 0x45 does not restart it).
Dome ringside panel stills PROT 1221 and 1222

Status: resolved. Evidence: disassembly

Two 320x256 BGR555 stills in the dome's other6 bundle; the pair is picked by a computed index, so no literal names them.

  • Streamed in four 0xA000 strips by FUN_801F6B24 (PROT 0978) into VRAM (384, 0) (rect 0x801F735C); 320*256*2 is the entry size exactly, the first five sectors a 16-line top pad.
  • Selection: addiu a0,s0,0x4c7 at 0x801F6C3C with s0 = (party slot 0 current HP < max / 2), so int2.tim is the below-half-HP variant. A literal sweep cannot see a computed index.
  • Corroboration: the overlay's dev path strings h:\prot\field\other6\tim\int.tim / int2.tim, selected by the same s0.

Owning page: minigame-muscle-dome.md.

The display object's sound-bank category

Status: resolved. Evidence: disassembly

actor[+0x22C] points at the actor's spawned display object, whose +0x80 is the sound-bank category.

  • The pointer comes from FUN_80024C88 → FUN_80020DE0 and is installed at 0x800515E8 / 0x8005196C in FUN_800513F0.
  • +0x80 is zeroed at spawn (0x80020F50) and set at battle setup to 7 (party arm, 0x80051548) or 7 / 8 (the four enemy seats, loops at 0x80052238.. and 0x800522D0..), each loop paired with jal 0x8003E104 carrying the same literal in a1 - the bank-load slot.
  • The router copies it into the descriptor's category column (0x8004FFD8..0x8004FFE4, 0x80050070..0x8005007C).
  • The monster element byte is record +0x1D, a different field.
The evolved-Cort flow park

Status: resolved. Evidence: capture + disassembly

The boss stage module, not the flow ladder, moves flow 0x0C on.

  • The 0x0A arm writes 0x0B unconditionally and overwrites it with 0x0C for formation 0xB5 (0x801D0DE0..0x801D0E14); the ladder idles on 0x0C.
  • PROT 0968 (loader-B tracker 0x49) runs a 7-phase intro cinematic off ctx[+0x289] (jump table at 0x801F69D8) and writes 0x0B back at 0x801F713C (ra = 0x800564A0) when a dt countdown on its local word 0x801F73F8 expires - 3207 vsyncs later in the capture, with 0x0B → 0x14 → 0x1E following at once.
  • A ten-button sweep produces zero writes: no input shortens it.
  • Probe autorun_w4d_cort_flow_writer.lua.

Owning page: battle.md.

A spell its caster has not learned rolls 9999

disassembly + capture. An injected Zenoir or Gola Gola reads 9998 / 9999 in retail where the engine once read a few hundred. The summon roll looks the spell up on the caster's list (FUN_801DD864), a spell off the list reads list slot 0x20, and that slot's level is 0: the power's srl by level - 1 wraps, and the roll caps. It is an artefact of the injection, and no cast in play gets there.

Owning page: battle-formulas.md.

A scatter's density and Mushura's late rampage hit

Grade: capture and disassembly. An arm's per-pass spawn call seated once per one-vsync module tick, and a pass runs a frame step of those ticks (World::module_pass), so a cast held a step times the records: PROT 0915's 0x801F8108 trail stood at sixteen live parts against retail's five in one RAM read, and PROT 0957's sparks at twice retail's at step 2. A call now runs on the pass's first tick (CastFxState::module_pass_repeat). Mushura's first rampage hit lands on the frame retail's does since arm 9's pass stores the creature's heading on its target (jal 0x80019B28, sh v0,0x46(s4) at 0x801F7578..0x801F7594): the port had left arm 8's spin in +0x46, and arm 0xC's chase set off along it, sound cue score .500 to 1.000.

The case 0xA yaw clock

Grade: disassembly and capture. The word 0x801DC8B8 reads is the anim node's +0x68 cursor, signed, in sixteenths of a keyframe, and each state in the magic band that runs the case holds an endpoint of ring[1] * 2 + 0x700 - facing, slot 1 of the cursor ring being the pass's value ahead of its anim tick. The engine handed the case, and FUN_801D71B8's arms, the cursor rounded down to a whole keyframe, which aimed Rugi's Poisonous Breath 17 units short and Devil Pump's Multi Cutter 24. A moving endpoint also takes the displayed frame's walk back: the capture channel's clip_clock_end_shift gives the walk its 24 a pass, and Poisonous Breath's 3350 under 3368 walks to the 3320 an image scan of the frame finds independently.

Owning page: battle-stage-camera.md.

Noa's stand in Scythe Wind

Grade: capture. Noa stood 60 px off retail's in battle_jou_nova_v3_scythe_wind_mid_cast because the frame was on the replay's camera. PROT 0951's arms 3..=5 re-aim every pass, so the child's alignment agreed on no component and dropped the pose it was given, which is also why a yaw scan of that frame moved nothing. Standing on the walked-back pose reads .691 to .942, and the two other Scythe Wind states rise with it.

Owning page: retail-compare.md.

Why a monster's strike gate read a short age

Evidence: disassembly for the laws, capture for the states. Three separate faults read as one camera of about .87.

  • The replay rolled a shorter stream. The strike loop stages a byte and tests the terminator at the new cursor on the same pass (0x801E3998..0x801E39AC), so a one-byte queue leaves for 0x1F on its first pass. battle_cave01_mushroom_strike holds 06 06 00, cursor 1, the swing staged over the arrival's idle (+0x1DC bit 1). The replay drew 05 00, left 0x1E at age 0, and age_short re-ran it on the state's first tick, ahead of the first case-7 arm. Such a capture now takes the stream fallback.
  • A walk-clip capture's accumulator does not date the arrival. It counts from the walk's last loop re-commit. battle_nilboa_warfish_strike reads 288 at gap 1490, increment 150, one pass under case 7. The increments fall on a ladder from 0x700 (ceil(gap / 0xC), then gap -= inc * step), so the increment names the pass (strike_passes_off_table).
  • The monster's pull-in never opened. FUN_801E93C8 keeps ctx[+0x243] while the seat's clip has +0x87 set (0x801E9458..0x801E94A4); the live host had no such read, the gate stood open and the strike loop's tail cleared the latch the commit had stored. Case 7 then tested a stand-in for it, and its glide chased the unpulled depth.

Owning pages: retail-compare.md, battle-stage-camera.md.

How long 0x6E and 0x6F hold, and what the hold moves

Grade capture for the lengths, disassembly for what runs through them. The two states poll CD reads (jal 0x8003DE7C at 0x801E4F08, FUN_8003F2B8(1) in 0x6F), and over the harvest-state casts they hold 14..=17 and 4..=6 vsyncs whichever module or fight. The polls are not idle, so a load the engine answers at once still costs retail's look: 0x6E walks the framing each pass (0x801E4F00) and 0x6F pulls ctx[+0x6D0] in. Held in the engine, the walk matches retail to the unit through 0x6E, and the shots built on ctx[+0x6D0] land where retail's do.

What a hidden seat draws

Grade disassembly. The draw tick FUN_800480D8 reaches the after-image walk only past its word-zero test (0x800481CC), so a hide takes the ghosts too. A body hidden with its ghosts left walking reads as a pink double of the caster on V-Windhash's far shot and, as the camera passes it, an olive wash over the frame.

Owning pages: cast-sequence-oracle.md, battle-actor-rendering.md.

What stands the camera on the Windcutter frames

Grade capture, with the walk disassembly. Both Windcutter frames show the case-6 walk ahead of a one-frame shot, and neither is a mid-retake of something the seed cannot reproduce. The body filling battle_doman_boogie_pump_windcutter_mid_cast was the walk's start, not a depth or a ghost: seats 1 and 2 had both spent a Spirit, and the selector FUN_801DABA4 leaves no order behind, so the law's default took seat 2.

The walk's focus closes on the caster from that seat's framing, and the residue left after ten step-3 passes stands the eye 94 units nearer Gala on the shot's axis. She then clears the near gate 0xA1 (0x8001AEE8) and fills the frame. From seat 1 she falls under the gate and the frame matches, with the walk's TR z untouched; the earlier continuous search that wanted TR z 4569 was compensating for this. FUN_8004DC68 keeping her solid is correct and plays no part.

battle_rugi_barra_windcutter_mid_cast is the same walk from the round's first action, so it starts on the prompt's far framing. That framing's orbit is a clock the RAM does not date, and the yaw is read off the frame. Which of two Spirit seats went last is also not read from the RAM: the actor records of the seats differ in their idle clip's phase, and no rule over that has been tested. Each capture carries a walk pin, which moves no game state.

Owning pages: retail-compare.md, battle-action.md.

Why Death Game's replayed deal differed from retail's

Grade capture, with the deal disassembly. The draws were never the difference; the stream's position was. A breakpoint on rand() (0x80056798) over the first vsync of battle_jou_puera_v3_death_game_mid_cast logs eight calls: the battle frame driver's discarded draw ($ra 0x80046D34), the deal's six (0x801F7BEC), and one after (0x8004E2C4). Six draws from the state's own seed word 0x80009010, one past the driver's, deal retail's [1,1,1,2]; from the word itself they deal [2,1,1,1].

The engine runs the driver's draw ahead of its module tick too, so a replay with its stream set to the state's word at action state 0x70, module phase 0 (LEGAIA_CAST_RAND_SEED=112:0:<word>) deals [1,1,1,2] and puts the four cards' labels on retail's pixels. A replay seeded only at the encounter entry deals off another position.

Owning pages: cast-module.md, cast-sequence-oracle.md.

A battle target surface's replay

Grade disassembly for the states, capture for the frames. The command-flow dispatch tree tests eight sub-cursor values the selection band does not hold (0x801D0D30..0x801D0DA4): 0x5B..=0x5E and 0x64..=0x67, one per window and target shape, each with its own handler, confirm and cancel. BattleFlowState now decodes all eight, the World stands the flow byte on the one the open window's session is in, and the magic window's all-target spells wait for their confirm as 0x5E / 0x67 do instead of casting on the list's press. The menu drive walks the window's cursor onto the row the seat's +0x1DF names before it confirms. battle_magic_target_gaza2_retail (0x5C) and battle_item_allparty_gaza2_retail (0x66) are catalogued and reached on phase 1.000. On the second, the All Allies plate stands on record 0x3E's seat, which FUN_801D57E8 copies onto the target plaque (image .961 to .996); on the first, the breadcrumb's third tab reads Magic, spell 0's name, for a Seru spell (.990 to .991).

Owning page: battle-command-flow.md.

A walked-back pose about twenty units of yaw off

Grade capture. battle_map01_gomboo_steal_mid_cast (action state 0x6F) holds yaw 3757 on increment 21 toward 3940, and its frame peaks two passes back at 3610, where a walk back on a fixed endpoint lands at 3631. A PCSX-Redux film from the state (autorun_cast_sequence.lua, LEGAIA_PHYSICAL=1, recompiler) shows the next pass re-arm case 6 before it steps, on an endpoint four units on (3944, increment ceil(187 / 12) = 16, the yaw 3805), and the endpoint stays the yaw counter ctx[+0x6DA] plus 1864 on every pass, the counter climbing 4 a pass at step 3. So the pass before the RAM's chased 3936 and stood at 3694, and the pass before that at 3610 (increment ceil(326 / 12) = 28). The capture channel's walk back already lands there (image 1.000 in the baseline), so the gap was the older fixed-endpoint walk.

The focus follows the same law with an endpoint the bodies move. A film of battle_ropeway_party_gala_flying_knee_mid from the strike loop to the fade-down holds focus = previous + step * ceil((endpoint - previous) / 12) on 42 of 48 pass edges through 0x1E, 0x1F and the return 0x20 (the rest land within a unit of the endpoint, or split a pass across two rows). Walked back two passes on each pass's own endpoint, the focus lands within 8 units of the frame's; on the RAM's endpoint alone, up to 36 units off during the return.

Owning page: retail-compare.md.

Which frame the effect-script walk reads as a loop ends

Status: resolved - the advanced cursor, on the walk ahead of the commit. Evidence: disassembly + capture

FUN_80047430 hands (+0x68 >> 4) & 0xFF to FUN_801DEA50 at 0x800478B8, ahead of both the +0x1DC cut (0x800478F4) and the natural-end test (0x80047A48). A PCSX-Redux run of cort_evolved_card_first_menu, breaking on the walker's floor-part seat (0x801DEF08), shows the evolved Cort's first approach stomp its walk entry's gates 1, 4, 7, 10 and then gate 13 on frame 12 of the 12-keyframe clip (cursor 200), on the pass the arrival's staged idle cuts the walk. battle_jou_cort_181_strike holds the same walk: three rings 12 vsyncs apart, the newest on the ground Cort arrived on, spaced as the body pair stands at keyframes 6, 9 and 12.

On that pass the cut commits the staged idle, so the walk is not re-committed and the new cycle's gate 1 does not fire beside gate 13. A walk that runs past its clip's end without arriving re-commits the loop and walks frame 0 on the same pass (0x80047B54, then the post-commit walk at 0x80047C08); no recording holds that pass.

Owning page: battle-action.md.

Source: docs/reference/re-settled-threads/battle.md

Field / locomotion

# Settled threads: Field / locomotion

ThreadAnswerGrade
What starts the hunters' return in Rim Elm, and why does stepping out after the measuring not?Tetsu's talk after the spar
Why

town01 P2[6] runs only once 0x227 (the measuring, P2[5]) and 0x20B (the spar) are up; its one spawner is Tetsu's talk (P1[10] 44 5F), and only with 0x20C, which the spar's return raises when 0x227 was already set. v0_1_pre_battle_tetsu stands outside the house with 0x227 set and 0x22E clear. Page: cutscene.md.

disassembly + capture
Does a Tetsu spar fought before the measuring stop the hunters' return in retail?yes, for good
Why

The spar's return raises 0x20C only with 0x227 up, 0x20C has no other writer on the disc, and the return's one spawner waits on it. From s5_tetsu_battle (a human route, 0x227 clear) the fight's end leaves 0x20C clear. Page: field-sequence-breaks.md.

disassembly + capture
Does Mei's door close behind her in the Genesis Tree beat?yes, on her clip cursor
Why

P2[4]'s CC 01 A0 00 05 00 skips the JmpRel over the close while the door's +0x10 bit 0 is up, and CC 01 8C 18 00 closes only once the door's +0x68 (its clip cursor, FUN_800204F8) is off zero. Page: script-vm-menuctrl.md.

disassembly
What draws on deene's left rock face?a stretched ground cell
Why

The ground cell's own tile: record +0x1C / +0x1D widen the tile's V / U span (FUN_801F6D48 at 0x801F6EE4..0x801F6F0C), 96 rows here, and the state's packets hold those UVs. build_walk_heightfield carries them.

disassembly + capture
Which rule drops rikuroa_field_card_walk's near wall quad, whose near cut read as w units?the capturing emulator's polygon limit
Why

PCSX-Redux rejects a whole quad on any corner pair past the limit (SoftRenderer::checkCoord4, called from polyExec); the quad's halves span 513 and 443 rows. With that rule on the capture child, the near cut takes the GTE's true SZ = 6 * w and the frame holds its score. The console skips one triangle.

capture + emulator source
What draws the light wedge in rikuroa_field_card_walk's bottom-left, which the port left black?a floor quad only the capturing emulator draws
Why

The state's ordering table holds a POLY_GT4 with corners (109, 286), (-29, 887), (31, 194), (-285, 487): both halves span 693 rows, no corner has a negative y, and PCSX-Redux measures a pair only from a negative corner, so it draws the quad where the console skips both halves. No near-reject rule is involved. The port's emulator rule read the corners Y up, saw 887 as negative and dropped the quad; it now reads them as the packet does (renderer.md).

capture + emulator source
What sets a field primitive's place in the frame, and so the sky over concend's ridge and Octam's trees?its ordering-table link
Why

Every primitive links at its corners' mean SZ, at its farthest corner under an object record's flag 0x800, and ten buckets nearer on an actor's +0x10 & 0x80800000; a state's table holds each. Rules and port: renderer-port.

disassembly + capture
Does a later keikoku variant MAN replace P2[7]'s pushback?no - the inner doorway is a story wall on every visit
Why

The Ravine ships one MAN carrier, its bundle MAN (no streaming variant). P2[7] has no header gate and dispatches on its own flags: 0x142 (Mt. Rikuroa post-Caruban) splits it into before / after halves, each counting visits on a one-shot trio (0x2BB..=0x2BD, 0x2BE..=0x2C0, set nowhere else on the disc). Every arm's dialogue names another goal and ends in the player's compass walk back, and the map01 portals close on 0x193, so no enterable flag state passes the band. Disc-derived: the test keikoku_inner_doorway_record_has_no_variant_and_no_foreign_writer decodes every carrier and runs green against the disc.

capture (disc census)
Does retail run a field script context once per vsync or once per game frame?once per game frame
Why

Every FUN_801DE840 entry in a PCSX-Redux count falls on a vsync the actor-list walk FUN_8002519C ran on, one walk every DAT_1F800393 vsyncs, in seven field scenes at steps 2, 3 and 4; the walk is what reaches each context (FUN_8003BC08 -> FUN_80039B7C). The port stepped its script runners every vsync; it now steps them on game frames (script-vm.md).

capture + disassembly
Does retail step helper contexts while a timeline sits on an open text box?yes; only a context on its own text waits
Why

FUN_80039B7C gates on the context's own +0x9C, never on the box: 0 runs the slice, 1 (reached text) claims the box only while the state word 0x801F2734 reads 1 / 4 / 7 (0x80039F9C..0x80039FD8), 2 waits for 0 / 3 / 6 / 9 and resumes. The one whole-list stop is scratch 0x1F800394 & 0x400 in FUN_8003BC08 (0x8003BCF8). The port runs off-text helpers every frame; a second texter waits without typing and the first claimant owns the box (a_helper_off_text_keeps_its_slice_while_the_box_is_taken). korb3's apparent 0-frame helper is the counter: a WaitFrames park is kept off frames.

disassembly
Do retail's ending vignettes accept the pad while their entry record runs?no
Why

All 5 end-credits field-run states hold the player's engaged bit (+0x10 & 0x80000) with a running count of 24 to 476; the scene-init gap state has both clear. FUN_801D1344 skips locomotion on the bit (0x801D1694..0x801D16A0); FUN_80039B7C raises it on every stepped slice and clears it when the count drains. edlast's P2[1] then waits on a held Circle / Cross poll at +0x2608, which the port keeps as a wait (cutscene.md).

capture + disassembly
Which scene installs the code lock FUN_801EED58?doman only
Why

Field-VM 49 02 maps to handler slot 0x21; the census finds one shipped site, doman (extraction 0401, record 18, pc 0x0768), whose next op tests flag 9 - the flag the lock's verdict writes. Phase 0 installs window 12. Port field_submode_code_lock, drawn on both hosts; disc test code_lock_doman_disc. Settled: slot table 0x801F33A4[2] = 0x21, handler table 0x801F33B4[0x21]; phase 0 runs the program at 0x801F32F4 (0x801EED98..A0).

disassembly + capture
What is FUN_801E6F70?the coin counter's entry panel, field overlay
Why

The painter word of panel-window record 10 (PROT 0897, geometry (0x40, 0x26, 196, 78)): coin bank, six entry cells, gold and a total drawn in pen 9 when gold or the ceiling _DAT_8007BB90 falls short. The confirm descriptor 0x801F3360 is [1,11], so the entry panel stays drawn under the Yes/No. Port slot_machine::coin_entry_panel, both hosts through one builder.

disassembly
How is the fishing venue's menu reached?from the idle shore, on Triangle / Select
Why

State 0x0C (0x801CF990) tests the packed edge _DAT_8007B874 twice: & 0xC0 casts, & 0x110 (0x801CF9EC) raises SFX 0x21, zeroes the hub cursor 0x801D912C and jumps to 0x64 (FUN_801D0474(1)). 0x65 / 0x66 are the help pages (FUN_801D72A0(0x14, 0x10, page), turned on & 0xF0), 0x6E the tackle list (FUN_801D0F5C(1)), 0x78..0x7A the prize chain; both lists cancel to 0x64. Retail also enters the pond directly. Port engine-core::world::fishing_hub_exits, on all three hosts.

disassembly
Do concurrent script contexts lock the pad, or only the cutscene timeline?every stepped record locks it
Why

FUN_8003BDE0 sets +0x100 on a spawned record (0x8003C088..0x8003C0AC), and the per-actor tick FUN_8003BC08 steps such a record through FUN_80039B7C (jal at 0x8003BD34; the only other caller is PROT 0897 0x801DA7BC). FUN_80039B7C raises the player's 0x80000 on every stepped frame (0x80039DB8..0x80039DD4) and clears it only when the running count drains at a raw 0x21 (0x80039EE8..0x80039F14), and FUN_801D1344 skips the pad controller while it is up (0x801D1694). Being modal decides only the camera. Engine: World::script_context_engages_player.

disassembly
Why does the menu button open nothing after a Door of Light arrival on map01?a walk-on script holds the player; bounded
Why

The Door seats the player on map01's cave-mouth walk-on trigger (37,109), which spawns P2[9]. For its 294 vsyncs FUN_80039B7C keeps the player's engaged bit +0x10 & 0x80000 up (raised 0x80039DD4, cleared 0x80039F14) so FUN_801D1344 never calls the pad controller FUN_801D01B0 (0x801D1694). The first press after it opens the menu. The span is WaitFrames 40, the B7 F8 00 81 compass walk (16 vsyncs) and an AD F8 08 spin on scene-bank clip 13 (120 frames, latched by FUN_800204F8); the engine runs 296. field-locomotion.md.

capture + disassembly
Who sets a field NPC's moving-class bit +0x10 & 0x20000?the placement seater, and op 31 11; nothing clears it
Why

FUN_8003A1E4 ORs 0x20000 into every partition-1 placement's +0x10 (0x8003A3A4..0x8003A3B4; 52 of 52 in a town01 entry write-watch), and op 0x31 operand 0x11 (0x801DED9C..0x801DEDB0, PROT 0897) sets it at 26 partition-0 spawn-prologue sites (town01 P0[8] captured). No op 32 11 exists on the disc, and the capture saw no clear in 17161 writes (motion-vm.md).

disassembly + capture
What does FUN_801D79E8 do?the per-actor visibility cull
Why

Unless _DAT_8007BAF4 is set it raises +0x10 bit 1 for an actor whose tile is outside the region box 0x1F800384..87 or the view window 0x1F8003E8..EB (widened by +0x58) and clears it inside; FUN_8003BC08's height arm then holds the Y, while the clip still advances. 600 of 600 captured decisions reproduce through world::field_npc_cull (motion-vm.md). Settled: gate 0x801D79EC..FC, box 0x801D7A38..A98, window 0x801D7AA0..B00, ori 2 at 0x801D7B10, hold 0x8003BC44..48.

disassembly + capture
What arms the place-name banner?system flag 2, a one-shot
Why

The kingdom MANs' entrance records (and two in koroutx2) set flag 2 ahead of their 0x3F; FUN_8003AEB0 spawns the 4C E1 balloon from 0x8003BB40 when it is set and clears it on every path (andi 0xdf at 0x8003BBD4). The state 60 vsyncs before doman loads holds it set (place-names.md).

disassembly + capture
When does the system channel's clip-base store reach the settle?on every tick the player is not movement-locked
Why

FUN_801DA51C runs the channel only with the player's +0x10 & 0x80000 clear or its own +0x100 up (0x801DA78C..0x801DA7AC), and FUN_80039B7C's store at 0x80039D94 sits in the interaction-start arm. A talk opened running keeps the base its opening tick left (2), and a ledge hop sees no store (field-locomotion.md).

capture + disassembly
Where does a Door of Light / Door of Wind use go, tick by tick?the pause-menu session hands off to a travel art
Why

The session FUN_801ED308 runs phase 2 → 4 (level 0xF2), twelve ramp ticks, phase 6 / 7, then installs 0x29 Riremito / 0x2B Rula. Riremito gates 47 ticks on its effect, dwells to 0x50, spawns the fade, dwells to 0x28 and resolves; a Door of Light goes to the region record's triple 0x80084624..2C, refreshed when the menu button is pressed. Probe autorun_door_item_use.lua; engine test door_item_retail_timeline (field-locomotion.md).

capture + disassembly
What does _DAT_80084540 hold?the loaded scene's raw CDNAME define
Why

Two above the extraction-frame Scene::start in all 98 states checked (town01 3, town0b 0x0C, town0c 0x15, map01 0x55); the formation roll's map arm (lw v1,0x4540(v0) against 0x0C / 0x15 at 0x800517A4, battle init FUN_800513F0) and the intro style picker (0x801CEAB0 in PROT 0979, against 3 / 0x0C / 0x15) key on it; the new-game path stores 3 at 0x8001D718. Engine BattleState::map_id.

capture + disassembly
Why can't the pause menu open over a shop?the shop's overlay displaces the controller
Why

The menu accept lives in the field overlay's pad controller FUN_801D01B0 (PROT 0897), and the gold shop's buy commit FUN_801DB7F4 is PROT 0899's own code (its bytes lui v0,0x801e; lw v1,0x46ac(v0); addiu sp,sp,-0x20 sit at 0899 file 0xCFDC = 0x801DB7F4 - 0x801CE818). Both images load at slot A 0x801CE818 (static-overlays.toml), so while a shop runs no field code is resident to read Start. The casino_prize_shop state holds game mode 0x17 with 0899 in slot A. Engine: World::field_menu_open_allowed refuses while a shop, the prize counter, narration or a title card is open.

disassembly + capture
Which clip does the player hold through a kind-0 warp?idle
Why

The system channel 0x8007E694, ticked by FUN_801DA51C after the player, runs FUN_80039B7C, whose sw v0,-0x4228(v1) at 0x80039D94 stores clip base 2 on every field tick the player is not movement-locked. The settle reads the base before it (0x801D1D8C, 0x801D1E08), so while the pad step runs the reset is invisible; with the step skipped the settle reads 2, and a write watch sees clip id +0x5C read 2 for the whole warp with Down held (field-locomotion.md).

capture + disassembly
How long does the warp timer's -1000 sentinel live?the rest of the landing tick
Why

FUN_801D1EC4 parks _DAT_8007B6B0 at -1000 on the landing, and the same tick FUN_801DA51C's tail compares it with -1000 and stores 0 (0x801DA7D8), so no next-tick reader sees the sentinel in a scene whose system channel runs it (field-locomotion.md).

capture + disassembly
Where does an inn conversation end?at the last page's close
Why

The sub-5 acquire is the arm 0x801E2148..0x801E21DC (table 0x801CEF48 entries 5, 0xE, 0xF), refusing on s7 = 0 (beqz s7 at 0x801E21D0). In retock_innkeeper_talk_open the stay ends when its last page closes, with the cursor parked on the 26 9D FE loop-back; the next talk runs the loop-back and the acquire succeeds (s7 = 5) (script-vm.md).

capture + disassembly
What does the region battle-setup half of FUN_801D9E1C store?a backdrop variant, two Door gates, an object-keep bit, a world-map return point
Why

On every region hit (0x801DA058..0x801DA12C): _DAT_8007BD60 = region[+8] & 0x1F, the stage variant FUN_800513F0 loads; 0x1F800394 |= 0x300000, cleared per Door of Light / Wind by bits 7 / 6; _DAT_8007B64B = bit 5, keep backdrop object 1; and a return triple at 0x80084624..0x8008462C from region[+5], [+9..+11]; +4..+5 are one s16 (FUN_8003CE9C), whose low byte is the rate increment (0x801DA18C) and whose & 0x7F00 is the map word's high half (0x801DA108) (script-vm.md).

disassembly
Why does kor5's 0x619 read set on entry and clear after the chain?a spawn-section write
Why

P1[2]'s SET 0x619 sits between its leading 0x25 and first 0x21, so FUN_8003A1E4 runs it at every MAN-loading entry; FUN_801D6704 passes loader mask & 4 to FUN_8003AEB0, which skips the spawn loop (0x8003B8A0) on a same-scene reload, so the post-battle reloads never re-set it after P2[4] clears it (script-vm.md).

disassembly + capture
Where does a talk begin in an NPC record?the interaction cursor +0x9E, never script_pc0
Why

The interaction dispatch runs FUN_80039B7C from the cursor, so the spawn section runs only at scene load. Entering at script_pc0 instead re-runs the spawn section on every talk and skips the record's segment-selection prologue (script-vm.md). Settled: dispatch 0x8003BD10..BD34 (+0x10 & 0x100, +0x90 non-null); cursor lh 0x9e at 0x80039BD4 against base +0x90 (0x80039BD8).

disassembly
Who writes the field clip base _DAT_8007BDD8, and which clip is run?the pad step; run is bank slot 2
Why

FUN_801D01B0 stores it every frame (0x801D0424..0x801D04A4): idle 2, walk 1, any faster step 3, 99 under _DAT_8007B6A8. The hop machine writes 6 / 7 / 1; FUN_801D1EC4 writes 2 on one arm. Run is reached by Cross or R1 (run mask 0x48) (field-locomotion.md).

disassembly
What does a kind-0 walk-on tile do?a timed warp, not an instant one
Why

FUN_801D1EC4 arms _DAT_8007B6B0 = 0x26 and two fades, counts it down without moving anything, and on the landing frame stores the pad hold _DAT_8007B6B4 = 0x28, re-rolls a spent encounter counter, seats the player and runs the landing tile's kind-1 record. The player tick FUN_801D1344 drains the hold and skips the pad step while either word is live. s3_rimelm_freeroam: landing 38 vsyncs after the crossing, pad 40 later (field-locomotion.md). Settled: arm 0x801D2214, drain 0x801D1F2C..3C, hold 0x801D1F4C..54, re-roll 0x801D1F64..74; tick 0x801D161C..30, 0x801D16C8..E4.

disassembly + capture
What does op 0x43's acquire do when the target is mid-arc?waits and retries
Why

The acquire (0x801DF384..0x801DF40C) fails on halt bit 0x400 while the scene word *(_DAT_801C6EA4) + 8 is zero, and the failure leaves the PC on the op (beqz to 0x801DEE4C, move s8,s4). That word is non-zero only while a spawn section is being pre-run (SCUS 0x8003B73C / 0x8003B928, 0x801E2820 / 0x801E2BBC) (script-vm.md).

disassembly
Which FUN_801CF8AC arm do the ambient walkers take?the class arm, all of them
Why

FUN_8003A1E4 ORs 0x20000 into every placement it seats (0x8003A3A8..0x8003A3B4), and 0x01000000 for a >= 0xF0 party model, so the box test runs on +0x10 & 0x01020000; the no-class arm is for pool actors spawned elsewhere (motion-vm.md).

disassembly
What does op 0x3E do with op0 < 100?the scripted-battle install, one body with op0 = 0xFF
Why

FUN_801DE840's arm reads op0 twice only (beq 0xFF at 0x801E06FC, sltiu 0x64 in its delay slot). Both sides force the player's cached region tile stale (+0x8E / +0x8F = 0xFF), call FUN_801D9E1C(player, 0), skip on _DAT_8007B868 or a missing 0xFB context, set sys[+0x8A] = 1, point sys[+0x94] at MAN formation row op1, store the FUN_801DDF48 reroll into _DAT_8007B5FC and request mode 0xE. Ten clean non-0xFF sites (town0b, stone, jagaroom). Port FieldSceneHost::scripted_battle; region half in World::apply_region_battle_setup_at_player (script-vm.md).

disassembly
How many sites re-roll the encounter step counter?five, into one word
Why

find-address-word-refs.py --prot finds four jal 0x801DDF48 and no other reference: SCUS 0x8003AC90 (FUN_8003AB2C adds half a roll while the counter is below 487), 0x801D1F6C in FUN_801D1EC4 (a full roll when the counter is <= 0), the op-0x3E arm 0x801E076C and 4C EC at 0x801E34F8; FUN_801D9E1C carries an inlined copy (0x801DA2D4..0x801DA35C). All five store _DAT_8007B5FC, one counter across doors. FUN_801DDF48 is two BIOS draws, r1 % 487 - r2 % 487 + 0x3CE. Port region_encounter::encounter_counter_reroll, seated at all five sites (the warp landing through World::tick_field_warp).

disassembly
Does P2[5] write kor5's 0x436 organically?yes
Why

From kor5_post_43a_checkpoint, P1[0] spawns P2[5] on the battle's reload and it sets 0x436 at +0xD0D through FUN_8003CE08 (ra 0x801E3598) 3,336 vsyncs after 0x464 clears; P2[8] then sets 0x6C4 on its first (32, 86) crossing (kor5_post_436_organic). Caveat: the capture ran without a staged disc copy, so it carries the patched-disc caveat.

capture (synthetic: two trigger-tile pokes, Gaza's HP held at 1)
Where does the op-0x43 arc's chained record point, and what does op 0x34 sub-1 spawn?a release watcher; an attached billboard
Why

FUN_801D25EC allocates the arc actor from template 0x801F227C (0x801D2634) and a second actor from 0x801F22AC (0x801D2760), whose handler word is FUN_801D5D60 - a watcher that clears the arm's halt bit 0x400 when the arc lands, not an emitter. Op 0x34 sub-1 calls FUN_801E5668 at 0x801DFFE0, which allocates from template 0x801F28B8 (handler FUN_801E4470) and copies the parent link, position and rect.

disassembly
What is FUN_801E58A8?an actor anim-clip pick
Why

It writes +0x5E = -2 and a clip index into +0x5C from _DAT_8007BDD8, the party leader _DAT_8007B8F8 (stride 7) and the override _DAT_8007B6AC, then calls the clip selector FUN_800204F8 (0x801E58A8..0x801E59AC). The same arithmetic is the tail of FUN_801D1BA0 (0x801D1D88..0x801D1EAC).

disassembly
When does 4C 86 install its reflection controller?at scene entry, from the record's spawn prologue
Why

All ten shipped sites sit after the record's leading 0x25 and before its first 0x21 park, so no talk press is involved. A retail conc -> conc2 crossing seats all three controllers on one frame, each call returning to 0x801E227C. Over 132 in-rect tick pairs the image is (x, y, 2*zz - z) with facing -0x800 - a; 194 out-of-rect pairs leave it alone; the rect test quantises with (v + 0x40) >> 7.

disassembly + capture
Is juui1 dark in retail outside its tint beats?no
Why

With 0x3E1 cleared and 0x3E5 set by RAM poke, the black run from vsync 1040 to about 1327 is the door fade - the departure push from 0x801DDE24, then the arrival ramp from 0x80025034 - and after the last push the scene holds a dim purple vortex with the party visible, mean luma about 40 of 255. An organic save exists (the rugi block of the endgame card) but its route crosses the Rogue fight.

capture (synthetic gate)
Which flags do the retock / doman / nilboa entries write?per-scene entry families
Why

Each from a pre-entry card-boot state, every write through the field VM's SET (0x801E3598) / CLEAR (0x801E35C0) arms: retock SETs 0x493, 0x01F, 0x52A and clears 0x19B..0x1AA and 0x527..0x52E; doman SETs 0x49C, 0x01F, 0x6E7; nilboa SETs 0x014, 0x499, 0x01F, 0x52A. retock's 0x502 is not an entry write - its one non-debug writer is spawned from Eliza's talk loop, which 0x33B (set in jagaroom) unhides (inference from the card saves).

capture
Why did a walk-on tile poke under the movement lock never fire?a crossing made while locked is consumed
Why

FUN_801D1EC4 stores the new tile into its last-tile mirror 0x8007BDC8 / 0x8007BDCC on both failure branches - the cell test cell & 0x600 at 0x801D2144 and the +0x10 & 0x80000 lock at 0x801D214C..0x801D2158 - so a crossing made during the lock never fires later. The port's dispatch_walk_on_trigger consumes it the same way: it stores the tile and returns while a script holds the player.

disassembly + capture
Which records write the kor5 tail, doman and son flags?with kor5's 0x436 input poked
Why

kor5: P2[3] 0x43A -> P2[4] 0x464 and battle 165 -> reload, P1[0] clears 0x464 and spawns P2[5] -> P2[8] writes 0x6C4 at +0x75 (tile (32,86), C1 {0x6C4}, C2 {0x436}). doman: P2[4] on tiles (67,108..110) writes 0x3FB at +0x98D, organically from korb2 across the world map by tile poke. son: the arrival seat fires P2[5] (0x60D); P2[3] (18,85) sets 0x3A6, P2[4] (24,76) sets 0x3A7; the entry clears 0x3A6..0x3A8 on every visit, so they are per-visit latches.

capture
What are motion-VM ops 0x37 / 0x41?an eight-direction compass walk
Why

Both step along the compass table at 0x80073F14, not along one axis toward a target; 0x43 never completes, and target 0xF8 is the player, not the executing actor.

disassembly
What do field-VM 4C E4 and 4C DB do?a box test with a relative skip; the CLUT blend fade
Why

4C E4 builds its box from tile corners +0x20 / +0x60 / +0xA0 and takes a relative skip when the actor is outside (22 clean occurrences in three scenes). 4C DB spawns from descriptor 0x801F2930 through FUN_801E57F0 into the CLUT blend fade FUN_801E4D8C; jouine issues it four times.

disassembly
What colour does retail clear the field to?black
Why

Uncovered pixels read (0, 0, 0) in five of five field-mode crops (mei_house_inside, keikoku_chest_pre, new_game_cutscene_intro_a, name_input_ui, v0_1_tetsu_dialogue_accept).

capture
Does a scripted camera tile window survive into the next scene?yes - by 78 vsyncs
Why

A per-vsync poll across a real map01 -> town0c door: the scene word flips at vsync 37 and the window at 0x1F8003E8..EB keeps the previous scene's values until vsync 115, when the entry stamps (-7, -6, 5, 7) and the per-region writes take over. The port re-stamps per entry as retail does, but with FIELD_DEFAULT_VIEW_WINDOW (-8, -6, 6, 10), which is a later region's window. A Rim Elm house door is an intra-scene warp (town0c stays), so it is no fixture for this question. encounter.md.

capture
What is field-VM 4C 14?the actor clone, and it is eight bytes
Why

The only eight-byte instruction in outer nibble 1: the nibble's prologue advances seven and the 0x14 arm at 0x801E0E80 reads a sixth payload byte and adds one more in a branch delay slot. That byte names a cross-context source actor; FUN_801D835C copies its position, rotation, bound model and +0x68 onto a fresh pool node and writes the fade rate and modulation colour the two earlier operands carry. Ninety-four sites in six scenes, three rates; a seven-byte reading desyncs each record from its first occurrence. script-vm-menuctrl.md.

disassembly
What do 4C 86 and 4C 87 do?the reflection controller's install and teardown; neither parks
Why

4C 86 reads its last operand byte as a cross-context actor id and spawns from descriptor 0x801F2948, handler 0x801E5154, writing +0x90 = executing ctx, +0x94 = resolved actor and six s16 into +0x80..+0x8A. The tick reads +0x94 and writes +0x90, so the named actor is the source and the executing script is the image; the six words are the mirror line and tracking rect. 4C 87 retires every live one and advances two, as 4C 9F does against another handler. script-vm-menuctrl.md.

disassembly
Which model owns the field screen-effect fade?the push
Why

FUN_80024EE4(kind, blend, packed) (a0 clamped to the OT at 0x80024F00..2C, a2 under code 0x2B at 0x80024F30..5C) runs once per step from the actor op 0x34 sub-0 spawns, with the global multiply tint DAT_8007BCB8..BA neutral on all 900 measured vsyncs. The arm (0x801DFCD4..0x801DFEF8) is a pair: a walk-out from the previous target plus a walk-in, blend = (op0 & 1) ? 2 : 1 (0x801DFD70..8C), push kind 8 / 0 / 2 off the same byte (0x801DFD90..BC), and an all-zero operand clears the live actor instead of ramping to black (0x801DFDE4..0C). cutscene.md.

capture + disassembly
Why does a juui1 name hijack draw black?the tint belongs to the departing script
Why

Of 498 clean op-0x3F doors, 444 are immediately preceded by 34 05 FF FF FF 41 00 - a white ColorIntensity beat the leaving scene's own record runs as its door prologue (asset field-op-census --only 3F --context). A hijack rewrites the destination name and never executes that prologue, so the hijacked frame is black, and a hijack into a brightly-drawn control scene is equally black. The conc -> conc2 -> juui1 route is two ordinary doors (conc P2[11] +0x001E, conc2 P2[20] +0x0078).

capture (disc census)
Do shipped scene scripts carry developer flag-setting menus?yes - nine bundle MANs name theirs; the total is larger
Why

map01, geremi, keikoku, suimon, town0b, town0c, doman, kor5 and jou ship pickers whose own labels name them as flag menus, and whose arms are genuine 51 / 61 ops over nine-flag ladders. They are why a flag-writer census over-counts: the arms decode clean, so a beat and a menu row look identical to a scan that only reads opcodes. The nine are a floor: rikuroa, dolk2 and dolk carry more, and the classifier man_field_scripts::debug_flag_menu_arm marks 2,500 sites in 74 scenes; script-vm.md.

capture (disc census)
What gates the fishing catch HUD's depth and tension block?one word, DAT_801d91b4
Why

The catch HUD FUN_801d1580 gates the depth readout and the tension bar on the single word set at the hook - not on a phase or a separate visibility flag. A host that adds an idle-phase gate of its own draws a different HUD from retail's on exactly the frames between the strike and the hook. minigame-fishing.md. Settled: lw of 0x801D91B4 at 0x801D17C4, beq to the epilogue at 0x801D17CC - the routine's one branch past its clamp arms.

disassembly
Where does a field actor's heading live?it is the middle of a rotation triple at +0x24
Why

FUN_8001ADA4 hands the whole vector to the rotation setter - addiu a0,s0,0x24 / jal 0x80026988 at 0x8001AF04, and again at 0x8001B2A4 / 0x8001B2C8 / 0x8001B320 - so the actor carries pitch at +0x24, yaw at +0x26 and roll at +0x28. A reader taking the yaw halfword alone sees a heading and loses the tilt.

disassembly
What is _DAT_8007B854?the ambient-particle master gate, not an input lock
Why

Field-VM op 0x4C outer nibble 3 raises it and clears it, both stores in a j delay slot off the 16-entry table at 0x801CEEB8: 0x801E0F38 is sw v0,-0x47ac(v1) with v0 = 1 and 0x801E0F44 is sw zero,-0x47ac(v0). Six references exist disc-wide and none is pad state - two SCUS clears, the field render pass at 0x80026EBC which stages the particle table into scratchpad when the word is set and the game mode is 3, and the ambient emitter's own opening load at 0x801D605C. A field script decides per scene whether ambience emits at all.

disassembly
teien's hedge-base ground fill - does retail draw a kind-2 cell?no - retail has no 0x0800-cell draw channel
Why

A live teien field-run pass visits 1536 window cells and emits 370 - exactly the cells carrying 0x1000 - and none of the 42 0x0800-only cells. Only 8 of 84 images reach *(0x1F8003EC), only PROT 0900 / 0901 hold a per-cell pass, and each one's andi 0x800 reads an object record's +0x12, not a cell. teien's 0x0800 cells are a 6x6 platform block, a ten-cell row and three strays, the same shape edteien has. Cell bit 0x8000 is a per-tile depth-sort flag (own farthest SZ against a fixed far bucket (0x3FF6 >> ot_shift) * 4).

capture
Actor +0x16 - heading, facing, or footing?Y of the position triple
Why

+0x16 is the Y of the actor's (+0x14, +0x16, +0x18) position triple. No access masks it as an angle: 0 of 77 in the field overlay 0897 are angle-masked, and the four masked accesses in SCUS are actor[+0x96]. FUN_8003BC08's second arm is therefore a ground-follow: skip on & 0x2, authored -actor[+0x8E] on & 0x20000000, a global off-switch when & 0x20200 is clear and _DAT_8007B6A8 == 0, an outright snap to the sampler when & 0x2000 is clear, and otherwise a ramp clamped to +-6 * DAT_1F800393 per frame. Detail on functions/game-modes.md. Settled: 0x8003BC44..BCF4.

disassembly
Which index does the fishing bring-up rewrite?the rod index, not the lure
Why

FUN_801CF070's tail (0x801CF35C..0x801CF39C) probes the bag for item 0xA0 + _DAT_80084454 through FUN_80042F4C, steps the persistent rod index on a miss, wraps at 3 and gives up after six probes by storing 0 - so a rodless player fishes with rod 0. The lure gate is a different routine over a different band: FUN_801D712C walks items 0x9D..0x9F and rewrites _DAT_80084450. The same entry also seeds the 16-rung floor-height ladder at 0x1F80035C and carries a dev grant of 999999 fishing points behind _DAT_8007B9B0. See minigame-fishing.md.

disassembly
What writes a scene's kind-2 (height-override) trigger cells at runtime?exactly one writer
Why

Field-VM op 0x4C sub 0x83 at 0x801E20A8 - a rectangle re-floor through FUN_801D5630(2, x, z) with the coarse step in op[5]. Trigger kinds 0 and 3 have no writer at all, and the .PCH sidecar's +N fixup words have none either: all 97 on-disc tables carry zero there. The per-kind record strides are gp[0..3] = 4, 4, 4, 8, read by FUN_801D5AE0. See script-vm-menuctrl.md.

disassembly
The ending-scene widget family - how many sites, in how many scenes?311 sites, ten scenes
Why

The op-0x43 arm at 0x801DF354 bounds the sub-op (sltiu v0,v1,0x16) and jumps through 0x801CEDA8; in the decoder the sub-op is InsnInfo::ActorCtrl, not Insn::extended (that field is a cross-context target marker, 0x80). Counted on the sub-op (asset field-op-census, subs 0x10/0x11/0x13/0x14/0x15 over eleven carriers) the family has 311 sites across edteien, edbylon, edbalden, edlast, edretoin, edkorout, edson, edstati3, edbubu and eddoman.

disassembly + capture
FUN_801D6058 - a cutscene element?a field-overlay template, spawned once
Why

It is the +0x08 handler of the plain template at descriptor 0x801F271C, spawned exactly once by the field MAIN INIT FUN_801D6704 (addiu a1,0x271c at 0x801D6FC0, jal 0x80024c88 at 0x801D6FD8, sh s0,0x1a(v0) seeding the +0x1A = 1 scene arm), and gated on _DAT_8007B8B8 == 0. It is not cutscene-specific.

disassembly
Rim Elm's south gate - why a seated player exits and a walking one does notresolved
Why

The exit record is ungated, the second walk-on band is five inert bytes, and the wall is a collision row the gate object's own script paints; the open paint is nibble-7 sub 0 (andi v0,v0,0xf / sb v0,0x4000(v1) at 0x801E1CFC..0x801E1D00). details ↓

disassembly
Town/field free-movement locomotionresolved
Why
capture
Field ambient animation - what makes jou's ground pulse and the water shimmerresolved
Why

Three mechanisms: the bundle type-6 CLUT-walk table (12 carriers, 9 of them field scenes), the ambient move-VM tree the MAN P1 placements install at entry, and jou's flesh pulse = the mode-3 CLUT-cell HSV cycler (FUN_80019D50, called from the actor render tail's mode-3 arm at 0x80022868; lightning = flag 0x364). Full chain: field-ambient-fx.md.

disassembly
Ambient render-mode 4 - what the op-0x1E seat animatesresolved
Why

A cyclic VRAM-rect scroller, and it is what makes waterfalls fall. Per fired period (+0xC6 drained by the frame step alone, no speed scalar) the render tail rotates the seated rect +0xD0..+0xD6 left by +0xCC * frame_step and up by +0xCE * frame_step, each axis as StoreImage / MoveImage / LoadImage over a bump-allocated strip (80021df4.txt 0x80022CB8..0x80022EE0). Seventeen scenes carry one at plain entry; sixteen scroll upward over a texture-band rect, tunnelc's second seat scrolls a CLUT row sideways. Ported as engine-field::vram_scroll. details ↓

disassembly
Master ambient record 0 - what reads the 8-byte rowsit is not a stager record at all
Why

The per-scene sound-effect descriptor bank for cue ids >= 0x200. Both SFX readers resolve those ids as *(u32*)0x8007B8D0 + offsets[0] + (id - 0x200)*8 - i.e. record 0 of whatever bundle is installed at 0x8007B8D0, which in field mode is the scene prescript bundle (field_asset_loader 0x8001F850..0x8001F864 stores scene buffer + 0x12800). offsets[0] is the identical word FUN_800252EC reads for stager id 0. Rows are the 8-byte descriptor of sfx-table.md, category 3 (a variable VAB slot). details ↓

disassembly
town0e's morph-record installerpartition-1 placement 29
Why

The install is the ordinary 0x34 sub-3 arg 0 (stager record 1), carried by partition-1 placement 29 - a full placed actor with dialogue - as that record's second instruction, ahead of its SysFlag.Test 0x1A park/seat branch. A census keyed on script shape misses it; the entry-slice rule in the next row finds it. The pre-run loop (0x8003B8BC..0x8003B8EC) covers every placement 1..count-1 unfiltered, gated on a MAN having been dispatched in the load (0x801D6D98 tests the MAN arm's ori s4,s4,4), which town0e's bundle carries. details

disassembly
Which op-0x34 sub-3 installs fire at scene entryresolved
Why

The ones the placement spawn-prologue slice (FUN_8003A1E4) executes - not a distinguished kind of script. The pre-run is gated on the record's first opcode being 0x24/0x25, and the slice breaks after an opcode whose full byte is 0x21; both nops, only one ends the slice. Ported as engine-field::man_field_scripts::scene_entry_ambient_installs. details ↓

disassembly
Scene bundle type-7 slot content (VDF)resolved
Why

The scene's vertex-morph delta pack (61 bundles populated; jou = 17 sub-entries), installed at DAT_8007B7DC by dispatcher case 7 (0x8001F440), each sub-entry appended to the pointer table 0x80083E58 by FUN_8001FBCC (loop 0x8001F484..A8), which the morph stager FUN_8001C604 indexes (0x8001C6DC..F0). Parser legaia_scene_formats::scene_vdf; format in field-ambient-fx.md.

disassembly
VDF morph render substitution - what draws the staged vertices, and what arms the lanesresolved
Why

Per drawn group of a part whose flags carry op-0x0A's bit 0x1000, FUN_8001ADA4 (0x8001B424..) calls FUN_8001C604 (scratch copy + weighted-delta blend + group vertex-pointer retarget) and restores the pointer after the draw. Arming = op-0x0A mesh stager parts in the ambient tree (pack slot = model_sel - 5; rikuroa 69/70 behind flags 0x281/0x282, town0e 10/11, jagaroom 20/21); weights ramp via FUN_80020740 steered by op-0x32 envelope flags. jou arms nothing at entry (cutscene op 0x1F only). Ported to all three render surfaces; details in field-ambient-fx.md.

disassembly
What opens an inn stay in retail?nothing - there is no inn session
Why

Retail composes a stay inline in the scene MAN out of generic ops (dialogue, an MES picker, an op-0x4E gold gate, op-0x3A ADD_MONEY, fades) and then one 4C 82 <slot> per member - the only inn-specific opcode in the engine. Charge and restore are decoupled, so free rests are the same tail minus the gate. Port restore_party_slot. details

capture
Field collision-map sourcethe .MAP supplies the base grid
Why
disassembly
Tile-board grid moderesolved
Why

The _DAT_8007b450/DAT_801f35c0/801ef2b0 tile-grid walk is a puzzle / board minigame (procedural rand-filled board, per-cell drawn tiles), not town locomotion: a field-overlay (0897) construct driven from the field/event VM (op 0x49). The _DAT_8007b450 refs in the hub minigame overlays are only the shared equip-comparison layout hint FUN_801e5b4c. The func_0x800467e8 facing remap is a quantized 45° octant rotation. Boards are always procedural; no fixed board exists. There is no FUN_801e0b1c - it is a mis-based dump alias of 0x801EF334, interior to FUN_801ef2b0. Instruction detail and tile values: tile-board.md.

disassembly
game_mode 0x03 = field/town gameplayresolved
Why
capture
Scene prescript: field-VM event scripts vs move-VM stagers (dual consumer)resolved
Why

Single consumer. The op-0x34 sub-3 operand census across every scene MAN shows every prescript record is a move-VM stager: partition-1 effect-actor records stage the ambience on entry (record 0 = the master ambient record in 62 scenes), partition-2 cutscene timelines install the per-shot ids. Id space = record index (the RAM [u16 count][u16 offsets] relocation at _DAT_8007b8d0, live-pinned vs the file bundle). No retail path runs a prescript record through the field VM. See scene-bundles § consumer census.

capture
Engine VRAM byte-exactness for town01(major source); minor residue
Why
capture
CLUT row 510 population (env meshes' (64,510) CBA)(boot-resident system-UI strip band); residue = the exact boot walker call site
Why
capture
Scene-transition (0x3F door) destination indexingresolved
Why
capture
Intra-town (house / interior) door mechanismresolved
Why
disassembly
Field/town environment-geometry placementrenders
Why
capture
Overworld / town entrance story-flag gatingresolved
Why

An entrance's unlock is its own partition-2 record's C1/C2 gate (FUN_8003BDE0; C1 = one-shot latch, C2 = requires-all) against the system-flag bank _DAT_80085758. Ops 0x50/0x60/0x70 (SET/CLEAR/TEST) carry idx = ((opcode & 0x8F) << 8) | operand (raw flag number). Disc-pinned via man-scripts --system-flag-census: map01 keikoku portals C1=[0x193] (setter vozz P1[7], the only 0x193 SET disc-wide, byte-pinned by chapter1_hub_depth_oracle.rs), mist walls P2[34..36] C1=[0x482], town01 dinner chain P2[4]→550→P2[5]→551; the dinner does not re-fire. See world-map.md + field-locomotion.md.

capture
Overworld story-conditional destination (dolk→dolk2)mechanism + engine port
Why

Beyond the record-level C1/C2 gate, an entrance record can switch its 0x3F target by an in-record op-0x70 SysFlag.Test. map01's dungeon entrance (P2[1]/P2[2]) branches on flag 0x142: clear → dolk (pre-boss), set → dolk2 (post-boss), same trigger + arrival tile - so dolk2 is not reached from a dungeon interior. overworld_portal_sites decodes the conditional 0x3F pair (ConditionalDest); the seeder resolves via World::system_flag_test (chapter1_boss_spine_oracle Part D). The 0x142 setter is rikuroa's streaming-carrier script records. See world-map.md.

capture
Retail-vs-engine NPC + story-flag state parity across the capture librarybreadth oracle
Why

The sweep oracle crates/parity/tests/field_npc_state_parity_disc.rs compares every catalogued field-mode library capture against a cold engine entry with the capture's DAT_80085758 bank seeded byte-for-byte: park/place visibility, seat position within the patrol-locality bound, heading (diagnostic), post-entry flag neutrality. Divergences are classified in-test (KNOWN_DIVERGENCES); the dominant class is capture-mid-beat dynamics - a mid-visit choreography re-arranged NPCs after retail's own entry, while the engine reproduces the FRESH-entry arrangement (cross-pinned by sibling captures, e.g. rikuroa pre_caruban).

capture
Entry pre-run channel slice ends on a no-mask 4C 70 wall paintthe slice continues
Why

All four nibble-7 paints continue. 0x801E3624 is the epilogue: all four sub-ops return, and their advances differ (subs 0/1 +6, subs 2/3 +7); there is no shared continue label and no label-call idiom. The slice continues because the caller loops; breaks come only from an executed 0x21 NOP, a stalled PC, or a next opcode whose & 0x7F is < 0x20. Detail: script-vm.md.

disassembly
Writer of the Rim Elm opening flag (549)a self-latching script SET
Why

town01 P2[3] itself, one site: a plain 52 25 SET at body +0x3 in the very record its C1 gates (the rikuroa-P2[50]/0x142 self-latch shape). Runtime: a reader-watch from s2_rimelm_town01 sees the SET at ra 0x801E3598, script-PC +0xF. Static: the walk reaches it only when the preceding 4C ED op carries its width - without it the walk desyncs one byte short. Not a second site in a gameover_data "dev copy": that is town01's own MAN seen through a neighbouring block's window. See script-vm.md; anchor flag_549_writer_is_the_rim_elm_p2_3_self_latch.

capture
Field .MAP PROT resolution - which entry holds a scene's mapcensus-pinned
Why
capture
World-map CLUT cycling beyond the ocean headclosed (operand table + emitter + cadence pinned)
Why
capture
init_data UI-tile page residency; the map03 terrain columnboth premises falsified
Why
capture
What transitions retail into game over?resolved
Why

Retail has no mode-0x12 transition. A wipe exits battle to mode 2; MAIN INIT FUN_8003AEB0's back-from-battle arm stores game_mode = 0x16 (CARD INIT) + _DAT_8007BB00 = 1 at 0x8003B5D4, landing on the title screen - no GAME OVER art, no menu. Three more sites carry the identical pair (FUN_8003C7EC, FUN_801D84B4, the STR attract exit 0x801CF048). Mode 18/19 + PROT 0902 are an unreachable dev harness. Both port hosts hold, draw nothing and hand to the title. details

capture + disassembly
Mid-visit NPC re-arrangement beats (dolk2 market crowd; garmel pre-Zeto staging)resolved
Why

dolk2: the swap is P2[11], spawned by the .MAP fallback walk-on-trigger rows (C1=[0x27C], C2=[0x142]) - eight CC <crowd> E3 <day> seats (op 4C nE sub-3, 0x801E3108) put P1[53..60] on the day cohort's tiles and A3 parks the day cohort at (127,127). garmel: the Zeto stager P2[12] materializes P1[3]/P1[4] beside the player (n3 sub-7 player-coord copy 0x801E0FB0); post-battle re-entries run P1[0]'s flag-consume arms. See script-vm.md; pinned by engine-scene/tests/man_midvisit_rearrangement_disc.rs.

disassembly + capture
Region story-flag gate families (record-header C1/C2 gates)structure, play order by milestone brackets, the two undecided orders unobservable
Why
capture + disassembly
Extraction-0874 §2 (player.lzs) F-variant pixelsinstalling event named
Why
capture + disassembly
Which chapter-1 scenes the engine can load, script, walk and leaveas a per-scene verdict; four of the five late "one-way" rooms leave in-engine
Why
capture
How a player leaves the Uru Mais chain (uru, urudre1..3) and jouineall five have walk-on exits carried by the scene's .PCH trigger sidecar
Why
disassembly + capture
Why did the port drop roughly half the disc's 0x3F destinations?the clean-label gate was lower-case-only; retail never compares the operand against a name table
Why
disassembly
Why do bubu1 and edbubu resolve no MAN?both ship a count = 5 asset table; the detector bounded count to 6 or 7
Why
disassembly + capture
Why did 28 scenes of the frontier closure become unwalkable at once?a scene change mid-ledge-hop leaked the hop's one-way steering lock - an engine bug, fixed
Why
disassembly + capture
What consumes the scratchpad window 0x1F8003E8..EB?the renderer's visible tile window; the 0x801F2778..84 mirrors are write-only
Why

Four signed bytes [nearX, nearZ, farX, farZ], tile offsets from the camera tile, written by the camera-zone loader FUN_801DBC20 and field-VM op 0x46. Read by the render library's cell emitters (FUN_801F7088 at 0x801F7434..0x801F746C and siblings), the camera scroll clamp FUN_801DAA50, the ambient emitter FUN_801D6058 and dev-menu rows 0x12..0x15. Invisible to the word scan: every access is lui 0x1F80; ori 0x314; lb 0xD4(rX). Details: encounter.md.

disassembly
What is the field run-button mask 0x800846DC?0x48 = Cross | R1, seeded once by the new-game data init; not configurable
Why

The last of four button-mask words at 0x80084140 + 0x590..0x59C (0x44, 0x21, 0x10, 0x48), stored by FUN_80034A6C (0x80034AA0..0x80034AB8) and read by the field mover at 0x801D0364 against the held mask _DAT_8007B850. No other writer in SCUS or any overlay; it sits inside the saved block. The engine's default mask is the retail pair plus Square (FIELD_RUN_BUTTON_MASK_DEFAULT); FIELD_RUN_BUTTON_MASK_RETAIL is the exact set (field-locomotion.md).

disassembly
Who latches the clip-end bit for a conversation's cross-context clip pokesport residual named
Why

The poked actor's own anim tick, on the poked actor's own +0x62. FUN_8003C83C short-circuits target 0xF8 to the live player object out of _DAT_8007C364 before its actor-list walk (0x8003C83C..0x8003C854), so an NPC record's A2 F8 <clip> / AC F8 08 / AD F8 08 reads and writes the player's clip words. details ↓

disassembly + capture
What is the scene_asset_table header's +0x04 word?sum of the descriptors' decompressed sizes; never read
Why

Equals Σ descriptor.size in all 105 containers of the family on the disc and exceeds the carrying entry in every one; FUN_80020224 reads +0x00 (lw s3,0x0(s4) at 0x80020288) and steps descriptors from +0x08, and a corpus sweep for loads off *(0x8007B85C) finds offset 0 only.

disassembly
How does a scene bundle reach _DAT_8007B85C?whole-sector block copy
Why

FUN_8003D26C(*(0x8007B85C), *(0x8007B8C4), sectors << 6) at 0x801D6918 (32 B per iteration = sectors * 0x800), into the 0x62C00 arena FUN_8001E1B4 allocates at 0x8001E28C.

disassembly
What consumes the "field-pack" entries?the scene texture pack at block +4
Why

FUN_800255B8 builds the path by mode (tim.dat 0x0A, move.mdt 0x0F, <scene>.pac 0x14) and loads into *(0x8007B85C); FUN_8002541C walks a bare pack into FUN_800198E0 (mode 0x0A) or the chunk chain into FUN_8001F05C (mode 0x14). Not a format of its own - see field-pack.md.

disassembly
What do the asset-table Flag(0x0A / 0x0F / 0x14) descriptors do?they are the streamed-file loader's mode argument
Why

FUN_8001F05C returns (case << 8) for those types (0x8001F574 / 0x8001F60C / 0x8001F658), FUN_80020224 ORs the returns, and 0x801D6BF8 shifts right by 8 before jal FUN_8002541C. Corpus: 28 blocks Flag(0x14) carry a DATA_FIELD stream at +4, 4 Flag(0x0A) a bare pack, 64 without a flag a pochi filler.

disassembly
Which screen opens menu window 46 (FUN_801D603C)?the casino prize counter's Yes / No confirm
Why

Script 0x801E4F2C = 01 2E 00 00 (byte-verified in PROT 0899's widget-script pool), handed to FUN_801D6628 by FUN_801DC1CC at 0x801DC408 / 0x801DC41C - index 0x20 of the sub-screen pointer table at 0x801E4F40, selected on entry-context kind 7 at 0x801DC8CC - staging _DAT_801E46D0 at 0x801DC414, the state word the painter's marker decode reads. The 04 2E closer at 0x801E4F38 has no reference in any image.

disassembly
Which prescript copy does FUN_800252EC install from - the sister entry or the .PCH +0x800 copy?the next PROT entry, through the efect.dat window; there is no split
Why

FUN_800252EC reads its [count][offsets] table from _DAT_8007B8D0 (0x800252F4), which the field asset loader sets to *(0x1F8003EC) + 0x12800 at 0x8001F864 - one sector past the .PCH, so the .PCH window at +0x12000 is never the source. Writers of gp+0x5B8: 0x8001F864, 0x8001FAC8 (the bse.dat battle buffer), 0x801CF018 (0975), 0x801CEEFC (0977).

disassembly
What does the 0x801F2858 template's tick do?the scene shutter blackout, FUN_801DD784 0x801DD784..0x801DD9B4
Why

Two full-width bars ease in from the top and bottom and meet, holding the screen black - a scene-change shutter, not a cinematic letterbox (falsified). Its one spawner is FUN_801DE754, from field-VM op 43 0C; FUN_801CFF3C is the same routine printed 0xE818 low. Settled: bar height caps at 0x73 (0x801DD884), edges -4..h-4 and 0xE0-h..0xE0 (0x801DD914..40, 0x801DD96C..98); spawn 0x801DE770..84; op arm 0x801DF884..90 (sub-table 0x801CEDA8[0x0C]).

disassembly
What is field-VM op 0x4C nibble 9?the scene floor-height ladder
Why

Sub-0xE installs all sixteen rungs (-words[i] into 0x1F80035C + i*2), subs 0..2 set one rung oscillating through FUN_801DDE34 -> FUN_801DA930, and sub-0xF retires every oscillator. Not a fade family: the destination is the elevation LUT FUN_80019278 and FUN_8003A55C read. See script-vm-menuctrl.md. Settled: arm 0x801E2478; sub-0xE loop 0x801E250C..38 (negate at 0x801E252C), jal at 0x801E24E8, sub-0xF passes 0x801DA930 as the tick to retire (0x801E2548..58).

disassembly
What is 0x801D44CC in the dance overlay?the step-marker mesh flipbook
Why

It selects the marker actor's mesh row from its +0x50, the clip - 6 value the floor pass stamps at spawn (addiu s0,s2,-6 at 0x801D2C00, sh s0,0x50(s1) at 0x801D2C30, read from the overlay image) - it flips the marker's picture and does not face a dancer. See minigame-dance.md. Settled: FUN_801D0640 indexes 0x801D44CC + (+0x50 << 7) + (+0x9C << 1) at 0x801D0674..90 and hands the halfword to FUN_80024E08 (0x801D06AC).

disassembly
Who calls the move-VM extension dispatcher FUN_801D362C, and is the port live?one caller disc-wide, and the port is reached
Why

The only reference of any form is SCUS 0x80023AE0, the move VM's own op-0x2F arm; the world-map controller does not call it directly. The port is live through MoveHost::ext_dispatch. See move-vm-overlay-ext.md.

disassembly
How does the shop's buy list order and ink its rows?a three-row Platinum Card hoist, with a last-rule-wins dim
Why

Case 0x0B splits the walked rows at record_count - 3: rows below the split stage into 0x801C6220 tagged 0x3000 and are appended after the last three, which go straight into the row buffer tagged 0xA000 and are inked 5. The last three are walked only when FUN_80042F4C(0xFF) finds the Platinum Card: Retock's Items Shop emits 13 rows with it and 10 without (autorun_shop_buy_list.lua). A row dims when purse < price or held >= 99. See shop.md.

disassembly + capture
Where do the tile-board's cells live?heap, one byte per cell, allocated at install
Why

DAT_801F35C0 = FUN_80017888(0, width * height) at 0x801EF3E8..0x801EF3F4 - the one writer - with width / height re-read from _DAT_8007B450[3] / [4]. Nothing pads it. The walk SM's teardown state 0xE frees it with the tile-actor table DAT_801F35BC through FUN_80017B94 (jal at 0x801EFE78 / 0x801EFE88); the per-scene control-block reset is a separate clear. FUN_80017888 is the logging wrapper over the best-fit allocator FUN_8002B468. See tile-board.md.

disassembly
What are the menu's HP / MP ink thresholds?FUN_800349EC / FUN_80035EA8, with a fixed ailment arm order
Why

The two routines carry the tier tests, and the ailment arms are evaluated in a fixed order, so a readout's colour is decided by the first arm that matches, not by a priority table. Rows in functions/menus.md; law in field-menu.md. Settled: HP 0x80034A0C..A60 (zero, quarter, +0x12E, half), MP 0x80035EC8..EF8.

disassembly
Who clears the -1 menu entry-context park?the submode dispatcher's retire arm writes Done; no menu teardown clears it
Why

When the pause-menu session clears +0x3E (0x801ED52C), FUN_801F159C stores 1 into the still-live park (0x801F16AC) and op 0x49's Done arm zeroes it (0x801E08D8); a capture at the town01 save point shows exactly one such store after the menu closed. Not a menu teardown leaf (falsified). Port World::release_menu_entry_context_park.

capture + disassembly
Does a save point open the pause menu by itself?yes - op 0x49's -1 rows are a scripted menu-button press
Why

The Idle arm spawns the menu button's subsystem actor (0x801E09A0); its enter half stores handler 7 before the table read (0x801F140C), and handler 7 picks the pause-menu session 0x30. The town01 capture logs park store, enter, state pick (+0x50 = 7), session (+0x50 = 0x30) and game mode 23 with no Start press. Ported as World::scripted_menu_open_pending on all three hosts; see field-menu.md.

capture + disassembly
What keys the menu entry-context byte?the record kind, not the screen's position
Why

0x00 shop -> 0x1A, 0x01 save -> 0x19, 0x07 casino -> 0x20, 0x0D -> 0x04, and the sentinel 1 -> 0x02, the debug character-parameter editor. Not positional: read by position, the save entry lands on 0x02 (falsified). Settled: FUN_801DC6B4 0x801DC860..E4 - sentinel compare at 0x801DC868, then one lbu 0(a0) test per kind.

disassembly
What arms _DAT_8007B8B8?a one-shot entry-mode argument, not a latch
Why

Ten writers and 25 readers over 84 images. One writer is 0x80016414 inside FUN_80016230, the mode-transition pass; two more are 0x80026094 and 0x80046E28 (the latter only when the word is already non-zero) and one is 0x801CEF18; six sites write zero. Nothing retains it across a load, so the field MAIN INIT's 0x801F271C spawn is per-scene, not boot-only. Its neighbours _DAT_8007BACC and _DAT_8007B76C have no writer at all, which makes the recentre-window form they gate unreachable.

disassembly
Which way round is actor +0x8A bit 0?it suppresses the scripted motion VM
Why

beq at 0x80038194: a zero byte runs the bytecode. The bit gates the player-engaged / actor-busy / off-map early returns at 0x8003819C..F4, so setting it stops an actor rather than starting one. Op 0x12 waits for the bit to change, seeded 1/2 at 0x800396B4, and consumes its tick unconditionally.

disassembly
What are motion ops 0x06, 0x0C and 0x0E?a home-relative wander, a tint/draw-mode fade, and a two-base model bind
Why

0x06 draws rand() & 6 and steps inside a box of signed 7-bit tile deltas taken from the actor's home at +0x8C / +0x8D - it never leaves one tile of where it was placed, and it is not a pad echo. 0x0C fades +0x74 (packed RGB tint, scheduler kind 3) and +0x78 (draw mode), not a position channel. 0x0E splits unsigned at 0xF0 between the scene model base 0x8007B6F8 and the second base 0x8007B824.

disassembly
How do motion ops 0x10 / 0x11 / 0x12 address their target?one selector over five halfwords
Why

The candidates are +0x10, +0x12, +0x62, 0x1F800394 and 0x1F800396; byte 1's 0x30 field picks the low half, and b1 & 0xC0 == 0xC0 asserts 0x3039 at 0x8007B828 and then dereferences null. Ops 0x02, 0x0A and 0x0B are gated on the 0x801C6470 record's 0x8C unset sentinel; op 0x09's callee FUN_80035B50 is an SFX-cue enqueue; op 0x14 writes +0x72, the render scale FUN_8001B964 reads. The table's slots 0x1A..0x1F point at the loop test itself, so 26 op bytes cover 24 bodies.

disassembly
How does the field follow camera get its pose?record -> parameter block -> compose -> ease or snap
Why

FUN_801DBC20 splits one MAN section-3 camera-region record into the parameter block at 0x8007B607..0x8007B627; FUN_801DAB90 turns that block, the player's position, the floor height from FUN_80019278 and the scratchpad attribute box into a staging descriptor at 0x801F3580; and FUN_801DB510 walks the six-entry list at 0x801F2798 toward it by delta >> shift plus the sign, on frames the player moved. FUN_801DB8EC is the same walk as a copy. Over the nineteen walkable states the composed pose is exact in all eight settled free-roam ones. details

disassembly + capture
Who runs the camera zone query in retail?the field VM, not the arrival actor
Why

FUN_801DBE9C queries only on its _DAT_8007B868 != 0 leg, and that word is the dev/dual-mode gate - zero in retail, with FUN_80034A6C storing DAT_8007B606 = (B868 == 0). Retail's query is FUN_801DE3E0(tile_x, tile_z), reached from field-VM arms [4C 38], [4C 39] and [4C C4 x z] plus the op-0x45 LOAD, and it installs a fixed miss set when no record covers the tile. Settled: gate 0x801DBEA0..B4 (query jals 0x801DBEF8 / 0x801DBF08 on the non-zero leg only), store 0x80034B58..60, VM sites 0x801E1068, 0x801E109C, 0x801E2884; the non-VM sites are the re-query thread's.

disassembly
What draws field fog, and what raises it?a pool of textured sheets, gated by one script word
Why

FUN_801D629C is a spawner: it maps the player's tile to a section-4 record ([enable][x0][z0][x1][z1][angle][spread][speed][unread][u16 flag]) (stride 11, 0x801D63B4) and pops one of eighty 0x18-byte pool records at _DAT_8007B7E0 (li 0x50 at 0x801D7374). The draw is SCUS: FUN_8003F348 walks the pool, FUN_8003F3FC updates, and FUN_8003F86C lays a POLY_FT4 (code 0x2E 0x8003F77C, OT SZ2 >> 5 0x8003FA34) per sheet. The width jitter FUN_8003F838 is dead: the caller seeds it with the rate (0x8003F714..38). The master gate _DAT_8007B854 is raised at 128 sites across 70 of 124 scenes, all in P1 records.

disassembly + capture
What composes the field camera's TR?the live eye trio is the eye-space translation; there is no eye-back depth constant
Why

FUN_800172C0 builds the field view from the live globals, never from the composer's staging descriptor: FUN_8005B4B8 copies the eye trio 0x800840B8/BC/C0 into a scratch matrix's t as three 32-bit words, FUN_8003D344 MVMVAs the focus through the scaled rotation with cv = TR and writes MAC1..3 back over that t, and FUN_8005B6A8 uploads it. The focus is the low signed halfwords of 0x80089118/1C/20. Held on every sampled frame of three field states. A 1x renderer divides the trio by the base matrix's scale. details

capture + disassembly
Retail's field camera focus Y0, on every sampled frame
Why

Only X and Z of the focus trio are ever written in the field - FUN_801DBE9C's retail leg and the focus clamp FUN_801DAA50 write that pair and nothing else - so 0x8008911C reads 0 on 19 of 19 library states while the player's footing on those frames does not. The composer's staging focus at +0x1A/+0x1E/+0x22 does carry the footing, and the view build never reads the staging descriptor. Vertical framing therefore rides the composed eye Y.

capture
Why the port's field framing sat low against retailentirely the focus-Y term
Why

Over the states whose pitch, yaw and H match, mean horizontal error is 0.1 px of 320 and mean vertical error 15.8 px of 240, always low - and it tracks the anchor exactly: 0.0 px at footing 0 (five states, IoU >= 0.996), +12.1 at -24, +31.3 at -128, +33.5..+38.9 at -192, with a pixel cross-correlation on town01 of +35 against an analytic +33.5. Both sides run the same projection kernel, and the composed eye trio is exact on seven of nineteen states with the four settled misses all mid-ease, so neither was the gap.

capture
The two scene-entry eye-trio writers - which runs last?they do not race, and the first field frame reads neither
Why

Field MAIN INIT FUN_801D6704 calls FUN_80025C24 at 0x801D698C and then FUN_8003AEB0 at 0x801D6DA8, which reaches FUN_801DE37C at 0x8003B01C; A before B on 3 of 3 entries, so B's (0, 0x200, 0x4000) stands. It does not matter either way - the follow composer replaces the trio within one vsync, and town0c's entry value equals the door-warp library state's trio exactly.

capture
Which sites re-query the camera zone?seven jal sites plus one gated per-frame site; a bare tile crossing is not one
Why

FUN_801DE3E0 (query + load in one) is reached from the three nibble-3 arms, [4C C4], the player seat / warp path at 0x801D1FE8..0x801D2014, its sibling at 0x801D2BCC and the SCUS field init at 0x8003B800. The field per-frame controller adds one at 0x801D17FC, gated on scratchpad flag bit 22; the per-mode seed copies a u16 and cannot set that bit, so only a script raises it, in eight scenes (a count of fifteen is the masked flag bit matched inside desynced records). The re-query rule is disc data, not engine policy. details

disassembly
Is the port's camera-relative pad remap retail's?exact over the whole input space
Why

Eight camera octants times eight held directions, latched from the same call of FUN_800467E8 on a live town01 field state: 64 of 64 cells agree with World::remap_pad_direction, including the ring's not-found case, and the ring DAT_800766FC carries the same eight values in SCUS_942.54, in RAM and in the port's constant - not the same bytes: retail's ring is u32[8] and the port's [u16; 8]. Retail publishes no ring step to read back - gp+0x2D8 is scene-authored (the free-roam state holds 0) - so the probe supplies the index. Probe scripts/pcsx-redux/autorun_field_pad_ring.lua.

capture
Is retail's actor facing the engine's heading plus a half turn?player+0x26 == render_26 + 0x800
Why

61 of the same 64 cells agree exactly; the three misses are the sweep's three 1536-unit turns, each 512 short at the cell's last held vsync because the actor eases toward the new facing.

capture
Does a zero field focus Y frame retail's field?yes - it is the whole vertical error
Why

Over the eight library states whose camera words match retail's, a focus anchored on the footing frames 15.8 px of 240 low on average and is exact only where the footing is zero; with focus Y zeroed the mean falls to 2.2 px, the four states with exact camera words are pixel-exact (player-box IoU 1.000), and a pure-shift pixel fit on town01 agrees to 0.3 px. Oracle crates/parity/tests/field_camera_zone_oracle.rs with LEGAIA_CAMERA_ORACLE_DUMP.

capture
What is the bag-normalize gate's base register?0x80084140, the live game-state block
Why

lui v0,0x8008 at 0x801E0580 and addiu s0,v0,0x4140 at 0x801E0584 sit immediately above the lbu 0x454(s0) == 2 / lhu 0x458(s0) == 0x100 pair at 0x801E05B0..C8 that guards the sole jal 0x800423E0 (0x801E05D0, PROT 0897): the member count 0x80084594 and the roster halfword 0x80084598, read at the tail of op 0x3C's accepted arm, so the bag normalizes when the party becomes Vahn and Noa. FUN_800423E0 itself opens with the active-window setter FUN_8004313C and walks the bag over gp[0x2D2]..gp[0x2D4] skipping zero ids.

disassembly
Is the resident scene-name string an input to the loader?no - it is the loader's output
Why

Overwriting 0x8007050C leaves it reading the replacement for the rest of the run while the game loads the original scene anyway; a capture that reads a scene name out of RAM is reading what the loader said, not what it will do.

capture
What does edbylon select when the tile query misses?nothing selects - the block is held from an earlier tile
Why

The ending-vignette state stands on tile (94, 43), outside the attribute box [77, 34, 104, 50], and all three of the scene's section-3 records are kind 0 with anchors outside it, so no record covers the tile (12,655 of 16,384 tiles select record #0). The scene's one [4C 38] site (arm 0x801E1048, jal 0x801DE3E0 at 0x801E1068) is the only query, so the block survives from wherever the player last crossed a queried tile; a query run there would miss and install the fixed zone-miss set (0x801DE408..0x801DE464).

disassembly + capture
Is the Throw Out cursor a bag slot or a list row?a bag slot, and the list hides empty slots
Why

_DAT_8007BB88 is the list kernel's selected-row payload, and the pause menu's Throw Out confirm FUN_801D8734 zeroes bag[cursor * 2] with it directly. A driven capture over a bag holed at slots 1/3/6 puts the cursor on 0, 2, 4, 5, 7, 8 and never on a zero-id slot across 561 vsyncs: the displayed list is compacted, the payload is not. The bag itself is not compacted on menu open - FUN_800423E0 runs zero times, and its sole reference is a field-VM arm at 0x801E05D0 gated on two words of the live block. Removing by display row throws the wrong stack away on a holed bag.

capture + disassembly
Is gp+0x2D8 (the pad-ring octant) camera-derived?authored - it is scene content
Why

Six writers disc-wide, all field-overlay: op 0x4C nibble 2's arm 0x801E0EB8 storing sub_op & 7, the tile-board walker's four (0x801EF8B0 / 0x801EF8B8 / 0x801EF8CC / 0x801EFE7C), and a clear at 0x801E5664. Two of its four readers are in SCUS_942.54: the pad remapper loads the word at 0x800467E8 and 0x80046840, which is what makes the octant a rotation at all. The arm also turns the player with it, so an author picks the octant to match the camera the same script installs. A free-orbiting port has no authored index to read and must derive one. details

disassembly
Who reads 0455_urudre1's descriptor 0?nobody - it is a switched-off duplicate
Why

The descriptor's type byte is 0x0A, a pure-flag arm the walk never dereferences (FUN_8001F05C's table 0x80010638 entry 0x0A is 0x8001F574: it ORs 0xA00 into the return word and leaves), and its payload is SHA-256-identical to the first 343,480 bytes of the live copy the scene actually loads. It is an authoring leftover holding a valid pack, not a slot with an unfound consumer. The three towns' reserved descriptor-0 payloads hash alike for the same reason.

disassembly
The 0x4C outer dispatch table, and its two non-handler armssixteen entries at 0x801CEE60; nibbles B and F are the error printer
Why

The outer table sits at 0x801CEE60, materialised by a lui+addiu pair and indexed by op0 >> 4 (srl v1,s3,4 at 0x801E0C44, sltiu v0,v1,0x10 at 0x801E0C48). Arm 0x801E3550 (nibble B) and arm 0x801E3538 (nibble F) each materialise their own string pointer with a lui+addiu pair and converge on retail's message printer at 0x801E3558 (jal 0x8001A068), so the two share a tail rather than a body; only 4C FF branches away first, to the ordinary continue at 0x801DF098 (beq at 0x801E3540). The disc agrees - the opcode census finds no coherent 4C Bx or 4C Fx in any scene.

disassembly
What the field state machine's slot 7 holdsthe submode return state, not a mode of its own
Why

The enter half installs it at 0x801F140C and parks it in scene[+0x40] at 0x801F148C, after which +0x50 is overwritten with the op-0x49 sub-op's own slot. So slot 7 is where the field returns to when a submode ends, which is why a port that collapses the chain and keeps no scene[+0x40] has nothing to return to.

disassembly
What does a field submode return to?nothing reads the parked word
Why

The op-0x49 enter parks a state twice through the scene pointer 0x801C6EA4 - scene[+0x2E] = -1, then scene[+0x40] = s4[+0x50] at 0x801F1400 and again at 0x801F148C; the pair idiom tiles PROT 0897 22 times. Nothing reads +0x40: SCUS_942.54 and all 86 extracted overlay images yield zero loads at that displacement off a register loaded from 0x801C6EA4, and a two-byte read watch records none while the pointer still names the scene struct. Later hits are the GPU working buffer the block is recycled into (0x80043F74, 0x800455E8, 0x80044054, 0x80059DE4). details

disassembly + capture
Does the field passive-ability badge column have a suppress gate of its own?no - it sits inside the party readout's
Why

FUN_801d095c has exactly one caller, the jal at 0x801D130C inside FUN_801D0D38, eight bytes above the epilogue that routine's suppress arm jumps to - so the badges carry the readout's suppression exactly and none of their own. The gate is the readout's entry block 0x801D0D38..0x801D0DBC, four terms: _DAT_8007B868, _DAT_800845C4 == 2, _DAT_8007B850 & 0xF000 and scratchpad 0x1F800394 & 0x00800000. A host gating the two columns on different predicates draws one of them over dialogs, cutscenes and battles.

disassembly
What do the fishing bite tick's two per-frame map reads do?one gates water, the other drifts the lure
Why

Both take the same point - the tick's own actor +0x14 / +0x18, the lure the cast spawned - and are otherwise unrelated. The water gate is bit 0x4000 of the +0x8000 cell halfword (andi v1,v1,0x4000 at 0x801D3374), whose class word FUN_800180EC rebuilds at 0x801D3384. FUN_801D7030's +0x4000 walk-grid probe feeds none of that: a hit drifts the lure's 24.8 x accumulator 0x801D9174 by frame_delta << 11, signed by the low bit of the lifetime cast counter _DAT_80084460. details

disassembly
Which screen opens window 25, and which opens window 41?different screens; no program opens both
Why

The menu image holds exactly one open-op (01 <win> 00 00) for each window: 0x801E4DDC, inside the Equip screen's candidate script 0x801E4DC8 (sub-screen 0x14, beside window 24), and 0x801E4E7C inside the shop-entry script 0x801E4E64. No program names both, and the equipment-buy recipient sub-screen FUN_801DB380 adds only window 36 over the set already up - the shop's recipient flow does not open both (falsified).

disassembly
What is the equip compare panel's category byte?the accessory passive index
Why

The lookup reads the item record's class byte first, then one of two tables indexed by that record's +1: class 1 takes the equipment bonus row 0x80074F68 + row*8 byte +5, everything else the item-effect descriptor 0x800752C0 + row*4 byte +3. Of 255 non-zero ids, 104 are class 1 and every bonus row they reach carries the 0x40 no-passive sentinel; 80 of the other 151 carry a real index - 9 under 6, 4 in 10..=12, the rest ATK / UDF / LDF. So the byte names the passive, and the panel shows the stats it moves. details

disassembly + capture
How many equip slots does the menu stat block sum?five
Why

FUN_801CF650's loop counter is bounded by slti a2, 5 at 0x801CF744, so the block behind windows 22 / 25 / 41 sums the first five equip bytes only; the battle-side aggregator FUN_80042558 walks all eight. On retail data the difference is inert - the accessory class the extra slots hold is absent from the equipment table - but the two are different routines, so a port sharing one kernel has to zero the tail for the menu side.

disassembly
What is the equip browse row's index space?a two-table slot map, not the equip-byte index
Why

Row 0 is the weapon row and takes a per-character halfword from 0x8007B42C, which reads 2, 3, 2 out of SCUS_942.54 - Vahn and Gala's weapon byte is 2, Noa's 3. Rows 1 and up index 0x801E43E8, whose bytes run 00 01 00 04 05 06 07, so the on-screen order is weapon, helmet, body, footwear, then the three Goods slots. slti v0, s0, 4 silences exactly the four gear rows, so retail resolves a compare category for the three Goods rows only, consistent with the 0x40 sentinel on all 104 class-1 rows.

disassembly
Which way does an obstructed sub-cell drift the fishing lure?by the cast counter's low bit
Why

Forcing the branch at 0x801D2E28..0x801D2E58 949 times gives 447 ADD hits, every one on an odd _DAT_80084460, and 502 SUB hits, every one on an even value - zero violations - with the 24.8 accumulator 0x801D9174 moving by exactly frame_delta << 11 per hit. Retail rarely sees it: at the catalogued fishing venue the walk-grid probe returned zero on all 949 calls, so the lure never drifts there.

capture
What writes the fishing cast counter _DAT_80084460?the bite tick itself, once per hook
Why

Reached through the save-block base rather than its own address - t0 = 0x80084140, then lw / addiu / sw 0x320(t0) at 0x801D2954..0x801D296C - which is why a displacement scan finds only the 0x4460 read. The increment sits on the hook arm, the one that raises cue 0x204 and sets SM state 0x19, so it steps once per hooked fish and the lure's drift direction therefore alternates between casts.

disassembly
What consumes the fishing bite tick's per-cell fish weight?it is the modulus of the caught fish's size roll
Why

One use: div s0, s4 at 0x801D3728, remainder taken with mfhi a3 at 0x801D3750, summed with three other terms and stored at DAT_801D91B8 (0x801D3814). The same value plus 0x400 becomes the render scale of the object the catch just spawned (sh a0, 0x72(s1) at 0x801D381C, on the object from the preceding jal 0x80024C88). It touches neither the species roll nor the bite credit - so a deeper cell makes a bigger fish, not a different one.

disassembly
What is the Baka Fighter VRAM-rect table DAT_801DBE84?exactly two records
Why

(0x340, 0xC8) and (0x340, 0xE0), both 6 x 0x18, both blitted to the same fixed destination (0x340, 0x86). Its eight bytes end at 0x801DBE8B, and everything above that to PROT 0976's 0xE000 end is zero - so it is the last initialised data in the overlay, and an index past the second record reads the destination's own column back rather than a third rect.

disassembly + disc bytes
Does retail's equip screen offer a Goods-slot candidate, and out of which id space?yes - item class 2, and the filter carries no character mask
Why

The screen has two candidate families. The slot-browse step writes window 23's +0 content id per row from the eight-byte table 0x801E4DC0 = 00 17 15 16 18 1C 1D 1E (PROT 0899 file 0x165A8), so the three Goods rows reach FUN_80030628 cases 0x1C / 0x1D / 0x1E, whose filter is item-record class 2 (bne at 0x800317D8) plus item-effect +3 != 0x41 (beq at 0x800317F8). The armament cases 0xE / 0xF / 0x10 carry their own character mask; the Goods cases carry none. On the USA disc 80 of 151 class-2 ids pass, and all 71 rejected carry exactly 0x41.

disassembly + measurement
What does the shop quantity window print?quantity / bound, not quantity x price
Why

Window 35's value row is a pair with a separator glyph between them (FUN_8003C1F8(6)): the second number call at 0x801D563C loads DAT_801E46B8, the word the buy picker's phase 0 fills with min(gold / price, 99, 99 - held). The price appears once, in the running total, and a currency pictogram draws at (WX + 0x58, WY + 0x24). The window scripts are buy phase 0 0x801E4EB0 (ending [01 23] -> window 35), buy confirm 0x801E4ED4 ([04 23]), sell phase 0 0x801E4F08 ([01 25] -> window 37) and post-sale 0x801E4F10 ([0A 26]).

disassembly
How does sub-screen 0x15 exchange two list rows?a two-press latch in one word
Why

Bit 0x1000 of the second cursor word means nothing is latched. A confirm taken with it set stores the hovered row index and clears the bit; the next confirm swaps the two rows and re-raises it; a cancel taken with a latch pending only drops the latch, leaving the list alone. So one word carries both the pending row and the fact that one is pending, and only the spell list's running step carries the arm at all. Settled in FUN_801DA2A0: step test 0x801DA714..1C, latch store 0x801DA748..64, swap and re-raise 0x801DA768..844, cancel 0x801DA854..A8.

disassembly
Which equip byte is the Ra-Seru slot?per character, from 0x8007B424
Why

The table reads 3, 2, 3 (lui 0x8008 / addiu -0x4bdc at 0x801DA5D0) - the exact complement of the weapon table 0x8007B42C = 2, 3, 2. So Vahn and Gala wear the Ra-Seru in byte 3 and the weapon in byte 2, and Noa the other way round; a reader taking either table as a constant gets one of the three characters wrong.

disassembly + disc bytes
Is the 0x801E43E8 byte run one table or two?three tables and a pad byte
Why

The browse row -> equip-byte map is seven bytes and stops. 0x801E43EF is alignment - no word, no lui/addiu pair and no branch in any image reaches it - while 0x801E43F0 (4 bytes, the per-character equip mask bits, anded with the equipment record's +6) and 0x801E43F4 (8 halfwords, the per-row slot pictogram ids) each have three materialisation sites of their own. Bytes [7..10] looking like the gear-slot indices is the pad plus the mask table's first three entries. field-menu.md

disassembly
What does retail's equip candidate step draw?three stacked panels; the compare category follows the hovered item
Why

One script, 0x801E4DC8, opens window 25 (name + one stat-row set), window 24 (item name with owned count, description, bonuses) and window 24's reserved box at (WX, WY + 0x38) sized 0x90 x 0x28 for an accessory passive. A pad ladder driven to each of the seven rows populates all seven lists. The slti v0, s0, 4 guard at 0x801D137C silences the four gear rows, so only the three Goods rows resolve a category - and they disagree with each other, because the row set follows the hovered item. field-menu.md

capture
Where is retail's own entry to sub-screen 0x15, and which lists can it reach?the root picker's row 3 = Status; steps 3 and 4 only
Why

0x801D6C4C, in FUN_801D6B20's row-3 arm, is the only site in PROT 0899 that writes 0x15 into DAT_801E46A4. Window 0x15 is a different id space - the Equip screen's party window, opened by script 0x801E4DA0. Inside the screen the character picker's confirm arm folds the second cursor and dispatches: folded 0 / 5 hop columns, 1 buzzes, 2 writes step 3 and 3 writes step 4. Nothing writes step 2, so the abilities list is decoded and has no door. save-screen.md

disassembly + capture
Does retail's wall slide fire in ordinary play?yes
Why

On s3_rimelm_freeroam the resolver FUN_80046494 answers the held mask on 261 of 276 calls and widens it on 15, all on one held-LEFT run down the town01 exterior wall (0x8000 -> 0xC000). At both pinned wall-press rest positions the resolver hands back the held cardinal, so those legs are slide-neutral. Port World::resolve_field_slide, called by the field stepper. field-locomotion.md

capture
Is the camera's visible-tile window a property of the scene?no - of the region
Why

A 3000-vsync pad-driven town01 walk never holds the window constant and never holds the port's default, alternating between (-8, -6, 8, 12) and (-10, -6, 8, 14) four times each as the player crosses regions - the camera-region record's mask-kind side-write. The walk stays in one scene; the door case is the scripted-tile-window survival row of this table. encounter.md

capture
What does field-VM 4C D8 spawn?a morph-weight actor; the two u16s are envelope rates
Why

FUN_801D77F4 allocates from the morph-weight descriptor 0x8007068C, whose +0x8 handler is 0x8002174C; the tail wires a VDF body off 0x8007B7DC by operand 1, a TMD off 0x8007C018 by operand 2 - a scene-bank index the op's arm has already offset by 0x8007B6F8 - and a rest pose snapshotted from the live vertices rather than loaded. The handler reads +0x3C / +0x3E at 0x80021890 / 0x800218B4 as the rise and fall steps of the weight at +0x6E, so the port's kind / variant names are the generic allocator's. script-vm-menuctrl.md

disassembly
Which target flow does a confirmed menu spell open?the spell's own stats +2 bit 0x20 picks
Why

Set routes to the no-pick group sub-screen 0x10 (FUN_801D9280, FUN_801D688C called with count = 0); clear routes to the per-member picker 0x11 (FUN_801D9594). A party-wide heal is therefore one confirm and a per-member resolve, not a folded single-target cast. field-menu.md Settled: lbu 2 off 0x800754C8 + id*12 and andi 0x20 at 0x801D9220..44, selectors 0x801D924C / 0x801D9258; a1 = 0 at 0x801D9334.

disassembly
Does retail's cold entry into conc clear flag 0x6DE?yes, twice - and the flag is a live position predicate
Why

A single-flag write watch from the memory-card load screen sees the scene load clear it from P1[1]'s spawn prologue (+0x10) and the P1[0] entry script (+0x18) - ra 0x801E35C0, the field overlay's CLEAR site - and then P1[0]'s per-frame body SET it every other frame from +0x10B (ra 0x801E3598) behind CD F8 0A 0E 33 48 at +0x100, for as long as the player stands outside tiles 10..=51 x 14..=72. Poking the player inside stops it (60 SETs before, 0 in 280 ticks after). The card-boot save stands at (17, 97). script-vm.md

capture
What position does a system script's CD F8 box test read?the live player's, on every evaluation
Why

Retail resolves cross-context target 0xF8 to the player object each time (FUN_8003C83C, 0x8003C83C..0x8003C854). The port re-seats its ctx-0xFB system context's anchor before each field frame slice (World::sync_field_ctx_player_anchor); without that, only the load-frame pre-run of opdeene / opstati / opurud seeds the anchor, the script install resets it to the origin, and every per-frame box test runs from tile (-1, -1) and answers "outside". 661 of the 668 clean 0x4D sites disc-wide are the CD F8 form, over 92 carriers.

disassembly + capture
What do the two screen-effect pushers of a conc -> conc2 door write?two channels, told apart by the OT bucket
Why

Departure: 33 passes at a two-vsync cadence from the field overlay's ra 0x801DDE24, two concurrent pushes - bucket 2 an ambient 0x00003030 decaying, bucket 0 a white-out ramping to 0xFFFFFF. Arrival: 15 passes from SCUS's ra 0x80025034 on bucket 1, 0xEEEEEE falling to 0. The first argument of FUN_80024EE4 therefore selects a channel with two callers, and red sits in the colour word's low byte.

capture
What does 4C D8's model operand index?the scene bank, not the global pool
Why

The arm adds the scene-bank base before the call - lhu s0, -0x4908(v1) (0x8007B6F8) then addu at 0x801E2DE0..0x801E2DE8 - and FUN_801D77F4 reads DAT_8007C018[slot] unadjusted. On all seventeen sites the one-record block's first_vertex + delta_count equals the vertex count of object 0 of scene model n exactly, balden's operands 99 / 100 / 109 / 110 included (through balden2's count-5 MAN-less table). script-vm-menuctrl.md

disassembly
Does retail walk the morph block with one record pitch?three, and they agree only on a one-record block
Why

The spawner's size sum steps 0xC (0x801D78D0..0x801D7900), its rest-pose copy n_vert * 8 (0x801D792C..0x801D799C), and the apply pass FUN_8002174C n_vert * 0x60 (0x800217B4, 0x80021860). Every block the disc ships is one record naming TMD object 0, so the disagreement is unobservable - a different claim from the pitches being equal. script-vm-menuctrl.md

disassembly
Does any beat take op 0x34 sub-0's forked arm?no writer of the gate bit executes
Why

The arm forks on _DAT_1F800394 & 0x800000 (lui v1, 0x80 at 0x801DFD1C and 0x801DFEB0 in PROT 0897; set -> FUN_80024E80, clear -> FUN_801DE2B0). A writer census of bit 23 over SCUS, every mapped overlay and every scene carrier finds no store the disc executes (scratch_global_bit_writers_real, and the field-op census), so the forked arm is never taken on retail. A block copy into the scratchpad is not excluded by a store census.

disassembly + capture
Does any MAN author motion-VM ops 0x10 / 0x11?no
Why

Zero of the 573 section-1 stream variants carry either, so the b1 & 0xC0 == 0xC0 assert arm that stores 0x3039 into 0x8007B828 has no shipped driver.

disassembly
What does FUN_8001FA00's one caller seed?the fog-particle pool's free stack
Why

Its only jal is at 0x801D7384 in MAIN INIT (PROT 0897), passing (pool, pool + 4, 0x50): an identity list of eighty slots under a one-based top index. Not a cutscene sprite list. Port engine-system::scus_leaf_kernels, seeding FogPool::reset.

disassembly
Is the 192-byte block at 0x801F21B4 one twelve-row probe table?three tables
Why

Three consumers form three bases: the actor-collision probes at 0x801F21B4 (six rows; lui/addiu at 0x801CFE74 and 0x801D5A70), the leading-edge wall probes at 0x801F2214 (four rows; 0x801CFEE8, 0x801CFFC0, 0x801D009C) and the interact facing compass at 0x801F2254 (eight ±64 points; 0x801D0834). The wall and compass tables are not caller-less rows of the first. field-locomotion.md, legaia_disc_audit::field_probe_tables.

disassembly
What clears the two halt bits an inn acquire sets?the acquire's own FaceTarget leg in the walk kernel
Why

FUN_8003BC08 runs the dialog SM and then, on +0x10 & 0x400, FUN_8003774C (0x8003BD50), which reads the acquire's CC F8 85 14 00 33 as a 0x14-frame player FaceTarget on bind 0x33; its terminal frame clears the player's bit at 0x80038004 and its own actor's at 0x80038028. A PCSX-Redux watch on retock_innkeeper_talk_open sees set at 0x801E21A4 / 0x801E21CC and both cleared 18 vsyncs later, the box already open (script-vm.md).

capture + disassembly
Whom does a talk acquire's FaceTarget turn toward?the actor its bind names
Why

The kernel resolves the bind at op +3 like any cross-context id (0x80037E00..0x80037EA8): 0xF8 the player, 0xFB the world-map entity, else the _DAT_8007C354 node whose +0x50 matches. Of the disc's CC F8 85|8E|8F acquires, 40 of the 146 in placement records bind another actor, and the 24 in object records and 861 in cutscene records have no own actor (script-vm.md).

disassembly + disc census
What does op 0x42 mode 1 test?the held pad against a compass
Why

0x801DFBDC..0x801DFC9C tests _DAT_8007B850, the packed held pad: & 0xF000 against the compass word 0x801F28D0[op1 * 4] for op1 < 8, Circle / Cross / Square / Triangle for 8..11, and op1 >= 0xC takes the jump untested (script-vm.md).

disassembly
Who places the pair concnow and concend hold on arrival?the arrival record, not a spawn prologue
Why

concnow P2[13] seats flats 0x32 / 0x33 with CC 32 51 1E 1C 84 0A / CC 33 51 1E 1C 84 11 (+0x7A, +0x81) and walks them with C7 32 1F 1D 33 / C7 33 1D 1D 33 to tiles (31, 29) and (29, 29); concend's record does the same with CC 07 51 2E 27 / CC 08 51 2E 27 and C7 07 2F 28 33 / C7 08 2D 28 33. A cold entry parks them because retail's own entry does.

disassembly
What wrote flat 72's seat in town0b_field_door_hop?a port defect, fixed
Why

install_boss_stagers_from_man: P1[36]'s 3E FF 03 makes it a stager, and the install wrote the first non-park 4C 51 in its bytes, the 0x141 arm's (15, 21), over the seat the entry pre-run took off the flags. The state holds 0x147 up and 0x141 clear, so retail's actor is parked. A record the pre-run ran keeps the pre-run's seat, and its contact is dead while the actor stands parked: rikuroa P1[3] stays off the map until 0x286 selects its (69, 75) arm.

disassembly + capture
What is 0x1F800394 & 0x80000?the walk-on same-tile re-poll
Why

While it is set and the movement lock clear, the dispatcher re-runs the tile's kind-1 record on an unchanged tile (0x801D2090..0x801D20F8); a crossing clears it first (0x801D2110..0x801D2120). 2E 13 is its only setter; its users are Rim Elm's stand-and-press-Down polls, town01 P2[12..14] on tiles (30..32, 19) and their town0b..0e twins (field-locomotion.md).

disassembly + disc census
What tag does the timed warp put on 0x801DA7F0?the 4C E1 text balloon
Why

The timer-running arm (0x801D1EF4..0x801D1F20) tags for tear-down the first live actor whose handler is 0x801DA7F0, the balloon op 4C E1 spawns (field-locomotion.md).

disassembly
What does FUN_801DB510 do while 0x8007B606 is clear?it pins the focus and runs the shake tail
Why

beq a1,zero,0x801DB820 at 0x801DB558: the pin leg sets the focus to the negated player position, composes and eases nothing, and falls into the shake tail. The same leg serves _DAT_1F800394 & 0x400. The byte's writers are the new-game init (SCUS 0x80034B60) and the developer menu's CAMERA row (0x801EAD38, 0x801EA1C8).

disassembly
Does FUN_801CF8AC change anything past its box test?only the +0x10 & 3 exempt early-out
Why

Its no-class arm needs bit 17 clear, which nothing on the disc clears, and all three callers keep only & 1 of its result (0x800384C0, 0x80038A78, 0x80038F3C), so that arm's 4 is dropped; the +0x98 link is overwritten before any read (motion-vm.md).

disassembly + disc census
Is MAIN_INIT's FUN_80017BEC refresh observable?yes, in three scenes
Why

Called unconditionally at 0x801D6BF8, it changes cells in retona (1), juui1 (1) and noaru (2, gaining the 0x800 elevation stamp). retona_field_card_boot holds 0x306B at tile (0x1D, 0x18) where the disc's .MAP has 0x006B; the dolk2 and chitei2 captures equal the refreshed map apart from 0x400 bind bits (field-locomotion.md).

capture + disassembly
Who spawns the scripted-scene actor FUN_801D4A60's openers?the two world-map travel arts
Why

The only other jal 0x801D5A24 sites are 0x801EE110 (Riremito, program 1) and 0x801EE3A4 (Rula, program 0); the pair it parks on is the side-band bank pair _DAT_8007BABC / _DAT_8007BAA0 (field-locomotion.md).

disassembly
What does Incense do?it suppresses encounters outright for a walk-tick window
Why

Item class 0x82 reaches FUN_80046870 (jal at 0x800421A0), which adds 0x40 to gp+0x2E8 (_DAT_8007B600), capped at 0x100. The walk tick FUN_801D0B90 drains it (0x801D0CD4), and the region roll FUN_801D9E1C skips its whole roll while it is non-zero (0x801DA174), step counter included. The Use list greys the row from 0xE0 (FUN_80046898, slti 0xe0) (field-menu.md).

disassembly
Does the field Magic screen charge the MP-saver discount?yes, on every path
Why

SCUS 0x8003118C..0x800311A4 inlines the discount and greys a row on +0x10A < cost (0x80031204); PROT 0899 debits the value FUN_80035394 returns (single cast 0x801D93C0, 0x801D9404..0x801D9418; group cast 0x801D972C), the re-cast gates compare against it and both panels display it. The engine prices field, battle and dome casts through one discounted kernel (battle-formulas.md).

disassembly
When does an Incense leave the bag?at the confirm, one copy
Why

FUN_801D8D94's Yes arm calls FUN_80042310(0x8A, 1) at 0x801D8E64..0x801D8E68 - before the class-0x82 applier at 0x801D8EAC - then runs the window script 0x801E4A80 and returns to the Use list (submenu 6). So one copy buys one top-up, and the list the route returns to no longer offers a copy the bag lacks. The port consumes the copy at the confirm (engine-menus::pause_screens) (field-menu.md).

disassembly
What does the Incense wear-off show?a one-line notice
Why

Entry kind 0x0B maps through 0x801F33A4 to slot 0x32, FUN_801F1E48: window record 16, painter FUN_801F1B64, one string naming item 0x8A and saying its effect is gone; confirm plays cue 0x20, hides, clears _DAT_8007B450. Ported as engine-core::incense_notice on both hosts (script-vm.md).

disassembly
What is menu window 8?the art-learned notice a Hyper-Art book raises
Why

FUN_801DCD58 renders the resident template 0x801E4700, referenced only by its own lui / addiu at 0x801DCD68. The book arm calls FUN_80035C00(slot, art) at 0x8004208C, two stores into gp+0x858 / gp+0x860; the Items screen seeds both to 0xFF (0x801D850C) and opens window 8 through script 0x801E4C60 only when _DAT_8007BB78 changed (field-menu.md).

disassembly
Which sub-screen does the menu driver open for an entry context?a kind table, with a literal-1 escape
Why

FUN_801DC6B4 0x801DC85C..0x801DC8E4 opens sub-screen 1 by default and maps kind 0 to 0x1A, 1 to 0x19, 7 to 0x20 and 0x0D to 4; a context pointer equal to the literal 1 opens sub-screen 2 and clears the pointer (field-menu.md).

disassembly
What are FUN_801DD330's arguments to FUN_801DA9F8?a window id and an exit sub-screen
Why

(0, 9, 0x30, 1): 0x30 is the settings window's id, patched into bytes +5 / +9 of the script at 0x801E4E08 (0x801DAA78), and 1 is the sub-screen stored on exit (0x801DAC24), the root command picker - not an init word and not a slot selector (field-menu.md).

disassembly
Which Magic-list rows does the field menu grey?every spell that fails any of three tests
Why

The list build (0x80031130..0x80031264) writes each row as 0x5800 | id and rewrites it 0x5000 | id only when record +2 bit 0x02 is set, MP covers the discounted cost, and the broadcast FUN_8003053C answers non-zero (field-menu.md).

disassembly
How does the fishing strike credit count pad nudges?one per mask hit on the newly-pressed word
Why

PROT 0972 0x801D343C..0x801D3468 loads _DAT_8007B874 once and adds 1 for each of 0x8000 (Left), 0x2000 (Right) and 0xC0 (both reel bits, one mask), so a same-frame Cross + Square counts once and the cast press counts none (minigame-fishing.md).

disassembly
When does Baka Fighter book an exchange?on the winner's strike keyframe
Why

Fighter block +0x0C is the strike state (0 armed, 1 landed, 2 consumed), +0x90 the pre-step cursor and +0x98 the landed keyframe. The combat tick FUN_801D3F44 calls FUN_801D6E5C at 0x801D4334; the resolver's arms require state 1 (0x801D36DC, 0x801D3730, 0x801D378C) and the damage kernel writes 2 (0x801D3EB0). A special commit sets DAT_1F80037D = 4 (0x801D4568), halving both clip steps for the round (minigame-baka-fighter.md).

disassembly
What is Baka Fighter's FUN_801D49E8?the special attack's afterimage
Why

Spawned on the special commit (0x801D4538..0x801D4634) with +0x74 = 0x81000000 and +0x78 = 0x800 (0x801D4588..0x801D4594); +0x78 is a depth-cue level FUN_8001B964 reads at 0x8001BC7C, not a yaw. It draws two ghosts of the thrower, three and six frames behind, at cue 0x800 / 0xC00, pushed 0x40 deeper (minigame-baka-fighter.md).

disassembly
Who is Baka Fighter's round-start cameo?scene model 3, the ring girl, who winks
Why

FUN_801D6310 spawns only with Triangle held at round setup (0x801D0190..0x801D01C4). The cabinet init stamps scene-bank base + 3 into its prototype (0x801CF2C8..0x801CF2D8) and the spawn store at 0x80020E70 writes 3: the PROT 1203 stage pack's fourth TMD. Its cell blit swaps an open-eye patch for a closed one at the pose (minigame-baka-fighter.md).

capture + disassembly
Can a retail disc reach the Baka Fighter keyframe editor?no
Why

State 0x190 is written only from the developer-menu arm (0x801D19DC), and that menu (0xC8) is entered only while _DAT_8007B868 != 0 (0x801D08E8); a disc-wide sweep finds two stores to that word, the constant 0 from FUN_8002B92C and a bit-clear (minigame-baka-fighter.md).

disassembly
Why does opdeene run long after its apply 4800 camera move?five record mechanisms; the port is within 2 % of retail
Why

NPC turns run on while the record advances, AD <id> 08 waits on the poked clip's end latch, B2 <id> 0A ends the actor's leg, the 4C 45 ramp advances, and only B3 F8 0A waits for a crawl. details ↓

capture + disassembly
Does a field item a player has picked up still block the tile it stands on?yes, for the rest of the visit
Why

A placement leaves FUN_801CF754's candidate table only on +0x10 & 3, and jouinc P1[17]'s pickup (50 5D, 39 EA, then a 21 loop) writes no flag bit, so the item's ±40 box stays. autorun_item_gap_probe.lua on the jouinc library state, the corridor's region box latched, stops a walk east into the item's wall gap (82, 39) at x 10552 before the pickup and at 10522 after it. The item's spawn arm draws it at scale 0 (4C 40), which hides it and changes nothing the collision reads. full-game-ladder.md

disassembly + capture

Long write-ups in this area

opdeene runs long after its apply 4800 camera move

Status: resolved, evidence grade capture + disassembly.

The record's length is the sum of five field-VM pacing mechanisms, all in the engine. Ground truth is a per-vsync capture of the zero-input chain from the s1_newgame_field state (autorun_opdeene_pacing.lua): the record's ctx[+0x9E] PC, the camera mover's progress, one vignette actor's flag word, clip ids, +0x62 / +0x68 / +0x6A, position, heading and the crawl roller's retire count.

  • NPC turns do not park the record. The op-0x38 budget arm advances by 3 for every target (li s7,3 in the delay slot at 0x801DEEFC) and parks only for the player; the record reaches the next op after a B8 08 82 21 on the same frame.
  • AD <id> 08 spins on the poked actor's clip end latch. A spin lasts the clip's remaining length (clamped) or the time to the next wrap (looping): actor 0x07's clip 0x18 holds +0x4E4 for 90 frames, actor 0x05's clips 8 / 9 for 27 / 57. The port binds the actor's clip cursor at the 0x22 poke from the scene bundle's record header (gate and divisor) and steps it at the live +0x6A, which CC <id> 41 halves - a clip at rate 4 runs twice as long.
  • B2 <id> 0A ends the actor's leg. The actor tick runs the walk kernel only while +0x10 & 0x400 is up (FUN_8003BC08), so the halt clear stops a compass walk or turn where it stands. Two Seru legs cut this way account for about 300 frames.
  • The 4C 45 ramp does not yield. The arm adds its 5 bytes at 0x801E12A4 and leaves through the common or the scheduler exit; the capture runs eight CC <id> 45 .. 28 00 ramps inside one frame.
  • Only B3 F8 0A waits for a crawl. The crawl spawn is a halt-acquire on the player (0x801E1F24) and the roller clears the bit as it retires its last page. opurud's record tests it before its 3F and changes scene four vsyncs after the last page; opdeene's does not, and its 3F runs with the 8-page roller three pages short of retiring (cutscene.md).
  • An NPC compass walk on an actor nothing has moved starts from the context's seat: actor 0x05's C1 05 00 C4 takes 129 frames.

Measured: from the record's second opening wait (+0x289) to its 3F the engine runs 3773 frames against retail's 3828; from the apply 4800 beat to the 3F, 2268 against 2313. The gap is the frame step: the capture holds DAT_1F800393 at 3, so retail's waits and clip ticks land on three-frame boundaries the engine's one-frame step does not reproduce; at +0xB4D that decides whether a looping clip's latch lands before or after the AC 04 08 that clears it (one loop, 60 frames). The retail-compare state parked on +0x6F6 keeps its exact camera. Pinned by opdeene_record_pacing.rs (3 % band).

The field follow camera's pose chain

Status: resolved - the chain is pinned by disassembly and the pose measured over the walkable state population

Retail derives the follow camera per scene and per tile through a four-stage chain; none of its outputs is a constant.

StageRoutineWhat it does
LoadFUN_801DBC20Splits one 18-byte MAN section-3 camera-region record into the parameter block at 0x8007B607..0x8007B627, choosing among three layouts on the high nibble of record[5].
ComposeFUN_801DAB90Reads that block, the player's position and floor height, and the walk-region attribute box at scratchpad 0x1F800384..87; writes the staging descriptor at 0x801F3580.
EaseFUN_801DB510Walks the six-entry descriptor list at 0x801F2798 toward the staging fields by delta >> shift plus the sign of the delta; shift from the sixteen-byte table at 0x801F2804 indexed by DAT_8007B60B >> 4.
SnapFUN_801DB8ECThe same compose plus list walk with a plain copy.
  • Compose samples the floor height through FUN_80019278 with the MAN's static elevation LUT swapped in, so a scripted floor-tier bob moves the player without shaking the camera. The pitch couples to that floor height, not to a heading.
  • Ease runs only on frames the player's position changed, so the camera stays short when he stops mid-glide.
  • The trig LUT pointers are _DAT_8007B81C sine and _DAT_8007B7F8 cosine.
  • FUN_8005B0B8 is a PsyQ-shaped SquareRoot0 over the 192-entry mantissa table at 0x80078E84, not a bit-packing helper.
  • Checked against the listing: the load's nibble test and layout forks (0x801DBC34..0x801DBD38), and the ease's staging pointer, shift table and descriptor list (0x801DB5B4..0x801DB5FC).

Owning page: encounter.md.

Chapter-1 scene frontier

Status: resolved as a per-scene verdict (evidence capture: a disc-derived ladder plus the capture library); the five scenes that read as sealed are not - see the Uru Mais and jouine exits

The chapter-1 reachable set is the BFS closure of town01 over each scene's own decoded 0x3F destinations. It is 27 scenes, it ends at exactly one kingdom boundary (jiji -> map02), and it holds the whole Drake kingdom past the Ravine: the boss chain, the four-deep Drake Castle interior, the Uru Mais rooms. All 27 load their assets, parse their MAN, enter in Field / WorldMap, settle their entry script, carry a door, and can be walked out by pad alone.

  • The closure is a reachability partition over 0x3F only. Scenes reached by the sibling 0x3E door warp (a scene-type selector, not a name) are outside it.
  • urudre2 walks out once op 0x45 CAMERA APPLY is decoded correctly (falsified reading).

Door shapes, from three decoders over the same MAN (the clean per-partition fall-through walk, the recovering destination-table pass, and the .MAP gate-1 trigger -> partition-2 record -> 0x3F join):

ShapeScenesWhat has a door
op, table and walk-on trigger22 of 27all three decoders
op behind text pages, trigger in the .PCH sidecaruru, urudre1, urudre2, urudre3all three decoders, plus the sidecar for the trigger; the clean walk crosses the inline 0x1F text kilobytes ahead of the 0x3F because a text segment is one decoded stride
FMV hand-off, trigger in the .PCH sidecarjouineno 0x3F at all - the exit is 4C E2 08 (FMV 8 -> town0e), on the FMV dispatch table

Probe limits that read as "sealed" on the bottom two rows:

  • The walk-on sweep stops at 48 deduped gate-1 tiles; uru carries 118 with its exit band at positions 63..66, urudre2 carries 186.
  • The 24-tick post-step budget cannot run a record that spends 300+ frames in explicit waits or, for jouine, a 6.8 KB boss cutscene.
  • The .MAP gate-1 join is half the join: these exits are carried by the scene's .PCH trigger sidecar, which the fallback read of FUN_801D5630 reaches.

On a first visit izumi's C1-gated spring record relocates the player about thirty tiles with the pad released, so a driven probe there cannot beat its own released-pad control; a revisit probe walks normally. That is a script, not a locomotion defect.

Measured by crates/parity/tests/chapter1_frontier_ladder.rs; nine of the closure's scenes are cross-checked against the capture library's main RAM and all nine enter in-engine.

The Uru Mais chain and jouine exits

Status: resolved - grade disassembly + capture (the uru exit fired live).

All five scenes carry a walk-on exit, and every exit band lives in the scene's .PCH sidecar (scene-v12-table.md), not its .MAP; only urudre2's is doubled into the .MAP.

SceneExit recordGate-1 bandTail op (MAN offset)Destination
uruP2[42](36..39, 5), .PCH rows 23..260x3F at 0x0D4B7MAP03 (0x24, 0x46)
uruP2[37] (C2 = [0x36F])(37..39, 44)4C E2 07 at 0x0CB11uru2 via FMV 7
urudre1P2[2](35..37, 22..24)0x3F at 0x01804uru (0x40, 0x40)
urudre2P2[9](26, 14) + (24, 13)0x3F at 0x01D78map01 (0x26, 0x51)
urudre3P2[0](51, 90)0x3F at 0x02461uru (0x40, 0x40)
jouineP2[16](17, 17..19), .PCH rows 3..54C E2 08 at 0x03E90town0e via FMV 8
  • uru also carries the three dream entrances (P2[29] -> urudre1, P2[33] -> urudre2, P2[31] -> urudre3), all .PCH-only and ungated.
  • The four 0x3F records share one byte-exact tail: B1 F8 13 (set flag 19), 34 05 FF FF FF 41 00 (white fade), the 0x3F, then the 26 FF FF / 21 / 26 FE FF park pair.
  • jouine's exit is the FMV hand-off of str-fmv-table.md (0689_jouine -> fmv_id 8 -> MV6.STR -> town0e, door 0x2E5), mapped by the engine's fmv_post_play_handoff. It cannot be pad-probed from the catalogued state, which is already inside P2[16] running the evolved-Cort fight.
  • Live pin. From uru_field_run (tile (38, 6)), holding the pad toward (38, 5) fires FUN_8003BDE0(36, 5, 42, 1) and then FUN_8001FD44("MAP03") with ra = 0x801DEB1C (the field VM's 0x3F arm); probe scripts/pcsx-redux/autorun_uru_exit_probe.lua. The row (36, 5, 42, 1) exists only in the .PCH; uru's .MAP gate-1 records are [1, 3, 43].
  • Ruled out in all five: a 0x3E door warp with op0 >= 100, a 0x4C staged-menu-warp arm, a kind-0 teleport row in the .PCH. The scripted-motion VM has no scene-change opcode.
  • Unexplained: urudre2 returns to map01 (Drake) while uru exits to MAP03 (Karisto).

Owning page: world-map.md.

The upper-case destination fold

Status: resolved - grade disassembly

A 0x3F destination name is case-insensitive in effect, because it ends in an ISO 9660 file open and ISO identifiers are upper case.

  • FUN_8001FD44 strcpys the operand into 0x80084548; the field asset loader FUN_8001F7C0 (0x8001F7E8..0x8001F88C) strcats it into DATA\FIELD\<name>.MAP for FUN_8003E6BC -> FUN_800608F0, the ISO file open.
  • 40+ distinct upper-case destination names appear across the 99 scene MANs (MAP01/02/03, KOR*, DREAM, RETOCK*, ROPEWAY, NILBOA, the ED* ending chain), every one a CDNAME label. The disc carries no mixed-case 0x3F name run.
  • legaia_scene_formats::field_disasm::clean_scene_name accepts a uniformly-cased 3..=12-byte alphanumeric label and folds it. With the fold the town01 closure is 68 scenes with 20 kingdom-boundary edges.
The count-5 asset tables

Status: resolved - grade disassembly + capture

Retail bounds the descriptor count nowhere: FUN_80020224 reads +0x00 (lw s3,0x0(s4) at 0x80020288) and loops that many descriptors - the only exit is slt v0,s1,s3 / bne at 0x800202C0..0x800202C4, each pass reading the descriptor pair at +0x08 / +0x0C of an 8-byte stride. The reliable signal is descriptor 0's anchor at 8 + count * 8 (0x30 for count 5).

  • The count-5 tuple is (TimList, Man, Move, Anm, Flag(0x14)) - the canonical seven minus Tmd and Vdf.
  • Census of every PROT entry parsing as this table: 1 / 2 / 10 / 4 / 8 / 80 entries at counts 1 / 3 / 4 / 5 / 6 / 7. Of the sub-6 set only two carry a MAN.
  • The detector admits count >= 4 gated on a type-3 descriptor, which reclassifies exactly those two entries.

Owning page: scene-bundles.md.

The ledge-hop lock leak

Status: resolved (engine bug, fixed) - grade disassembly + capture

start_field_ledge_hop ORs 0x0008_0000 into the player's move_state.flags (retail 0x801D25A8..0x801D25B8 on the player context's +0x10) and only the hop phase machine's end arm clears it. A scene transition landing mid-hop tears the machine down without that arm, and the locomotion step returns early on the bit in every later scene. Retail cannot reach the state: its hop always finishes before a transition.

  • Reproduction: walking the closure in one host, tower hops and ends at tile (0, 0), and all 28 scenes after it report the flag with zero driven tiles while each walks normally on a fresh host.
  • Fix: SceneHost::enter_field_scene drops the hop and the lock together.
Clip-end latch for cross-context clip pokes

Status: resolved from the disassembly of both halves and ported; residual is port fidelity, not an open question

ctx[+0x62] is the clip-control word and bit 8 (0x0100) is its end latch (script-vm.md). When a flag triple carries a 0x80-prefix target, the op runs against a different actor record, and the latch is written by that actor's own anim tick FUN_800204F8 - the same routine a prop reaches.

Resolver FUN_8003C83C:

  • 0xF8 returns _DAT_8007C364, the live player object, without walking any list (li v0,0xf8 / bne a0,v0,0x8003c858 / lw v0,-0x3c9c(v0) / jr ra).
  • 0xFB walks a second list for the entry whose +0xC handler is 0x801DA51C.
  • Every other id walks _DAT_8007C354 matching *(u16*)(ctx+0x50).

Anim tick FUN_800204F8 (ghidra/scripts/funcs/800204f8.txt):

HalfWhat it does
Binder (0x80020570..0x800205A8)Only when +0x5C != +0x5E: remember the id, sh zero,0x68 (cursor to frame 0), point +0x4C at the clip. +0x62 is not touched - hold / clamp / reverse are the script's to set, and clearing the latch is its AC <t> 08.
Advancer (0x800205AC..)Consume +0x62 bit 0x200 (restart), clear bit 0x100, step +0x68 by +0x6A unless bit 0x2 (hold) is set, then wrap or clamp at either end and set bit 0x100 there.
  • The advancer scales its step by the scratchpad frame-step byte _DAT_1F800393 (mult a0,v0 at 0x80020660 / 0x80020680). PropAnim::tick advances one step per call, which is identical whenever that byte is 1.
  • Worked example, the retock innkeeper's Yes branch: AC F8 01 (un-hold), A2 F8 04 (poke the clip), AC F8 08 / AD F8 08 (clear, spin), AC F8 03 (un-clamp), 4A 03 00, AB F8 03 (re-clamp), AC F8 08 / AD F8 08 again, then A2 F8 02 handing the player back to the locomotion move. Every bit is an ANIM_* bit; none is a per-record local flag.

Port. engine-scene::field_env::PropAnimBank holds a cross-context cursor per target byte (actor_clips, keyed the way the resolver keys its walk). World::step_inline_dialogue binds the poked actor's +0x62 into the executing context around each 2B / 2C / 2D, mirrors it back, and parks on the spin - the bind / re-sync / mirror-back discipline World::step_prop_interaction runs a prop's whole record under, narrowed to one word. PropAnim::tick is the port's only latch writer, and the cursor advances once per frame whichever driver reaches it first (the field frame's tick_prop_interactions, or the runner itself for a host that drives only a conversation). Pinned by crates/engine-core/tests/inline_clip_latch.rs: the latch appears on the poked actor's cursor, never on the record's own flag word, and a stalled cursor never lets the spin through.

Port fidelity. An actor's drawn clip and its latch cursor are two objects in the port where retail has one struct: the player's gesture is played by the host's FieldPlayerAnim off World::locomotion.player_move_cues, an NPC's by the host's clip player, and the latch is timed by the bank's cursor. Both take their step from actor_tick::clip_step with the record's gate and divisor and are sized from the scene ANM bundle where it resolves the poked id; a clip the bundle cannot name falls back to a stand-in length. No capture rules out another runtime writer of bit 8; a script could set it with a 2B <t> 08 of its own, and none of the records read does.

Ambient render-mode 4 - the VRAM-rect scroller

Status: resolved - decoded from the disassembly and ported

Render mode 4 is a cyclic VRAM-rect rotation, seated by move-VM op 0x1E and fired by the render tail on a countdown.

  • Seat (80023070.txt 0x80023694..0x800236F0): +0x5A = 4, then seven operands into +0xC4 (period reload), +0xCC / +0xCE (horizontal / vertical step) and the rect +0xD0..+0xD6. The live countdown +0xC6 is not seated, so a new part fires on its first tick.
  • Fire (80021df4.txt 0x80022CB8..0x80022EE0): drains +0xC6 by DAT_1F800393 alone (the mode-3 sibling also folds in the DAT_1F80037D speed scalar) and fires on the tick the stored halfword's sign bit sets (sll v0,0x10; bgez).
  • Rotate, horizontal then vertical: FUN_8005842C captures the leading strip into a buffer bump-allocated off 0x1F8003A0, FUN_80058490 slides the remainder, FUN_800583C8 re-inserts the strip at the far edge.
  • Carriers. Seventeen scenes seat one from their plain scene-entry ambient tree. Sixteen scroll vertically only, upward, over a rect at x >= 0x200 (falling water, energy columns); tunnelc's second seat is a full-width one-row rect at (0, 508) stepping right - a CLUT row. Count carriers by walking the records through the move VM: the records jump, so a linear scan finds 0x1E-shaped bytes inside operand streams.
  • Port. engine-field::vram_scroll, applied in tick order by World::step_ambient_fx (the rotate is destructive; the mode-3 write is recomputed each frame from a cached capture). Disc-gated crates/engine-core/tests/ambient_mode4_scroll_disc.rs.

Owning page (per-scene rect table): field-ambient-fx.md.

Which op-0x34 sub-3 installs fire at scene entry

Status: resolved - decoded from the disassembly and ported

FUN_8003A1E4, the pre-run the placement spawn loop calls per just-spawned placement, carries its own copy of the per-actor script runner's frame slice rather than calling FUN_80039B7C. An install fires at entry exactly when it sits inside that slice.

  • 0x8003A480: lbu the first opcode, addiu v0,v1,-0x24, sltiu v0,v0,0x2, beq v0,zero,<skip> - unless the first byte is 0x24 or 0x25 the VM loop is skipped and the script never runs at load.
  • 0x8003A498..0x8003A4F4: run while (opcode & 0x7F) >= 0x20. After dispatching, beq s1,s4 against li s4,0x21 breaks the slice on the raw byte 0x21 (a cross-context 0xA1 does not break), as does an unchanged returned PC.
  • 0x21 and 0x25 both disassemble as "nop" and only 0x21 ends the slice. A record written 25 / 34 30 00 / ... fires its install in the load slice whatever follows, so a dialogue-bearing placed actor installs its ambient tree like a dedicated effect-actor script; an install placed after a 0x21 (edkorout P1[15]) does not fire at plain entry.

Port: the scene-entry prologue pre-run executes the slice itself, so each install fires once at its context's position. The static census engine-field::man_field_scripts::scene_entry_ambient_installs takes the unconditional prefix of the slice for tests and the .glb export - a deliberate under-approximation, since a flag-gated install deeper in a record (nilboa P1[3], suimon P1[4]) depends on runtime state. Disc-gated coverage crates/engine-core/tests/ambient_entry_install_census_disc.rs.

Owning page: field-ambient-fx.md.

Master ambient record 0 - the per-scene SFX descriptor bank

Status: resolved - the premise ("a stager record with unknown rows") was wrong

0x8007B8D0 is a shared current-bundle pointer, and record 0 of the bundle it points at is the runtime sound-effect descriptor bank for cue ids >= 0x200.

  • The field asset loader points the slot at the scene prescript bundle (0x8001F850..0x8001F864: lw v0,0xd8(s3) with s3 = 0x1F800314, plus 0x12800), which is how FUN_800252EC reaches stager records through it.
  • FUN_800250D4 (0x800250FC..0x8002514C): desc = base + offsets[0] + (id - 0x200)*8, then SpuKeyOns +3 & 0x1F consecutive voices.
  • FUN_80016B6C (0x80016C24..0x80016CB0), the cue-ring drain: same address arithmetic; it hands bytes +0..+4 to the debug print "setbl p:%d t:%d l:%d n:%d id:%d".
  • offsets[0] is the same word FUN_800252EC reads for stager id 0.
  • Disc shape: every populated row has category +4 = 3 (a variable VAB slot), voice count 1-2, a level in the low 60s and a zero +5..+7 trailer - the layout of the static table in sfx-table.md. Size is per scene: jou reserves 96 rows and populates 40, rugi carries 21. jou's own tree cues 0x20B and 0x20E..0x211 - rows 11 and 14..17 of its record 0.
  • The boot sound-bank loader FUN_8001FA88 writes the same slot and immediately saves its bank's record-0 address at gp+0x678, because the next scene load overwrites the slot.
Rim Elm's south gate

Status: resolved

The game's first scene exit is held shut by the collision grid, not by a trigger gate. The exit record is ungated; grid row 47 walls z in [5888, 5951] across the doorway until a story-flag paint opens it.

The gate's two .MAP kind-1 gate-1 bands:

RecordTilesScript
P2[10](24..26, 45), (25, 44)21 21 26 FE FF - Nop; Nop; JmpRel-to-self. Five bytes, no scene change (inert).
P2[0](24..26, 46)CFlag.Set, an Effect fade, 0x3F naming map01 at entry (0x60, 0x19). C1=[] C2=[] (ungated).

The wall is cut by town01 P0[20], the gate object's own record, bound by the gate-0 kind-1 trigger at tile (23, 43) and executed by the scene-init bind prologue (FUN_8003A55C). It clears the approach with three 4C 70 paints, then branches on system flags 327 / 321:

327321PaintsGate
clear-none; the base row-47 wall standsshut
setclearre-blocks rows 44..46, seats the gate at (24, 44)shut
setset4C 70 18 2D 19 2E - cols 24..25, rows 46..47open
  • A cold boot cannot leave Rim Elm in retail or in the port. On the port's loaded grid the three flag states give exactly the three collision states above, with col 26 re-blocked in the open one.
  • An oracle that seats the player onto (25, 46) fires the exit; a player walking toward it is held by the wall, so the walk-on dispatch is not at fault.
  • Carrier: town0c holds the paint sequence twice (entry script P1[0] and P0[20]); town01 holds it only in P0[20]. Applying nibble-7 deltas from entry scripts alone leaves town01's gate sealed in every story state.
  • Pinned by crates/engine-scene-host/tests/south_gate_disc.rs; the pad-driven exit is a rung of crates/engine-shell/tests/critical_path_replay.rs.

Owning page: script-vm.md.

Town/field free-movement locomotion

Status: resolved

The player free-movement controller is FUN_801d01b0 (field overlay 0897), pinned by a runtime write-watchpoint on *(0x8007c364) + 0x14/0x18 (autorun_player_pos_watch.lua). It camera-remaps the held pad, computes a per-frame speed, and steps the player 2 units at a time with per-axis collision. The 801db81c..801dbf9c cluster is the field camera system, not movement.

  • Controller. Pad remap func_0x800467e8 + FUN_80046494 -> direction bits & 0xf000; speed base_step * player[+0x72] >> 12 * DAT_1f800393 with terrain-slow and diagonal modifiers; facing in player[+0x26].
  • Wall collision, FUN_801cfe4c (overlay 0897 @ 0x801CE818, bias table DAT_801f2214): three leading-edge footprint probes (48 units ahead in the positive directions, 47 in the negative, ±16 lateral), each sub-cell derived as zc = (z>>6)+2, xc = ((x+0x3f)>>6)-1 (0x801CFF04..0x801CFF40). The +2 Z bias is authored into the wall bits; the floor sampler FUN_80019278 reads the same bytes with plain floor indexing. Pinned by the rimelm_wall_press_down (step-exact 47-unit standoff) and rimelm_wall_press_left captures.
  • Actor collision, FUN_801cfc40 (bits 1 / 4): walks the active-actor table DAT_801c93c8, box-testing the three DAT_801f21b4 probe points (64 / 63 ahead, ±32 lateral). A static entity anchors at its MAN object record (tile*128 + sub*16, half-extent 0x40+0x10, footprint offset including the +0x52 & 8 correction from record flag bit 0x8, verified against four captures' spawned static actors); a moving actor uses its live position with caller extents (±40). Each 2-unit step is gated on the actor bits and the wall bit together.
  • Village NPCs take the moving-actor arm (bit 1, ±40 box). In rimelm_npc_press_tetsu the mutual +0x98 collision link is active both ways and the NPC's flags+0x10 = 0x08020884 carries the 0x20000 bit.
  • Touch / interact dispatch, FUN_801d5b5c (decoded from a live overlay image; the static 0897 copy is garbled at that VA): sets the player engaged flag 0x80000 and the actor touched mark 0x100, bumps the counters, saves facing to +0x5A, and kicks the FUN_8003c9ac NPC-motion pause. It fires per contact step for static props (bit 4), and for NPCs on the just-pressed interact button through the probe table DAT_801f2254 (overlay file 0x23A3C: a radius-64 compass point per 45-degree facing sector, extents 0x20 -> ±72 NPC box) with a face-the-NPC turn (func_0x80019b28).
  • Interaction end. The dialog SM FUN_80039b7c exit path restores the actor facing from +0x5A, drains the +0x2A / +0xA touch-counter pair, and clears the player's 0x80000 flag and ctrl+0x60 when no interactions remain.

Port.

  • Walls: World::field_tile_is_wall (retail's derivation; sample_field_floor_height keeps floor indexing) and World::field_dir_blocked (the three-probe footprint), gated by World::locomotion.leading_edge_wall_probes - on by default in play-window, --no-edge-collision clears it; the centre test stays the World default for the oracles and nav drivers. The engine stepper reproduces both retail rest positions byte-exactly, also through a real enter_field_live scene entry.
  • Actors: World::field_actor_dir_blocked over World::npcs.positions, gated by World::npcs.solid (--no-solid-npcs clears it); props via Scene::field_object_placements collider centres (field_prop_colliders_live.rs); interact probe World::field_interact_probe_slot.
  • NPC motion: man_field_scripts::placement_motion_route decodes each placement's pre-text 0x4C 0x51 move-to-tile waypoints and World::tick_field_npc_motions drives them through the ported motion VM (FUN_8003774C), writing live positions back. Autonomous patrol is gated by World::npcs.animate (--no-live-npcs clears it); an interaction prologue's 0x4C 0x51 walks the interacted NPC regardless.
  • Cutscene cross-context walks: a partition-2 record's targeted 0x47 yield (C7 <id> <tx> <tz> <mode>) parks the record on CutsceneTimeline::walk_wait and glides the target (NPC channel or the 0xF8 player anchor) to the tile at the op's own speed; the paired A2 <id> <move_id> ExecMove surfaces the walk / idle clip cue (the town01 Mei walk-in; see script-vm.md § yield family).
  • Walk-touch: placement_walk_touch_event classifies 0x3E door warps and cross-context player-channel 0x23 teleports; World::check_field_walk_touch posts once per ±80-box contact through trigger_field_interact. A .MAP object bind's contact resumes the object's record through the prop runner; only a partition-1 placement's contact applies the decoded effect.
  • Not modelled one-to-one: the full FUN_801d5b5c post-kernel state (engaged flag, facing save / restore, touch counters), per-actor field-VM channel execution for yield-paced patrol scripts (the engine loops the decoded waypoints), the exact retail NPC glide speed, and prop scripts beyond the two decoded walk-touch classes.

Tests: parity/tests/field_collision_discriminator.rs (probe-model + engine-rest legs, including npc_press_pins_moving_actor_arm); field_npc_motion_disc.rs / field_walk_touch_disc.rs; unit equivalence world.rs::tests::field_tile_is_wall_matches_retail_subcell_derivation; standoff leading_edge_wall_probes_rest_at_retail_standoff. Both wall-press sessions park in town0c, whose .MAP (PROT 0019) is byte-identical to town01's.

Owning page: field-locomotion.md.

Field collision-map source

Status: resolved

The collision grid at *(_DAT_1f8003ec) + 0x4000 (1 byte per 128-unit tile) arrives whole from the scene's .MAP - the live grid byte-matches PROT 0109 with zero diffs - and the field VM paints story-conditional deltas over it.

  • Paint op: 0x4C outer-nibble 7 (op0 in 0x70..0x7F), inline operands [4C, 0x7s, col0, row0, col1, row1, mask]; sub-op = clear-walkable / block-all / clear-mask / set-mask. 6 bytes for subs 0 / 1, 7 for subs 2 / 3.
  • The handler 0x801e1c64 is entry [7] of the jump table at 0x801CEE60 - an intra-function label, not a function. Its loops run columns col0 ..= col1 and rows row0 + 1 ..= row1 + 1 (addiu a3,v0,0x1 / addiu t2,v1,0x2 at 0x801E1C6C / 70, addiu a2,v0,0x1 at 0x801E1CBC): the row operands are one above the painted row. Sub 0 masks & 0x0F (0x801E1CFC), sub 1 ORs 0xF0 (0x801E1D70), and both advance six (0x801E1D24).
  • High nibble = 4 sub-cell wall bits. Low nibble = floor-elevation tier, a 4-bit index into a 16-entry short height LUT at scratchpad 0x1f80035c, filled at scene entry by FUN_8003aeb0 from the MAN header (_DAT_8007b898+2, 16 negated values) and consumed by the object spawn iterator FUN_8003a55c to offset each placed object's Y.
  • +0x8000 is a per-tile u16 object / attribute map, not a terrain-flag grid: low 9 bits = object-record index into the +0x0000 table; bit 0x400 = footprint flag ORed in by FUN_8003aeb0.
  • Nothing zero-initialises the grid; there is no +0x4000 init site to find.
  • town01 runs at game mode 0x03, the same as the runtime-pinned field map03.

Owning page: field-locomotion.md.

Field .MAP PROT resolution - define − 2, universal

Status: resolved (census-pinned; engine resolver corrected)

A scene's field .MAP is its retail block's first entry - extraction index define - 2, because CDNAME defines are raw-TOC indices shifted +2 from the extraction frame (cdname.md) - for every field scene. Scene::load converts windows to the retail frame, so Scene::field_map_index is the block's first entry and walk_field_map_index is an alias.

  • Census (crates/engine-shell/examples/field_grid_census.rs): each save's live field buffer (scratchpad _DAT_1f8003ec -> +0x4000 grid) classified against candidate on-disc bases. keikoku sessions match PROT 0109 (define 111 - 2) with zero diffs while 0118 differs by 3855 bytes; koin3 matches 0559 exactly (0568 differs by 531); town01 sessions match 0010, which equals 0001.
  • The in-block decoy. In an unshifted window the first 0x12000 entry is the next scene's map; corpus-wide, every block's in-block 0x12000 hit is exactly the next block's define - 2 entry. The per-entry extractor's shifted filename labels attribute a map to the previous block's tail.
  • Object-index grid (+0x8000, the Scene::field_object_placements / field_terrain_tiles source): residuals of 0..96 bytes against the resolved entry across town01 / town0c / keikoku / koin3 sessions (story-conditional cell mutations - opened chests, prescript object toggles), thousands against every other candidate. Guarded by the disc + save-library gated parity/tests/field_map_object_grid_live.rs.
  • town0c's .MAP is PROT 0019, byte-identical to town01's (0001 / 0010). PROT 0028 is izumi's (define 30 - 2), not town0c's.
  • The footprint is corroboration, never the resolver. 111 entries are exactly 0x12000 bytes and only 101 are maps. Five of the ten others sit inside named scene blocks (dolk+5, dolk2+5, taiku+9, taiku+10, rugi+7) and are scene_tmd_stream entries ([u32 size] then the 0x80000002 TMD magic), so a footprint scan within a block can land on a mesh stream.

Owning page: field-map.md.

game_mode 0x03 = field/town gameplay

Status: resolved

_DAT_8007B83C = 0x03 is the in-town / on-field gameplay mode, and the in-field pause menu runs under mode 0x17 (23, the CARD pair).

  • Captures. v0_1_pre_battle_tetsu (Vahn walking in Rim Elm / town01) and the runtime-pinned free-movement controller on map03 both hold 0x03. All six menu-open library captures (equipment / status / options, field map01 + town town01) hold 0x17.
  • Handler map. The index -> handler / param / name map is read off the disc by legaia_game_tables::mode_table (asset mode-table; disc-gated mode_table_real). Field / town is modes 2 / 3 MAIN; MAPDSIP (12 / 13) is the world-map display mode, not the field. 12 of the 14 per-frame modes share the generic per-frame handler 0x80025EEC; only mode 13 (world map) and mode 23 (memory card) carry their own.
  • Next-mode field. The retail +0x0A field is ModeEntry::next_mode: -1 = self-managed, 0 = fall back to mode 0. The word 0xFFFF0000 is -1 over a zero low half, not a sentinel.
  • Port. legaia_engine_mode::mode::GameMode::scene_mode() maps MainMode (3) -> SceneMode::Field; legaia_engine_mode::mode holds SceneMode::Field for both modes 2 / 3 (an init mode holds its successor's scene mode, as the Mapdisp / Battle / Str pairs do) and maps the CARD pair to SceneMode::Menu. BootSession hosts the field-menu session headlessly (open_field_menu / the Start-edge path in tick), and the windowed host layers its sub-session UI on the same session.
  • Oracles. mode_trace_e3 + v0_1_playthrough drive the engine into the field (enter_field_live) and converge on the retail 0x03 snapshot; mode_trace_e3 drives menu scenarios with a scripted Start press and asserts scene mode, active scene and the engine-emitted game_mode = 0x17. The table's name / param / next fields are cross-checked against the disc map by the disc-gated mode_table_reconcile.

Owning page: boot.md.

Engine VRAM byte-exactness for town01

Status: resolved (major source); minor residue

Single-snapshot byte-exact VRAM is not achievable: about 40% of the texpage band is dynamic or residual (two town01 captures disagree on about 40%). The oracle vram_oracle_e1 compares the static mask - words stable across same-scene captures, excluding the runtime NPC / character CLUT band - and with the field pre-pass doing DMA-every-TIM (BuildOptions.upload_all_tims) town01 passes byte-exact on every static pixel it uploads.

  • Extraction-0874 §2 TIMs (retail player_data / player.lzs §2, the field-character texture band; etim.dat is extraction 0870, a different file): 4bpp pages at fb(320/384,256) etc., field-resident, pixel-matched 256 rows byte-exact. The engine uploads them at field entry (scene::upload_effect_textures_into_vram) - image pages only, since retail uploads their CLUTs at battle entry.
  • The menu-glyph atlas (PROT.DAT[0x11218]) is boot-resident: its image page and flat-strip CLUT match the disc bytes in every captured phase, title included. The (960,400) 60x24 rect belongs to the next bundle TIM (PROT.DAT[0x19438]), which retail uploads after the atlas and which overlays that part of the atlas image. Uploading the whole system-UI bundle in on-disc order reproduces the retail band; see CLUT row 510 population.
  • Residue, x=896..1024, y=256 (about 12k words): (a) the boot-resident system-UI band (the (960,256) atlas page + its overlay TIMs; static disc bytes), and (b) the character / party-texture region uploaded by the battle / character targeted-CLUT pass, which the field pre-pass excludes by design, plus about 2.5k words of UI residue.

"Stable across same-scene captures" is not the same as static in two capture-pinned cases:

  • The extraction-0874 §2 texture band is global, history-dependent state. Row 271 holds a 3-word F-variant in some lineages ((853,271): 0xFFFF words) and the disc bytes (0x3333) in others, and the first battle effect use restores the disc bytes - see the F-variant stamp. The oracle demands cross-scene staticity inside scene::effect_texture_image_rects.
  • The world-map walk view palette-cycles specific columns of the kingdom terrain CLUT rows 506 / 508 / 509 in place. vram_oracle::WORLD_MAP_CLUT_CYCLE_CELLS excludes exactly those columns for world-map scenes; row 507 and the static columns of 506 / 508 / 509 are asserted.
World-map CLUT cycling beyond the ocean head - CLOSED (operand table + emitter + cadence all pinned)

Status: closed. The head-walk operands are a literal disc table - kingdom-bundle slot 5 (type byte 0x06), a 516-byte 8-entry CLUT-walk animation table byte-identical across all three kingdoms; the emitter is the SCUS actor walker, not the script-driven CLUT-cell family; the cadence is the table's own per-frame hold bytes.

The chain, each link byte-verified against live RAM and the disc:

StepRoutineWhat it does
LoadFUN_8001F05C case 6Sets DAT_8007B7C8 to the decoded slot-5 table.
Spawnfield-init FUN_801D6704One render-mode-0xB actor per entry via FUN_80024CFC; entry pointer at actor+0x4C, accumulator actor+0x68 seeded 100 so the first copy fires at scene entry.
EmitFUN_8001ADA4 case 0xBacc += DAT_1F800393; on acc >= frame.hold issues a 16x1 MoveImage from the frame's source cell to the entry's destination cell, resets acc = 0, advances the frame index.
  • Cadence (PCSX-Redux MoveImage exec-BP traces on all three kingdoms): intervals are constant at ceil(hold/dt)*dt vsyncs - hold 8 -> 9, hold 10 -> 12, hold 20 -> 21 at overworld dt = 3. The non-multiples rule out subtract-remainder semantics.
  • All eight entries fire their first frame on the same vsync at world-map entry, then free-run independent phases with zero drift.
  • The 18-step head cycle is A,B,f0..f7,(f6,f7)x2,f8..f11: two extra wave frames parked before the OCEAN_ANIM_FRAME0_HEAD signature in kingdom slot 0; ocean frame 12 is never shown.
  • Checked against the listing: the spawn loop 0x801D6C8C..0x801D6CD4, the seeds in FUN_80024CFC (0x80024D58..0x80024D64) and the emit arm 0x8001B664..0x8001B700.
  • The field overlay's script-driven CLUT-cell family (FUN_801E4C58 / FUN_801E4794) is not the emitter. It carries only the row-498 park one-shots / fades (map01's eight 4C 61 ops; scene_clut_cell_fx, disc-gated map01_clut_fx_disc). At overworld idle row 498 is a source strip for the (32,508) / (48,500) walkers; the map01-only row-508 "mirror" is slot-5 entry 6 copying from the script-parked row-498 cells.
  • Row-506 cols 32..47 are written wholesale by slot-5 entries 3 / 4 from the row-503 / 502 strips - parked disc bytes walked in place, not runtime colour math.
  • Destination cells are literal u16s in the table, not park row + 8.
  • Port. The engine consumes the table directly (WaterAnim::Walk in play-window; vram_oracle::WORLD_MAP_CLUT_CYCLE_CELLS = the slot-5 destination fold). The park strips are raw CLUT-block records, not TIMs, so the scene pre-pass does not upload them; the map02 / map03 bundles ship only rows {501, 503, 505} and retail relies on VRAM residency from the map01 upload, which the engine mirrors by parking the byte-identical Drake complement.

Owning page: world-map.md "Ocean animation"; parser legaia_scene_formats::clut_walk.

init_data UI-tile pages - journey-dependent residency (resolved); map03 texture column (resolved - "not uploaded" premise falsified)

Status: the keikoku oracle drift is resolved (residency class pinned); the map03 texture divergence is resolved - the "engine fails to upload PROT 0392" premise is falsified, the current pre-pass does write the real terrain

init_data (PROT 0) carries two 64-word x 256 UI-tile TIMs at fb (704, 0) / (704, 256), and those rects are journey-dependent residency, not stable shared texture.

  • Overworld transit leaves kingdom-bundle content over parts of the rect: every Drake-stage capture (keikoku, the field-menu states) holds the same kingdom bytes at (704, 256) where the boot-fresh town01 states hold the disc tiles.
  • Town scenes mask this because their own scene TIM overwrites the slot; keikoku carries none.
  • The parity oracle pools captures across all scenes against scene::block_image_rects(index, "init_data"), the same cross-scene treatment as the befect band.

The map03 terrain rect is map03's own content, and the engine pre-pass writes it.

  • asset tim-scan: PROT 0392 uploads 8 real 4bpp TIMs into fb x=576..640, y=320..448. The fbx=576 fby=320 96x96 4bpp TIM (PROT 0392, lzs0_off 0x03BDEC) byte-matches the retail resident VRAM at (576,320), 2304 / 2304 halfwords.
  • Direct pre-pass measurement: map03 uploads 58 TIMs, and the 576..640 x 320..448 region holds 7945 real terrain texels with 37 stray 0x3332 cells scattered in-tile - no hole.
  • PROT 0392 slot 0 is byte-identical to 0391 slot 0, which the engine uploads (the kingdom sibling-skip in crates/engine-scene/src/scene_resources.rs), so uploading 0392 as well would write identical bytes to identical cells.
  • Limit of the evidence: the comparison is a pre-pass measurement, not a full VRAM oracle, because the capture corpus has no map03-WorldMap-resident save.
CLUT row 510 population (boot-resident system-UI strip band)

Status: resolved (source + upload semantics + retail residency pinned; engine pre-pass uploads the bundle - legaia_ui_formats::system_ui_bundle); residue = the exact boot-time walker call site only

CLUT rows 510 / 511 are the flat-strip CLUT band of the boot-resident system-UI TIM bundle, so the prims that sample them are validly textured in retail. The prims in question: town01 env-pack slots 21 / 26 / 74 and rikuroa slots 50 / 51 / 63, CBA (64, 510), texpage (960, 256) 4bpp; no scene TIM uploads row 510.

  • Source. The prot::timpack at raw PROT TOC entry 0 (LBA words toc[0]=3 / toc[1]=55 precede init_data's 121 - indexed, just below the extraction space; CDNAME's #define init_data 0 names this block). A second single-TIM pack sits at raw entry 1.
  • Upload semantics. The per-TIM uploader FUN_800198E0 uploads every TIM CLUT block as a w*h x 1 strip at the declared origin (ghidra/scripts/funcs/800198e0.txt). The atlas at PROT.DAT[0x11218] (declared CLUT (0,510,16,16), image (960,256) 64x256) lands as the 256-entry strip on row 510 x=0..255; the 0x19438 UI-strip TIM adds x=256..319; three more bundle TIMs tile row 511 x=0..319.
  • Residency census. Across mednafen library states in every phase - title_screen_new_game, new_game_cutscene_intro_a, v0_1_pre_battle_tetsu, keikoku_chest_pre, mei_house_inside, sebucus_overworld_resident, v0_1_battle_start_tetsu - the row-510 / 511 strips are byte-identical to the on-disc CLUT data (256/256 + 64/64 + 256/256 + 48/48 + 16/16 halfwords per strip), and the (960,256) image page matches the disc TIM on every row not covered by a later bundle member.
  • Row patches. Compositing the bundle in on-disc order (images at declared rects, CLUTs as strips) reproduces the whole retail (960, 256..511) band. The six 64-word rows at y=456..458 / 460..462 are bare row-patch members of the same pack: raw-entry-0 members 10..15 at PROT.DAT 0x1A018..0x1AA7C, a [u32, u32] preamble + TIM-style [u32 bnum][u16 x,y,w,h] block declaring (960, y, 256, 1), byte-exact against live captures (RowPatch in legaia_ui_formats::system_ui_bundle).
  • What the prims sample. CBA (64,510) = atlas strip entries 64..79; UVs (u 0..2, v 240..242) hit a constant mid-grey texel patch - a flat material through the textured pipeline.
  • Not scene-loaded or a runtime targeted upload (it is resident before the title screen), and not a CBA misread: x=(cba&0x3F)*16, y=(cba>>6)&0x1FF is correct.
  • Unpinned: which boot routine issues the byindex-style read of raw TOC entries 0 / 1 and walks the pack into FUN_800198E0. A cold-boot write-watch on the row-510 upload (scripts/pcsx-redux/autorun_town01_vram_upload_census.lua) would name it.

Owning page: npc-palette.md.

Scene-transition (0x3F door) destination indexing

Status: resolved

A field scene reaches another through the field-VM 0x3F named-scene-change op, which carries the destination scene name inline and is selected by partition-2 slot, never by absolute reference.

  • Dispatch trace (autorun_door_dispatch_trace.lua on drake_castle_to_worldmap): the 0x3F ops are partition-2 MAN records reached through the partition-2 record-offset table. The controller sets the VM bytecode base to man_base + data_region + partition2[slot] and runs the record by fall-through (a0 - man_base == data_region + partition2[0] exactly).
  • The op's index field is only the destination-scene id passed to the warp packet (FUN_8001FD44).
  • Census (clean partition walk): 160 destination ops in 48 scenes, 153 in partition 2, zero absolute-reference ops at or after any destination op.
  • Consequence. Variable-length door editing is safe: resizing a destination name is a partition-table + section-offset + intra-record-jump-delta + descriptor-size fixup, implemented in legaia_scene_formats::man_edit and shipped as the door randomizer (man-relocation.md).
  • The 0x3E door warp (7-id map_id) is SCUS-resident end to end: FUN_80025980 (mode-24 OTHER INIT entry), FUN_80026018 (exit). It carries no destination name; the sub-id selects a minigame overlay (extraction PROT 972..977, 980 via the loader math param + 0x37F). Its name handling is a backup / restore (an eight-byte copy at 0x800259B4, the define at 0x800259BC / 0x800259FC; the loader call is 0x80025A1C..0x80025A30) of the current scene name (0x80084548 <-> 0x8007BAE8, plus _DAT_80084540 <-> 0x8007BAC4) so the exit re-enters mode 2 on the original scene.

Owning page: script-vm.md § 0x3E warp.

Intra-town (house / interior) door mechanism

Status: resolved

Entering a house is not a scene change. It is an intra-scene reposition: the scene-name buffers 0x8007050C / 0x80084548 stay put and only the player's position jumps to an interior sub-area of the same loaded scene.

  • Writer. Field-VM dispatcher FUN_801de840 case 0x23 (0x801debc4 sh v0,0x14(s5)), converting the tile operand to world as tile*128 + 0x40. Pinned by the probe.step.find_writer Lua primitive, a width-correct range write-watch over the player position block. A width-2 watch at +0x14 catches only a 2-byte no-op re-store in the ledge-hop FUN_801d1878.
  • Captures. door_warp_rim_elm_to_mei_house / mei_house_inside (mednafen), mei_house_door_pcsx / mei_house_inside_pcsx (PCSX).
  • Door marker. House-door warps use the cross-context form 0xA3 0xF8 xb zb: opcode 0x23 | 0x80 dispatched into the player system channel 0xF8. Plain 0x23 moves the executing actor (NPC / prop positioning).
  • Carrier records. Partition-0 records with their own header form ([u8 n][n x 2 SJIS name][u8 attr], distinct from partition 1) and a naming convention pairing entries with exits: fullwidth IN / OUT, 入口 / 出口 gates, A / B elevator endpoints, optional digit suffixes. The Mei's-house warp is 0xA3 0xF8 0x61 0x36 in town01 partition-0 record 34, an IN record.
  • Three player-move forms. A3 F8 <xb> <zb> (op 0x23, instant), CC F8 51 <xb> <zb> <depth> <mv> (op 0x4C nibble-5 sub-1, teleport + move anim), and C7 F8 <xb> <zb> <mode> (op 0x47, animated walk). A door record is a branching script whose arm is selected by story flags, so a door can also be a 0x44 SPAWN_RECORD of a partition-2 choreography that does the seating itself.
  • The bind position is the .MAP object's contact box, not the trigger tile, which is a lookup key and usually a wall.
  • Kind-0 tile doors. The .MAP trigger block's kind-0 sub-table is a second, larger door class: [tile_x][tile_z][dest_x][dest_z], no object and no script. Crossing the tile seats the player at (dest_x*64 + 64, (dest_z + 1)*64) (FUN_801D1EC4's kind-0 arm at 0x801d21c0 latches the record; the seat is the drain's 0x801D1F88..0x801D1FB0). 2330 records across 73 scenes; most house exits are these. Vahn's house has an IN record and no OUT because its exit is the kind-0 tile (97,9) inside the room, ungated by any story flag.
  • Randomizer. legaia_patcher::house_door shuffles only the classified door warps, class-preserving (IN among IN, OUT among OUT), so every exit still lands outside (randomizer.md).

Owning page: field-locomotion.md.

Field/town environment-geometry placement

Status: resolved (renders)

A town's environment meshes (terrain, buildings, props) are object-local Legaia TMDs in the LZS streams of the scene's scene_asset_table PROT entry (town01 = entry 4), placed by FUN_8003a55c from the field .MAP.

  • Placement. The object-index grid at +0x8000 (cell & 0x1FF = object id) selects a 0x20-byte record in the +0x0000 table. Placed tiles (record +0x12 bit 0x4) give the world transform: world_y = -floorHeightLUT[nibble] + y_off, the LUT being 16 s16 at the MAN header +0x02.
  • Mesh per object is the record's +0x10, for every object id (retail FUN_80020f88, actor+0x64 = record[+0x10] + prefix); anim_id only animates. Records 1 / 2 / 3 are no exception: neither FUN_80020f88 (0x80020FDC..0x80020FF0) nor the spawner FUN_8003A55C tests the id, and a live conc2 actor list holds record 1 (the archway double door, +0x10 = 61) at +0x64 = 0x42. Reading them as "protagonist / NPC meshes from the shared pool" left that door, and the record-2 props of rikuroa and retona, undrawn.
  • Validation. A live town01 save (Vahn's house id 137 -> mesh 36), and the retail GPU prim pool: town0c cell (30, 17) (id 99, record +0x10 = 2) draws its surface from env mesh 2 - the quad's cba / tsb / UVs match that mesh's primitive byte-for-byte - not from mesh 94.
  • Not a positional "field-actor band" (obj_idx - 5, ids 93..=118): that rule swaps ten town meshes per Rim Elm map, drops the terrain slab south-east of the spawn and leaves a clear-colour hole in the ground.
  • Port. Parser legaia_scene_formats::field_objects; Scene::field_object_placements; play-window renders the town via resolve_field_placement_draws.

All 46 town01 placements draw, on two pipelines (disc-gated field_object_placement_disc::town01_dropped_placements_split_untextured_vs_missing_clut):

  • 45 on the textured VRAM path. That includes pack 74 / obj 347 (6 placements, 4 textured prims), whose CBA row 510 / texpage (960,256) source is the boot-resident system-UI bundle layered under the field build (BuildOptions::system_ui); no placement drops for a missing CLUT. Object 114 resolves through +0x10 to the textured pack 84.
  • 1 on the vertex-colour path: pack 31 / obj 315, 30 untextured (per-vertex-RGB) prims. The textured-only builder tmd_to_vram_mesh_filtered skips prims with no UVs, so legaia_tmd::legaia_prims decodes the colour blocks into Prim::colors, legaia_tmd::mesh::tmd_to_color_mesh builds a ColorMesh, and engine-render draws it through a dedicated pipeline (upload_color_mesh / Scene::color_draws). Pack 109 (12 untextured prims) builds a colour mesh the same way.
  • Mixed meshes render both halves: the colour mesh is built unconditionally and is disjoint from the VRAM mesh (tmd_to_color_mesh skips textured groups).
  • A textured prim whose CLUT is not resident is dropped rather than drawn flat with CLUT[0]; a per-vertex-RGB fallback would render it wrong.
  • Untextured colour block (per-mode record layouts F4 / G3 / G4, the 00 01 03 02 quad winding remap, no per-prim normal): tmd.md § Per-prim color / texture block.

Owning page: field-locomotion.md.

Region story-flag gate families

Status: resolved as structure across the chapter-2/3 regions. Play order is capture-confirmed for retona, dohaty, taiku, the Sebucus spine, korb3, the kor5 chain head and the map03 hub latch; in-bracket order is pinned by disassembly for rayman, bubu2, retock and deroa's 0x46D; the two orders no gate decides are unobservable (see the play-order residual).

Every field scene's MAN carries one partition-2 record per cutscene or story beat. Each record's header holds two flag lists the spawn evaluator FUN_8003BDE0 checks before running it:

  • C1, one-shot: the record is suppressed once any listed flag is set.
  • C2, requires-all: the record spawns only when every listed flag is set.

Regional progression is expressed almost entirely through these header gates. They are not inline 0x50 / 0x60 / 0x70 opcodes, so the inline flag census (man-scripts --system-flag-census) cannot see them, and a flag read only by a header gate looks write-only there. legaia_engine_field::man_field_scripts::partition2_record_gates decodes them; the anchor tests named below pin each region's exact lists.

Self-latches. A record that both sets a flag and lists it as its own C1 gate.

  • 0x1BE (Jeremi's arrival): geremi P2[0] (anchor geremi_p2_0_is_the_0x1be_self_latch).
  • 549 / 0x225 (the Rim Elm opening): the same shape across the Rim Elm variants, readable once the 4C 0xE_ op widths are decoded.

Chapter 2 - Sebucus (map02 and its dungeon spokes). The spine needs no chapter-specific engine code: each beat's script latches its flag through the ordinary field-VM SysFlag.Set path, so the generic seeder drives the arc. Chain: teien (0x1C8 -> 0x1C9 -> 0x332) into tower (0x1C7, gated on the teien arc) into a post-tower geremi beat, with balden self-latching 0x5B3 and map02 P2[9] mirroring the teien arc onto the overworld. Proven by chapter2_sebucus_spine_oracle, chapter2_sebucus_gate_spine and chapter2_sebucus_hub_sweep_disc (which drives the arc through real 0x3F scene transitions).

SpokeFamilyAnchor
taiku / doman / raymanSelf-latch pairs plus the linear rayman chain 0x201 -> 0x1FB -> 0x200 -> 0x1FC. rayman2 is the same MAN with a shared C1 on the low flag 0x7, a variant discriminator.chapter2_dungeon_gate_families
balden / balden2 / stationbalden is an arc around its reached-flag 0x1D5; balden2 is a sibling carrier with an identical gate family, selected by streaming slot rather than a flag. balden gates on the ropeway2 switches; station / station3 gate on taiku's 0x38F.chapter2_balden_station_gate_families
ropeway / ropeway2 / jijiropeway2 hosts a four-bit switch puzzle (0x3FF-0x402); see below.chapter2_ropeway_jiji_gate_families
retonaFive-step ladder 0x353 -> 0x354 / 0x355 -> 0x356 -> 0x357; see below.-
dohaty / retock / retockin / stonedohaty opens with a six-record 0xF first-visit group; retock depends cross-scene on balden's 0x1D5 and gates on retona's 0x357 before its own 0x502; retockin is the 0x7-gated interior variant sharing 0x502 / 0x357; stone is a single one-shot.chapter2_dohaty_retock_stone_gate_families
tunnelb / tunnelcSmall internal one-shots, read back only by the tunnels themselves.-
map02 hubA router: two gated records, both overworld mirrors of a dungeon-arc completion.chapter2_map02_hub_gate_family

Sebucus detail:

  • rayman streaming variant adds a P2[18..20] tail latching 0x34D / 0x34C (P2[18] body +0x2C2, at a JmpRel branch arm after 0x1FE / 0x1FF tests).
  • taiku variant P2[16] SETs the pair 0x380 + 0x382 at its head (body +0x11 / +0x21, between SceneFade and the particle emitters). 0x382 is a cross-chapter gate: son P1[14] branches its NPC dialogue on it (body +0x4A), and the clean census reads span doman(V) / retockin / ropeway / ropeway2 / map03 / koin2 / korout.
  • ropeway2 payoff records P2[31..=34] are C2-gated on all four switches plus the 0x359 commit - an internal consumer the inline census cannot see.
  • jiji P2[8] latches 0x304 from three branch arms of one cutscene (each 4C CD -> Set -> JmpRel to the shared tail; bodies +0x912 / +0xCD6 / ..).
  • retona records. P2[8..14] gate on 0x353 / 0x354 / 0x356; P2[15] chains C2=0x354 / C1=0x355; P2[17] (C1=0x357, C2=0x356) is the pre-beat rendition; P2[18] (C2=0x356) is the beat that SETs 0x357 (body +0x5EF, after the 4C 73 tile run + BGM cue). P2[10] separately latches 0x354 (+0x673), read by rugi.
  • retona backstop. The entry script P1[0] normalises: Test 0x357 -> skip; Test 0x3AD -> Set 0x357 at +0xF4. 0x3AD is also the C2 of map02 P2[10], the overworld mirror 0x357 retires.
  • 0x357 is the Jeremi-arc cross-scene gate - clean reads in retock / retockin / map02 / geremi / edretoin - so the 0x357 half of retock's 0x357 -> 0x502 chain is retona's output.
  • stone partition-0 walk-on scripts latch a local band: P0[2] -> 0x32B, P0[3] -> 0x32A, P0[4] -> 0x32D, P0[5] -> 0x32C (+0xB7F, then SpawnRecord 0x1E).
  • 0x32C is a write-only latch; no reader exists. Every census read (about 50 scenes) is the ASCII s, bigram in dialogue, and no C1 / C2 list in the pinned regions carries it. A word-aligned scan of SCUS_942.54 plus all 15 static overlay images (crates/asset/data/static-overlays.toml) finds no immediate 0x32C load into any register, no access to the flag byte 0x800857BD under any viable lui / addiu encoding, and no constant 0x32C argument at any flag-helper call site (FUN_8003CE08 set / FUN_8003CE34 clear / FUN_8003CE64 test). The only remaining reader is the 0897 dev-menu flag browser, which reads any flag on demand.
  • tunnelb P2[34] latches 0x322 / 0x326. tunnelc P1[4] latches 0x360 + 0x362 from two branch arms (bodies +0x107..+0x110 / +0x2AB..+0x2B4); tunnelc P2[6] latches 0x34A.

Rim Elm town variants. town01, town0b and town0c share the opening chain (549 -> 0x226 -> 0x227, plus sub-chains) byte-for-byte in P2[3..=11]: story-state renditions of one town. town0d is the 0x7-gated later variant. A town0c visit in the chapter-2 capture is a revisit (the "scene" listed beside it in the poll is the capture CSV's column header, not a map). Anchor town0c_is_a_rim_elm_state_variant_not_a_ch2_spoke.

Rim Elm revisit chain (town0b band 0x228..0x233). A second flag band alongside the opening chain.

RecordC1C2Writes
town0b P2[7]0x22B, 0x1410x147self-latches 0x22B at its head (+0x26, before the flash + waits); SETs 0x228 / 0x229 / 0x22A from branch arms (+0x377 / +0x804 / +0x8F9, each at a JmpRel boundary inside camera / emitter choreography)
town01 / town0c P2[7]0x22B0x147the same gate shell; only town0b's copy mints the band
town0b P2[8]0x2310x22Fsets 0x231
town0b P2[9]0x2320x141sets 0x232
town0b P2[10]0x2330x232sets 0x233
town0b P2[11]0x1410x231-
  • P1[1] is the state seeder: sets 0x22F + 0x147, clears 0x141 (same record in town0c).
  • Readers: town01 P0[1] (the entry walk-on) branches on 0x22F / 0x229 (+0x69 / +0x6D); the NPC record town0b P1[39] selects dialogue over 0x22F / 0x148 / 0x147 / 0x228 / 0x229 / 0x22A in sequence.
  • Late one-shots town0b P2[30] / town0c P2[29] latch 0x5C4 (+0x3CD, behind a Test 0x35 battle-victory guard), read by the ending scene edlast.
  • There are three renditions, not four. gameover_data's CDNAME window is a subset of town01's and holds no asset-table bundle; a MAN read out of it is town01's own, reached by an entry-size over-read (script-vm.md).

Rim Elm final variant (town0e), per-NPC band 0x5DC..0x5F0 + 0x6DC. Every NPC interaction record P1[1..24] opens with the same head: Test <own flag> -> skip, Set <own flag>, then Test the neighbouring NPCs' flags (P1[2]: Set 0x5DC at +0x20, then tests 0x5D8..0x5DB). It is a talked-to-everyone tracker - scene-local flavour state, not progression; record indices map 1:1 onto the band.

Uru Mais (uru / uru2) beat band. uru's cutscene tail latches 0x3BE (P2[30]), 0x3BF (P2[34]), 0x3C0 (P2[32]) and 0x3FC (P2[38], body +0x8B7 after a BGM cue). P2[30] is the party-recompose beat: PartyAdd char 1 + Set 0x11, PartyAdd char 2 + Set 0x12, then Set 0x3BE (+0x72) under a camera reconfigure. All four flags read back only within uru.

Nivora Ravine (nilboa). An entry group sharing 0x456, a 0x47x puzzle cluster, and a cross-scene successor gated on 0x370; nilboa2 is the 0xF-gated variant carrier. Anchor nilboa_nivora_ravine_gate_family.

  • 0x456: nilboa P2[11] both SETs and CLEARs it (Set 0x455 + Set 0x456 at +0x37..+0x39, inside a CC .. C3 per-actor run).
  • 0x370 writer: doman variant P1[15] at MAN offset 0x06397, a 53 70 SET in a clean choreography run whose loop-back JmpRel re-enters the record's gate-test head; the head's own Test 0x370 -> +0x301E jump lands on the very next op (the Dr. Usha "Do you understand? The first TimeSpace..." briefing branch) - the self-latch shape. The record's other three 53 70 occurrences are "TimeSpace Bomb" prose aliases.
  • 0x370 readers: the doman P1[3..=18] clean head TESTs (arc-gate dispatch chain, alias-immune operands). Pinned by man_variant_carrier_census_disc.rs::flag_0x370_writer_is_the_doman_p1_15_usha_latch; a live organic SET (the poll auto-snapshots flag 880) confirms play order.

Chapter 3 - Karisto (map03 and its spokes). Anchor map03_karisto_region_gate_families.

  • map03 is a router with no gated records. Ungated hub state exists as inline latches: map03 P2[15] SETs 0x378 (+0x9E, between a 180-frame camera hold and the particle emitters), read back by doman and map03 itself.
  • bubu2: a small requires-all chain. bubu1 carries no field MAN.
  • son and deroa: sparse one-shots; deroa leads to the underground chitei2. son's NPC records use the per-NPC one-shot head (P1[14]: Test 0x62E -> skip / Set 0x62E at +0x52) and branch on taiku's 0x382.
  • korb3, the Karisto castle approach: a nine-record collection group P2[5..=13], each record gated on a distinct flag under one shared 0x403 "all done" latch.

Chapter 3 - Karisto castle depth (kor / koin cluster + chitei2). Anchors chapter3_karisto_castle_gate_families + chapter3_koin_family_and_writer_pins.

  • kor: one-shot beats (0x408 read by korout, self-latches 0x409 / 0x40A) plus a door group C2-gated on 0x612, an arm-then-consume mechanic - the partition-0 entry scripts SET 0x612 and each door record clears it. kor3 / kor4 gate their doors on the same flag.
  • kor5: a three-step chain 0x43A -> 0x436 -> 0x6C4.
  • koin1b is koin1's story-state sibling (same gate shape + a spliced 0x00B toggle pair); it owns the 0x3DA SET koin1 gates on. koin1's P2[9..10] are a 0x50A set / clear toggle pair.
  • chitei2: the 0x470 / 0x4F0 and 0x4C4 / 0x4C6 / 0x4C8 / 0x4C9 families; 0x4C8 is co-written by map03 P2[19].
  • korb2 / koin2 / koin6 are gateless.
  • koin3 P2[8] and its stale sibling copy other7 P2[5] co-latch 0x430 (koin3 body +0xA40, a JmpRel branch-arm set inside CC camera choreography), read by the ending scene edlast.
  • 0x50A is the Sol game-hall minigame result toggle, written natively by the mode-24 minigame overlays - a space the MAN script census cannot see. The Muscle Dome module (PROT 0977) CLEARs it in the post-match settle (0x801D0FF8) and win-re-SETs it (0x801D101C, labelled by the overlay's own WIn on / WIn off debug strings). The dance trio (0978..0980) SETs it at session start (0x801CF968) and CLEARs it on a missed goal (0x801CFF10). koin1 hosts the Muscle Dome + Baka doors (3E 69 / 3E 68), koin3 the dance doors (3E 6A), and koin1 P2[9] (C2=[0x50A]) is the returned-victorious beat.
  • 0x5D6 is a script self-latch. koin4 P1[15] sets the flag it gates on: behind the "If you have money" line sit 48 (a one-byte no-op) and 55 D6, and its P2[3] twin carries the same block. The bytes to search for are 55 D6, not D6 05, and a walk that stops at a record's first text segment misses the site. The native-space sweep (scripts/asset-investigation/flag_helper_call_sweep.py, the move-VM ext flag sub-ops, the motion-VM census) is a negative for native writers only. See script-vm.md § native flag-bank writers; guard koin_gates_0x50a_writer_less_0x5d6_self_latched.
  • Runtime oracle chapter3_karisto_spine_oracle.rs: the Conkram -> deroa -> chitei2 bridge, the kor5 chain, the door arm-then-consume and the koin toggle all sequence through p2_record_gates_pass + install_gated_p2_record with no chapter-specific engine code.

Chapter 3 - Conkram (conc*, the "past" arc). Anchor chapter3_conkram_gate_families.

  • Pivot pair 0x3E1 / 0x3E5: conc2 P2[12] SETs 0x3E1, the flag deroa C2-gates the chitei2 descent on (the cross-region bridge). conc3 self-latches 0x3E5 (P2[10]) and SETs 0x3F9 (ungated P2[9]); conc P2[10] chains on both.
  • conc / concnow carry r1..rN soldier rows, all C1-gated on the low flag 0x007, which is SET by concnow P0[34] + conc2 P0[21] (a "soldiers disperse" beat).
  • conc has eleven doors on 0x6DE, armed by the entry script's player-position BBoxTest run (the same mechanic as kor's 0x612). All four carriers' entry scripts (conc / conc2 / conc3 / concnow P1[0]) SET 0x6DE.
  • concend is a single ungated epilogue record.
  • concnow one-shot ladder, each C1 gate a self-latch in its own record: P2[13] -> 0x3ED, P2[14] -> 0x3EE, P2[15] -> 0x3D2 (at its tail +0x1483), P2[16] -> 0x3CE, P2[18] -> 0x423, plus P2[20] -> 0x3CF.
  • 0x3EF is the chapter-wide "Conkram revelation" gate. concnow P2[15] SETs it from a branch arm (+0xDDD, after the emitter run + BGM cue, jumping straight to the record tail). Its operand byte is outside ASCII, so census reads are alias-immune: clean Test sites in fifteen scenes spanning Sebucus (balden / balden2 / bylon / dolk2 / geremi / jiji / rayman / rayman2 / retock / ropeway) and Karisto (koin1 / koin2 / son / doman).
  • 0x423 is a cross-scene message, not a one-shot. conc2 P1[0] consumes it on entry (Test 0x423 -> Clear 0x423, Set 0x664, SpawnRecord 0x69 at +0xDB..+0xE8): the concnow beat posts the flag and the next conc2 visit converts it into 0x664 (read by conc) plus a spawned follow-up record.

Cross-cutting patterns.

  • Two low-numbered flags are variant discriminators, gating nearly every record of an alternate or interior carrier: 0x7 (rayman2, retockin, town0d) and 0xF (dohaty, nilboa, nilboa2).
  • 0x7 latches inside the opening-commit beat (the opdeene -> town01 handoff of a fresh new game, alongside 549 / 0x226; captures/state_poll/2026-07-29T20-20-05Z), so the rendition selection is armed from the start of play.
  • Region hubs hold little or no gate state; the progression logic lives in the spoke dungeons.
  • The story-numbered band 0x522..0x531 is engine scratch: a one-hot exit selector + fade handshake repeated in nearly every scene's entry script (script-vm.md § the 0x527..0x531 scene-transition scratch band).
  • Clean-tagged census rows over flags whose operand byte is printable ASCII can be dialogue bigrams (ta / s, / Sp); the wide reader lists of 0x461 and 0x32C dissolve entirely under that check (script-vm.md § ASCII dialogue aliases).

Play-order captures (poll tier). Corpus captures/state_poll/2026-07-29T20-20-05Z / 2026-07-29T22-21-04Z / 2026-07-29T22-53-56Z, mined via analyze_state_poll.py --only flags. Screening rule: a save-state load emits a sub-bulk flag delta whose signature is a mode churn plus flagclr rows plus a full inventory re-key at one tick, with the destination scene registering about 43 ticks later; every burst with that signature is excluded.

RegionLive SET order (tile)
retona0x354 @(67,80) -> 0x355 @(69,78) -> 0x356 @(68,80) -> 0x3AD @(66,79); 0x357 latches during the next scene entry (mode-2 transition frame after an overworld round trip - the P1[0] backstop converting 0x3AD); then 0x367 @(67,78). 0x353 is already latched in the starting state.
dohatyOne corridor walk: 0x343 + 0x63D @(23,42) -> 0x344 @(23,50) -> 0x345 @(23,53), leads 0x39F / 0x65A interleaved; the P2[10] 0x344 one-shot and the 0x63D pair both fire. The last beat @(23,56) latches 0x1D4, a flag in balden P2[0]'s C1 list (a cross-scene edge).
taiku0x517 @(54,17) -> 0x519 @(54,7) -> 0x38F @(54,40) -> the P2[16] pair 0x380 + 0x382 @(16,28), one beat one tick. The other self-latch pair 0x390 does not fire (branch / optional content).
kor50x43A @(32,92) -> 0x436 @(32,40). 0x6C4 appears only inside a load delta.
korb30x41D @(36,21) (its C2 includes kor5's 0x436, satisfied organically beforehand) -> 0x41E @(36,23) -> 0x41F @(37,24).
map03 hubP2[15] 0x378 @(93,69).
Sebucus spineteien 0x1C8 @(41,45) -> 0x1C9 @(44,43) -> 0x332 @(44,47) -> tower 0x1C7 @(13,69) -> geremi 0x1BF @(34,117). tunnelc P2[6] 0x34A @(23,45); its P1[4] 0x360 / 0x362 do not fire (branch arms). balden's 0x1D5 latches at the tunnelc exit tile (60,16); its 0x5B3 self-latch does not fire.
Rim Elm opening549 at the opening commit, with 0x226 in the same beat.
  • korb3's collection group plays backwards from its gloss. 0x403 latches on the map03 overworld at the castle-approach node (72,41) before any collection flag, and all nine C2 flags (0x43E..=0x444, 0x459, 0x45C) mint together in a single korb3 arrival burst (BGM stop -> nine sets -> new BGM, no load signature). The group's records are C1-retired before ever spawning.
  • ropeway / ropeway2 / jiji are walked organically in the capture corpus as well.
  • Walked in this corpus without an organic family SET: retock / retockin (entered with 0x357 latched; 0x502 does not fire), doman (only the unpinned lead 0x379 fires; 0x3FB does not), nilboa / nilboa2 and son (entered mid-arc from loaded states; the only nilboa flag burst is a load frame).

In-bracket write order (disassembly). The playthrough cards place each remaining family between two milestone saves; where a family writes more than once inside one bracket, the scripts fix the order. Counting only story SET sites (clean, not a prose alias, not a developer flag-menu arm):

  • rayman: 0x201 -> 0x1FB -> 0x200 -> 0x1FC, forced. 0x201 is set by the talk record P1[47]; 0x1FB by P2[12] (C2 0x201), 0x200 by P2[18] (C2 0x1FB), 0x1FC by P2[19] (C2 0x200). Each link has one writer and each writer requires the link before.
  • bubu2: 0x609 then 0x3D3, one beat. Once 0x608 is set (the bracket before), the live writer is P2[0] (C1 0x3D3, C2 0x608), which SETs 0x609 and 0x3D3 on adjacent instructions (body +0x1E / +0x20). P2[2] carries 0x608 in its C1 and P2[3] is retired by P2[0]'s 0x609.
  • retock: 0x502 before 0x33B. 0x502's writer is retock P2[33] (C1 0x502, C2 0x357), spawned only by Eliza's talk dispatch P1[31], which tests 0x33B first and diverts to her post-0x33B lines when it is set, then spawns P2[33] once 0x63C (set by P2[16], the first talk) is up. 0x33B's writer is jagaroom P2[8] (C2 0x351, which the jagaroom P1[8] actor sets when it spawns), and nothing there waits on 0x502. A play that latches 0x33B first can never write 0x502; the Dohati save holds both. Full chain: 0x357 -> 0x63C -> 0x502 -> 0x33B -> 0x34F (jagaroom P2[9], C2 0x33B).
  • deroa: 0x3E1 before 0x46D. P2[4] (C1 0x46D, C2 0x3E1); P2[5] / P2[6] carry only their own latch.
  • retock P1[1] / retockin P1[1] also write 0x33B, 0x502 and 0x357, but as developer flag-menu arms (flag-op runs ending in a JmpRel back to the picker), not story writers.

Pinned by man_variant_carrier_census_disc.rs::region_gate_in_bracket_write_order.

The play-order residual

What is not played organically, and why none of it leaves a question open. The generic C1 / C2 seeder drives every family.

Arrival measured, family SETs unplayed: rayman, station, station3, bubu1 / bubu2 and deroa, each entered from a card-boot state (script-vm.md).

  • No gate family fires on arrival. Each spoke writes only its own arrival latch in 0x491..0x49C at entry-script +0x18, map03 keeps a one-hot story-stage word in 0x56D..0x570, and every entry clears the band 0x526..0x52E.
  • The chains fire only on walk-on and talk beats: rayman's 0x201 -> 0x1FB -> 0x200 -> 0x1FC, bubu2's requires-all list, station / station3 behind taiku's 0x38F, deroa's 0x3E1-gated descent. rayman2 also needs flag 0x1D5, which every ladder save from Mt. Letona on carries.

Walked without an organic family SET: retock / retockin - 0x357 is pre-latched and 0x502 does not fire, its writer hanging off Eliza's talk loop. Captured outside the poll corpus: doman's 0x3FB (P2[4]), son's arrival family, and the kor5 tail's 0x6C4 (P2[8], with its 0x436 input poked rather than played). The retock / doman / nilboa entry families are measured (field / locomotion). retock, doman, nilboa, son and the kor5 tail are walkable from a catalogued card block; the remaining spokes are the ones no card reaches.

Card brackets place every remaining family between two milestones. The two playthrough cards hold one retail save per story milestone, so a flag clear in one save and set in the next is written by the play between them (region_gate_card_brackets, save-library gated).

FlagsBetween
rayman's 0x201, 0x1FB, 0x200, 0x1FCSky Gardens and the Fire Path
0x1D5the Fire Path and Mt. Letona
retock's 0x357Mt. Letona and Ratayu
0x33B, 0x502Ratayu and Dohati's Castle
bubu2's 0x608Dohati and Sol Tower
bubu2's 0x3D3 / 0x609, doman's 0x3FBthe Sol Tower B2 and Usha saves
the kor5 tailthe two Sol Tower saves
0x370Usha and Nivora
0x378, 0x3A6, 0x60DNivora and Zora
0x38F, 0x3A7Zora and Conkram
deroa's 0x3E1 / 0x46D..0x46FRogue's Tower and Jette's Fortress

Two in-bracket orders are not script facts, and neither is observable:

  • doman's 0x3FB against bubu2's pair. doman P2[4] carries only its own C1 latch, so the order is the route the player takes between the two scenes.
  • deroa's 0x46E / 0x46F against 0x46D. P2[5] and P2[6] carry only their own C1 latch and no op 0x44 in deroa spawns them, so a walk-on tile does and the order is where the player walks.
  • Every reader of the three flags reads that one flag alone. 0x3FB has no field-VM TEST anywhere; its only C1 holders are its writer doman P2[4] and the two ending epilogues edbalden / eddoman P2[3], each listing [0x3FB] alone.
  • 0x46E's one reader is deroa P2[8], whose whole body is that test in front of [4C 38] (the camera-region query at the player's tile) and an idle loop.
  • 0x46F's two readers are deroa's scene objects: P0[0] parks its object at MoveTo 7F 7F when the flag is set, and P0[5] takes [4C 42] target -600 when it is set and -1500 when it is clear.
  • No record lists any of the three in a C2 gate, and the SCUS and overlay images carry no literal call of the flag helpers FUN_8003CE08 / _CE34 / _CE64 with these ids. Each flag's value is pinned by the milestone saves on both sides of the bracket. Pinned by region_gate_unordered_flags_have_no_joint_reader.

Measuring a spoke's arrival without a walk. A walk-on door is an exact tile match in the .MAP kind-1 trigger table, so writing the player object's position onto a door tile crosses it in about ninety vsyncs (autorun_w5a_poke_walk.lua; door tiles come from each .MAP's +0x10000 / +0x12000 gate rows). A door that opens a Yes / No picker takes one confirm press on top. Paired with the single-flag write watch (autorun_w5a_flag_watch.lua) that is one run per spoke from a card block that reaches its region; it measures arrival, never the walk. A flag written every other frame from an entry script's per-frame body is a one-hot selector or a position predicate, not a progress latch (chitei2, conc; script-vm.md).

Extraction-0874 §2 (player.lzs) F-variant pixels - a one-shot opening face-frame stamp, not a menu writer

Status: resolved - the installing event is named

The three F-variant halfwords in the field-character texture band are stamped once per game by the Rim Elm opening cutscene, through a field-VM MoveImage op.

  • The words. (853,271) 3333 -> ffff, (856,271) 3333 -> fff3, (857,271) 1e33 -> 1e3f - row 271 cols 1 / 4 / 5 of the Noa strip (TIM 2 at (852,256) 20x128; rows 271 / 273 = its rows 15 / 17).
  • Installer. town01 MAN P2[3] (★OP, the Rim Elm opening timeline record, C1-gated on the opening latch 0x225), body +0x392 / +0x3A0. After the opening's white flash + 60-frame wait it stamps the Noa face cell: MoveImage (852,336,6,16) -> (852,268) and (852,368,4,8) -> (853,284).
  • Op. 4C 60, a literal-operand MoveImage [4C 60 src_x src_y w h dst_x dst_y] with six misaligned u16s read via FUN_8003CE9C; handler arm 0x801E1B28..0x801E1B90, jal FUN_80058490 at 0x801E1B84.
  • Disc location. MAN offsets 0x735A / 0x7368 (PROT 0004 §1, LZS at container 0x25BEB). The operands are misaligned u16s, so an aligned scan does not find them.
  • Live catch. At ra = 0x801E1B8C the stamp reproduces the s3 anchor band byte-exact (autorun_s2s3_atlas_stamp.lua). The parked alternate frame differs from the boot cell at exactly the three halfwords.
  • Lifetime. The 0x225 C1 gate fires once per game, so every post-opening save carries the variant; the first battle effect-texture re-upload restores the disc bytes. A freshly booted game does not hold it: the title screen is all-zero and the mode-2 field-entry load uploads the disc bytes.
  • Not the pause menu (grade capture, exhaustive): with every DMA2 kick chain-walked for A0/80/E3/E4/E5 packets and GP0 PIO stores hooked, the whole pause walk issues zero image transfers and the band is byte-identical before and after. A 49-state library census shows plain field saves carrying the variant with no menu in their lineage while s1 / s2 hold disc bytes; the flip brackets inside the town01 opening (s2 -> s3).
  • Not a parked wrap-scroll phase. The three words equal the disc words at (x,273) by frame-content coincidence: the Noa strip is not shift-invariant, so a parked +2-row rotation would move dozens of rows. The wrap-scroll installers (move-VM op 0x1E, body 0x80023694; the op 0x45 sibling) and the FUN_80021DF4 dispatch-4 arm never fire across a full s2 -> s3 replay while the flip reproduces (autorun_s2s3_scroll_installer.lua).

Owning page: character-mesh.md.

What the op-0x49 entry-context kind byte is, and which screens it selects

Status: resolved (disassembly + disc measurement) - save-screen.md § Root command picker

The pause / save driver FUN_801DC6B4 routes on *_DAT_8007B450, and the byte it reads there is the op-0x49 sub-op.

  • Writers. Ten sw rt,0xb450(rs) sites exist across SCUS_942.54 and every extracted PROT entry. Two store a dereferenceable pointer: the field VM's op-0x49 Idle arm (0x801E09A8, storing the script's operand pointer, whose first byte the arm reads at 0x801E0984) and FUN_801D0B90's countdown expiry (0x801D0D04, pointing at the static record DAT_801F2278, kind 0x0B). The other eight store 0 or the 1 Done sentinel; the resume clears the slot at 0x801E08D8.
  • Kinds that select a screen, each at exactly one selector write in PROT 0899 (of 66 sw rt,0x46a4(rs) writers): 0 -> sub-screen 0x1A, 1 -> 0x19, 7 -> 0x20 (the casino prize exchange), 0x0D -> 4. Kind 0x0D also sends the root picker's cancel to sub-screen 3 (0x801D6D18).
  • Kind 0x0D is a scripted pre-battle party menu, briefed on entry and ready-checked on exit. Sub-screen 4 draws window 6, six static VAs in the overlay pool loaded at 0x801d636c..0x801d6448 (a pre-battle briefing); sub-screen 3 draws window 5, whose two headings (0x801CEC78 / 0x801CEC94) are a battle-start ready check, not a "really leave?" gate.
  • Reachability. crates/engine-scene/tests/op49_sub_op_census.rs walks every scene MAN's field-VM script and tallies the sub-op operands twice - a bounded, offset-deduped opcode walk and a raw byte upper bound - and kinds 7 and 0x0D appear in both. The walk decodes no tile-board sub-op (5) that the byte bound finds, so an absent walk row means "not decoded here", not "not on the disc".
  • Port. World::record_op49_park / World::menu_entry_context_kind, FieldMenuSession::{open_entry_screen, Notice, ReadyConfirm}.
What Soren Camp's sky is

Status: resolved (disassembly + capture)

The sky over son is an object actor like any other, seated by a cutscene: P0[5], the kind-1 bind of the map's one placed record (cell (16, 103), pack mesh 57), opens with 23 7F 7F and parks, and the system script seats it for each shot, CC 05 51 08 10 05 00 here. The 4C 51 arm writes tile and heading into whichever actor the op names (0x801E1880, 0x801E1898, 0x801E1900); son_field_door_hop holds the actor at (1088, 146, 2112), heading 0xA00. Port: the object arm of run_to_tile, and both hosts keep a record parked at scene entry in their bake and hide it per frame (World::hidden_object_records), so a script can bring it back.

What the wedge at a party member's wrist is

Status: resolved (disassembly + capture)

The character's Ra-Seru. See character-mesh.md: FUN_8001EBEC copies the arm group with the Ra-Seru over the bare arm once the record's slot is non-zero. The centre dancer's green 0x7785 texels in minigame_dance_retail_go_in, Vahn's red bracer and Gala's blue glove are that one swap.

Source: docs/reference/re-settled-threads/field.md

Text / fonts / dialog

# Settled threads: Text / fonts / dialog

ThreadAnswerGrade
Where does the escape-expansion buffer at 0x800740EC end?the longest shipped string uses 179 bytes of the 0x206-byte zero run
Why

FUN_80036514 has no bound and expands one NUL-terminated string per call, so the extent is the longest drawn string. Only FUN_80036888 forms the buffer address (0x800368D8). Expanding every exported string with its own substitution operands puts the longest at the sparring tutorial's lesson line (PROT 0967 0x801F7CA8, 178 bytes, drawn as a text widget): 0xB3 bytes, to 0x8007419E. Strings joined at run time by FUN_8003CAC4 are bounded by their parts, not measured. dialog-font.md.

disassembly + census
How wide may an item or spell description line be?144 px, unwrapped
Why

No routine on the path tests a width. FUN_801D0F1C hands item-table +8 to FUN_800337B0 at (WX, WY+0x10) (0x801D0F94..0x801D0FB8), which draws it through FUN_8003CD00 (0x80033818 / 0x8003388C); FUN_801D2E74 draws the spell description through the same routine at (WX, WY+0xE) (0x801D3018), two rows above "MP Used" at WY+0x2A. FUN_8003CD00 compares only for the widest result (0x8003CD50) and a first byte below 0x20 (0x8003CD68): it does not wrap. Both windows are 144 px wide from WX. Pinned as TEXT_LIMITS item_info_description / spell_info_description. dialog-font.md.

disassembly
When does a sliding picker first take input, and does the inn picker's cursor wrap?the call after the count reaches zero; 0x2A clamps
Why

The call that takes the slide count to zero ends j 0x801D95AC at 0x801D9414, past the cursor handler at 0x801D941C, so the hand is drawn on that call (0x801D9BB4) and input is read from the next. Both edge arms of the cursor handler test for state 0x12 (0x801D9474 / 0x801D94D0) and store the end index instead of wrapping; every other picker wraps. Ported once as engine-dialog::dialog_picker_slide, read by both hosts through picker_rect.

disassembly
Does the PAL (SCES) text renderer remap 0xD7 and 0xF8?no - retail draws the placeholder boxes
Why

All four PAL executables share one renderer (FR 0x80037088, the FUN_80036888 sibling) that addresses the glyph cell straight from the byte after one preprocessor pass (FR 0x80036BB4); the 0xD7 / 0xF8 immediates match SCUS, the French copies of 0897..0899 add none, and no image carries a translation table. A French cold boot with drawn strings rewritten to D7 / F8 / DD shows the page's labelled boxes beside a correct Î from 0xDD (autorun_pal_glyph_cells.lua). dialog-font.md.

disassembly + capture
Does a PAL executable rewrite text at draw time?French only: oe / OE to the ligatures
Why

The French renderer folds oe to 0x9C (œ) and OE to 0x9D (Œ) at 0x80036D14; the German, Italian and Spanish executables do not, and the French script stores both letters. All four PAL renderers also treat 0xC1 n with n >= 8 as an x-tab to the line's start x plus n (FR 0x80037138).

disassembly
How does a dialog picker open?it slides in from off screen
Why

A press in 0x19 stores the sentinel +0x54 = 0x309. The odd state's first call (0x801D92F4 for 0x11, 0x801D9350 for 0x13 / 0x15 / 0x17) sets span +0x50 and count +0x54 to 0x18 plus start, target and size; each call subtracts the frame step, and at count 0 the shared tail (0x801D93F4..0x801D9418) steps to the even state, where the hand and input appear. The box sits at target + (start - target) * count / span. 0x2A slides in from (0x150, 0x4A) to (0xD8, 0x4A) at size (0x58, 0x1A); 0x27 / 0x28 / 0x29 rise from (0x26, 0xF0) to the N-option rect. Captured: input opens 26 vsyncs after the press. mes.md.

disassembly + capture
Does the USA dialog font carry accented glyphs?yes - 32 inked high cells
Why

The font TIM at PROT.DAT 0x7F40 (boot pack member 3) inks CP437 0x80..0x90 / 0x95..0x9A, 0x9C, 0x9F, and 0xA0..0xAC one cell below CP437; the SCUS width table 0x80073F1C gives 26 of the 32 a zero advance, so they overprint the next letter. Five mednafen states hold the VRAM (896, 0) page byte-identical to the disc TIM. Disc-byte evidence; pin translation_accent_font_real (dialog-font.md).

capture
When does a dialog picker open, and what presses for the player?only on a 0x19 press; _DAT_80073F00 presses automatically
Why

The picker states 0x11 / 0x13 / 0x15 / 0x17 are stored only behind state 0x19's press test (0x801D8FCC; stores 0x801D9058..0x801D909C). While _DAT_80073F00 is positive each 0x19 call subtracts the frame step, and the call that reaches zero presses confirm (0x801D8F4C..0x801D8F88); a poke of 21 reads 19 .. 1 over eleven calls. Op 4C 89 is its only writer (37 sites) (mes.md).

disassembly + capture
What does a confirm press do during a short-row hold?clears the hold and latches skip speed 0x25
Why

On the 72-tick hold the press clears the hold and latches 0x25 on the next call; the call after goes to 0x0D. The engine matches all 379 vsyncs of the trace (dialog_window_disc) (mes.md).

capture
Does retail wrap dialog text to the box?no - no text surface wraps
Why

FUN_80036044 returns the typewriter glyph count (move v0,a3 at 0x80036504; it reads no width table and is not a wrap pre-pass). FUN_80036888 draws the expanded string with no clip and no width test; its one bound is a2, a cap on units drawn (counter steps at 0x80036938, 0x80036B20, 0x80036B9C; exit test 0x80036908..0x80036910), which the pager feeds its typewriter counter for the typing row (0x801D9720) and 0 elsewhere. The pager FUN_801D84D0 never measures a row. Line breaks are authored 0x7C bytes and 0x1F lines. dialog-font.md.

disassembly
How wide can a field dialog row be?244 px at one extra pixel per glyph
Why

Rows draw at the box x with DAT_800740E8 = 1 (0x801D97D8) in a 0xF4-wide centre rect (0x801D99CC), three rows a page; the v0_1_tetsu_dialogue_accept display list steps glyphs widths[c] + 2 apart. Menu, shop and battle columns: dialog-font.md.

disassembly + capture
What ends an NPC talk?FUN_80038050's verdict on the byte after the box
Why

FUN_80039B7C hands the byte after a scanned box to FUN_80038050 (jal at 0x80039C84) and ends the talk when it returns 0 (0x80039C8C). Its jump table 0x80010F38 (bytes 0x21..0x4C) continues on 0x24 / 0x25 / 0x48, the option bytes 0x27..0x2A and 0x4C FF / FE; 0x21 steps past itself and ends; every other byte ends with the cursor left on it. The retail inn capture parks on the 26 9D FE loop-back, which the next talk runs (script-vm.md).

disassembly + capture
When does op 0x4C's sub-5 acquire refuse?two tests
Why

In the arm 0x801E2148..0x801E21DC: a target other than the player whose +0x94 is zero (0x801E2148..0x801E2164), and a target already carrying 0x400 while the scene word *(_DAT_801C6EA4) + 8 is 0 (0x801E2168..0x801E218C). Either leaves s7 = 0 and the PC on the op. The second is the same test as op 0x43's acquire and the dispatcher's halted-target early-out.

disassembly
Where does an NPC's first dialog line start when it opens on an escape?at the escape
Why

113 of the 1586 talkable partition-1 records open their first line on an escape, 1F C1 00 ... (the lead's name) the common case; the 0x00 is C1's argument, not a terminator (the substitution gate addiu v0,a0,0x40 / sltiu v0,v0,0x8 at 0x800360FC..0x8003610C takes 0xC0..=0xC7 as a two-byte token, and the C1 arm reads the byte after it at 0x80036158), so a scan to the first 0x00 starts one line late. man_field_scripts::first_inline_dialog_offset walks lines with dialog_box::line_end (mes.md).

disassembly
What draws the battle command chips' words?text: placement-record payload strings
Why

Each chip is a screen-element placement record whose +0x14 payload points at a string: Auto, Command, Attack, Item, Run, Begin in the executable's small-data pool 0x8007B658..0x8007B690, Spirit and the Ra-Seru names in the battle overlay (battle.md).

disassembly
How does a localized build lay out its UI strings?by length, so pools move while their references stay
Why

The Spanish build's Automatico sits in another pool from the USA Auto while the placement record pointing at it is the same record: strings of up to eight bytes land in the $gp small-data pool, longer ones in read-only data. On the USA executable ($gp = 0x8007B318, set at 0x80026CA8) all 42 data-pointed strings in the $gp window are at most eight bytes with the terminator, all 704 longer ones lie outside it, and the short ones outside are PsyQ libcd names and two code-word false hits. The lift pairs a pool string through the code and data words that reference it (pal-localizations.md).

disassembly
Do PAL builds keep the narration crawls' page counts?no - they reflow with blank pages
Why

A crawl's page count is part of the script (CC F8 80 N), and the PAL builds reflow a block into more pages with blank " " pages between paragraphs - opdeene is 14 + 8 pages on USA and 16 + 9 on the Spanish disc (pal-localizations.md).

capture (disc read of both builds)
Where does the narration crawl's geometry come from?a scene config block seeded by CC F8 E8
Why

FUN_80037174 reads *0x801C6EA4 +0x4C top, +0x4E line slots, +0x50 divisor and +0x52 release count; FUN_8003A024 resets them to 0x40 / 8 / 4 / 0 and a seed op before each block overwrites the first three. Line pitch is a fixed 16 (addiu s3,s3,0x10). The cold-boot opdeene capture holds the frame-step floor DAT_8007B9D8 at 3, so at divisor 4 the crawl climbs a pixel every two frames (cutscene.md).

disassembly + capture
Dialog font extractionresolved
Why

The glyph page lives at VRAM (896, 0)..(960, 256); its on-disc carrier is a plain 4bpp TIM at PROT.DAT offset 0x7F40 (framebuffer (896, 0), CLUT (0, 510)), byte-identical to the save-state page. Two extractors: legaia-font's font-extract from any in-game save state, and legaia_font::Font::from_disc_tim_and_scus from the disc alone (the browser pause menu uses it). dialog-font.md.

capture
Inline dialog-box format (0x1F-lead segments)resolved
Why
disassembly
Tetsu 4-option spar menu mechanismresolved
Why

The menu is a standard 0x29 4-option MES inline picker in the sparring partner's dialogue (cursor *(0x801C6EA4)+0x0C; confirming index 2 "I want to practice with you." starts the spar - live 0x03->0x09->0x15, driven by the dialog SM not the field VM). It uses the immediate-labels form (labels straight after the N jump entries, no continuation byte), which legaia_mes::picker::parse_picker_at accepts. Engine: a CarrierMenu on World::carriers.menu presents the picker and engages the carrier only on the index-2 fight option. Tests: parses_immediate_labels_picker, tetsu_spar_picker_disc, carrier_spar_menu_* (mes.md).

capture
Pause Items/Magic screens: remaining sub-flowsresolved
Why
disassembly + capture
What does PROT 0892 (card_data) carry, and who loads it?the memory-card screen's JIS X 0208 level-1 kanji font; a container-formats::pack of two TIMs, not a truncated stream
Why
disassembly + capture
Which routine samples the card-screen kanji page (VRAM (320..447, 256..511), CLUT rows 475..482)?negative (nothing does - it is loaded and parked)
Why

A sampler would have to carry tpage 0x15/0x16 and CBA 0x76C0 + plane * 0x40; a byte sweep of SCUS plus every based overlay finds neither as an immediate or a data halfword, the uploader FUN_800198E0 records no handle, and a GPU-FIFO watch at card-screen entry (mode 0x16 -> 0x17) draws nothing from the page. The USA build ships the Japanese glyph page and never reads it. See save-screen.md.

capture (image sweep + GPU-FIFO watch)
What is the battle plaque's element badge, and how is it selected?a caret escape inside the monster's own name string
Why

The badge is not a separate field: the name carries ^A..^H, the preprocessor FUN_80036514 rewrites ^X to 0xCE, X - 0x2D (addiu v0,v0,0xd3 at 0x80036564), and escape-table entries 0x14..=0x1B at 0x80074050 name sprites 0x8B..=0x92 - so the badge is letter - 'A' into that strip; and element -> caret is the fixed permutation [4, 3, 0, 2, 1, 5, 6, 7]. The map is a zero-exception bijection over the shipped records (^H on Cort is no exception). Parser MonsterRecord::plaque_badge; see battle-hud.md.

disassembly + census
How is the text-cell placement run seeded, and what is the overlay_0897_801dbc30 dump?three placement records, four stores each in a fixed order; the dump is a chimera of two PROT entries
Why

The loop is PROT 0898's 0x801D3FC0, based at 0x80076C10 + 0x408 (placement record 43, bound sltiu 0x2E, so records 43..45 - not 46 scratchpad records). Per record it writes +4, +0xC, +0xA, +2, and the +2 store carries the record's previous +0xA, read before the +0xA store lands. The overlay_0897_801dbc30 dump splices PROT 0897's 0x801EA448 onto this 0898 loop past its j 0x801EA7AC, through 0897's over-read. See script-vm.md.

disassembly
What does FUN_80036044 count past a two-byte unit?one extra byte past the NUL per unit
Why

A pre-walk sizes the loop to the string's byte length, and the loop decrements that size once per iteration whatever it consumed, so each 0xCE, 0xCF or substitution runs it one byte past the NUL and counts what is there; 0xC0 and 0xC6 count nothing. The field pager consumes the count at 0x801D8A6C (mes.md).

disassembly
How fast does the field pager type, and when does a row end?one unit a frame; a row ends on its glyph count
Why

Every pager open stores speed _DAT_801F2754 = 1 (0x801D9118 and three siblings). A row ends when the reveal counter reaches FUN_80036044's count, and a row under 0x22 units holds (0x22 - count) * 4 in _DAT_801F275C, drained by 32 * DAT_1F800393 a call - ceil((0x22 - count) / 8) frames, at most five (dialog-font.md).

disassembly
When does the field pager finish a typed row?one call after the reveal counter passes the glyph count
Why

The row gate compares with slt v1,s0,v1 at 0x801D8A7C, so a row ends once the count is below the counter, not when they meet. Each typing call adds min(dt, 4) capped at three units; a short row then holds (0x22 - count) * 4 in _DAT_801F275C, drained 32 * dt per call (0x801D866C..0x801D8690), and a page's last row never holds (state 0x19 clears it at 0x801D865C). A town01 trace matches the engine's dialog_pacing for 61 of 61 vsyncs (dialog-font.md).

disassembly + capture
What does the field pager do when a page holds more rows than the box?it scrolls; a page ends only at a control byte
Why

After a row, (b & 0x7F) < 0x20 at 0x801D8AB4 looks for another line; with the three slots full the window scrolls (state 0x0C, scroll word _DAT_801F2738) and typing continues with no press. A 0x24 page turn keeps the row table and types beneath the old rows (state 5); at the page end state 0x0F scrolls the carried rows away. A confirm while typing latches _DAT_801F2750 = 0x25 and state 0x0D completes the page in two calls; the page wait is 0x19. Engine dialog_window; the disc test matches retail for 411 of 411 and 335 of 335 vsyncs (mes.md).

disassembly + capture

Long write-ups in this area

Pause Items/Magic screens - remaining sub-flows

Status: resolved. Evidence: disassembly + capture.

The four sub-flows of the pause Items / Magic screens, and the 0x800 row dim bit, are traced from disassembly and ported in engine-ui / pause_screens.

Sub-flowRetailNote
Window-14 target panelFUN_801D0520Its preview modes are the permanent-stat Water previews, not an HP-restore preview.
PAGE spriteUI-icon 0x76
Kind-4 list kernelFUN_80032A44 + allocator FUN_80030104SCUS-resident.
Class-0x80..0x82 Use routessubmenus 0xA..0xDSingle-target apply FUN_801D8308; Door of Light / Wind FUN_801D8A58 / FUN_801D8B90; Incense FUN_801D8D94.

The 0x800 dim bit is set at build time, never on focus:

  • Writer: the SCUS content builder FUN_80030628, content-id-3 case. It dispatches on live window +0x1C, copied from descriptor byte +0x0 at create (0x80032990).
  • Rule, in order: equipment is always dim; Door ids 0x88 / 0x89 are scratchpad-gated; the field-usable bit 0x2; then the applicability probe FUN_8003043C (battle context gates bit 0x4).
  • No focus-dependent write exists. The white-to-grey flip is the kernel's mode-4 park override; a capture shows the row words bit-identical across focus states.

Owning page: field-menu.md; the row build is at field-menu.md.

Inline dialog-box format (0x1F-lead segments)

Status: resolved (prologue, pager-side dispatch, option-list format, multi-segment box packing). Evidence: disassembly.

Placement-NPC and event dialogue is inline in the field-VM interaction record, not in the scene MES: a run of 0x1F-lead, 0x00-terminated segments of MES glyph bytecode, each one line. The record is ordinary field-VM bytecode around those lines, and the dialog SM FUN_80039B7C runs the field VM over it, handing a text byte to the pager FUN_801D84D0.

Where the text is

  • The opcode-decoded text_id is a box-config id; it never resolves through SceneMes::message_offset (0 of 13 town01 placement-NPC ids resolve).
  • The text is recovered structurally, not from the 0x3F op's len: a text-heavy record desyncs under linear disassembly (a literal > is 0x3E, the warp / interact opcode; ASCII punctuation hits the 0x37 / 0x41 yield bytes), so len reads empty for every town01 NPC.
  • Each record holds the NPC's whole line set - every line of every story-state branch, with Yes / No labels interspersed (Village Elder 80 segments, Val 59). Multi-page speech is several 0x1F segments, not 0x80..=0x9F control bytes inside one. 36 town01 placements carry renderable dialogue (the sparring partner, Meta the dog, villagers, leftover "dummy" developer placeholders, and a developer story-flag toggle menu at placement P1[1]).
  • No "box-geometry header" exists. The bytes between script_pc0 and the first 0x1F are the interaction prologue: CFlag / SysFlag.Test / JmpRel / Nop / 0x4C 0x51 (move to tile) / 0x4C 0x52 (menu-activation poll). The prologue's story-flag branches JmpRel to the segment the box opens on.

The dialog SM FUN_80039B7C

  • State 0 calls the field-VM dispatcher FUN_801DE840 on the stream and enters the pager only when the PC rests on a byte with & 0x7F < 0x20 (a 0x1F lead or the 0x21 terminator).
  • It advances actor[+0x9C] 0 -> 1 -> 2 through the segments. Its state-2 advance masks (byte & 0xF0) == 0xC0 and consumes the escape's data byte, so a 0xC? escape whose argument is 0x00..=0x1E (0xC1 0x00) does not end the line.
  • Retail's interaction cursor is one instruction past the 0x21 the spawn section executed, not script_pc0 (script-vm.md). It persists across talks: the forwarded PC is stored before the 0x21 test (0x80039E24), so a chest's second touch resumes behind its grant.
  • A raw 0x21 after the prologue has spawned a record ends the talk (FUN_8003CF7C breaks on it): vozz P1[7] spawns P2[13] and stops.
  • The talk ends on FUN_80038050's verdict on the byte after the box (see the "What ends an NPC talk?" row); the pager itself only clears rows and returns no status.
  • Not the renderer: FUN_8001EBEC is a per-character TMD-pose copier (party slots 0..2, indexed by the slot-4 freeze flag _DAT_8007B824; seven u32s from TMD +0x124..+0x140 or +0x140..+0x15C, gated on record flag +0x75E; see op 0x4C sub-3 in script-vm.md). FUN_8003AB2C is the per-frame field-VM driver and FUN_8003BDE0 the partition-record dispatcher.

Box packing

  • Consecutive 0x1F lines pack into one window of _DAT_801F2740 = 3 rows: a 0x00 terminator directly followed by 0x1F is "same box, next row". A fourth row scrolls the window (state 0x0C); the page runs on to the next control byte.
  • The contiguous run stops where the pool hands control back to the field VM (a non-pager control byte, Dispatch::Unknown).

Post-page dispatch (pager state 0x19) reads the control byte past the box, matched as byte & 0x7F (so 0xA7..0xA9 are accepted; the field corpus stores the bare form):

ByteNext stateMeaning
0x250end
0x243next page, same box (own prologue, jumps away at 0x801D916C)
0x489new box
0x4C 0xFF6terminate
0x2A0x11 -> 0x12inn Yes / No picker
0x27 / 0x28 / 0x290x13 / 0x15 / 0x17 -> 0x14 / 0x16 / 0x182 / 3 / 4-option picker
other9new box
  • Each odd picker state is the slide-in (see the picker rows above).
  • Three arms run the box-reset tail: states 0, 6 and 9. They are byte-identical over their 0x98 bytes except the li v0,N choosing the successor (0 -> 1, 6 -> 7, 9 -> 0xA).
  • JT[1] == JT[4] == JT[7] == 0x801D8708 is the teardown (early return in state 4, so 0x24 keeps its rows); JT[0xA] == 0x801D92A4 is the box-open animation. So 0x25 and 0x4C 0xFF behave alike and both differ from 0x48 - the port's End / Terminate versus NewBox grouping.

Option list (picker control region)

  • Layout: [open][N * 2-byte i16 LE jump table][continuation?][N * 0x1F label segments].
  • The continuation byte is optional: a post-page dispatch byte (0x24 / 0x25 / 0x48 / 0x4C), or absent with the labels starting at once (the immediate-labels form - the Tetsu spar and town01's other pickers).
  • Each 2-byte entry is a signed relative jump FUN_80038050 applies on confirm: new_pc = (open + 1 + index*2) + i16_LE(entry[index]). The entries are not the labels. The four izumi re-emissions shift every entry by one per-emission delta, and every option lands in-bounds.
  • Each picker arm sets the box size from a per-N table and clamps the cursor at *(DAT_801C6EA4 + 0xC); on confirm it reads the continuation byte at open[N*2 + 1] through the post-page table.

Port

PieceLocationPin
First-line finderman_field_scripts::first_inline_dialog_offset (printable-ratio gated); classify_placement -> PlacementKind::Npc::dialog_inline
Segment pooldialog::decode_inline_segmentsfield_actor_placements_disc::inline_dialogue_decodes_into_full_segment_pool
Prologue decodefield_disasm::LinearWalkerfield_actor_placements_disc::dialog_prefix_decodes_as_field_vm_bytecode
Box packinglegaia_mes::dialog_box (pack_box / pack_boxes, LINES_PER_BOX = 3, Dispatch)field_dialog_boxpack_disc (all 561 town01 boxes <= 3 lines; the spar opening = three 0x24-chained pages -> a 4-option Picker; the Mist appeared, .., but line survives its 0xC1 0x00)
Pickerslegaia_mes::picker (scan_pickers / parse_picker_at / Picker::jump_target)field_dialog_pickers_disc (config On / Off / Exit, shop haggling, the Genesis-Tree quiz; in-bounds jumps)
Dialog SMengine_field::inline_dialogue, World::step_inline_dialogue (PORT FUN_80039B7C)field_interact_dialogue_disc, inline_dialogue_prologue_selects_segment_by_story_flag, ..._falls_back_when_it_cannot_reach_a_segment
  • The dialog SM path runs the record through the field VM, so an option's branch executes its flag ops and scene changes before the reply. It is gated by World::toggles.use_vm_dialogue, the play-window default; --simple-dialogue types World::npcs.dialog through OwnedDialogPanel::from_inline_dialog instead.
  • The untruncated record is kept as World::npcs.dialog_prologue (man_field_scripts::placement_interaction_record, entered at the interaction cursor). On a first talk the runner falls back to the first segment when the prologue reaches none. A talk resumed from the cursor an earlier talk wrote back ends on an executed raw 0x21 before any box, as the SM does, and never falls back (InlineDialogPrologue::resume): a chest's second touch runs its "empty" loop, a pickup spot's is silent (the interaction cursor).

Owning page: mes.md (pager and picker layout).

PROT 0892 is the card-screen kanji font

Status: resolved. Evidence: disassembly + capture.

PROT 0892 (card_data) is a container-formats::pack of two TIMs carrying a JIS X 0208 level-1 kanji font for the memory-card screen; the entry is 33 sectors.

  • Loader: mode-22 CARD INIT FUN_8002574C only - alloc 0x19000 at 0x800257D0, retail leg li a0,0x37e + jal 0x8003eb98 at 0x8002580C (raw TOC 0x37E = extraction 0892), then a pack walk at 0x8002581C..0x80025850 handing each member to FUN_800198E0.
  • Members: two 0x8220-byte 4bpp TIMs, CLUT (0, 475), pages (320, 256) / (384, 256).
  • Encoding: the CLUT is a bit-plane selector over a 1bpp font packed four planes deep - 12 px pitch, 2,965 inked cells (the level-1 kanji count), plane 0 in ku-ten order.

Owning page: data-field.md; the page is never sampled in the USA build (save-screen.md).

Source: docs/reference/re-settled-threads/text-dialog.md

Animation

# Settled threads: Animation

ThreadAnswerGrade
At what cadence does retail step a gated field clip?(rate*2 + div - 1) / div
Why

FUN_800204F8 steps a gated clip (rate*2 + div - 1) / div (0x800205B4..0x800205EC, div = clip byte +6) and an ungated one rate, each times DAT_1F800393, wrapping to 0 or clamping at the end. A Rim Elm capture matched 13455 of 13455 checkable calls. Port actor_tick::clip_step in FieldClipPlayer, shared by both hosts (anm.md).

capture + disassembly
Does FUN_8001BE80's blend gate equal the clip tick's scaled-step bit?yes - one header bit
Why

Both test clip byte +1 bit 0 through actor+0x4C: the blender at 0x8001BF70 / 0x8001C0EC, the clip tick at 0x800205B4.

disassembly
What does the field frame blender interpolate?translations linearly, angles through FUN_8001D088, with a retry
Why

Translations blend as cur + ((next - cur) * frac >> 4); angles go through FUN_8001D088(next, cur). The next entry is frame + 1; on the last frame it is the entry itself under +0x62 & 8, else frame 0. The fraction is +0x68 & 0xF. Past a summed arc of 0xC00 the angles are re-blended against (x+0x800, -(y+0x800), z+0x800) (0x8001C170..0x8001C1CC). Port PlayerAnmBundle::sample_bone, live on both hosts (anm.md).

disassembly
Does FUN_80021DF4's default motion block run for dispatch 3?no - arms 3 and 5 both branch past it
Why

beq to 0x80022B80 at 0x800228A8 (arm 3) and 0x800228B0 (arm 5) skip the block 0x800228B8..0x80022B80; the +0x9C counter gates only arm 3's FUN_80019D50 call. Port engine-vm::actor_tick::tick_actor skips the block for both (actor-vm.md).

disassembly
Player ANM per-record layoutbyte-4 low nibble carries high4(t_z)
Why
disassembly
Battle anim-id space + record[0] "strike family"resolved
Why

Anim ids are entry indices (commit FUN_8004AD80; idle id = 0; FUN_801D5854 ids 6..9 = a camera program space). Tags 2/3/4/5/0xB = the hit-reaction family (+0x1EF..+0x1F3 map; FUN_800402F4 stages flinch/knockdown). Swings = the equipment-section splice (slots 0xC..0xF) + dynamic art slots 0x10/0x11 from the +0x58 art bank. Capture-pinned + disc census. See monster-animation.md / battle-data-pack.md.

capture
FUN_80047430 callerresolved
Why

A single dispatch site (capture autorun_anim_node_tick_caller.lua, mid-battle save): jalr v0 at 0x800252B4 inside FUN_8002519C, the per-frame actor-list tick iterator, calling the node's +0x0C handler slot with the node pointer in a0. The anim-node tick is an ordinary list-node tick handler; no other caller fires. See functions.md.

capture
Record[0] +0x5C pointer + art-anim bank stream source+0x5C = vestigial paired-relocation
Why

Art streams = "ME" archives in readef.DAT slots 3*char+1/3*char+2. +0x5C is a self-relative pointer rebased at load, paired with +0x58, by FUN_80052FA0 (0x800532BC..C8, 0x800532D4..E4). +0x58 has a reader; no +0x5C reader exists in SCUS - a sweep of all 110,080 text words finds one non-sp word load (lw) there, the relocation itself (the nine lh / lhu there are not pointer reads). The overlay side is open: 11 images are dump-only (dump-corpus-integrity.md). See battle-data-pack.md.

disassembly (SCUS exhaustive; overlays partial)
Do the dance hall's beams move?yes - plain floor actors on a clip
Why

The floor pass FUN_801D2A10 stamps a non-marker cell's record class byte into +0x5C and the plain template's tick FUN_801D4098 runs the clip driver on it. other7 binds clips 1, 2 and 4; minigame_dance_retail_go_out holds fourteen such actors on cursor 312. Port dance::HallProps + DanceVenue::props (dance).

disassembly + capture
Which carriers arm the effect ribbon FUN_801CFA48?op-0x42 nodes in four cast modules, battle only
Why

Move-VM op 0x42 stages the ribbon block through the +0xAC-relative anim block (s1 = actor + 0x80, 0x80023088); the carriers are one node in PROT 0923, two in 0934, one in 0957 and one in 0964, each seeding 0x3039 with a plain step cap in +0x9C (12, 10, 10, 10 and 7 across the five nodes) and no +0xCA growth rate, so every bolt draws at full length from its first frame. The draw-kind-4 emitter reaches the ribbon through jal 0x801CFA48 at 0x8001B120, a SCUS call into slot-A code, so it lands on the ribbon only while the battle overlay is resident (move-vm.md).

disassembly
Is the ambient ramp pool FUN_80036D80 part of the motion VM?no - an actor template of its own
Why

Its only reference is the tick word at 0x800742F4, so ramps keep landing on frames where FUN_80038158's op loop is gated off.

disassembly
How is the effect ribbon drawn?as the actor's own TMD model
Why

FUN_801CFA48 builds a Legaia TMD object header at out + 0xC - one group, count steps * 6, flags 0x26, ilen 9, mode 0x3C - and FUN_8001ADA4 writes it into every slot of the actor's +0x44 model list (0x8001B08C..0x8001B0B4), so the ordinary TMD renderer draws it. Each step emits the core strip twice, then two flare and two fringe packets, all at UVs (0..2, 0xF0..0xF2), tpage 0x001F, CLUT 0x7F84 (0x801CFFF4..0x801D025C). Engine: effect_ribbon::ribbon_mesh_for_actor, drawn on both battle hosts; Gilium (spell 0x95, PROT 0923) is the cast that shows it in play.

disassembly
Where does move-VM op 0x34 store?through actor + 0x90
Why

Arm 0x80023B64 (jump-table word 0x80010848) stores its lh operands as sign-extended words - +0xAC, +0x9C (sign half in +0x9E), +0xA0, +0xA4, +0xA8 - and its lhu operands as halfwords at +0xB0, +0x90, +0x92. All eight operands are stored; none wraps to an 8-bit block offset (move-vm.md).

disassembly

Long write-ups in this area

Player ANM per-record layout

Status: resolved (container + per-(bone, frame) semantic). Evidence: disassembly.

A record is a 16-byte head followed by one 8-byte entry per (bone, frame); each entry is a nibble-packed translation plus three byte angles, decoded as the retail sampler FUN_8001BE80 (ghidra/scripts/funcs/8001be80.txt) does.

  • Size: record_size = 16 + 8 * (a & 0xFF) * b, with a & 0xFF the bone count and b the frame count. It holds byte-exact on all 296 records of the 5 pinned scenes, and on every other scene bundle the corpus sweep finds.
  • Head: 8-byte (a, b, marker_1 = 0x080C, flag) header + 8-byte per-anim prologue. Offsets in the bundle's offset table are absolute byte offsets, not relative to +4.
  • Translation (bytes 0..4): three signed 12-bit values (t_x, t_y, t_z). Byte 2 is high4(t_y) << 4 | high4(t_x); byte 4's low nibble is high4(t_z) (andi v0,v0,0xf at 0x8001BF38) and its high nibble is unused. Sign-extend on bit 11.
  • Rotation (bytes 5/6/7): three u8 angles (r_x, r_y, r_z), each << 4 to a PSX 12-bit angle (4096 = 360 degrees), composed Z, then Y, then X through FUN_8004638C / FUN_8004629C / FUN_800461A4.
  • Posing: a piece poses R*v + T about its own object origin (no centroid subtraction); frame 0 of an idle clip is the rest pose.
  • Blend: when clip byte +1 bit 0 is set, FUN_8001BE80 blends two frames on the 4-bit fraction *(u16*)(actor+0x68) & 0xF: translations as a + (((b - a) * frac) >> 4), angles through the wraparound-aware FUN_8001D088, composing into scratchpad 0x1F8002C0.
  • Not this layout: FUN_80021DF4's +0x5A == 6 block uses a separate 24-byte-per-bone keyframe layout.

Port: legaia_actor_formats::player_anm - BoneTransform::decode (one entry, pinned by bone_transform_decode_signed_12bit on town01 record 17) and PlayerAnmBundle::sample_bone (the two-frame blend); the size invariant is the disc-gated crates/actor-formats/tests/player_anm_real.rs. The site characters page runs the same (t, r) pipeline.

Owning page: anm.md.

Source: docs/reference/re-settled-threads/animation.md

Audio

# Settled threads: Audio

ThreadAnswerGrade
Why does track 2016 score worse under Rim Elm's river?the river's volume writes delay the score's key-ons
Why

The emitter re-volumes its voices each part tick through FUN_800657D0, which raises the voices' flags, and the flush calls FUN_8006C048 for every flagged voice before its KON write, so on the field's every second flush the score keys about 2.5 samples later. Modelled, the five river states' emitter channel reads 0.997..0.998, against 0.985..0.991. An emitter's level rides the score's flush.

disassembly + capture
Does a walk into East Voz Forest keep the overworld theme?no walk: map01 P2[31] is a cutscene the entry script spawns, and the engine keeps its music
Why

map01's P1[0] spawns P2[31] (flat 0x34) when flag 0x2C3 is up, which vozz P1[7] raises ahead of its own sub-op 6; no walk-on trigger names the record, and P2[28]..P2[32], map02 P2[11] and map03 P2[16] are entry-spawned the same way. The retail chain from vozz_genesis_tree_songi keeps 2035 through both doors; vozz_ozma_hatching_hover's 2000 came from its poke route. Write-up.

capture + disassembly
Does a field script change the BGM word inside a swap's read?no script record does
Why

A walk of every scene MAN record finds no second sub-op 1 a frame or more behind a first inside the 66-vsync read; the same-frame pairs are harmless. Write-up.

disassembly
What clears the drive word 0x8007BC20 before a clip ends?the stop FUN_8003ED04, which no battle path issues over a sounding clip
Why

Seeks and stream loads stop a clip, but in battle the selector, the module seeks and the side-band applier all keep off a sounding one. Write-up.

disassembly + capture
What raises a summon voice's CD volume past the cue level?resolved
Why

Any change of the audio level cell _DAT_8007B910 under the clip: FUN_8001698C hands it to FUN_8002614C (0x80016A1C..0x80016A34), which opens on SsSetSerialVol(0, 0x3FFF, 0x3FFF) (0x80026168..0x80026174). Orb's duck is still ramping when its voice starts (180 to 161 over its first 21 vsyncs, frame-stepped), and the recording plays the clip 0.74 dB over the cue gain. Port: StreamResampler::raise_cd_volume_to_full on every level step.

disassembly + capture
What renders a cast's cues for the real-PCM channels?resolved
Why

The engine itself: the retail comparison corpus's seed of the battle state, re-run with its director pulled once a tick and recorded from the gate (LEGAIA_DIAG_AUDIO_WAV), mixed into the track render at a fitted sub-vsync offset: the cast channel. Its first run found Call Wave playing Big Wave's voice and folding as a hit on Zeto; zeto_call_wave_mid_cast reads 0.9992 at +0.01 dB over the cast.

capture
What does PROT 0944's voice follow?resolved
Why

The roll arm 0 latches: FUN_801DD6B4(0x38E, ..) at 0x801F6AD8, then 0x1AD when the victim's HP is below it and 0x1AB otherwise (sltu at 0x801F6B2C), so Cort announces a kill. The hit lands on arm 3. No retail recording of the cast has been scored.

disassembly
Why does one 2064 key-on go out with ADSR2 0?resolved
Why

A layer the scan drops: FUN_80066B00's failure tail stores 0 to record 24's +0x2A (0x80066D74), which is voice 0's shadow ADSR2 0x801CE08A, so a voice-0 key-on staged earlier in that calc keys with ADSR2 0. A write watch in the interpreter off s2_rimelm_town01 sees 0x80065B50 store 5FC8 and 0x80066D74 store 0 in calc 12651 (allocator).

disassembly + retail interpreter
Why did the keying differential pick other voices?resolved
Why

The port's silent history started at index 0, retail's at the state's _DAT_801CE204; the release test reads the fixed words 0..14, so the phase decides fifteen or sixteen flushes before a silent voice is free (phase). Seated, what voice rows remain are nearly all 2068's release tails.

disassembly + differential
Does a note-off look at the channel?resolved
Why

No. FUN_8006688C tests key, program, owner and VAB (bne at 0x80066904, 0x8006691C, 0x80066934, 0x8006694C), the program being the channel's as it stands (lbu a2,0x37(a2) in the caller at 0x80061BDC), so a program change strands a held note and a sibling channel's note-off releases it.

disassembly + differential
When is a cast's voice heard?resolved
Why

On the arm of ctx[+0x279] that holds the head cue's FUN_8004FCC8 call, past the module's seek and stream wait: the caller log has PROT 0904's call on arm 3, 120 vsyncs into the phase byte's walk, where the port had raised it at the arming seam. Port: module_head_cue_arm, World::raise_cast_voice_on_arm.

disassembly + capture
How long does a CD-XA clip take to start after its trigger?on mednafen
Why

62 or 88 ms with the head in the clip's file and 371 ms from another file, plus one sector per channel: a clip's start. No console has been timed.

capture
Is the zeto_call_wave_mid_cast track level a mix-law gap?no
Why

The +5.09 dB is the cast's own cues: per two-second window +4 to +14 dB over the recording's first six seconds, then within 0.11 dB to its end.

capture
Why does Palma's parked flinch sound in the engine and not in retail?resolved
Why

Nothing holds retail's clip: seats.csv commits the row's clip 2 inside arm 0xF, and arm 0xF's pass stores 8 into its cue cursor (sb a1,0x1F6(v0) at 0x801F8360, li a1,0x8 at 0x801F82D8), one past the eight-slot track, so FUN_800508DC fires nothing for the rest of the clip. It is the only slot-B module with that store. Port: World::apply_palma_writes.

disassembly + capture
Why does a flee's walk sound no steps?resolved
Why

The flee staging zeroes the eight cue slots of every party slot's walk record, bank entry 1, ahead of its +0x1DA = 1: 0x801E7D68..0x801E7D80 in the run band, 0x801F7600..0x801F7618 in PROT 0925. A write watch on flute_spikefish_midcast catches both stores at 0x801F7600 / 0x801F7618 on the flee's pass, and the walker FUN_800508DC then reads a zero track. The gate at 0x800478C0 reads 0x8007BD71 = 0xFF and is open.

disassembly + capture
What is 0x8007B724?resolved
Why

The last sustained cue the drainer keyed (sw s0,0x40c(gp) at 0x80016DC8), -1 after the teardown. The battle funnels drop a repeat against it (the repeat rule). tunnelb_nova_pre_scythe_wind: the caster's cue cursor steps twice, the ring takes 0x253 once.

disassembly + capture
When does a fight's music change?resolved
Why

The intro init keys the field track off for sound set 0, three vsyncs after the mode-8 store; the battle track attaches at the mode-0x14 store, 139 vsyncs after it (timeline).

disassembly + capture
Does the sequencer key notes on a coarser grid than retail?no
Why

Under PCSX-Redux FUN_80065BAC holds its vsync phase within 0.2 of a sample on a field and a battle state; the KON write falls 8.7 to 20.8 samples after its entry, by the frame's work. The port against its own render with that spread at random reads the same residual as retail, so the track channel's -12 to -15 dB is CPU cost (real retail PCM).

capture + inference
Does a channel hold its pitch-bend wheel for later notes?no
Why

FUN_8006166C reads the wheel byte (lbu a3,0x0(a3) at 0x800616DC), hands it through FUN_80067C1C to the per-voice scan FUN_80067A1C and stores it nowhere: the only store after the read is the delta time at 0x800616FC. So a key-on plays at its own pitch. party_battle_gobu_gobu's track 2026 leaves channel 0 at wheel 0x0F and retail's later notes read 0x80E, the note's own; holding the wheel gave 0x4C2 and a 0.743 PCM correlation, 0.987 without (sequencer port).

disassembly + capture
Does any XA stream on the NA disc use 8-bit ADPCM?no; every channel is 4-bit at 37.8 kHz
Why

demux-disc-all reports bits_per_sample = 4 for all 316 channels across the 34 *.XA files. The 8-bit decode path is covered by synthetic tests only and is wired through the demux, CLI and cutscene consumers, which map each channel's reported width (xa.md).

capture
Does a BGM track change close the side-band (3) or reward (11) VAB slot?no
Why

FUN_800243F0 loads each track into slot 1 (0x80024678 / 0x80024780) and never calls the closer FUN_8001FF58. Slot 3 is closed by the battle mode init; slot 11 by the field init FUN_801D6704 (0x801D68A4..0x801D68B8 closes 7, 8 and 11 through delay-slot a0). Port legaia_engine_audio::bgm_tail, shared by both directors.

disassembly
Does retail age SFX cues under the pause menu or a shop?yes
Why

Mode 0x17's per-frame handler FUN_80025F74 calls the drainer FUN_80016B6C at 0x80025F9C, gated only on FUN_8001698C and FUN_80017978 returning 0. Both hosts now step the scheduler there.

disassembly
Is a field track the script left running audible under a mid-game movie's XA, town0d and jouine included?yes, where the script left it sounding
Why

The movie path makes no BGM-slot or SsSeq* call: the play loop's only sound calls toggle SPU CD input (0x801CF2D4, 0x801CF50C and their pairs). Poked triggers keep a sounding voice in 81 / 83 of 84 samples (town01, chitei2); a stopped score stays silent. town0d P2[31] and jouine P2[16] are uncaptured but end on a sub-op 0xA commit attaching a fresh track (2034, 2069) before fmv_id 6 / 8, the town01 shape; sub-op 5 fades and stops the slot's occupant, in a 9 · 5 · 0xA window the outgoing track (audio.md).

capture + disassembly
What does BGM sub-op 7 do, and sub-op 5?7 stores the next scene's entry level ramp; 5 is a timed release
Why

Sub-op 7 only stores _DAT_8007B880 (-1 = none), which the field per-scene initializer reads at 0x801D6B48 for its entry level ramp. Sub-op 5 is FUN_800267A8(0, n): it arms the auto-release cells gp+0x808..+0x81C (target 0, duration n) and ramps the slot's sequence through FUN_80062004. Whether the result is heard as a fade or a cut is not captured.

disassembly
What does a movie do to the BGM?nothing; only the title attract acts
Why

None of the movie entry FUN_80025FB4, the dispatch FUN_801CEA3C, the play loop FUN_801CF098 or the mode-change pass FUN_80016230 makes a BGM-slot or SsSeq* call, and the sequencer is clocked by the root-counter callback (the one reference to FUN_80062F98 is the data word at 0x8007A910), so a movie inherits the score the script left. The title attract releases the slot (0x801DDD7C / 0x801DDD84 in PROT 0899), and CARD INIT FUN_8002574C restreams the title theme and re-attaches it from its first beat (0x80025948). Whether the inherited score is audible under the XA is open.

disassembly
What do BGM sub-ops 2, 3 and 4 do to the sequence?stop+rewind, key-off pause, replay from the top
Why

Sub-op 2 is FUN_800266E0 -> FUN_80064370 -> FUN_800641EC: notes killed, cursor reset to +0x4, flag 0x4. Sub-op 3 is FUN_80026740 -> FUN_8006282C -> FUN_8006275C: flag 0x2 raised, cursor kept, notes keyed off by the next tick. Sub-op 4 is FUN_80026478 -> FUN_80062880(id,1,1) -> FUN_800628F0, which resets the cursor to +0x4 for every mode (0x80062934..0x80062948) before playing. So no script word resumes a track mid-phrase; the FUN_800628F0 mode-0 pause is real but no BGM script word reaches it.

disassembly
What does a scene-local BGM id (below 2000) load?global slot 2, never a scene bank
Why

FUN_800243F0 stores *(0x80084540) + 6 + id only as its change-test index (0x80024458); the load arm replaces the index it loads with *(0x8007BC64) + 2 (0x800245A4..0x800245BC) and parks the id in gp+0x728, read only by the WARNING BGM NO %d print. 0x8007BC64 is 990 in 12 of 12 states, so it loads extraction 990 - the track id 2002 plays. Retail stages no scene bank. Engine SCENE_LOCAL_BGM_FALLBACK_ID (audio.md).

disassembly + capture
Where does the credits bank land in SPU RAM?VAB 10 at slot 0's base, over the SFX banks
Why

FUN_800265E8 seeds 0x800917B0[10] = 0x1010, the same base as slot 0 (sw v1,0x28(v0) at 0x80026668), and FUN_8002630C with a3 = 0 opens there; the bodies span 0x1010..0x641C0, over slots 0 to 3 and short of slot 4 / 7 at 0x65010. The latch 0x8007B9B8 is cleared only by the debug overlay, so retail never re-stages the SFX banks in a session. Pin credits_bank_spu_layout_disc (audio.md).

disassembly
How does a Muscle Dome round enter battle mode?the arena stores mode word 0x14
Why

PROT 0977 stores 0x14 into 0x8007B83C at 0x801D15B8 (sh v0,-0x47c4(v1)), so a round takes FUN_8001DCF8's mode-0x14 arm like any battle. A round's slot residency itself is not captured (open row in open-rev-eng-threads.md).

disassembly
When does battle mode init close the field bank?only when the mode word reads 0x14
Why

FUN_8001DCF8's close-slots-6-and-3 and latch-clear arm is gated lh 0x8007B83C / bne 0x14 at 0x8001DF74..0x8001DF80. A battle transition reaches it; a minigame overlay's call with the mode word 0x18 skips it, so after the Baka Fighter's warp slots 2 and 6 are both enabled over one region - slot 6's header PROT 0876's, the samples PROT 0869's (audio.md).

disassembly + capture
Which field scripts push SFX cues, and how are the ids keyed?op 0x36 sub 0 / 4 and motion op 0x09, ids straight from the bytecode
Why

jal 0x80035B50 at 0x801E0348 and jal 0x80035BAC at 0x801E03D8 (PROT 0897), a0 = (s16)word1; motion op 0x09 at 0x80039178. Ids below 0x200 key the static table, the rest the scene prescript's record 0; across every scene MAN only balden2's 0x20B falls one row past its bank (sfx-table.md).

disassembly
Which bank does an op-0x36 sub-1 request stream?a vab_01 bank, by four overwriting arms
Why

FUN_800243F0 at 0x800248B4..0x8002494C: < 2000 -> vab_01 + 2, 2000..=2999 -> + id - 2000 in slot 3, >= 3000 -> + id - 3000 in slot 6, 0x1000 parks. *(0x8007BBE4) reads 1072 in the catalogued states (sfx-table.md).

disassembly + capture
Who writes the frame-step floor DAT_8007B9D8?each mode's init, and the scene through move-VM ext 0x2F
Why

Field init writes 2 (0x801D6990); jump-table slot 0x2F of 0x801CE868 stores the halfword operand (0x801D45E4), and five scene prescripts carry it - opdeene's record 16 opens with operand 3, which the cold-boot capture holds. Name entry FUN_801F03F0 holds 1 while open (actor-vm.md).

disassembly + capture
How do the field bank and the class-2 bank share SPU memory?one region, refilled per mode, behind a latch
Why

Slots 2 and 6 share a base. The field init loads PROT 0876 into slot 6 only while 0x8007BAFC is clear and closes 2 / 7 / 8 / 11; battle init closes 6 and 3 and clears it (0x8001DFC0), as do the minigame warp (0x800259A4, gp+0x7E4) and op 0x36 sub 3 (sfx-table.md).

disassembly
What does a cue whose bank is closed play?nothing
Why

The drainer FUN_80016B6C skips a cue whose mixer record's +0xB byte is zero (lb v0,0xb(v1) / beqz at 0x80016CE4..0x80016CEC), and the VAB close FUN_8001FF58 zeroes that byte in the delay slot of its jal 0x80068C80. So a category-6 cue in battle is silent, and FUN_80035BD0(0) is a cancel in the field.

disassembly
What is FUN_80035B50?a round-robin ring of four, not a delay queue
Why

It writes the cue and a zero timer into slot gp+0x158, latches that slot into gp+0x15A and advances the cursor modulo four with no free-slot search (cue sh 0x80035B6C, latch 0x80035B80, timer sw zero 0x80035B9C, wrap on bne a1,4 0x80035B98); FUN_80035BAC sets the latched slot's countdown (0x80035BCC) and FUN_80035BD0 overwrites its cue and zeroes its timer (0x80035BE4, 0x80035BFC). A fifth pending cue replaces one (sfx-table.md).

disassembly
Why does the port sustain more sounding voices than retail on the same track?two instrument defects, no engine change owed
Why

The pairing was never aligned: frame 0 of an engine trace is tick 0 of a track while a capture sits wherever play parked it, and the s3_rimelm_freeroam window aligns at engine frame 3111 of 3601. Aligned, the sides carry the same ten packed ADSR words, the same per-tone key-on count on nine of ten tones, and 71 engine key-ons against 67 retail. The rest is the envelope channel, which a per-vsync save-state capture cannot measure at all - PCSX-Redux steps ADSR on an audio-paced thread. The surviving comparand is the key-on rate. See audio.md.

capture
Which voice clip does a Seru cast play?the module names it, not the caster
Why

Each slot-B cast image hardcodes a literal cue id near its head - 62 of the 64, with PROT 0936 and 0937 forming theirs at run time - so the mapping is per module. Resolved through the cue dispatcher the ids land on fifteen streamed-audio files: XA7, XA9..15, XA18..20, XA22, XA23, XA25 and XA34 (e.g. PROT 0903: li a0,0x134 at 0x801F6E3C, jal 0x8004FCC8 at 0x801F6E50). Three literal ids at four non-head sites sit below the 0x100 streamed threshold and are ordinary ring cues: 0x22 at 0x801F88FC / 0x801F894C and 0x21 at 0x801F8B54 in 0957, 0x56 at 0x801F87BC in 0958.

disassembly
What bounds the cue dispatcher's streamed-voice leg?two decline gates, and nothing bounds the table index
Why

FUN_8004FCC8 tests sltiu v0,s0,0x100 at 0x8004FCD4 and nothing else: above the threshold it declines on the context byte *(gp+0xA0C)+0x276 being non-zero and on the stream poll FUN_8003DE7C(1) returning non-zero (a data read in flight), then indexes DAT_800788B8 at id - 0x100 with no upper bound (lhu at 0x8004FD44). The clip slot is (id - 0x100) >> 3 with slots 1 / 3 / 5 remapped to 0x1A / 0x1B / 0x1C. Measured off the executable the table is 0x110 entries with live rows as high as 0x10F and several interior zero runs (0x37..0x40, 0x78..0x88, 0xE8..0x10A); a reader that stops at 0x40 drops every cast cue.

disassembly
Which tracks do the in-world minigames start?through the piecewise extraction map
Why

The loaders name PROT entries 1043 / 1048 / 1054 - raw TOC 0x415 / 0x41A / 0x420 into jal 0x8001FC00 (Baka Fighter li a0,0x415 at 0x801CF2EC; dance 0x801CFB24 / 0x801CFB14) - which the music_01 bank's piecewise map sends to global BGM ids 2055 / 2060 / 2066. Subtracting a single 990 + slot base gives 2053 / 2058 / 2064 - three tracks off, and the gap at 1056/1057 is why one base cannot work. See music-tracks.md.

disassembly
What does the sequencer's pause do to sounding notes?it is a key-off, not a freeze
Why

FUN_800628F0 mode 0 falls through both compares (bne a2,v0(=1) at 0x80062988, bne a2,zero at 0x800629D8) to ori v0,v0,0x2 / sw v0,0x98(v1), raising slot flag 0x2. The per-tick FUN_80062F98 tests andi v0,v0,0x2 and calls FUN_800638D8, which calls FUN_800684CC to release every sounding voice whose owner halfword matches the (sequence, channel) key, stores 0 at the channel's +0x14, and clears the flag with li v1,-0x3 / and. The play cursor is untouched, so resume continues where it stopped - the notes do not.

disassembly
What drives the CD / XA transport?resolved
Why

FUN_8003D764 is an 11-state machine over the transport word at 0x800111C4. The port models it as xa_transport, tagged REF: rather than PORT: - the states are the shape the engine's own streaming follows, not a routine a host calls.

disassembly
SPU reverb live routing (C7-REVERB)wired; Studio C, global
Why
capture
XA channel map / STR demux SMstatic decompile of PROT 0970 + SCUS
Why
disassembly
FUN_80018DB0 is a rumble cadence, not an audio onelibpad, not SsAPI; no cue to pin
Why
disassembly
Key-on pitch: what does retail put in the voice pitch register?unity on centre; the port was an octave low
Why
disassembly
SFX cue bank routing - the category byte selects the VAB slotmechanism + the two pinned banks; ported
Why
capture
Which PROT entries fill SFX VAB slots 1 / 3 / 6 / 11slot 6 = 0876, slot 11 = 0889; 1 / 3 are variable banks
Why
disassembly
The FUN_8006EF18 trio is a BIOS kernel-patch sequence, not an SPU initnegative
Why
disassembly
_DAT_8007B910 is the live audio level, not screen brightnessboth hosts' labels corrected
Why
disassembly
XA clip-table writer + (clip_id, chan) cue censuswriter pinned statically; census in audio.md
Why
disassembly
Hyper Arts fanfare selector - what audio fires when a Hyper executesper-(char, art) coin flip over a fixed channel pair of the even-slot fanfare bank
Why
disassembly
Op-0x35 sub-op 0xA - what the "unhalt-pause toggle" waits onit is the track-swap commit; both globals pinned; ported
Why
disassembly
What do bse.dat's record columns mean?the static SFX-table columns; +4 is a u8 category
Why
disassembly
Who consumes the bse.dat record-table pointer gp+0x678 (0x8007B990)?cue router FUN_8004FE5C + the overlay-0971 sound test, via lui+lw the word scan cannot see
Why
disassembly
When does bse.dat load?battle-scene setup, not boot
Why

FUN_8001FA88's only caller on the disc is 0x80051A3C in battle init FUN_800513F0, reached from the battle tick FUN_80046A20 at 0x80046F74 under ctx[+0x11] == 0.

disassembly
bse.dat 888 vs its 1195 siblingone format, two occupants of the >= 0x200 bank role
Why

888 is the battle occupant; a scene prescript's record 0 fills the same slot in the field (FUN_8001F7C0 at 0x8001F864 repoints it). 1195 is such a prescript and the detector should keep matching it.

disassembly
Is there a second bse.dat record family past the table (0888 0x94C, 1062 0x51AD)?no - a neighbouring file's PsyQ VagAtr tone rows left in the sector
Why
capture
What is _DAT_8007BA9C?the BGM-swap force-reload latch
Why

Read at 0x8002457C and XOR-paired with _DAT_8007BAB8; when the two disagree the next track request re-reads its bank instead of reusing the resident one. It is the barrier op-0x35 sub-op 9 waits on before it makes sub-op 1's own _DAT_8007BAC8 store.

disassembly
What is FUN_80065034?SsUtKeyOnV
Why

Its arguments are (voice, vabid, prog, tone, note, fine, voll, volr) - the second is the VAB id, which is what makes the cue's bank explicit rather than implied by the resident bank: a1 and a2 go to the program lookup 0x80068B98 as (vabid, prog) at 0x800650B4..0x800650C4, a0 is range-checked as a voice (sltiu 0x18, 0x800650AC), and the four stack words 0x50..0x5C load as note, fine, voll, volr (0x8006503C..0x80065074). The CUE_LEVEL name an overlay port gave the argument ("channel mixer level") describes no parameter of it.

disassembly
How is a retail VAB carried in its DATA_FIELD stream?two chunks, and the bodies are the second
Why

The 4-byte word in front of every pBAV is a chunk header whose payload is the bank's header part only - 0x20 + 0x800 + 0x200 * ps + 0x200 - which holds in all 424 retail banks. The VAG bodies are a separate chunk of the same stream, and that chunk's own header is the "+4 skew" the decoder had recorded as a property of the format. Six entries put the SEQ chunk before the body chunk, so a fixed {0, 4} probe cannot recover their origin; psx_vab::vag_body_origin walks the stream for it.

capture (disc census, crates/vab/tests/corpus_chunk_carriage.rs)
What is the resident monster.snd index?resolved
Why

[u32 reserved][u32 count][u32 start_sector[count + 1]] at 0x801C8980, staged by PROT 0895's memcpy of 0x400 bytes at 0x801CEF74. The reader FUN_8003E104 bounds its argument against the count word, streams sectors [table[i], table[i + 1]) relative to raw TOC entry 0x37D's start LBA, and forks on the build-mode flag for the dev filename path. The trailing entry is the archive's own sector length rather than a bank, which is what makes the span arithmetic total.

disassembly
A VAB's VAG-body origin - is the +4 a format property?two wrongs that cancel on all but six carriers
Why

The parse reports a body origin four bytes early and the upload re-slices by the same four, so the pair cancels on 212 of 218 carriers and the skew read as a property of the format. Six entries break the cancellation - 0886, 1058, 1059, 1063, 1064, 1065 - because their SEQ chunk precedes the body chunk, so the four bytes uploaded as sample data are SEQ bytes; the legal-filter share on those rises from 0.53-0.84 to 1.00 once the origin is resolved off the stream. psx_vab::vag_body_origin_at + parse_in_stream answer it; vab.md.

capture (disc census; both wrongs are the port's own parse and upload)
What is in PROT 1062?a single-chunk DATA_FIELD stream carrying a SEQ
Why

One chunk, type byte 0x02, payload pQES; past it the last sector holds only zero fill and leftover tone-row bytes. It is the shape that made the stream walker's coverage of that entry read as partial rather than the entry being unparsed.

capture (disc read)
Where does a scene's entry BGM come from?the scene's own script, not the loader
Why

The BGM request word _DAT_8007BAC8 has four sw sites disc-wide and all four are in PROT 0897: 0x801E012C (field-VM op 0x35 sub-1), 0x801E0254 (sub-9), 0x801EAD1C (the dev menu's BGM CALL row) and 0x801D6844 - a conditional sw $zero inside the per-scene field initializer FUN_801D6704. So the loader clears the id and the script installs it, and a port expecting a scene load to select the track has nothing to read. The sweep has to be gp-relative: 14 hits across the disc, none of them visible to an absolute-address scan.

disassembly
What is the dev menu's BGM CALL arm?it plays a track; cycling is a different routine
Why

The arm at 0x801EACBC..0x801EAD20 inside FUN_801EA9B0 indexes the sound-test table at 0x801F2E94 by the cursor _DAT_801F2E90 - a 10-byte stride of [i16 global bgm id][8-byte ASCII label], 71 rows plus an OFF row whose id reads -1 and which raises _DAT_8007B438 instead. The row index is not the id: the rows carry 2000..=2043 then 2045..=2071, so cursor and id part company above the missing 2044. The input half - stepping the cursor - is FUN_801E9F64. details

disassembly
What makes a captured SPU voice audible?the envelope level, not the phase word
Why

Mednafen's ADSR.Phase has no Off member: it runs 0 Attack through 3 Release, and a key-off parks a voice in Release forever, so a phase test reports every voice a state ever keyed as live. The audibility predicate is ADSR.EnvLevel != 0 (the PCSX-Redux analogue is ADSRInfoEx.EnvelopeVol). Over the 98-state retail corpus, 1624 of 2352 voice slots sit at level zero, and reading the level gives a median of 7.5 audible voices per state - the same order as the engine's 4-8. Everything a phase-keyed comparison concluded about retail's voice counts was the predicate talking.

capture
What is BGM id 0x1000?a park sentinel, shared by both streaming slots
Why

4096 is outside the 2000..=2077 pool and the resolver never tries: at 0x8002454C it compares _DAT_8007BAC8 against li v0, 0x1000 and falls into 0x80024560, which copies the pending index _DAT_8007BAB8 onto the loaded-index barrier _DAT_8007BA9C, so the equality test four instructions later skips the load. The block at 0x800244C0 applies the same test to the second slot (_DAT_8007BABC -> _DAT_8007BAA0). In the catalogued corpus only the ending states carry it. audio.md

disassembly + capture
Does a save state's scene decide which BGM it holds?no - 0x8007BAC8 does
Why

Of six catalogued town01 PCSX-Redux states only one holds 2000; the rest hold 2016, the id town01's own prescript selects. The 2000 state walked into the town from the world map, and 0x8007BA9C / 0x8007BC64 corroborate through the resolver's own base + (id - 2000). Pairing a retail capture with an engine trace by scene rather than by this word compares two pieces of music.

capture + disassembly
What is retail's reverb depth and work area?vLOUT = vROUT = 0x3264, mBASE -> 0x79020
Why

The depth pair is what SpuSetReverbDepth writes and sits outside the 32-register preset block, so matching the Studio C coefficients says nothing about it; the work-area size 0x6FE0 is Studio C's own, a second confirmation of the preset. Per-voice EON reads 0x00FFFFFF - all 24 voices - on every frame of a 90-frame per-vsync capture, where a .mc freeze shows only the 15-22 a given moment has keyed. The engine's placeholder 0x4000 is replaced by the measured value.

capture
Is slots-per-note a difference between the port and retail?no - it is a property of the arrangement
Why

Sounding voices over distinct (sample, pitch) pairs reads retail 1.158 / port 1.002 on track 2000 and retail 1.002 / port 1.037-1.061 on track 2016 - the sign reverses with the track. A second retail capture taken in the same town on the same walk reports 1.002, which also disposes of the "town SFX in the retail window" caveat. The sequencer's three note-drop paths fire zero times across the window. audio.md

capture
Does a Muscle Dome round load the class-2 bank?yes - PROT 0869 into slot 2, as a field battle does
Why

From the arena's hub into a round, FUN_8001DCF8(0x0C) (from 0x80055D64) runs with the mode word at 0x14 and closes slots 6 and 3 (0x8001DFB4 / 0x8001DFBC); the battle loader (ra 0x800523B4 / 0x8005241C) stages raw 0x367 into slot 2, and the header at 0x8008D708 turns from PROT 0876's (0x2C090) into 0869's (0x2FB00). The run starts from the one library state whose resident SCUS carries only the starting-bag seed patch (audio.md).

capture
What do FUN_8001E54C's chunk types install?bank and score, no LZS
Why

Jump table 0x80010600: 0 releases the SEQ slot and copies the header; 1 and 3 are the VAB open and body transfer (FUN_8002630C, 3 budget-split); 2 detaches and closes the SEQ player, copies the payload into the slot's staging buffer and opens it through FUN_80026410 (SsSeqOpen); 0xC opens the fixed record 0x800705AC. All 90 disc entries with a pQES score past offset 0 carry it as a type-2 chunk, and no stream carries 0xC (audio.md).

disassembly + disc scan
Is every music_01 slot a [VAB][SEQ] pair?75 of 81
Why

The installer walk splits 75 of the 81 slots into a bank and a score; slot 72 is a score with no bank, 76..79 are one-sector fills and 80 is a bank with no score, and the old magic hunt agrees on all 81 (engine-scene-host/tests/seq_chunk_walk_disc.rs, audio.md).

capture (disc-gated walk)
What does field-VM op 0x35 sub-5's expiry do?a scheduled field-BGM pause
Why

The expiry arm of FUN_800267FC (0x80026828..0x8002686C) is FUN_800266E0's body inline - sub-op 2's pause on the field-BGM slot 0x8007052C. The engine now emits a BGM sub-op 2 event at expiry (audio.md).

disassembly
What does the field init's slot-10 load serve?the credits theme 0x814
Why

FUN_801D6704's two-part arm (0x801D71A0..0x801D72D0) stages the score from raw 0x428 (extraction 1062, one SEQ chunk) and the instruments from raw 0x422 (extraction 1056, a 53-program VAB-only bank) into sound slot 10, starts the sequence with FUN_80026478(0x800705BC) and sets the latch 0x8007B9B8; while it is set FUN_800243F0 returns at once (0x8002440C). The ending states hold extraction 1062's SEQ chunk in slot 10's buffer byte for byte, and all sixteen programs the score keys are defined in bank 1056. Engine: mode_entry_init::two_part_bgm_stream (audio.md).

disassembly + capture
Is FUN_80064090 (the sequencer's channel restart) reachable?no
Why

Its only reference is the jal at 0x80063C94, behind channel[+0x22] != 0xFF, and the byte's only two writers in SCUS (0x80061DAC, 0x80064580) both store 0xFF. The port is REPLACED-BY.

disassembly
What does field op 0x35 sub-op 8 (FUN_80019898) sound?nothing, on every catalogued state
Why

It replays once the sequence bound to the second sound-source record 0x8007057C through FUN_80026478; that record's id is whichever of 1 / 2 the field-BGM record 0x8007052C does not hold, and across 98 mednafen states its channel is either unstreamed (97) or inactive (+8 == 0, one), so the call returns before replaying. The engine's default no-op is faithful.

disassembly + capture
Are the .MAP / .PCH strings in the sound-driver cluster SoundArtist intermediates?no - they are field files, loaded by FUN_8001F7C0
Why

Each is referenced once, by the field-file loader: its dev branch builds DATA\FIELD\<scene>.MAP (0x8001F834) and .PCH (0x8001F8CC, read to buffer +0x12000), its retail branch reads 0x28 sectors from the scene block (0x8001F9A4..0x8001F9B4). They are the field map and the walk-on trigger sidecar, both on the retail disc. .spk is built only on FUN_8001FC00's dev branch (0x8001FC4C). sound-driver.md

disassembly
Who writes the 0x75EA CD input volume a battle voice plays at?the clip player's CD-ready state machine
Why

FUN_8003D764 state 6 loads addiu a0,zero,0x75 at 0x8003D92C once the drive status shows a read (bit 0x20) and reaches the shared jal 0x8003EE7C at 0x8003D9F4, which is SsSetSerialVol(0, v, v): 117 * 0x102. Every one-shot cue plays there; movies play at 0x7FFE. The port's XA_CUE_GAIN is that level. audio.md

disassembly
Who writes the positional emitter's per-row scale pair at 0x80091658?the boot-time mode initialiser, once
Why

FUN_8001DCF8 stores 0x80, 0x80, 0 at 0x8001DD60..0x8001DD74; a $gp / lui / base-displacement sweep of every image finds no other writer. The emitter arm indexes the table by the part's +0x22 (0x800223F0..0x80022400), and rows past 0 hold zeros in all 98 mednafen library states. audio.md

disassembly + capture

Long write-ups in this area

Op-0x35 sub-op 0xA is the track-swap commit

Status: resolved; ported. Evidence: disassembly (a store-offset writer census over SCUS and every based overlay image, address-reference-scan.md).

Sub-op 0xA (arm 0x801E0264, field overlay 0897) is the commit half of the sub-op 9 / 0xA swap handshake, not a toggle: wait until the incoming track is staged, release the slot's paused occupant, acknowledge with flag bit 4 (which unstalls the poller's install), clear pause bit 1.

  • Nothing writes _DAT_8007B868. Its only store in the static corpus is a read-modify-write clearing bit 1 at 0x8001E008 in the boot mode-init FUN_8001DCF8; a raw byte sweep over all 1233 PROT entries adds one incidental data word (0392_map03.BIN +0x2bc40, surrounded by non-code). The word never goes non-zero in retail play. It is the dev / dual-mode gate the whole actor-sound family (FUN_800266E0 / 80026520 / 26740 / 26478 / 26410) checks, so the arm's early return mirrors its callees.
  • _DAT_8007B750 bit 3 has one setter: ori v1,v0,0x8 at 0x800246D0 in FUN_800243F0, the BGM resolver / poller's load-settle stage. It is reached only while a track swap is in flight, after the settle countdown at gp+0x768 (armed to 0x1E frames at load start, 0x3C when master mode is 2) hits zero. The poller then stalls its own install while bit 0 (sub-op 9's script-owned start) is up and bit 4 is not (0x800246E0..E8).
  • FUN_80026520 closes what FUN_800266E0 only detaches. 800266E0 resets the pan state and rewinds / stops the bound sequence (FUN_80064370, the SsSeqRewind wrapper), leaving the source active. 80026520 VSyncs, clears the source's active flag (+0x8), rewinds and closes the handle (FUN_80061E94, the SsSeqClose shim). Together they are a full slot release; the poller's own teardown calls both.
  • Sub-ops 2 / 3 / 4: sub-op 2's pause sets flag bit 1; sub-op 3 also sets it and calls the voice-stop FUN_80026740; sub-op 4 clears it and calls the re-attach FUN_80026478.

Port: SceneHost::route_bgm_events routes sub-op 10 to BgmDirector::unhalt_pause (release the source only while the pause latch is set, then clear the latch unconditionally), overridden by AudioDirector, the one director both play hosts run; its starts also clear the pause gate, as retail's sub-op 1 arm does. Test: crates/engine-scene-host/tests/bgm_midscene_change_disc.rs (town01's cutscene records carry the op).

Owning pages: audio.md (protocol + flag-word bit map), script-vm.md (the arm). See ghidra/scripts/funcs/800243f0.txt, 800266e0.txt, 80026520.txt, 8001dcf8.txt.

Hyper Arts fanfare selector

Status: resolved - selector pinned in code and capture.

A Hyper art fires no pool shout: its action constant sits below the shout table's lo bound. Instead:

  • The staged-animation materialiser FUN_8004AD80, on the Hyper class byte 0x1A at actor+0x1DA, reads the queued art constant and fires the jingle queue (FUN_8004FCC8 -> FUN_8003D53C) with jingle_id = rand()%2*3 + base (class test li v0,0x1a at 0x8004B71C; one arm's draw at 0x8004B91C..0x8004B944, joining the jal at 0x8004BB34).
  • That is a per-(character, art) coin flip between the fixed channel pair {base, base+3} of the character's stereo fanfare bank (the even clip slots: Vahn XA1.XA, Noa XA3.XA, Gala XA5.XA). There is no avoid-repeat memory, unlike the shout pool.
  • Super and Miracle expansions take a sibling branch to fixed ids 0x101 / 0x111 / 0x121 (0x8004B7AC..0x8004B7D0), the same bank's generic channel 1. A Miracle's finisher additionally fires its anim cue track (FUN_800508DC, ids 0xC8..0xFF rebased +0x38).

All nine per-art Hyper rows are capture-witnessed off the FUN_8003D53C staging globals, and every witnessed duration reproduces the 0x800788B8 table arithmetic against the real SCUS. Unwitnessed: the second pair member for seven of nine arts is rule-derived, as are the Vahn / Noa Miracle finisher cue-track ids and XA1 channel 0.

Engine table: legaia_art::hyper_fanfare::CAPTURED_FANFARES. Owning page: battle-action.md.

XA clip-table writer + (clip_id, chan) cue census

Status: resolved - writer pinned; cue census decoded.

  • Table. 0x801C6ED8 holds 34 [CdlLOC][len] slots = XA1..XA34; a title capture matches the disc files byte for byte.
  • Writer. FUN_801CFA78 in PROT 0895 init.pak (base 0x801CE818, recovered from four in-blob string refs) sprintf-generates \XA\XA%d.XA;1 per slot and fills [BCD-MSF][size] through the ISO9660 lookup FUN_8005DBB4. It is called once from the init boot tick 0x801CF500. The two lui 0x801c sites in SCUS (FUN_8003D53C / FUN_8003EAE4) are the readers; the writer is overlay-resident, so no absolute-form scan of SCUS sees it.
  • Cues (caller census of FUN_8003D53C / FUN_8003EAE4): menu voice FUN_8004FCC8; the normal-move grunt (XA30 chan 0 / 4 / 6, overlay 0x801EEB44); the arts shout (FUN_8004C140 -> XA2 / XA4 / XA6 per character, per-art channel pool); SM state 0x6E (XA9 via 0x800787AF); slot machine XA1.
  • Capture. scripts/recomp/xa_cue_capture.py reads live-battle fires frame-tagged off the FUN_8003D53C staging globals and pins the live table variant and the packed second-half spans; witnessed picks are legaia_art::arts_voice::CAPTURED_ART_CHANNELS.
  • Census caveat. A PROT-entry over-read aliases call sites into neighbouring overlays; dedupe by true entry extent (gameover 0902 and world-map 0901 have zero genuine XA calls).

Owning pages: audio.md (full one-shot + streamed cue census), battle-action.md.

SFX cue bank routing - the category byte selects the VAB slot

Status: resolved - a cue names its own bank, and both hosts stage two banks and route by category. Which PROT entry fills each slot is the next entry.

  • A descriptor's +4 byte is a category. It selects the 12-byte mixer record at 0x80091508 + category*12.
  • That record's +8 is a VAB slot id, not a level: FUN_80065034 hands it to FUN_80068b98, which rejects it unless the per-bank open-state byte _DAT_801CE368[id] == 1 (lbu / bne at 0x80068BBC..0x80068BC8) and then repoints the current-bank globals at that slot (0x80068C34..0x80068C48) before the program / tone lookup.
  • Across the catalogued save states record N holds +8 == N and +0 == slot N's live VabHdr, in every record of every state: category N selects slot N.
  • Slot 0 is PROT 0868 (a live field state's 512-byte slot-0 VagAtr program-0 page occurs verbatim in that entry at VAB offset +4, ps = 5 agreeing). Slot 2 is the class-2 bank PROT 0869, which the battle scene loader FUN_800520F0 loads with a1 = 2.
  • Histogram over the 100 descriptors: category 0: 16, 2: 53, 6: 30, 11: 1.

A port that fires every cue through one bank neither errors nor goes silent: both banks carry a one-VAG-per-semitone UI key map at program 0, so a category-0 id resolves to a sibling sample (PROT 0869's is roughly twice as long and a fifth lower, because its center bytes are authored higher). Peak, duration and "did a voice key on" all pass; only the source entry of the samples separates the two, which is what the disc-gated oracles assert.

Port: legaia_game_tables::sfx_table carries the law (slot_for_category, prot_index_for_slot, SLOT_BANKS, PINNED_SLOT_BANKS). engine-session's boot and the browser play page each stage the two resident banks out of one SPU allocator over their shared reserved region and resolve every cue through its own slot; categories 6 and 11 fall back to the class-2 bank.

Owning page: sfx-table.md.

Which PROT entries fill SFX VAB slots 1 / 3 / 6 / 11

Status: resolved - slot 6 is PROT 0876 and slot 11 PROT 0889; slots 1 and 3 hold banks that are re-selected at runtime, so neither has a fixed entry. Evidence: disassembly for the bindings, with a capture byte-pin and a structural cross-check.

A bank reaches a slot through one pair of calls. FUN_8001FC00(raw_toc_index, category, buf, append, len) streams the entry in; it ignores its second argument. FUN_8001E54C(category, buf, len) installs it: it indexes the same 12-byte mixer record the descriptors do, takes the header buffer from +0 and the VAB slot from +8, and opens the bank via FUN_8002630C -> SsVabOpenHead (sticky, at the SPU address the per-slot table at 0x800917B0 holds) -> SsVabTransBody. The binding is therefore a0 at each call site of FUN_8001E54C:

SlotFillerCall site
0PROT 0868resident system bank
1current BGM bank (music_01), variableFUN_800243F0, raw = *(0x8007BC64) + id - 2000
2PROT 0869 (raw 0x367) / 0875FUN_800520F0, FUN_801CF00C
3a vab_01 side-band bank, variableFUN_800243F0, raw = *(0x8007BBE4) + id - 2000 from _DAT_8007BABC
6PROT 0876 (raw 0x36E)field init FUN_801D6704
7 / 8the two monster.snd banksFUN_8003E104 from FUN_800520F0
11PROT 0889 (raw 0x37B)battle-end reward resolution FUN_8004E568

Cross-checks on the two pins:

  • PROT 0889 populates exactly one ProgAtr slot, number 10, and the one category-11 descriptor (0x50) names program 10 with 2 voices against that program's 2 tones. The function that loads it is the one that fires the cue.
  • PROT 0876 holds 30 VAGs for the 30 category-6 descriptors, and its populated programs 1..=7 cover 29 of the 30.
  • A catalogued field state's live slot-6 and slot-1 header buffers match extraction 0876 and 0998 byte for byte - unique hits across all 218 VABs on the disc once the runtime-written ProgAtr +8..0xF words are excluded.

Two laws from the same read:

  • FUN_8001D424 writes +8 = record index for all 16 mixer records, so "category is the slot" is the initialiser's own statement.
  • It assigns four pairs of records one shared header buffer, which FUN_800265E8 matches with one shared SPU base. Slot 6 and slot 2 are the same physical bank in two modes, so retail needs no extra SPU room for the field cues, and a host that stages once at boot cannot simply add them. The open-state array _DAT_801CE368 holds slots 0,1,3,6 in every field-family state and 0,1,2,7 in every battle state, never 2 and 6 together.

Owning page: sfx-table.md (map, budget arithmetic, port surface).

The FUN_8006EF18 trio is a BIOS kernel-patch sequence, not an SPU init

Status: resolved-negative - the trio touches no SPU register, voice block or libspu global. Evidence: disassembly (the veneer bodies and the patch payloads are both read out of the executable).

  • FUN_8006EF68 is a bare BIOS stub (li t2,0xb0; jr t2; li t1,0x4c) = B0 0x4C StopCARD. Its neighbours 8006EF48 / 8006EF58 are the same shape with 0x4A InitCARD and 0x4B StartCARD.
  • FUN_8006F088 calls GetB0Table, takes entry 0x5B (ChangeClearPAD) as a version-stable anchor, and swaps five words between +0x9C8 off it and the static block at 0x8006F058. The shipped block is a jalr trampoline back to 0x8006F058, so after the swap the kernel calls a buffer holding its own displaced instructions, falls through into a 0xC8-iteration busy-wait at 0x8006F070, and returns: a timing delay spliced into a kernel routine. A swap is its own inverse, so install and teardown both call it.
  • FUN_8006F118 calls GetC0Table, takes entry 6 (ExceptionHandler) and copies three words from 0x8006F180 over +0x70..+0x78. That blanks the immediate pair its install-side sibling FUN_8006EFD0 reads to reconstruct a kernel address (and then patches at +0x28 with a jump out into SCUS).
  • Both are bracketed by EnterCriticalSection (syscall(1)) and FlushCache (A0 0x44).
  • The install veneer is FUN_8006EE8C(pad_enable): ChangeClearPAD(0), InitCARD, then _EFD0 + _F088. FUN_8006EF18 is its teardown mirror, which the caller FUN_8002035C runs after closing eight kernel event handles.

Owning page: functions/runtime-libs.md.

_DAT_8007B910 is the live audio level, not screen brightness

Status: resolved - the cell is a volume and _DAT_8008457C is its persistent reference. Evidence: disassembly.

  • FUN_80062004(a, b, c) tail-calls FUN_80061EDC(a, 0, b, c) (the argument shuffle at 0x8006200C..0x80062024) = SsSeqSetVol(slot, channel 0, vol, ...), so the halved cell ((v << 15) >> 16) that FUN_800267A8 passes lands in the volume argument.
  • FUN_80026478 hands v >> 1 to FUN_8002657C, which writes it as both channels of FUN_80064890(slot, vol_l, vol_r): a symmetric level, not a directional pan.
  • The dumped corpus holds 26 read sites of the cell: SsSeqSetVol (six), SpuSetCommonAttr (FUN_8006BCB4, four, each building an SpuCommonAttr on the stack with the cell in the CD-volume pair), the audio-context volume re-apply FUN_8002614C, FUN_8002657C, and arithmetic / tween plumbing. None reaches a draw primitive.
  • The cold reset FUN_8001FFA4 seeds 0xD7 (li a0,0xd7 at 0x8001FFE0) into both _DAT_8008457C and _DAT_8007B910, then calls FUN_8002614C(0) (0x80020020). A 0..255 cell halved is libsnd's 0..0x7F.

What the ramps are:

  • Battle-action states 0x35 / 0x6F / 0x70 duck the mix to 75% of the configured level (50% for spell ids >= 0x99); 0x51 restores it.
  • The world-map sub-list halves it on open and doubles it on close.
  • The field VM's MENU_CTRL sub-0xD sets it to (input * _DAT_8008457C) >> 12, a percentage of the player's setting.

Not brightness: a summon does dim the screen in step, but through a different scalar, _DAT_8007B440, ramped by FUN_801ED308 and drawn by the wipe / curtain emitter FUN_8003479C (clamped 0xF2).

Port names: BattleActionHost::duck_audio_level, BattleEvent::DuckAudioLevel, SubListEffect::ScaleAudioLevel, PanelActorHost::audio_level (seeded 0xD7). Owning page: battle-action.md.

Key-on pitch: unity on centre

Status: resolved - note == center keys 0x1000, unity, 44.1 kHz.

Both the SFX / direct key-on path (FUN_80065034) and the sequencer note-on path (FUN_80066308) reach the same arithmetic (FUN_80066e50 / FUN_80066d8c) and hand the result to FUN_80067550, which stores it verbatim into the shadow register file at 0x801CE084 + voice*16 (voice +4 = pitch). Nothing rescales it afterwards:

n     = note + 60 - center + carry        (MIPS div: truncates toward zero)
pitch = PITCH[(n % 12) * 16 + fine] << (n / 12 - 5)
  • Checked against the listing of FUN_80066E50: note + 0x3C - center + carry at 0x80066EB4..0x80066EC0, the divide by 12 at 0x80066ECC..0x80066EDC, the table index at 0x80066EEC..0x80066F04, the load at 0x80066F1C, the - 5 at 0x80066F08 and the two shifts at 0x80066F2C / 0x80066F40. FUN_80067550 stores the pitch at 0x800678AC.
  • PITCH is the 192-entry table at DAT_8007A940 (SCUS file 0x6B140). Every entry is exactly floor(0x1000 * 2^(k/192)) (192 of 192 verified against the disc; first entry 0x1000, last 0x1fe2): a one-octave table at 1/16-semitone resolution starting at unity, with the octave applied by the shift. The closed form is exact, so no disc bytes are needed to reproduce it.
  • The retail cue arm passes fine = 0x40 at every traced FUN_80065034 call site, so a cue keys half a semitone above the sequencer for the same tone. The two paths also differ in whether the fine index saturates or carries a whole semitone.
  • A 22.05 kHz VAG body is authored with center twelve semitones high: the sample rate is already encoded in center. Applying a 22050/44100 factor on top keys every voice one octave low.
  • Capture: 126 of 128 voices holding a non-zero staged pitch match this law exactly; the 2 misses are records whose bank was swapped after key-on.
  • The recomp PCM oracle cannot check this law, because it mirrors retail's captured pitch into the engine SPU rather than deriving one.

Owning page: audio.md § key-on pitch law.

FUN_80018DB0 is a rumble cadence, not an audio one

Status: resolved - the surrounding cluster is libpad, DAT_800915DA / DB are port 0's actuator bytes, and the kernel plays no sound. There is no retail footstep SFX cue id to pin. The identification is instruction-level: the FUN_8006CE30 and FUN_8001D230 windows are read straight out of extracted/SCUS_942.54 at 0x800 + va - 0x80010000.

  • FUN_8006E2B4(buf0, buf1) = PadInitDirect. FUN_8001D230 bzeros 0x44 = 2 x 0x22 bytes at 0x800840F8 and calls it with (0x800840F8, 0x800840F8 + 0x22) (addiu a1,a0,0x22), the canonical pair of 34-byte direct-mode report buffers. It clears 0x1E0 = 2 x 0xF0 at 0x801CE628 (one context per socket), stores the two buffers at each context +0x30, seeds each buffer [0] = 0xFF / [1] = 0, and fills six bytes at context +0x5D with 0xFF (PadSetActAlign's unassigned default). The pad pump FUN_8001822C decodes those buffers as [status][type nibble][inverted u16 buttons], port 1 at +0x22 / +0x23.
  • FUN_8006CE30(socket, table, len) = PadSetAct. Three arguments in the instructions: a0 passes untouched into the context resolver jalr _DAT_801CE564; a1 / a2 are stashed in s0 / s1 and forwarded. Ghidra's C drops param_1 (artifact #1 in ghidra.md). The tail FUN_8006D7B4 stores ctx+0x28 = table, ctx+0x34 = (u8)len.
  • Siblings. FUN_8006CA7C = PadGetState (report status byte through ctx+0x30, then normalises ctx+0x49). FUN_8006CB3C = PadInfoMode, whose term = 4 branch returns the id-table length ctx+0xE3 for offs < 0 and otherwise the bounds-checked ((u16 *)ctx[0])[offs]. FUN_8006CDB0 = PadSetActAlign. FUN_8006D1E0 / FUN_8006D2AC = PadStartCom / PadStopCom (ChangeClearRCnt(3, 0) vs (3, 1)). FUN_8006EE8C / FUN_8006EEE0 call ChangeClearPAD (B0 0x5B) and wrap InitCARD / StartCARD (B0 0x4A / 0x4B). FUN_80056618 = _bu_init. The eight OpenEvent / EnableEvent pairs on 0xF4000001 / 0xF0000011 are the memory-card event set.
  • The bytes FUN_80018DB0 writes are the actuator table. It stores to 0x800915DA / 0x800915DB, and FUN_80018F94 registers the same block per port with PadSetAct(socket, block+2, 2), where block = 0x800915D8 + (socket>>4)*0x40 + (socket&3)*0x10. The 0x40 stride matches FUN_8001D230's s1+2 / s1+0x42, and the 0x80-byte bzero matches 2 x 0x40.
  • DAT_8007B79C is not a footstep-active flag. FUN_80018F94 sets it from _DAT_800845A8 == 0 && PadInfoMode(socket, 2, 0) == 0 (the pad reports no extended-mode data). It selects between two actuator payload layouts: set -> act[0] = 0x40 fixed with act[1] carrying the pulse; clear -> act[0] carries the pulse and act[1] is loaded with the low byte of gp+0x618 every frame.
  • No audio call in the kernel. Its other branch counts down ~1200 frames and calls FUN_8005C034(9, 0), the retry wrapper over CdControl (FUN_8005CF80) issuing CdlPause: a CD-drive pause, not a voice stop or rewind.

Consequences:

  • The two "per-voice trigger bytes" are one actuator payload: a per-step on / off pulse and an intensity level, transmitted by libpad every poll.
  • gp+0x614 / gp+0x618 are vibration-intensity requests, not a locomotion speed (+0x618 is written verbatim into an actuator level byte). With gp = 0x8007B318 they are _DAT_8007B92C / _DAT_8007B930, whose writers are the field VM's op 0x4C nibble-4 subs 6 / 7, the encounter handoff, the battle intro, the battle overlay, the summon and cast modules and the options screen's Events commit (audio.md).
  • _DAT_8007B8A4 pinned at 2 across four field and overworld runs means nothing requests vibration while walking, as the game's Vibration options (battles / events / encounters) predict.

Not SsAPI: the 0x8006C000..0x8006F000 band does hold libspu / libsnd code and the stride-0xF0 record array with an 0xFF idle fill resembles a sequence-worker table, but the resolved context's +0x30 is the button report FUN_8001822C decodes, PadInfoMode's id-table branch has no sequencer reading, and the record count is 2 - the number of controller sockets.

Port: legaia_engine_system::rumble carries the // PORT: tag and drives the controller from the motor words, written by the field scripts and every battle-side writer (audio.md). The old engine-audio::footstep copy, fed a stand-in walking speed, is retired. Owning page: audio.md.

See ghidra/scripts/funcs/8006e2b4.txt, 8006ce30.txt, 8006d7b4.txt, 8006ca7c.txt, 8006cb3c.txt, 8006cdb0.txt, 8006d1e0.txt, 8006d2ac.txt, 8001d230.txt, 8001822c.txt, 80018db0.txt, 80018f94.txt, 8005c034.txt.

XA channel map / STR demux SM

Status: resolved - both halves are statically decompiled from PROT 0970 at its base plus the SCUS St library.

  • No XA channel selector exists in the STR overlay. FMV playback reads with Setmode 0xE0 (Speed|RT|Size1, sector filter off): the drive hardware-plays every ADPCM sector, and each MOV/MV*.STR interleaves exactly one XA track at (file 1, chan 0) (raw-subheader-verified across all six movies). The per-cue channel selector is the SCUS XA-clip sequencer FUN_8003D764 (CdlSetfilter {file 1, chan}, mode 0xC8 at 0x8003D80C), used for the XA1..XA34 voice / music files, not for movies. Not the \DATA\MOV.STR container: that is a dev path in slots 11..=22 of the dispatch table, absent from the disc. See cutscene.md § XA channel selection.
  • The FMV dispatch table stride is 32 bytes (sll v0,v0,0x5 at 0x801CEC9C), not 64. All nine retail slots 0..=8 resolve, every movie on the disc plays, and MV3.STR carries four abutting segments. The master dispatch FUN_801CEA3C hands each mid-game FMV off to a return scene (the seven-label table at 0x801CE8AC plus a spawn word). Parser legaia_game_tables::fmv_dispatch (disc-gated fmv_dispatch_real); the engine resolver legaia_engine_system::movie::cutscene::fmv_index_to_str_filename mirrors the nine-slot map and the return scenes. See str-fmv-table.md.
  • The 24-byte records at 0x801CAE08 are libcd's directory cache: CdlFILE structs ([loc][size][name[16]]). A name-first parse pairs each name with the next record's location, which is what produces an apparent "MV1 points at disc MV2 / MV6 points at XA15" shift. See str-fmv-table.md.
SPU reverb live routing (C7-REVERB)

Status: resolved - retail runs Studio C, master-enabled, globally. There is no selective per-cue reverb-enable source. Evidence: the mednafen save-state corpus, read by psx_mednafen::PsxSpu (CLI mednafen-state spu <state>).

PsxSpu reads the SPU register shadow (Regs block): reverb_master_enabled (SPUCNT bit 7), reverb_registers (the 32 reverb coefficient / address registers at 0x1F801DC0..0x1F801DFF), and voice_reverb_mask (the per-voice EON enable at 0x1F801D98 / 0x9A, which mednafen also mirrors under its Reverb_Mode sub-entry - a byte-for-byte cross-check in every state). Across all 45 mednafen states (field / town / battle / summon / title / minigames):

  • Master reverb is always enabled (SPUCNT bit 7 set everywhere). No scene toggles it.
  • The preset is Studio C everywhere. The 32-register block is byte-identical in every state and matches the StudioC libspu preset exactly (dAPF1=0x00E3, dAPF2=0x00A9, work area 0x6FE0). engine_audio::ReverbMode::identify resolves the captured block to StudioC.
  • Per-voice reverb send (EON) is broad: 15-22 of 24 voices in any state, BGM and SFX alike. Reverb is the default routing, not a per-cue effect ("Spirit Arts / echo cues opt in, everything else dry" is not what the states show).

Port: the engine calls Spu::set_retail_reverb once at SPU init (StreamResampler::new): ReverbMode::StudioC with every voice routed. The PCM oracle's retail side reads the EON mask as a mask. Not modelled: the output-depth setting (SpuSetReverbDepth, vLIN / vROUT); the engine uses a fixed half-scale approximation.

Owning page: audio.md.

bse.dat record columns and the gp+0x678 consumers

Status: resolved - grade disassembly.

gp = 0x8007B318 (lui gp,0x8008; addiu gp,gp,-0x4ce8 at 0x80026CA8), so the record-table pointer FUN_8001FA88 stores at 0x8001FBC0 (sw a0,0x678(gp)) lives at 0x8007B990. Seven readers exist and every one forms the address as lui rX,0x8008 + lw rY,-0x4670(rX) - the pair the five-form address scan does not accept, which is why the consumer read as untraced: 0x8004FFAC / 0x8004FFE0 / 0x80050078 in the battle SFX-cue router FUN_8004FE5C, and 0x801CEE48 / 0x801CEFC0 / 0x801CF038 / 0x801CF0A0 in the overlay-0971 debug sound test. Sweep: scripts/ghidra-analysis/find-gp-relative-refs.py.

The columns are the static SFX-table columns, pinned by shared code rather than analogy: FUN_80016B6C picks its arm at 0x80016C24 (slti v0,s0,0x200), resolves either 0x8006F198 + id*8 or record[id - 0x200] off gp[0x5B8], and then falls into one block of field reads from 0x80016CB0 - +0 program, +1 tone (+i per voice), +2 note level, +3 low-5 voice count / 0x20 sustained, +4 category into the 12-byte mixer record 0x80091508 + cat*12. +4 is a u8: +5..+7 are zero in every retail row. The router's two tinted legs (sb v1,-0x31c(v0) at 0x8004FFEC, sb v1,0x40c(v0) at 0x80050084) both reduce to record[cue_id - 0x200] + 4, and the sound test stores 7 at 0xDC(gp[0x678]) before enqueuing cue 0x21B = 0x200 + 27 - row index is cue id minus 0x200, and the category byte is rewritten per cue. Layout and carriers: bse-dat.md.

No second bse dat record family

Status: resolved (negative) - grade capture

Entry 888's 1,716-byte tail is byte-identical to entries 886 and 1063 at the same file offsets; entry 1062's 1,616-byte tail matches entry 1056 (two bytes differ where its SEQ padding clipped a row). The builder fill C0 00 C1 00 C2 00 C3 00 occurs in 221 entries, always at offset ≡ 0x1C (mod 0x20) (VagAtr + 0x18), and only these two carry it without a pBAV of their own. bse_bank::detect stops on a residue row's structurally-zero vib / por fields

  • reliable, but a foreign row's field. 1062 as a whole is a SEQ-only music_01

entry (sound-test track 72) borrowing another entry's bank. See bse-dat.md.

How a score ends, and what a loop end does

Status: resolved - grade disassembly, capture

FUN_8006139C (CC 99) jumps back only while the count a data entry stored lasts, and FUN_80063AA8 (end-of-track) never reads a Loop Start: it restarts from the score head or, with the play count spent, stops. The field BGM attach plays with a count of 1, so 2050, 2057, 2070 and 2071 play once; a capture of 2050 runs off its end with its flags at 0. A jump touches no voice, and does not wait the delta behind the Loop Start again. See seq.md.

What a track's residual against retail PCM was

Status: resolved - grade capture, disassembly

The -12 to -15 dB the track channel left once voices, volumes and pitches matched was three defects, each read off a per-note fit of the render against mednafen's recording: a key-on's start (seventh output, sample 3, on real history), the flush's cost before its KON write (FUN_8006C048 per staged voice, 1.2 samples a voice against the recording and 1.34 under PCSX-Redux's cycle counter), and a loop jump that re-waited the delta behind its Loop Start (FUN_8006139C). See real retail PCM.

The level of the port's mix against retail's

Status: resolved - grade capture

Against mednafen's recorded output, the SPU model resumed sample for sample reads correlation 1.000 and level within 0.003 dB, and a track from the state's read cursor reads within 0.2 dB, under 0.1 dB per band. The recorder's own 0.75 is divided out. See real retail PCM.

What a monster's strike sounds like against retail's

Status: resolved - grade capture

Dealt retail's hand (--strike-deal: the state's queue, approach terms and ATK, retail's seed word going into each hit), most strike recordings match cue for cue: the swing, the block or flinch voice, the impact pick and their times, which no chance could do across their hits. So the block roll, its draw order and the damage rolls hold, and what the plain score reads is mostly two streams dealing two hands. The rest are not all read: see the oracle's list of what the deal leaves out. The frame driver's discard at 0x80046D2C runs ahead of the hit, so a block roll's words sit one step past the frame's first word. See the oracle.

Why an impact cue rang on a block

FUN_80047430 runs its three walks in the order hit kernel, effect script, cue track (jal at 0x800478A0, 0x800478B8, 0x800478E4), and a blocked hit steps its attacker's cue cursor over the impact cue (0x801EE6EC..0x801EE6F8). Nova's clips 7 and 8 both carry cue 0x04 (ring 0x03) after the swing. On clip 8 it stands on the hit's own keyframe, so a port that walked the cues first sounded it two ticks ahead of every block voice. A --sfx-callers recording of battle_dohaty_nova_v2_strike has the router called with 4 on the one swing that lands a flinch and never on the three blocks. Grade: disassembly + capture.

Which bank Terra's battle keys

FUN_800520F0 loads raw 0x36D (PROT 0875) over slot 2 when DAT_8007BD11, party seat 1's roster id, is 4 (0x80052378..0x800523AC). With 0869 in its place her attack cues 0x40 / 0x41 are silent and eleven art cues sound where 0875 holds nothing. legaia-engine cue-differential --battle reads terra_party_battle alike on every row once the bank is the loader's. Grade: disassembly + capture.

Where a monster's +4 category comes from

FUN_800520F0 stores 7 at node +0x80 of every seated monster (0x8005225C) and then 8 on the seats holding the formation's largest id when the formation mixes ids (0x8005234C); FUN_800513F0 stores 7 on each party node (0x80051548). The summon seat's node has no such store and reads 0 (gimard_summon_visible, battle_rugi_rogue_rogue_thunder_mid_cast, battle_rugi_rogue_rogue_wind_mid_cast). cue-differential --battle prints the bytes of every battle state against monster_sound_category: the only disagreement is v0_1_battle_loading_tetsu, whose loader has not reached the store. Grade: disassembly + capture.

Where Glare's landing cue sits

Status: resolved - grade disassembly (the id and the arm), capture (one landed roll)

PROT 0940's 0x3C arm 2 counts out at 0x801F6F30, rolls the Stone test (0x801F6F9C), calls FUN_8004FE5C(0xF4, ctx[+0x13]) on a landing (0x15B for Zora, 0x801F7018) ahead of the guard tests, and steps to arm 3. Ring id 0x290. The port rolls on that pass, not at the band's closing seam. See the cast module page.

Why son_field_door_hop's records hold two 0x201 voices

Status: resolved - grade capture (the ring writers and the records over time), disassembly of the disc's sample flags (the voice length)

At the state one move-VM part stores cue 0x201 into ring slot 3 (op 0x1D, 0x80023688) every 63 vsyncs, and the drainer keys it on voices 23 and 22 by turns (autorun_keyon_census.lua from the state: stores at 34, 97, 160, key-ons two vsyncs later). The cue keys tone 0 of bank 2038 at note 60 against centre 84, pitch 0x400, and its VAG ends at block 268 with flag 1 (end without repeat), so the voice stops about 40 vsyncs in on hardware and in the engine (38), and only one voice sounds at a time. The records' +0x06 reading 7FFF on both is PCSX-Redux's ENVX, which its SPU thread advances on the host's audio clock (not emulated time): a watch of the two records from the state holds a voice at 7FFF for 30 to 90 vsyncs after its key-on, varying period to period. A second part spawns 175 vsyncs past the state (FUN_80021B04 from FUN_800252EC, its first store at 0x80021DC8) and runs the same loop 12 vsyncs behind the first, after which two voices do overlap; the engine spawns it too, the two streams 11 vsyncs apart, past the sweep's listening window. So the sweep's ambient miss on this state is the capture's ENVX, not the engine.

What sounds at the end of Zenoir's cast?

Status: resolved - grade capture (the ring and the seats frame by frame), disassembly (the show loop)

zenoir_summon_mid_cast stepped frame by frame under mednafen shows the cue ring DAT_8007B6D8 take every kill-phase cue twice - 0x18 in slots 1 and 2 at vsync 778, 0x18 in slots 0 and 3 at 814, 0x27A in slots 1 and 2 at 856 - and two seats react: arm 11 strikes the victim (seat 3, 3469 -> 3268) and seat 4 (3542 -> 3350). Seat 4 is struck because arm 10 showed it: the arm copies the victim's body pair onto the creature ((-751, 968)) and shows every live monster seat 3..6 whose ground pair lies under a Manhattan 0x400 of it (0x801F7A2C..0x801F7A9C, +0x04 = 0x20080200, +0x21C = 0); seat 4 at (0, 750) is 969 away, seat 5 at (600, 825) is 1494. The arm-11 splash skips a hidden seat, so the port, which showed the victim alone, struck one seat and sounded each cue once, 4..5 dB under the recording. It now runs the show loop (zenoir_bump_in_reach); the cast channel's level went from +1.38 to +0.09 dB. The cues land a few vsyncs later than retail's because the module's arms run on the port's frame step 4 where retail's alternates 3 and 4. See the cast module page.

When does a ring cue sound against the frame that pushed it?

Status: resolved - grade disassembly (the frame driver's order) + capture (frame-stepped key-ons)

Answer. A ring cue keys where the frame that pushed it ends - one frame step after the vsync its slot first shows the id - and so one frame step ahead of that frame's picture. The cast channel's per-arm residual is a term of the comparison, not a cue the player hears late. The measurement behind it:

Frame-stepped under mednafen, a battle cue's voices key one game frame after the vsync its ring slot first shows the id: zeto_big_wave_mid_cast at step 4 (pushes at 351, 391, 399, 407, 419, 483, voices keyed at 355, 395, 403, 411, 423, 487) and zenoir_summon_mid_cast at step 3 (778 to 781); son_field_door_hop's field part keys two vsyncs after its store at step 2. Retail's mode handler runs the ager FUN_8001698C, the frame body FUN_80016444, then FUN_80016B6C, which drains at its head and paces the vsync wait after (0x8001702C..0x80017068), so a cue keys where the frame's work ends. The port drains on the tick that opens the frame, which puts the cast channel's cues up to a frame step early (Big Wave's party reactions, the reading the cast channel gave as four vsyncs). The recordings put the onset a whole frame step after the push (Big Wave's first cue 3.9 vsyncs after the engine's, which pushes on retail's vsync), so the lag is the step, not step - 1. Holding the key-on a battle frame step leaves every cast row's median correlation where it was and lowers the tenth percentile on Swordie (0.77 to 0.33), Zenoir (0.68 to 0.33), Theeder and Guilty Cross, raising it only on Big Wave (0.48 to 0.52): the port's battle step is its cost model's, 2 through Big Wave's reactions where retail runs 4, and 4 through Zenoir's kill phase where retail alternates 3 and 4, so a lag on the port's step moves half the cues the wrong way. It is not ported. What closes it: the battle frame step itself on those arms (the cost ring's table), after which the lag is the step. The step byte is not always the frame's length either: on the PCSX-Redux film of Gaza's Neo Star Slash (gaza2_card_pre_neo_star_slash) capture phase 3 holds the step byte 4 and the cost at its 700 clamp while the frames take 6 vsyncs rising to 15 (48 frames in 583 vsyncs, the frame counter gp+0x480), so a frame that overruns its step delays its cues by the overrun as well; the engine runs the same phase in 240 ticks. Held on the port's own step from the arms' current cost rows, the same run reads Big Wave's tenth percentile 0.478 to 0.522, Zenoir's 0.682 to 0.329, Swordie's 0.774 to 0.332 and Theeder's 0.342 to 0.308, the medians unmoved: the table still names a step retail does not run through the kill phases.

The lag is not a sound-against-picture offset the port owes, and holding the cue alone would create one. FUN_80016B6C drains the ring at its head (0x80016BF8), then waits out the frame (jal 0x8005FB84 at 0x80017088 / 0x800171C0), and only after the wait swaps the display and draws the ordering table the frame built (0x80017230, 0x80017254, DrawOTag at 0x80017288), into the buffer the next frame's swap shows. So retail keys frame N's cue at the end of N and shows N's picture at the end of N + 1: the sound leads its picture by one frame step. The port presents a tick's picture and keys its cues on that tick. A cue held one step would trail its picture by a step, two steps from retail's order, where the unheld cue is one step from it. The cast channel's residual is therefore a term of the comparison (its one offset per cast cannot absorb a step that changes arm by arm), not a cue the player hears late. What would still close the number: the cast render delaying each ring cue by retail's step on that arm, in the oracle.

What is left is the oracle's, not the game's: a cast render that delays each ring cue by retail's step on that arm, which needs the battle frame step itself on those arms (the cast channel).

No script changes the BGM word inside a swap's read

The swap poller's stage 6 latches the requested index of its own call (0x800247B4..0x800247C4), so a sub-op 1 that changes the word after stage 2's read plays the old score and reports the new one loaded, where the port starts the newer word. Whether a script reaches that window is a property of the disc's bytecode. scene-formats/tests/bgm_swap_word_census_disc.rs walks every scene MAN record from each sub-op 1 along every control-flow path (both arms of each conditional, the picker's table) and adds up the explicit waits, WAIT_FRAMES targets and one per yield, at one vsync a frame, the shortest a frame can be. Ops that block on other state only lengthen a path, so the sum is a lower bound.

Of 201 sites in 81 scenes, 71 reach another word with no wait between: all in a scene's entry record, where both stores run in one VM frame, before the poller's next call, and stage 2 reads the last one. None reaches a different word a frame or more on inside the 66-vsync read (autorun_bgm_swap_latency.lua: stage 6 at +66 / +67). Sub-op 9 re-runs its own PC while requested and loaded differ (0x801E0238), so it waits the walk out. Grade disassembly (of the disc's scripts). The walk cannot see two records each starting a track as separate contexts; no such pair is known.

What clears the drive word before a clip ends

0x8007BC20 reads 2 while a one-shot plays: the starter FUN_8003D53C stores it (0x8003D65C) and the read callback's stop FUN_8003EE00 zeroes it once the read passes the clip's end sector (0x8003DA90, 0x8003EE2C). The other way to zero it is FUN_8003ED04, which mutes the CD input (FUN_8003EE7C(0)), zeroes the word (0x8003ED28) and pauses the stream: the clip stops. Its callers are the starter itself, the seek FUN_8003EAE4 (0x8003EB18), the stream queue FUN_8003DDA0 (0x8003DE28), the loaders FUN_8003E104 (0x8003E234) and FUN_8003E800 (0x8003E850), the mode resets FUN_80015E90 / FUN_80017714, the debug menu and PROT 0969.

In battle none of them lands on a sounding clip. The seek-ahead selector FUN_8004DA00 runs only while the word is 0 (bne at 0x8004DA14). A summon module seeks on an arm ahead of its head cue. The side-band applier FUN_801F12D0, the battle's one stream loader, opens with slti v0,v0,0x2 on the word (0x801F12E4): while a clip sounds it stores stage 1 and returns, so its read starts over from the base slot once the clip ends; its caller FUN_801F17F8 runs it only while the stage is up (0x801F180C..0x801F181C).

A PCSX-Redux Theeder cast (spell 0x82 from party_basic_attack_vs_gobu_gobu, cast-sequence.py --watch 0x8007BC20) shows the word's life on that drive: the selector's seeks (0x8003EB78 then 0x8004DBA8, vsyncs 278 and 428), the module's arm-1 seek (0x801F6C5C, 433), the voice (0x8003D65C, 553), and the callback's stop (0x8003EE2C, 1386): 833 vsyncs for the clip's dur of 807, with no store between. Mednafen shows the applier's hold itself: stepped frame by frame from theeder_summon_mid_cast (drive-timeline.py), the word goes to 2 at +229 and the cast reaches 0x51 at +1001 with the voice still sounding; the next turn's side-band stage stands at 1 from there until the word drops at +1060, 831 vsyncs after it rose, and only then steps (2 a vsync later, through 6). Both emulators hold the word about 25 vsyncs past the clip's dur; with one clip measured, whether that is a constant start cost or proportional is not separated, and the port holds dur. Grade disassembly + capture. Port: AudioState::battle_xa_busy_frames (the word, dur vsyncs), the applier hold in World::tick_sideband_token.

Source: docs/reference/re-settled-threads/audio.md

Title / boot / overlays

# Settled threads: Title / boot / overlays

ThreadAnswerGrade
Which routine uploads the system-UI TIMs ahead of extraction entry 0?FUN_801CECD0 in init.pak, PROT 0895
Why

The TIMs are raw TOC entry 0 (CDNAME init_data). Arm 2 of the boot asset-load state machine calls FUN_8003EB98(0, buf, 1) (clear a0 at 0x801CEDA8, jal at 0x801CEEF8; dev twin h:\prot\field\init\init.pak) and arm 3 uploads each TIM_LIST member through FUN_800198E0. Raw entry 1 (gameover_data) is the GAME OVER overlay's load (PROT 0902, FUN_8003EB98(1, ..) at 0x801CE94C), and 98 retail states hold none of its CLUT. boot.md

disassembly + capture
Do MOV15.STR and MV1A.STR ship on the disc?no - path strings only
Why

\DATA\MOV15.STR;1 and \MOV\MV1A.STR;1 sit in the STR overlay (PROT 0970) beside the six numbered movies and are selected only by the dev dispatch slots 9 / 10, the only slots choosing the STRv2 / v3 decoder. The disc's MOV directory holds MV1.STR..MV6.STR and there is no DATA directory (cutscene.md). MOV15 is the 15 fps test file; MV1A an alternate cut of MV1.

capture
What does FUN_801E0418 draw, and which entry uploads its page at VRAM (512, 256)?the title's own strips; PROT 0890
Why

It redraws the title TIM's strips behind the Load window when that window is opened from the title: records 0/3/4/2/5 of the descriptor table at 0x801E50A8 (wordmark, NEW GAME, CONTINUE, TM line, copyright) centred at (0xA0, 0x50/0xA0/0xAE/0xBE/0xCC), the row the title cursor _DAT_8007B820 is not on at half brightness, gated on _DAT_8007BB00. The page is the title TIM in PROT 0890 at 0x14228: in save_select_idle all 256 VRAM rows of the page and the CLUT row are byte-equal to it. Ported and live: engine-ui::title_strip_rows. save-screen.md.

capture + disassembly
How many rows does retail's title menu have?two
Why

The title tick wraps its row counter with andi v1,v1,0x1 at 0x801DDC00 and the confirm arm branches on row 0 against everything else (overlay_title_801dd6b8.txt), so no title route yields Options; retail reaches Options from the pause menu (host-drift.md).

disassembly
How does a slot-B module hand a spawn call its record?three shapes beyond an adjacent pair
Why

The pair can complete in the jal's delay slot, the pointer can arrive through a saved-register copy formed up to 256 words back, and switch arms can each load $a2 in the delay slot of a j to one shared call (PROT 0912's lui s4,0x8020 at 0x801F6C38; PROT 0957's shared jal 0x80050ED4 at 0x801F7F08). The delay-slot shape accounts for 52 of the 97 spawn calls an adjacent-pair-only resolver sends out of the image into a neighbour's records (slot-b-module-layout.md).

disassembly
Which images take the slot-B layout walk?the seventy at the link base, not the sixty-four in the index band
Why

The walk's three regions are each recovered by resolving a word against 0x801F69D8, so it belongs to every image mapped there: the 64-entry cast band plus 0900 / 0901 / 0967 / 0968 / 0969 / 0978. With the walk the six gain a head-table claim (accounted bytes 0968 60.4% -> 99.8%, 0969 78.7% -> 99.4%, 0978 51.3% -> 91.6%). is_slot_b_module answers the band question - which images the cast dispatcher reaches - and is_slot_b_image the layout one.

disassembly
What is PROT 0967's residue?a head table, dumped code, one leaf, a consumed prompt pool and a neighbour's tail
Why

408 bytes of head jump table, 2,744 bytes of dumped code, one 92-byte frameless leaf FUN_801F7628 at file 0xC50..0xCAC, 1,757 bytes of prompt pool and 1,143 bytes inherited from PROT 0966. The pool is consumed: twenty-eight strings formed by the module's own code at forty-one sites between file 0x278 and 0xA7C. The leaf is reached by jal from 0x801F7184 and 0x801F7460 inside the image, so a slot-B image can call itself. The link-base layout walk admits 0967 at 96.6%; the cast band (0903..=0966) is a different predicate. byte-accounting.md

disassembly
What is in the PROT 0898 head block below 0xDF8?a string pool and twenty-two jump tables; two more sit above it
Why

Each consumer is sltiu / beqz / base / sll 2 / load / jr, so a table's extent is its sltiu bound times four: twenty-two bases tile the head, 850 arms in all, among them 0x801CF1CC (179 arms, jr at 0x801EA9FC) and 0x801CF49C (5 arms, ctx[+0x28A]). Above the head sit nine arms at 0x801CF614 (jr at 0x801F3AC0) and seven at 0x801CFA2C (jr at 0x801F3EB4), with the Seru side-effect banner pool between them. Counting VA-word runs yields nine, because abutting tables merge. legaia_disc_audit::battle_jump_tables; byte-accounting.md

disassembly
What is PROT 0970's 131,172-byte zero hole?the overlay's own uninitialised data region
Why

It decomposes exactly, in the image's own operands: the 0x50 decode context at 0x801D19A0, two 0x7800 slice staging buffers at 0x801D19F0 and 0x801D91F0, four globals at 0x801E09F0 and the 0x11000 STRv2 VLC table destination at 0x801E0A00, ending flush against the unpacker. The loader transfers the whole sector extent, so an overlay's .bss travels with its code and reads as a hole. The table's packed source is in the same image at 0x801F1AE8. byte-accounting.md.

disassembly
What is PROT 0974's 10,836-byte run of readable text?an 81-record 0x84-stride roster
Why

Not a string pool: the loop operands in FUN_801CED68 walk a table at 0x801CEF40 on a 0x84 stride, and the text inside each record is Shift-JIS read as little-endian u16. The image is a USA-build dev module carrying Japanese dev text - 34 of 37 SCUS jal targets land on this disc's function heads, where the foreign-build control scores 0 of 42 - so "Japanese text" is not evidence of a foreign build. Parser legaia_disc_audit::other3_roster.

disassembly
What is PROT 0975's undumped 0x801D4138 run?PROT 0972's code, inherited at the same file offset
Why

The 1,760 bytes from file 0x5920 to the entry's end (0x801D4138..0x801D4818 at the slot-A base) are byte-identical to PROT 0972 at the same file offset - the packer's buffer is indexed by file offset and never cleared, so a short entry ends in the previous image's bytes. That is why the run has no prologue, no jr ra and no caller, and why it is not 0975's own jump table, data block or function interior. disc-coverage.md.

capture (disc byte compare)
Which of PROT 0978 / 0979 / 0980 is the dance overlay?only 0980
Why

0978 is field_back_read, the staged background loader (slot B, jal 0x801F6B24 at 0x80025404); 0979 is the battle-intro image (jal 0x801CE8CC at 0x80025ED4); 0980 is the dance overlay - minigame case 6 of FUN_80025980 calls 0x801CEF54 (jal at 0x80025AE0), and only 0980 holds a prologue there (addiu sp,sp,-0x138, file 0x73C).

disassembly
The front-end mode chain, re-derived independentlythe six stores, the four-word edge and the INIT hand-offs all hold
Why

A three-form opcode scan for stores to the mode word over 84 images finds 53 store sites, and every row of the boot chain, the mode-change edge's four cleared gp words and the per-handler INIT hand-offs match it. Mode 18's hand-off is li v0,0x13 at 0x801CE8D4 then the store at 0x801CE8DC, inside 0x801CE844 in PROT 0902.

disassembly
Which mode does the retail title screen run under?card mode 0x17, not 0x10
Why

The front end is a chain of six mode stores, each written by the handler that hands off, not by a table's next field: 0x8001D5B8 -> 0x10, 0x801CEC94 -> 0x11, 0x801CF4D4 -> 0x16, 0x80025974 -> 0x17, 0x801DFC00 -> 0x02, 0x80025E50 -> 0x03. Mode 0x10 is one frame of logo INIT; the alternate arm at 0x801CF4E4 is the dev CONFIG route on the entry word. 0x80025974 is li v0,0x17 + sh v0,-0x47c4(at) at 0x8002596C; 0x801CEC94 is the sh in a jal delay slot. See boot.md.

disassembly
What reads the title entry word _DAT_8007BB00?two readers, both real
Why

0x801CF4B0 in the boot image and 0x801DD97C in the title overlay. The port's counterpart is mode_entry_init::ENTRY_WORD_ADDR.

disassembly
What does a mode-change edge clear?four gp words, not three
Why

The edge at 0x800161F4 clears 0x8007B938 alongside the words at gp+0x538 and gp+0x55C; gp+0x564 and gp+0x494 are mode copies, not cleared state.

disassembly
_DAT_8007B8C2 - how many writers?exactly one
Why

main() at 0x80015F08 (sh v0,0x5aa(gp)), confirmed across 70 access sites in 84 images. The build-mode selector is set once at startup and never re-written, which is what makes the dev/retail fork in FUN_8001F87C a load-time decision.

disassembly
_DAT_8007B8C2 polarity, and its writer!= 0 is retail, == 0 is dev
Why
disassembly + capture
Actor-VM (FUN_801D6628) program source - which carrier, what selects onemenu-overlay-resident program table
Why

The interpreted programs are data in PROT 0899's own data segment (file 0x16260..0x16740), one per jal FUN_801D6628 caller via lui+addiu (or a forwarded register); byte 1 of each instruction indexes the window descriptor table at 0x801E4738, making the VM the menu's window-widget choreographer. No per-scene carrier exists - resolution is per-boot. Spec window-script.md; scanner legaia_game_tables::widget_script::scan; engine wiring engine-menus::menu_widget. Sprite-VM readings: re-do-not-re-walk.md.

disassembly
title.pak PROT entryresolved
Why
capture
Does FUN_801CE9C0 pin the per-logo quads?no - it uploads; the quads are a descriptor table in the same image
Why
disassembly
The title menu's law (FUN_801DD35C sub-mode 0x10)+ ported on both hosts
Why
disassembly
FUN_801D71F0 and its "shared armament placer" 0x801E5AE8one routine, FUN_801E5A08, unreferenced
Why
disassembly
Save-screen info-panel view mode 4 ("Return")dead - the grid has 15 cells
Why
disassembly
Title screen mode-table PROTno row is named for it - it runs under the CARD mode pair 22/23; FUN_801DD35C is PROT 0899 +0xEB44
Why
disassembly
Load-screen panel 9-slice geometryengine renders byte-perfect
Why

Retail composes the 81×29 panel at dst (6, 4) from 14 textured-sprite primitives (GP0 cmd 0x64) sampling the system-UI sheet with CLUT (32, 511); no interior fill sprite is drawn - the "marbled blue" look is the dimmed title art showing through the empty middle of the frame. Per-tile rects: save-screen.md, exported as legaia_ui_formats::title_pak::OVERLAY_SYSTEM_UI_PANEL_* and emitted by legaia_engine_ui::save_select_chrome_draws_for (test save_select_chrome_emits_9slice_panel_and_pills).

capture
Key-item area consumers (0x800859E8..0x80085A40)narrow negative; the reader enumeration is closed
Why
disassembly
XP-table source + reader+ ported
Why
capture
New-game world-state seed store widths (FUN_80034A6C)the port matches the disassembly
Why
disassembly
Overlay identity from the disc (static extraction)pipeline legaia_overlay_images::static_overlay
Why
capture
SCUS recomp gap - render/GTE + boot/init clustersaliases + libgte residue + dev tooling; main() documented
Why
disassembly
Options/menu overlay PROT entryRAM-verified; PROT 0899 @ 0x801CE818
Why

The options/pause/inventory-equipment-status menu overlay is PROT 0899, not 0896: FUN_801CF650's signature byte-matches PROT 0899 file 0xe38, and the .text+.rodata prefix is byte-identical across six menu-open saves. It is the VA-alias sibling of the field overlay 0897 in slot A - the menu overlay replaces the field overlay at the base.

capture
PROT 0896 (bat_back_dat) identityresolved
Why

The unique ~0x9000-byte head is the vestigial Japanese-build field-menu / config / status overlay - the debug-string sibling of the English retail menu overlay PROT 0899 (same ~0x801D0000 window-renderer VA family, a "FWIN ERR %d" printf at file 0x3D4, 0x414-byte char-record indexing). 0899 ships the English label set with zero FWIN; a signature scan finds 0896 resident in 0 of 140 states (control: English "Battle Voices" resident in 10), so the USA build never loads it. details ↓

capture
Slot-A scene-overlay family beyond field/battle/menuin the static map
Why

The rest of the slot-A (0x801CE818) VA-alias family is read from the disc: 0970 cutscene_str (STR/MDEC FMV, modes 26/27) and the minigame overlays 0972 fishing / 0975 slot_machine / 0976 baka_fighter / 0980 dance (the mode-24 0x3E door-warp sub-id slots 0/3/4/6), each cross-checked by a documented function landing on a prologue at the base. Found with asset overlay scan + the leading dev string. "slot_machine = 0973 @ 0x801CA818" is a phantom base: that image sits in 0973's over-read tail, and the canonical entry 0975 recovers 0x801CE818 and is the one the warp streams.

disassembly
"world-map / save / shop" overlay PROT entriesnot separate entries
Why

The world-map / overworld controller FUN_801E76D4 lives in the field overlay 0897 (base+0x18EBC), and the save-slot dispatcher FUN_801DC6B4 + the shop/buy session live in the menu overlay 0899 (save at base+0xDE9C) - each function's instruction signature byte-matches only that one entry (asset overlay find-sig; the 32-byte window from lui v0,0x8008 at 0x801E76D4 and from lui a0,0x801E at 0x801DC6B4 occurs in no other PROT entry). So "world-map", "save", and "shop" are subsystems of existing slot-A overlays, not separate PROT entries; recorded in the 0897 / 0899 map notes.

disassembly
PROT 0977 / 0978 extraction + the dump re-keyresolved
Why
disassembly
Slot-B capture-module band 0935..0966 per-entry identitystatically derived, capture-corroborated
Why
disassembly
Phantom-VA sweep of the PROT 0897 importsresolved
Why
disassembly
Debug flag 0x8007B98Fthe MSB of the debug-mode word _DAT_8007B98C
Why
disassembly + capture
New-Game opening chain + narration rollerchain + caption + roller + prologue gold grade; far-geometry residual resolved-negative
Why
capture + disassembly
Overlay-loader index off-by-2 - remaining rippleslot A reconciled; slot-B per-spell identity capture-pinned
Why
capture + disassembly
Slot-B overlay cluster (0900..0969) per-entry identityfor every entry
Why
capture + disassembly
PROT 0968 - what it is, who loads it, and how big it really isidentity and extent by disassembly, residency by capture
Why
capture
0x80010390 - the SCUS word that looked like a lead on 0968it is the slot-B overlay destination pointer, shared by every slot-B entry
Why
disassembly
Who registers the MDECin DMA callback wrapper FUN_801CFE98?nobody - linked libpress residue; retail hooks only the MDEC-out twin
Why

Zero references in eight forms over 1234 images (SCUS, 31 based overlays, every raw PROT entry). Its twin 0x801CFEBC - the same nine instructions with channel 1 (the two differ only in the jal 0x8005FDE8 delay slot, clear a0 at 0x801CFEA8 against li a0,1 at 0x801CFECC) - is what PROT 0970 calls, at 0x801CF524 (clear) and 0x801CF9C4 (install). See cutscene.md.

disassembly (the residue reading inference)
PROT 0895 (init.pak) identity + baseslot A 0x801CE818; mode 16's whole body is its FUN_801CE9C0
Why

Base recovered from the image's own jal graph (8 votes, 22/25 pointer resolution, 7 string anchors); 0x801CE9C0 is a clean addiu sp,sp,-0x230 entry at file +0x1A8, 784 bytes, building the four logo primitive records and storing mode 0x11 at 0x801CEC94; the code region closes byte-exactly at +0x216C (20 functions) ahead of the first TIM at +0x21C4. SCUS FUN_8002612C (mode 16) is a frame, that jal, and an epilogue.

disassembly
Does anything draw the init.pak WARNING screen?negative (the TIM is uploaded and never drawn)
Why

PROT 0895 uploads the health-warning TIM to VRAM (704, 0) and gives it descriptor 1 of the six-record sprite table at 0x801F369C, but none of the five FUN_801CFBB8 call sites in that image passes id 1, nothing else on the disc references the table, and a boot capture never draws from that page. The USA build loads the screen and skips it. See boot.md.

disassembly + capture
Which title sub-mode does a cold boot show - 0x02 or 0x10?0x10, always
Why
disassembly + capture
Where does fmv_id 5's trigger op live?taiku's streamed MAN, P2[28]; every FMV trigger sits in partition 2, none in partition 1
Why

Entry 0373 is a DATA_FIELD stream whose type-3 chunk is an uncompressed MAN; at P2[28] +0x20 it reads 4C E2 05 00 00 00, after a BGM op and raising flag 0x55A, then parks. The census counts nine clean 4C E2 across nine scenes and none elsewhere, so the raw-sweep hits in koin1b, level_up, card_data and the VAB entries are not script. Page: str-fmv-table.md.

capture (disc census, asset field-op-census --only '4C E2' --context)
How the slot-B module pager turns a request into a PROT entryextraction entry = a0 + 895, with no upper bound
Why

FUN_8003EC70 adds a constant 895 to its argument and pages that extraction entry into slot B; nothing clamps the top of the range. Its site at 0x8005269C passes _DAT_8007B64A + 71, so selector byte values 2 and 3 reach PROT 0968 and 0969 while 0 skips the load entirely. The selector's writers are in battle.md; the one for 2 is below.

disassembly
What is PROT 0970's run above its one-shot init flag?two MDEC command packets, then the register-pointer block
Why

0x801D0D58 holds the quant packet header 0x40000001, with the luma and chroma matrices at 0x801D0D5C / 0x801D0D9C; 0x801D0DDC holds the IDCT packet header 0x60000000 and a static IDCT matrix. 0x801D0E60..0x801D0E9B is fifteen hardware-register pointers (0x1F801080..0x1F8010B8 DMA channels 0-2, 0x1F801820 / 0x1F801824 MDEC0 / MDEC1, 0x1F8010F0 DPCR). Neither uninitialised data nor code; claimed off legaia_game_tables::fmv_dispatch::MDEC_*_PACKET_VA.

disassembly
What does PROT 0970's FUN_801CFCDC do?the MDEC quant table upload - not an output-rect stager
Why

It copies sixteen words from a0[0..0x40] to 0x801D0D5C and sixteen from a0[0x40..0x80] to 0x801D0D9C, then calls FUN_801CFFDC(pkt, 0x20) twice - the quant packet 0x801D0D58 (0x801CFD48) and the static IDCT packet 0x801D0DDC (0x801CFD58); only the quant packet is written. FUN_801CFFDC ORs 0x88 into DPCR, programs DMA0 (MADR = pkt + 4, BCR = (n >> 5) << 16 | 0x20), writes the packet header to MDEC0 through the pointer at 0x801D0E90, and starts DMA0 with CHCR 0x01000201. cutscene.md

disassembly
What is PROT 0899's 3,552-byte zero run at file 0x1EB28?inter-asset fill, not uninitialised data
Why

It sits between the save-menu atlas's end and the save-slot icon sheet at 0x1F908 (0x801ED340..0x801EE120 at the slot-A base), and no instruction in any image forms an address inside it (find-gp-relative-refs.py --prot, zero hits). An overlay's .bss is named by its own operands; this run is named by none.

disassembly
What are PROT 0899's options-screen tables?four, each bound to a consumer
Why

The display layout 0x801E4404 (ten [u16 row_id, u16 advance]), the string-pointer table 0x801E442C, the row-node list 0x801E44B8 (eight-byte nodes, zero-terminated - lw / bnez at 0x801D2A0C) and the casino prize table 0x801E4518 (0x60-byte blocks of eight-byte rows). All four are formed by lui/addiu pairs in the options row renderer and the prize-exchange session. field-menu.md

disassembly
Where does PROT 0901's own content end?file 0x252A, donor PROT 0900
Why

0901's code ends at jr ra on file 0x24DC (0x801F8EB4), and the run from 0x252A opens mid-routine on 0900's epilogue of the routine at 0x801F8E6C (its prologue at 0900 file 0x2494); only 0900 references it. That is the packer-buffer inheritance shape; a cut at 0x26B0 is not the boundary, and 0900 / 0901 are not shifted copies of each other (an entry-size over-read artifact).

disassembly

Long write-ups in this area

A cold boot always shows title sub-mode 0x10

Status: resolved. Evidence: disassembly + capture.

FUN_801DD35C's Init arm (0x00) writes state[+0x204] = 0x02 and overwrites it with 0x11 whenever the entry word _DAT_8007BB00 reads non-zero. On retail that always happens, so the 0x02 two-row menu (rows y 107 / 120, confirm mask 0x44, advancing to 0x14) is unreachable from a cold boot.

  • Entry word is always raised. The boot init.pak sets it unconditionally - li s2,0x1 / sw s2,-0x4500(s0) at 0x801CEB84 with s0 = 0x80080000, in the mode-16 body. The three sites that store zero back are all behind dev-flag or pad-hold arms.
  • A surviving 0x02 is rewritten. The AttractDelay (0x11) arm writes 0x10 at 0x801DDAC4 when its hold has drained; the shared epilogue's exits at 0x801DFED8 / 0x801DFEF8 do the same.
  • Capture. A per-vsync cold-boot poll sees _DAT_8007BB00 go 0 -> 1 in the frame the master mode steps 0x10 -> 0x11; the sub-mode is written 0x11 the frame after the title mode is entered and 0x10 about 75 vsyncs later; 0x02 is never observed. Returning from the attract FMV the word reads 2, so the overwrite holds on the second entry too.
  • The tick. PROT 0899 file +0xEB44, 12 104 bytes / 3 026 instructions, dispatching 0x801F0204 through the 24-word jump table at 0x801CF244 (slot 0x11 = 0x801DDA90), with 56 stores to that word and one shared epilogue at 0x801DFC3C. _DAT_8007BAB4 is its pre-attract hold, not an active-submenu index.

Falsified readings: the sub-mode word's address, the slider clamp. Owning page: boot.md.

_DAT_8007B98F is byte +3 of the debug-mode word _DAT_8007B98C

Status: resolved - no byte-granular reader exists; the 32-bit word is the consumer surface. Evidence: disassembly + capture.

0x8007B98F is the MSB (little-endian byte +3) of the debug-mode word _DAT_8007B98C. Nothing reads or writes the byte on its own, so SELECT+START / a GameShark write of 0x8007B98F = 1 sets the word non-zero and every _DAT_8007B98C != 0 gate reads debug mode active.

  • Byte references: zero in SCUS and every overlay dump (ghidra/scripts/funcs/ has no 8007b98f).
  • Word readers, SCUS: FUN_8001822c (8001822c.txt:500/533), plus 80016230 / 80016444 / 800173bc / 800188c8 / 8003cbf8 / 8004ad80 / 80025cb4.
  • Word readers, overlays: an aligned word search of the 23 static overlays finds 14 lw ...,-0x4674(reg) reads (base reg 0x80080000) in the field overlay 0897.
  • Sole sw writer: the shared menu/title/save-init routine (overlay_menu_801de234 / overlay_title_801ddccc, internal offset 0x4158). It clears the word, so the gate does not survive scene initialisation and has to be held asserted for a session.
  • Not BIOS-zeroed: the PS-X EXE header carries b_addr = 0, b_size = 0, so no BSS is cleared for this executable; the same holds for _DAT_8007B8C2.
  • Runtime (static recomp): asserting the word and pulsing SELECT + △ on controller port 2 opens the game-owned developer menu - port 2 follows from the _DAT_8007B850 &= 0xFFFF mask, which puts every debug binding in the upper half. Forcing game mode 0 loads PROT 0971's full-screen configuration tester instead, as the CONFIG INIT row in boot.md says. The developer menu's MAP CHANGE appliers are resident in field overlay 0897, matching the 14 gate reads there.

Owning pages: boot.md § Debug flags; combo table in builds.md § Debug input bindings.

New-Game opening chain + narration roller

Status: resolved - chain, caption, roller and prologue gold grade; the far-geometry brightness gap is resolved-negative. Evidence: capture + disassembly.

The opening is a five-scene chain - opdeene → opstati → opurud → map01 → town01 - all under master mode 3 with zero input. Its narration is a bottom-up scrolling crawl that runs in a child context while the parent timeline keeps cutting the camera, and its gold look is a palette-space collapse, not a depth cue.

  • Chain. Each leg's record spawn is an exec-BP hit on FUN_8003BDE0 (exactly 5): field-VM op 0x44 SPAWN_RECORD in the first three legs' entry scripts, the walk-on tile trigger (FUN_801D1EC4 → FUN_801D5630) for map01 / town01. The FUN_801D1344 town01 packet is the intro skip, not the hand-off gate. Name entry auto-opens from op 0x49 STATE_RESUME sub-op 3 at town01 P2[3] body offset 0x02c6 (_DAT_8007B450 parks there); retail order is establishing pan → name entry → Vahn's walk-out.
  • Crawl. Roller actor FUN_80037174, spawned as a child context by CC F8 80 N (N = page count). Retail opens every crawl non-blocking and holds only at the record's terminal 0x3F SceneChange while narration is active. A cold-boot capture (scripts/pcsx-redux/autorun_crawl1_capture.lua) shows the eye cutting through the Genesis-grove foliage to the villager tableau while the creation crawl scrolls. The one-caption presenter is the separate 4C E1 balloon op (FUN_8003C764 / FUN_801DA7F0).
  • Roller config. CC F8 E8 ... (nibble-E sub-8 0xE8; handler 0x801E3378 in overlay_0897_801e0c3c.txt, reader 80037174.txt) reads four signed-16 LE words and stores three into _DAT_801C6EA4 (sh at 0x801E34B0 / 34B4 / 34BC): +0x4C window top Y, +0x4E visible line count, +0x50 scroll-cadence divisor. word3 selects seed / pause / resume / kill and is never stored. Not op0 0x88, which writes _DAT_80084628/.... RollerSeed::config_op_before (crates/engine-field/src/cutscene_narration.rs) reads it off the scene's timeline bytecode.
  • Caption. "It was the Seru." is not text: a pre-rendered 112×32 4bpp TIM (two CLUT palettes = the fade steps) in PROT 0749 (opdeene) at LZS-decoded offset 0x01EC30, VRAM fb=(384,0), drawn as a screen-space textured quad. Every UI text/image draw path fires zero times in the caption window and the string is in RAM in no encoding (autorun_text_census.lua, autorun_seru_blit_probe.lua, a full-RAM dump). tim-scan extracted/PROT/0749_opdeene.BIN renders it.
  • Camera. The per-frame mover is FUN_801DC0BC (FUN_801DB510 is the follow / scroll camera); FUN_801DD310 attaches ten (start, end) pairs plus one shared progress / duration / curve to a mover actor, so a glide runs in parallel with the record that staged it and a beat landing mid-tween re-seeds every axis from the live pose. One curve applies to all ten axes - mode 1 is linear on pitch/yaw too (three independent beats, one a 2000+-frame yaw dolly) - and the town01 arrival H glide (P2[3] +0x00C4, apply 600, H 412 → 512) is mode 4 ease-in-out (op0 0x13 >> 2; disc pin town01_arrival_camera). Eye-back depth is offset-trio slot 5, 0x800840B8 (no separate eye-distance scalar); the op-0x45 params map to the camera globals and the rotation build is FUN_800172C0 -> FUN_80026988.
  • Timing. Record durations count retail display frames (op 0x4A and the mover both accumulate DAT_1F800393); FUN_8002519C walks the actor lists in full every frame, so there is no hidden step parallelism (script-vm.md).
  • Gold grade. Four CLUT-cell cyclers in the prologue's effect scripts grey and re-tint the CLUT rects (0, 473, 320, 2) / (0, 476, 320, 34) to L = max(r,g,b) → (L, (L*247)>>8, L>>1) (5-bit, STP preserved; settled) and the scripts collapse authored colour packets to the amber family ~(M, 0.94M, 0.43M); neutral 0x80 ground quads stay neutral. Node +0x78 (IR0) is 0 on every render node (0x8007C34C..) at every beat, so there is no per-node depth cue (re-do-not-re-walk.md).
  • Far-geometry brightness (resolved-negative). Retail spires / wings read B/R ≈ 0.15..0.16 at brightness ~51 against the port's 0.27 at ~80; the tableau ground is identical. No CLUT-rewrite loop exists in overlay 0970 (28 functions, pure MDEC/STR), field 0897 (690) or SCUS_942.54 (945): the rewrite is a table/DMA upload. The gap is neutral packets on lit-descriptor prims, which retail draws through the GTE far/back colour FUN_80029888 loads (DAT_8007B788 = 0x00202020 in opdeene, 0x00FFFFFF in town01) - the port's no-field-light-op boundary (Field decoration path).

Port: a non-blocking crawl in the field timeline, paced off the 60 Hz sub-clock (opening_chain_wall_time: within ~4 % of retail wall time, each leg short by the unmodelled scene-load window, so its bands are asymmetric); legaia_engine_camera::camera_mover (2471 of 2480 sampled axis values against a live capture, the rest inside the probe's read skew; camera_mover_recomp_oracle under LEGAIA_RECOMP_TRACE_DIR replays the snap / mode-1 / mode-2 / mode-4 beats bit-exact); Renderer::set_palette_grade (palette_law_word / palette_collapse_prim, staged by play-window when World::scene_color_grade is active; tableau ground G/R 0.890 against retail 0.88); play-window renders through psx_camera_mvp.

Owning page: cutscene.md - crawl roller, roller operands, sepia grade.

Overlay-loader index off-by-2 - remaining ripple

Status: resolved - slot A reconciled; per-spell slot-B identity capture-pinned for every block (player, evolved, flutes, enemy). Evidence: capture + disassembly.

The overlay loaders (FUN_8003EBE4 / FUN_8003EC70 → FUN_8003E8A8(param + 0x381)) resolve against the in-RAM TOC at 0x801C70F0, which is raw PROT.DAT from byte 0 (byte-verified against the door_warp_town01_to_map01 state). Extraction indices slice entry starts two words higher, so the loaded entry is extraction param + 0x37F; a param + 0x381 attribution is 2 high.

Slot A (each content- or prologue-anchored; boot.md): field 0897 = mode 2, battle 0898, menu 0899 = mode 22, STR-path 0969, cutscene 0970, debug menu 0971 = mode 0, the seven 0x3E minigame slots, efect-test 0979 = mode 8.

Slot B per-spell stagers. The player span 0x81..=0xA0 is one unbroken linear run: loader id = spell - 0x79, extraction = loader id + 895 = 903 + (spell - 0x81).

Spell idsLoader idsExtractionWhatPin
0x81..=0x8B0x08..0x12903..=913base Seru summons (0907 = Nighto)one mid-cast state per spell
0x8C..0x950x13..0x1C914..923evolved Seru: Gola Gola 914, Mushura 915, Aluru 916, Barra 917, Kemaro 918, Spoon 919, Slippery 920, Iota 921, Puera 922, Gilium 923eight by mid-cast states (evolved_summon_binding); 0x90 / 0x91 by injected casts
0x96 / 0x970x1D / 0x1E924 / 925rare-Seru flute summons Lippian / Spikefishflute_lippian_midcast / flute_spikefish_midcast, probe autorun_flute_cast.lua
0x98-926unused: a one-sector jr ra stubdisc bytes
0x990x20927Evil Seru Magic (Juggernaut)mid-cast mednafen state
0x9A..0x9D0x21..0x24928..931Sim-Seru Palma / Mule / Horn / Jedomid-cast mednafen states
0x9E..0xA00x25..0x27932..934Ra-Seru Meta / Terra / Ozma (untitled entries head with a pre-linked slot-B pointer table)mid-cast mednafen states
  • Method. The loader-B current id (gp+0x934 = 0x8007BC4C) is read out of catalogued save states (scripts/pcsx-redux/match_prim_groups_to_disc.py::extract_ram walks the gzipped-protobuf .sstate to the RAM blob), together with the predicted entry byte-resident at slot B 0x801F69D8. The id is a last-load tracker - an idle Begin/Run-menu state holds a stale 6 - so only in-cast states are evidence.
  • Gimard. All three player cast states (gimard_summon_start / _visible / _burning_attack) hold id = 8 → 0903 through spawn, steady render and attack; the id never moves off 8, so 0900 does not overwrite the stager mid-cast. The enemy Gimard "Fire Tail" frames (battle_gimard_tail_fire_a/_b) hold id = 5 → 0900: the enemy special pages the move-FX module, not a stager.
  • Attack-titled heads are display names, not separate uses: 0907 "Hell's Music" (Nighto; the dance overlay has no slot-B loader callsite), 0927 "Dark Eclipse", 0924 "Ultimate Rave" (the failed-kill banner; a landed kill shows "Ultimate Death"), 0925 "Blowfish", 0918 "Canine Fangs", 0919 "Holy Eyes".
  • Evolved block. All ten entries trim to move-VM stagers with 4..67 spawn sites (EVOLVED_SUMMON_STAGER_PROT, disc-gated summon_overlay_block). The injected casts (autorun_evolved_cast.lua; states evolved_0x90_midcast / evolved_0x91_midcast) write the spell into the caster's record spell list and MP into the record and battle-actor +0x150: the battle Magic submenu reads the record spell list live, the MP gate reads actor +0x150. Loader-B flips when the slot-B load is queued, so the probe saves 90 frames after the flip; the image then matches 100 % over the entry's full LBA footprint. The two 0x4000 render-mode carriers (916 Aluru, 921 Iota) are player casts, so neither seats a live render-mode part.
  • Flutes. SummonFlute items (effect classes 126 / 127) enqueue the spell id directly, so they ride the same stager mechanism.
  • Enemy arm (six catalogued final-boss Cort mid-cast states), id band 0x2B..0x47 → 938..966: Mystic Circle 0x2B → 938, Mystic Shield 0x2D → 940, Guilty Cross 0x31 → 944, evolved-form Final Crisis / Ultra Charge 0x42 / 0x43 → 961 / 962, Cort's Evil Seru Magic 0x47 → 966. The player and enemy arms of one spell ship separate stagers (927 vs 966).

The 0977 sub-id-5 minigame. 0977 ("Ronginus") is the mode-24 case-5 door/init slot: the 0x801CEA6C init prologue, the arena monster-name roster and other6 dev paths. The Muscle Dome match SM FUN_801D0748 and all its data live in the battle-action overlay PROT 0898 - the arena is a mode of the battle engine. asset overlay find-sig of the controller prologue (lui v0,0x8008; lw v0,-0x42dc(v0), reading the ctx _DAT_8007bd24) lands on 0898 at base 0x801CE818, file offset 0x1F30, and the deck / sub-draw / victory tables resolve in-overlay (legaia_minigame_formats::muscle_dome::verify_resident); the Duckstation overlay_muscle_dome.bin capture is that overlay's slot.

Port. OVERLAY_PROT_BASE carries the extraction-space 0x37F (the host chain prot_one_shot_load → entry_start_lba_retail consumes extraction indices; its toc array starts at raw dword 2), with a unit test documenting the raw-vs-extraction shift. engine-effects::summon::summon_stager_prot_entry maps spell id to stager entry.

Muscle Dome match shape: an ordinary battle ladder, not a card battle

Status: resolved. Evidence: disassembly.

The arena is a ladder of ordinary battles. The four "cards" are the four d-pad direction commands 0xC..=0xF, each carrying that fighter's own AP cost - the input a normal battle command screen takes, bounded by AP.

  • Course descriptor table 0x801D1A08 in PROT 0977: three { i32 rounds; ptr first } records walking 29 { u32 label; u32 monster_id } round records at 0x801D1920.
  • FUN_801D1510 stores the round's id into formation slot 0 at 0x8007BD0C.
  • Courses are 8 / 8 / 13 rounds, matching the populated rows of the score table at 0x801D1860; the 29 ids resolve against PROT 867 to the curated casino.toml line-ups 29 of 29 in order.
  • The HP ratio is x 100: the "out of 108" (0x6C) reading consumes only part of the compiler's shift-add chain at 0x801d0f38..0x801d0f4c.
  • 0x8007BD0C is the formation cell, not a battle-type byte: the strip's gate reads "the first enemy is monster 0xB6" - Koru, the one four-turn timed boss - and no dome round fields that id.

Port: engine-world-views::muscle_dome (parse_course_ladder, course_score_cell, resolve_turn playing whole strings per actor, DomeDamageModel - the one retail damage kernel both hosts resolve through). Falsified readings: re-do-not-re-walk.md. Owning page: minigame-muscle-dome.md § Course ladder.

The dome runs two state machines; the outer one is the contest

Status: resolved. Evidence: disassembly.

The battle round driver FUN_801D0748 is the inner machine and has exactly one contest-gated arm (0x801D322C, the flee path). The contest - which (course, round) is staged, whether the run continues, what a cleared leg is worth and what the run pays - is a second machine wholly in PROT 0977.

  • Contest machine: FUN_801CEA6C, re-entered after every leg, and the hub FUN_801CF870 dispatching DAT_801D1A78 through a 51-entry jump table at 0x801CE990.
  • Course / round: packed in the low byte of the mode-24 sub-id word _DAT_8007BAC0 (course = ((w-1) & 0xFF) >> 4, round = (w-1) & 0xF); a finished leg is w += 1.
  • Story flags: 0x536 / 0x537 / 0x538 pick which course opens; only the Master course's length is clamped, by 0x378 / 0x382 / 0x471.
  • Leg survived: DAT_8007BD60 & 0x80 at 0x801CEDD8, cleared by the battle's own 0x5A party-wipe scan - not an arm of FUN_801D0CD4 or FUN_801D0068. settle_contest's continuing input is therefore derived, not prompted.
  • Tally rows: three of the six are HP recovery (round*2, min(turns,8), [8,12,4,2][outcome], each × max_hp / 100) draining into the restore accumulator DAT_801D1AC8; only the (course, round) score cell reaches the coin tally.
  • A cleared course banks its whole score row: the Master reward is 13830 (the curated table's 13856 is wrong).

Port: engine-minigames::muscle_dome::DomeContest, driven by World::report_muscle_leg / World::settle_muscle_contest and the browser's muscle_contest_* bindings. Owning page: minigame-muscle-dome.md § Two state machines.

Battle arts-input UI decomposition (dome = standard battle input)

Status: resolved. Evidence: capture.

The arts command input (the FUN_801D0748 state-0x50 arm) is decomposed piece by piece from a live dome match in the static recomp (slot-5 savestate + scripted pad), read through the runtime's gpu_frame_dump GP0 ring plus a same-moment full-VRAM dump.

PieceSource
High / Left / Right / Low chipswidget-page hexagon pieces + baked label strips + diamond ends
Input bartiled maroon widget bar filling with command pennants at cost-wide pitch
AP plate (right)reads the Spirit gauge; the entry budget's only visible form is the bar
Triangle list5-row-per-page learned-arts window: system-UI interior tiles under a 0x40..0x88 gouraud; name / arrows / AP columns are the SCUS arts-name table's, drawn through orange sub-palette 15
Green Triangle circleits own 64x32 gap TIM at PROT.DAT 0x7B00

Behaviour: per-press ctx+0x6dc debit + actor+0x1df append; auto-end on exhaustion (0x50 -> 0x5a); the 0x5a -> 0x6e Begin|Reselect chain; Triangle inert at learned-art constant 0.

Port: engine-world-views::muscle_dome (selection_exhausted / reset_selection), web-minigames::muscle_web (arts_input pieces + muscle_arts_list_json). Owning page: minigame-muscle-dome.md § Arts command input.

Slot-B overlay cluster (0900..0969) per-entry identity

Status: resolved for every entry. Evidence: capture + disassembly.

The slot-B buffer (link base 0x801F69D8) timeshares the 0900..0969 blobs. Each is extracted statically at the link base and cross-checked by in-file self-pointer resolution (static_overlay::pointer_resolution, ≥70%). A shape census over the cluster (per-entry lui 0x801F/0x8020; addiu in-file resolution, FUN_80021B04 / FUN_80050ED4 spawn-call counts, damage-wrapper jal words) corroborates slot-B linkage for every entry except slot-A 0902. The CDNAME label is xxx_dat (a dev placeholder) across the cluster and identifies nothing.

EntriesIdentity
0900 / 0901the slot-B default render pair: FUN_80025BA0 loads param 5 or 6 by flag DAT_8007B6A8 (0900 field scenes, 0901 world-map scenes)
0902GAME OVER - a slot-A image (FUN_8003EBE4(7) in the mode-18 init); a slot-B reading of it is a pointer_resolution false positive
0903..0913player summon stagers, spells 0x81..=0x8B
0914..0923evolved-Seru stagers, 0x8C..0x95
0924 / 0925 / 0926rare-Seru flutes Lippian 0x96 / Spikefish 0x97, and the unused-0x98 one-sector jr ra stub
0927..0934Evil Seru Magic (Juggernaut), the Sim-Seru quartet, the Ra-Seru trio (0x99..0xA0)
0935..0966the capture-class cast-module band, extraction = 935 + sub_id - its own section
0967the battle sparring-tutorial overlay (capture-pinned, s5 needle sweep); battle-stage id 1
0968the evolved-Cort battle's stage overlay, battle-stage id 2 - details ↓
0969the STR-path table the STR-mode init pages (FUN_8003EC70(0x4A); boot.md), and Cort's form-transition module

Per-spell pins for 0903..0934: the loader-index section.

0969's battle-side load uses the same gate as 0968's. At 0x801E6D04 the battle SM reads *(u8 *)0x8007BD0C - the first formation monster id - compares it to 0xB5 and pages 0x4A (jal 0x8003ec70 at 0x801E6D14, overlay_battle_action_801e6968). The guard just above is lhu v0, 0x14C(actor) on slot 3 of the actor table 0x801C9370, taken only when that actor's HP has reached zero - so 0969 is paged when a Cort form dies. The 0xB5 is not the Lapis Wave spell id: the byte the branch reads is the formation id, and formation 0xB5 is Cort (monster-archive id 181).

0x80010390 is the slot-B overlay destination pointer

Status: resolved - a slot constant, not a lead on 0968. Evidence: disassembly.

0x80010390 is a SCUS-resident global holding the slot-B overlay load address 0x801F69D8, and 0x8001038C is its slot-A twin. It is the one literal-word hit for 0x801F69D8 outside the overlay band (SCUS_942.54 +0x390) in an address-reference sweep over all 1234 images, and it says nothing about any one slot-B entry.

  • FUN_8003EBE4 reads *(0x8001038C) at 0x8003EC24; FUN_8003EC70 reads *(0x80010390) at 0x8003ECCC. Both then run FUN_8003E8A8(param + 0x381) and FUN_8003E800 into that buffer.
  • The two loaders differ only in the residency tracker they stamp: gp+0x924 (slot A) vs gp+0x934 (slot B).
  • No instruction in SCUS_942.54 stores to either word: a sweep for lui 0x8001 paired with a memory op at +0x38C / +0x390 finds nine sites, all lw.
  • In-band hits for the base are equally uninformative: ~70 sibling images share the VA, and every jal / j / branch to it is that sibling's own code. When overlays share a load base, a reference to the base is a reference to the slot, not to a tenant.
PROT 0968 - the Cort battle stage overlay

Status: resolved. Evidence: capture (residency), disassembly (loader chain and extent).

PROT 0968 is the stage overlay of the evolved-form Cort fight: a 7-state scripted battle set-piece paged into slot B by battle-stage id 2. Only 2600 bytes of its two sectors are its own.

Loader chain

  • Stage overlays are paged by a computed parameter, so no 0x49 constant exists anywhere: sub-states 0x0E / 0x10 of the battle loader read the stage-id byte _DAT_8007B64A and call FUN_8003EC70(stage_id + 0x47) (battle.md).
  • The selector is a hardcoded override ending the formation fix-up in FUN_80055B6C, the battle scene initialiser, at 0x80055D2C:
lbu   v1, -0x42f4(v1)   ; v1 = *(u8 *)0x8007BD0C - the first formation monster id
addiu v0, zero, 0xb5
bne   v1, v0, 0x80055d48
addiu v0, zero, 2       ; delay slot
sb    v0, -0x49b6(at)   ; *(u8 *)0x8007B64A = 2 - the battle-stage id
  • Formation id 0xB5 is monster-archive id 181, Cort's evolved second form (HP 65535). The first-form fight is a separate formation headed 0xB4 (id 180, HP 50000); both carry the display name "Cort" (asset monster-archive --id 181 on PROT 867).
  • So stage id 2 → param 0x49 → extraction 968. The same byte against the same constant pages 0969 mid-battle when a Cort form's HP reaches zero.

Residency

  • cort_evolved_battle_first_menu (PCSX-Redux; scene jouine, first command-input screen, before any cast; fingerprint in scenarios.toml): loader-B tracker 0x8007BC4C = 0x49, entry 968 100% byte-resident at 0x801F69D8 over its 0xA28 extent, formation head *(u8 *)0x8007BD0C = 0xB5. Script check-0968-residency.py. It is the same observation pair that pins 0967 for the Tetsu tutorial.
  • The bracketing field states cort_evolved_approach_cutscene / cort_evolved_pre_battle show the co-resident library 0900 at 100% with tracker 0x05, so the page-in is the battle load.
  • The six cort_*_mid_cast mednafen states read formation head 0xB4 (four first-form) or 0xB5 (two evolved-form), and in every one the slot-B page is 100% byte-identical over 0x1000 bytes to that state's cast stager (0938 / 0940 / 0944 / 0961 / 0962 / 0966) with the tracker reading the stager's id; 0968's window matches at chance (10.5-12.1%). A cast stager is a full slot-B page, so the fight's first special or summon evicts the stage overlay.
  • _DAT_8007B64A reads 0x00 in all nine mid-fight / bracketing states: it is transient around the load, not a mid-battle marker.

Extent

  • Own content is file 0x00..0xA28: a 7-entry dispatch table at offset 0 (every target inside the window) and code from 0x1C. Every jal, j and LUI+ADDIU materialisation in the window resolves inside it, into SCUS_942.54, or into the co-resident slot-A battle overlay; none reaches past 0xA28. The first instruction reads the battle-context pointer _DAT_8007BD24 and writes ctx[+0x6D6] = 0x100.
  • The trailing 1496 bytes are byte-identical to PROT 0967 at the same file offsets, cut mid-string at the sector boundary - packer-buffer inheritance from the tutorial overlay. They contain FUN_801F747C, a text-box placement routine dispatching jr *(0x801F6B48 + style*4): in 0967 that address is a 10-entry jump table at file 0x170, right after 0967's 91-entry step table; in 0968 file 0x170 is live code. The window also materialises 0x801F7C80, a string that exists only in 0967, past 0968's end.
  • Structural measures taken over all 4096 bytes ("pointer-table head, 10 of 11 self-pointers, 2+8 spawn calls") mix the two modules.

What it calls

CalleeRoleCalls
FUN_80050ED4summon / effect-actor pool allocator8
FUN_80021B04actor spawn2
FUN_80024E80screen-fade spawn2
FUN_8003541Ctext actor1
FUN_8004FCC8cue / streamed-voice dispatch1
FUN_80058490MoveImage VRAM blit1
FUN_80035F04, FUN_80050E74-1 each
0x801D829Cin the co-resident slot-A battle overlay4

That is the external-call family of the tutorial overlay 0967 minus the tutorial's prompt helpers. Owning page: battle.md.

PROT 0977 / 0978 extraction + the dump re-key

Status: resolved - both entries are in the static overlay map and every overlay_0977_* / overlay_0978_* dump resolves. Evidence: disassembly.

The static map (static-overlays.toml) carries 0977 (arena_init, the Muscle Dome door/init slot-A overlay at 0x801CE818, anchor FUN_801D0F60) and 0978 (field_back_read, slot B 0x801F69D8, pinned by the SCUS FUN_80025358 state-2 call into FUN_801F6B24); asset overlay verify reproduces both fingerprints from the disc. With those images indexed, check-dump-base-integrity.py classifies all 22 dumps in the two families and none is NOT_FOUND:

DumpsVerdictBytes live in
801d050c 801d08ec 801d1288 801d1308 801d14b0, slotA_801d0f60MATCH0977 at the printed VA
other_game_801f6b24MATCH0978 at the printed VA
0977 801c085c 801c0f48 801c2748SHIFTED +0xE8180977 own code (801C085C→801CF074, 801C0F48→801CF760, 801C2748→801D0F60 - the War God Icon settlement)
0977 801c614c 801c6268 801c6804 801c6cf8SHIFTED +0xA0180979 (801C614C→801D0164, 801C6268→801D0280, 801C6804→801D081C)
0978 801c2b58 801c3004 801c39b8SHIFTED +0xD8180979 (→801D0370 / 801D081C / 801D11D0)
0978 801c5c58 801c7b40 801c82dc 801c8b04 801c8d0cSHIFTED +0x9818dance 0980 (801C5C58→801CF470 - the documented beat-clock SM)

Each delta is one wrong base seen through an over-read footprint imported at 0x801C0000:

  • 0977's footprint holds its own 0x3800 bytes, then 0978 (0x1000), then 0979. Own-content prints re-key at +0xE818 (0x801CE818 − 0x801C0000), 0979-stratum prints at 0xE818 − 0x4800 = +0xA018.
  • 0978's footprint holds 0979 from +0x1000 (+0xD818) and the dance overlay from +0x5000 (+0x9818).
  • The two-hit 801c614c signature (a duplicated 10-instruction run inside 0979) is disambiguated by the batch-constant delta: its program siblings resolve single-hit at +0xA018.
  • A "baka_fighter_0976 at +0x5710" hit is a cross-overlay duplicate of a sequence that MATCHes 0977 at its printed VA.
  • 801c2b58, 801c3004, 801c39b8, 801c614c, 801c6804 are four distinct routines of the field-battle-intro overlay 0979; two of the dumps are the same routine FUN_801D081C reached through two different wrong bases, which cross-checks the decode.
Slot-B capture-module band 0935..0966 per-entry identity

Status: resolved - the per-entry map is static spell-table data in SCUS_942.54, capture-corroborated. Evidence: disassembly.

A capture-class spell record (class byte 'c' at stats +0) pages its cast module as FUN_8003EC70(record[+1] + 0x28) (lbu a0,0x1(v0) at 0x801E44E4, jal 0x8003EC70 with addiu a0,a0,0x28 in its delay slot at 0x801E44EC, PROT 0898), and the loader resolves raw TOC index param + 0x381 (addiu a0,s1,0x381 at 0x8003ECC4) = extraction param + 0x37F, so the module is extraction entry 935 + record[+1]. Enumerating the 'c'-class records yields the complete map: the sub-id space covers 0935..=0966 exactly, with no orphan entry.

  • Parser legaia_game_tables::spell_names::capture_class_records / capture_module_prot; the disc-gated spell_names_real test asserts band coverage and every independently pinned leg (capture-pinned boss stagers 938 / 940 / 944 / 961 / 962 / 966; playtest-pinned Delilas / Xain modules 952 / 953 / 958 / 959 / 960).
  • Shape census: every band entry resolves its lui 0x801F/0x8020; addiu self-pointers in-file at the slot-B link base, spawns through FUN_80021B04 / the FUN_80050ED4 pool wrapper, and carries damage-wrapper jals where the battle-formulas wrapper census puts them.
  • 0957 = the Death Game / Thunder Storm module; its head strings Dies/Puera/Both/Damage/Recover are Death Game's roulette outcome labels, not a summon-effect descriptor or debug table.
  • 0965 = the Doomsday module. It is not a shifted sibling of 0967: the claimed shift 0x5FE8 lies wholly past 0965's real 0x2000-byte extent, and the two entries share no content.

Owning page: spell-table.md § capture-class module index.

New-game world-state seed store widths

Status: resolved - the port matches the disassembly. Evidence: disassembly.

Every (offset, width, value) entry in legaia_game_tables::new_game::new_game_seed_words matches the stores FUN_80034A6C issues, decoded from SCUS_942.54. Ghidra's DAT_ / _DAT_ naming is a symbol-size heuristic and is not width evidence (ghidra.md).

  • The routine holds the save-context base in $s0 (lui $s0, 0x8008 / addiu $s0, $s0, 0x4140 = 0x80084140, at 0x80034A74) and issues each seed write as an sb or sw at $s0 + off: six sb over 0x454..0x45B and nine sw over 0x45C..0x478 / 0x590..0x59C, all inside 0x80034A88..0x80034AE8.
  • The C's absolute globals DAT_80085958 / DAT_80085959 are sb $v0, 0x1818($s0) / sb $v0, 0x1819($s0) - the starting-item pair at INVENTORY_SC_OFFSET, SC-relative and issued after the template expander, so not part of the pre-expander set.
  • The starting-item pair is stored at 0x80034B10 / 0x80034B18, after the jal 0x800560B4 at 0x80034AFC.
  • The story-flag clear is a downward walk from $s0 + 0x1FF over sb $zero, 0x1618($v1) (0x80034B04..0x80034B28, loop on bgez), covering SC + 0x1618..0x1817 - 0x200 bytes, the port's STORY_FLAGS_LEN.
  • The disc-gated new_game_seed_disc::world_state_seed_matches_the_routines_stores re-derives the table from the instruction encodings in the user's executable and fails on a wrong offset, value or width.

Owning page: new-game-table.md.

_DAT_8007B8C2 polarity, and its writer

Status: resolved - != 0 is retail, == 0 is dev. Evidence: disassembly + capture.

The halfword at 0x8007B8C2 is the build-mode selector. main() stores 1 into it once at cold boot; the non-zero arm of every reader is the path a retail disc can service.

  • Writer: main() (FUN_80015E90) at 0x80015F08, sh v0,0x5aa(gp) with gp = 0x8007B318, storing the return of FUN_8003F084 - a two-instruction leaf (jr ra / addiu v0,zero,0x1) whose sole caller is 0x80015F00. A stubbed-out build-mode predicate; the dev build presumably returned 0 (inference).
  • Readers: every read is an lh - 43 sites in SCUS_942.54 (40 in the absolute lui 0x8008 / lh -0x473e form, three gp-relative lh v0,0x5aa(gp) at 0x80015FD4 / 0x80016038 / 0x8001631C), 57 across the dump corpus including overlays.
  • != 0 arm: resolves assets by PROT-TOC index (FUN_8003E8A8 + FUN_8003E800, or FUN_8003EB98).
  • == 0 arm: opens a path through FUN_800608F0, whose entire body is break 0x103 - a PsyQ dev-station host trap - on h:\ paths that do not exist on a retail disc. No site dissents; at 0x80016038 the bnez v0 at 0x80016040 skips the jal FUN_8003E6BC dev-path call when the flag is non-zero.
  • FUN_8003E6BC does no CDNAME name resolution. Its body is strcpy → break 0x103 → fseek / fread / fclose; the path_opener label is Ghidra's, not evidence.
  • Capture: the halfword reads 1 in 60 of 60 Mednafen save states - field, battle, world-map, stock and randomized discs.
  • Not zero-initialised: the PS-X EXE header carries b_addr = 0, b_size = 0, so the BIOS clears no BSS for this executable.

The store and three of the reads are gp-relative, so a sweep for only the absolute lui 0x8008 / -0x473e form reports zero writers and 40 readers: ghidra.md.

Key-item area consumers

Status: resolved on the narrow negative; the reader enumeration is closed. Evidence: disassembly.

The range is inventory slots >= 72 of &DAT_80085958. No consumer treats a key-item byte as an unguarded index, and no instruction on the disc names an address inside the band.

Indexing is bounded by construction. Readers use the id byte as an index into 256-entry, 12-byte-stride item tables: an lbu yields 0..255, so the maximum offset is 3060 against a 3072-byte table. The only signed lb reads (0x8004250C, 0x80042510, in FUN_800423E0) are a compaction move immediately re-stored via sb, with no index use.

The enumeration is closed by three structural facts.

  • The array sits 0xA640 above gp and ends at gp + 0xA840, so no imm(gp) instruction can address any byte of it.
  • Neither 0x80085958 nor the block base 0x80084140 occurs as a literal 32-bit word anywhere in SCUS or the 1233 PROT entries, so no pointer table offers an indirect route.
  • Every access materialises the block base inline (lui + addiu 0x4140, sll slot,1, addu, lbu|lb|sb ...,0x1818/0x1819).

Decoding for that displacement pair gives 125 sites over six images:

ImageSitesNotes
SCUS_942.545113 functions, 0x8003004C..0x800430A0
PROT 0899 (menu)5519 functions; four hide behind a lui in a branch delay slot
PROT 0897 (field)5
PROT 0898 (battle)2
PROT 0941 (Steal)7no active window; clamps its random slot against a live count (0x801F7828)
PROT 0954 (Fatal Decision)5no window, no clamp: rand & 0xFF over all 256 slots (0x801F81A4..0x801F81B0, retry cap 0x400) - the only reader that reaches the key-item band outside the menu window

Every other based overlay and PROT entry has zero. A displacement-only match gives 156 hits across 11 files, of which 31 are data-byte coincidences in five scene entries. The two absolute sites are the new-game seed's slot 0 (0x80034B10 / 0x80034B18).

Other facts

  • The & 0x3ff mask belongs to four packed-handle sites, not to readers generally, and it admits slot 1023 - 0x5FE past the array - so it is not a 256-slot bound.
  • The add / find / consume helpers bound their scans by the live window, but the id store at 0x800422BC is not bounded: when the free-slot loop at 0x80042270 finds no empty slot the index exits equal to the window limit and sb writes one slot past the scanned window. The slt guard at 0x800422C0 is downstream and gates only the quantity byte. The overflow index derives from the window-limit global, not from an item byte, so it is a bounded one-slot write - the range amplifies to game-state corruption, not a native chain step (reachability).
  • lb $reg,0x5aXX($zero) "hits" in overlay dumps are mis-decoded data tables: 117 occurrences across 74 files, and SCUS's 7 sit at 0x80010AE4..0x80010AFC as a stride-0x10 progression - a pointer table, not code.

Owning page: inventory.md.

title.pak PROT entry

Status: resolved. Evidence: capture.

There is no single title.pak bundle entry: the dev-tree title.pak content is split across two PROT entries, both confirmed by fingerprinting a title-phase RAM snapshot (the title_screen_new_game save state) - the method that identifies 0895_bat_back_dat as init.pak, with the same CDNAME-mislabel pattern.

  • Title wordmark TIM: PROT 0890 at file 0x14228 (parser legaia_ui_formats::title_pak); the big-logo RAM TIM at 0x80170DF8 fingerprint-matches it. Offsets quoted against 0888 (0x1AA28) or 0889 (0x19A28) are the same absolute bytes reached through over-read entry sizes.
  • Options / config-menu bundle: PROT 0899 (xxx_dat). Its indexed payload opens with the config-menu string pool ("Display Off / Gradual / Immediate / Field HP Display / Encounters / Vibration / Dual Shock / Voices / Battle Camera / Monaural / Stereo ...") followed by the small config TIMs (the four RAM TIMs at 0x8010FEF0..0x80110130, CLUTs byte-matched at 0899 offsets 0x169DC / 0x1F91C+).
  • Title-overlay code: the same entry - the title tick FUN_801DD35C is PROT 0899 file +0xEB44 (below).
Title screen mode-table PROT

Status: resolved - no row is named for it, but it is a mode: the CARD pair, 22/23. Evidence: disassembly + disc bytes.

The title screen runs under the CARD mode pair. The mode table's 28 x 24-byte records at 0x8007078C carry fourteen even/odd name pairs - CONFIG / MAIN / MONSTER / TMD / EFECT / TEST / MAPDSIP / MAP / READ / GAME OVER / BATTLE / CARD / OTHER / STR - and none says "title".

Chain from main() to the title tick

StepSiteEffect
SeedFUN_8001D424 at 0x8001D5B8_DAT_8007B83C = 0x10 (mode 16 READ)
Pre-loop overlay load0x8001612C jal 0x8003ebe4, a0 = 0extraction 0895, the boot init.pak
Mode 16 init FUN_8002612Ccalls 0x801CE9C0 (0895 +0x1A8)publisher-logo pass; sets mode 17 at 0x801CEC94
08950x801CF4D4writes mode 22
Mode 22 init FUN_8002574C0x800258B4, a0 = 4loads PROT 0899, spawns descriptor 0x800706D4, sets mode 23
Descriptor handler 0x801E36A0 (0899 +0x14E88)jal 0x801dd35c at 0899 +0x14E94calls the title tick every frame

Loader param 0 is therefore producible - by main() - so 0895 is statically reachable; 0896 is not (boot.md).

Ownership of FUN_801DD35C

  • Its 48-byte prologue occurs exactly once in all of PROT.DAT, at extraction 0899 file +0xEB44 (0x801CE818 + 0xEB44 reproduces the VA); it is absent from SCUS_942.54.
  • Its own master-mode stores: 0x801DDCF0 (0x1A, attract → STR) and 0x801DFC00 (2, NEW GAME → field).
  • overlay_801dd35c.txt is a different routine: the 436-byte FUN_801DD310 from PROT 0897, a VA alias.
  • The engine carries two ports of the routine (menu.rs, title_overlay.rs): vm-inventory.md.
XP-table source + reader

Status: resolved + ported. Evidence: capture.

The retail XP curve is the static-SCUS per-level delta table DAT_80076AF4 (u16), and the delta is the closed form delta(n) = ⌊n²/4⌋ + 1.

  • Reader: the level-up applier FUN_801E9504 (overlay-resident, called from the reward resolver FUN_8004E568 at 0x8004F34C). The running sum to the current level is scaled (sum × 9999999) / 0x140FE for level < 0x11 (else sum × 0x79) and compared ≤ record cumulative XP in a multi-level do...while loop. Checked against the listing: the level test is sltiu 0x11 at 0x801E95D0, the x 9999999 shift chain and the 0xCC2ABE13 reciprocal (>> 48, a divide by 0x140FE) run 0x801E95D8..0x801E9614, and the x 0x79 arm is 0x801E9618..0x801E9624.
  • Not 0x8007123C / 0x80070A3C: those are an off-by-0x800 file/virtual confusion and a sin-LUT slice. A New Game Status capture shows "Next Level 121", the real L2 threshold, not 50.
  • Port: legaia_save::RETAIL_XP_CUMULATIVE / retail_xp_table() ship the derived base curve (121, 365, 730, ..., 9_646_483); the boot-time disc parse (legaia_game_tables::level_up_tables::xp_thresholds_from_scus → BootSession) cross-validates it byte-identically.
  • Capture: library-wide record sampling (+0x0 XP / +0x4 next threshold / +0x130 level at 0x80084708 + slot×0x414) matches through L37 including the Noa / Gala ± corrections (New Game 121 / 102 / 140; L99 carries 0). The Status menu (FUN_801D33D8) draws +0x0 / +0x4 verbatim.

Owning page: level-up.md.

Overlay identity from the disc (static extraction)

Status: resolved (pipeline legaia_overlay_images::static_overlay + asset overlay ...). Evidence: capture.

PSX overlays are clean copies of a fixed-VA-linked blob (FlushCache + jump, no per-load relocation), so each runtime overlay is extracted statically from its PROT.DAT entry and disassembled at its load base, with identity taken from the source entry rather than a guessed label. That resolves VA aliasing (0x801DD864 = battle-action in one overlay, muscle-dome in another).

  • Proof case: the battle overlay (PROT 0898 @ 0x801CE818) is byte-identical to its resident RAM image over the full .text + .rodata (0x28800 of 0x29800 bytes; only the trailing .bss diverges).
  • Base recovery: from the overlay's own internal jal call graph (static_overlay::recover_base). For a call graph too sparse, the cross-check is a documented function landing on a prologue (anchor_va, slot A) or the fraction of internal absolute self-pointers resolving in-file (static_overlay::pointer_resolution, slot B).
  • Committed map (crates/asset/data/static-overlays.toml): the slot-A scene family (field / battle / menu, the cutscene/STR overlay 0970, the minigame overlays 0972 / 0975 / 0976 / 0980) and the slot-B entries (render 0900, the summon stagers including 0903 Gimard, 0905 spell 0x83, 0907 Nighto "Hell's Music", 0924, 0927; GAME OVER 0902 is slot A; 0957, labelled summon_effect_table, is the Death Game cast module).
  • Reconnaissance: asset overlay scan (range sweep: base + leading dev string) and asset overlay find-sig (locate a function-head signature → infer the host overlay).
  • It complements the dynamic captures: runtime values still need live probes.

Owning page: static-overlay-pipeline.md.

PROT 0896 (bat_back_dat) identity

Status: resolved. Evidence: capture (identity and non-residency), disassembly (link base).

The unique ~0x9000-byte head is the vestigial Japanese-build field-menu / config / status overlay - the debug-string sibling of the English retail menu overlay PROT 0899. It links at 0x801D4DF0 against an executable this disc does not carry, and the USA build never loads it.

Identity (decode of the head off extraction entry 896, located by its "FWIN ERR" bytes)

  • A Shift-JIS label pool: config toggles, the Item / Summon / Equip / Status / Config / Save top menu, the ATK / UDF / LDF / SPD / INT / AGL + EXP status labels.
  • The "FWIN ERR %d" window-manager debug printf at file 0x3D4 (FWIN = Field WINdow); no fwin / bat_back reference exists in SCUS_942.54.
  • Real MIPS (~54 prologues): a status / name-draw routine indexing the 0x414-byte character records, and head function-pointer tables holding ~61 addresses across 0x801D81C0..0x801DC700 (the window / screen renderers) - the VA family of 0899's 0x801D33D8 status renderer and 0x801DC6B4 save SM. Also a large byte-map-like data block (rows of gradually shifting byte values).
  • 0899 carries the English versions of the identical label set and zero FWIN.
  • From file +0x9000 an over-read footprint carries the field overlay's bytes; they are not 0896's.

Link base 0x801D4DF0

  • Call-graph recovery over the 0x9000-byte entry: ten corroborating targets of eleven distinct internal jal targets, ten of them addiu sp, sp, -X prologues.
  • All 218 internal j instructions land inside the file there; none does at 0x801C5818 or 0x801C0000.
  • Three runs of consecutive in-image VA words resolve every word, one holding the base itself.
  • It reconciles the corpus's two phantom import programs: the function printed at 0x801C6534 and at 0x801C0D1C is one function at file +0xD1C.
  • 0x801C5818 (60 jal votes) is an over-read artifact: the field overlay's self-consistent code at 0x801CE818 fixes a whole-footprint recovery to 0x801CE818 − 0x9000 by construction. A lui-pair resolution ratio is one-sided and is not a base test (falsified).

Never loaded by the USA build

  • Foreign link. Of the image's 322 calls into the SCUS address range, zero land on a function entry of this disc's SCUS_942.54 (the same test scores 0897 at 1203 of 1307 and 0899 at 793 of 884), and no constant shift of the executable within +-0x20000 brings more than 7 of its 42 distinct SCUS targets onto an entry.
  • No loader reaches it. A full-image scan of SCUS_942.54 for jal FUN_8003EBE4 / FUN_8003EC70 with the a0 setup decoded finds 16 sites; every constant param maps to extraction 897..902, 969..981, or the spell- / stage-driven bands (id - 0x79 summon stagers, +0x28 special-attack, +0x47 battle stage). Extraction 0896 needs param == 1, which no site produces (the three computed-param sites have +0x74 / +0x47 / 5-or-6 bases). The raw indices 0x381 / 0x382 appear as immediates only in the two loaders' own param + 0x381 adds, so there is no direct FUN_8003E8A8 / file-open path either. The +0x47 site reaches only 967 / 968 (battle.md § Stage-overlay dispatch).
  • Not resident. A distinctive-signature scan across 140 catalogued RAM states (37 PCSX .sstate + 98 gzipped mednafen states, all phases) finds 0896 in none; the positive control, English "Battle Voices" (live 0899 config), is resident in 10 menu-phase states. The scenarios.toml save_select_idle note "overlay 0896 paged in" uses the extraction-index name for what is the English 0899.
  • Not the mode-24 OTHER overlay. A live capture of the Baka Fighter entry (autorun_minigame_overlay_capture.lua, triggered on the 0x8007B83C = 0x18 write; sub-id 0x8007BA34 = 4, confirming the 0x3E operand−100 model) dumps the overlay window at +0 / +10 / +30 vsyncs, spanning the SCUS-resident OTHER INIT handler's completion (its "other init end" print) and the minigame overlay streaming into slot A. 0896's bytes appear at no offset in any dump, nowhere in main RAM in the pre-transition save, and in none of 45+ parked library states.

The CDNAME label bat_back_dat is not corroborated: no captured battle state holds the data, and under the raw-TOC index shift the #define covering 0896's extraction slot may belong to a neighbour. With the base committed, the image's code region is dumped and accounted - a base buys coverage, not a caller (byte-accounting.md). Owning page: static-overlay-pipeline.md.

SCUS recomp gap - render/GTE + boot/init clusters

Status: resolved, the general-game band included. Evidence: disassembly.

The static recompilation's function inventory lists SCUS entries with no dump, doc or port tag, clustered by VA band. Every member is attributed, mostly as block splits and +4 entry skews of documented functions; disc-coverage.py puts every SCUS_942.54 code byte inside a dumped body.

"COP2 render gap" band (0x43000..0x47000) - not render code

Inventory entryIs
0x800430D4..0x80043134interior of FUN_800430AC (party-wide accessory unequip-by-id)
0x80043238..0x8004325Cinterior of FUN_800431FC (knows-spell)
0x80043290 / 0x800432A8interior of FUN_80043264 (accessory-equipped)
0x80043580 / 0x8004361Cinterior blocks of the cluster-A renderer FUN_80043390 (far-colour / ZSF setup + its custom-convention epilogue)
0x80046498FUN_80046494 + 4, the locomotion collision resolver (not a render→overlay draw seam)
0x8004697CFUN_80046978 + 4, palette fade

The 14 gte_execute entries are statically linked libgte per-op wrappers (MulMatrix0, Square12/0, AverageZ3/4, OuterProduct12/0, DCPL / DPCT / INTPL, the RotTransPers3-shaped RTPT projector) with zero static callers and zero runtime hot-profile hits: link residue, since the render paths issue COP2 inline. All ignore-listed; table in functions.md § libgte primitives.

Boot/init cluster - aliases of documented functions

Inventory entryIs
0x80016448FUN_80016444
0x80016B74FUN_80016B6C
0x800173C0FUN_800173BC (dev profiler HUD, ignored)
0x80016998interior of FUN_8001698C
0x80017914FUN_80017910
0x80017A04 familyinterior of FUN_800179C0
0x8001A078interior of the dev printf FUN_8001A068
0x8001A814interior of FUN_8001A78C (RGB→HSV)
0x8001AA14..0x8001AA60the six hue-sextant jump-table arms inside FUN_8001A8DC (HSV→RGB)
0x80019BC0..0x80019D48interior of the atan2 bearing resolver FUN_80019B28
0x8005B2A4 / 0x8005B340interior of PushMatrix 0x8005B268 / PopMatrix 0x8005B308

New identifications

  • FUN_80015E90 = main() (boot.md § The main loop).
  • The dev draw cluster: FUN_8001CE34 (3-D line), FUN_8001CAD8 (wireframe box - the sole source of 8001CE34's in-degree of 12, so it is not a heavily used boot utility), FUN_8001CCFC (2-D line), FUN_8001C7A0 (4x8 digit printer).
  • FUN_800430AC, whose Ghidra auto-analysis body is degenerate until force-created.
  • FUN_8004CE2C, the largest SCUS function in the inventory: the per-frame battle actor maintenance pass (battle.md § Per-frame actor maintenance), not a mode dispatcher.

General-game band

  • 0x8002A9F8 is the branch target past the header test of FUN_8002A9CC, the "ME" channel-delta codec: beq v0,v1,0x8002a9f8 at 0x8002A9E8 takes it when (b0 & 0xC0) == 0x40, and the fall-through is the jr ra; clear v0 reject. A block split, hence no static caller (functions.md § battle).
  • 0x80025DA4 is FUN_80025DA0 + 4, the Mode 12 MAPDSIP INIT handler, split after its leading lw v0,0x798(gp) (boot.md).
  • 0x8004DC68 is the near-camera ghost pass (see the Battle area); 0x80036D80 is the ambient ramp pool's own actor template (see the Animation area).
  • 0x80056208 is not a libgpu-band bridge: it is a battle side-band tick (three submodes off DAT_8007B64A) at a PsyQ-adjacent address, ported in engine-render.
  • 0x8002149C and 0x80059E10 both carry full disassembly.
  • The PsyQ sound-driver cluster is tracked under Audio.
Full-window item-add OOB reachability

Status: resolved - the write primitive is real; normal play cannot reach it. Evidence: disassembly (full window) + inference (the half-window sub-case).

FUN_800421D4 can store an item id one slot past the active inventory window, but the retail add call sites cannot drive it there in normal play.

The write

  • The id store sb t0,0x1818(a0) at 0x800422BC is unconditional and precedes the slt / beq guard (0x800422C8 / 0x800422CC), which gates only the count store at 0x80042300.
  • When the free-slot scan (0x80042254..0x8004229C) exhausts the window it leaves the index == end, so the id lands at base + end*2: 0x80085A58 for end = 128, 0x80085B58 for end = 256.
  • The window is installed only by FUN_8004313C, as [0,256), [0,128) or [128,256) - never a 72-slot span.
  • No add caller pre-checks room; each loads an item id and jals the helper directly (shop buy-confirm 0x801C38A4 loading a0 = rec+8; battle loot 0x8004F380 / 0x8004F608; the menu / save / fishing / world-map / minigame / equip-refund helpers). The helper's scan is the only backstop.

Reachability

WindowInstalled whenVerdict
[0,256)any party of >= 2 (live-verified at 3 members)unreachable: the merge pass keys on the id byte (andi a3,t0,0xff @ 0x800421F4), so each non-zero id occupies at most one slot and 0 is the empty sentinel; at most 255 distinct ids fill 256 slots, a hole always remains
[0,128) / [128,256)a single playable member with story flag 20 clearnot capacity-bounded: the static item-name table 0x80074368 carries 250 non-empty names over 256 ids (blank: 0x00, 0x12, 0x1A, 0x52, 0xB9, 0xFD), so 128 distinct live ids is arithmetically possible. The bound is how much of that population a lone character can obtain - a progress bound, unmeasured (the inference)
  • Cast module PROT 0954 (Fatal Decision) reads the whole array with no window and no live-count clamp (rand & 0xFF over 256 slots).
  • A non-add path (debug menu, cheat engine, a crafted save seeding duplicate live ids) can still force the exit with an attacker-influenced byte; that is outside normal play.

Port: legaia_save::retail_inventory (ItemWindow::oob_reachability, MAX_DISTINCT_ITEM_IDS, OobReachability). Provenance: ghidra/scripts/funcs/{800421d4,8004313c,8004e568,8003ce64}.txt, overlay_0971_801c36b0.txt. Owning page: inventory.md.

Phantom-VA sweep of the PROT 0897 imports

Status: resolved - the three residues the delta arithmetic leaves open are byte-decided. Evidence: disassembly.

The two measured deltas (0xE818 base error, 0x25000 over-read) re-key most 0897-import prints. The rest are decided by a word-level comparison (resolve-phantom-va.py): each dump is compared against every candidate (image, base) reading at the printed VA, with Ghidra's data-as-instruction renderings (nop, <load> rt,imm(zero)) re-encoded into exact 32-bit words so data regions decide at full strength. Every verdict is word- or token-exact against the corrected-extent images, and each rival reading is excluded by the same comparison.

  • Boundary band 0x801E4000..0x801E6000: every dump resolves to exactly one reading, and the strata switch exactly at 0x801E5000. The two open addresses are 0897 own-content data - pointer tables at true VAs 0x801F3308 / 0x801F3450 (14/14 and 13/13 words; the rival 0898 reading scores 0). 0x801E5134 is printed by two programs with two owners: one print correct, one a phantom of 0898 0x801CE94C.
  • 0x8020D05C: 0898 rodata at true VA 0x801F6874, a (pointer, count) table into 0898's 0x801CF9xx band. Its words include values with no R3000 decoding, matching the dump's zero-instruction halt_baddata; every rival reading maps the VA to code that would decode. Not a function under any reading.
  • PROT 0896: the overlay_0896_* prefix covers two imports of the over-read footprint - untagged at 0x801C0000 (three strata: own content < 0x9000; field + 0x5818; battle - 0x1F7E8) and tagged base=0x801C5818 (prints are 0896's own bytes at printed - 0x801C5818). Every addressed dump resolves under exactly one program, and the header-tag partition agrees with the byte partition dump for dump. The same function printed by both programs pins the pair: file +0x5C90 at 0x801C5C90 / 0x801CB4A8, file +0xD1C at 0x801C0D1C / 0x801C6534.
  • 0x801FD4C0: its dump starts at printed 0x801FD150 and is the battle image's FUN_801E6968; the printed VA is that body's interior at 0898 VA 0x801E6CD8, not the field image's FUN_801E6B34.

Owning page: overlay-va-aliases.md § the byte-level sweep.

The publisher-logo quads

Status: resolved - FUN_801CE9C0 uploads and does not draw. Evidence: disassembly; the descriptor table reproduces from the extracted image.

FUN_801CE9C0 (PROT 0895 +0x1A8) uploads the four init.pak TIMs through FUN_800198E0 after writing their CLUT and pixel VRAM rects, selects the 640x480 display env (FUN_8001DAF8(0x400)) and spawns the two boot actors. It contains no primitive emit: its jal set holds the four uploads (0x801CEA68, 0x801CEA9C, 0x801CEC40, 0x801CEC54), the display-env call (0x801CEB18) and the two spawns (0x801CEB60, 0x801CEB70), and no call to the OT linker FUN_8003D2C4.

  • Quads: a six-record, 20-byte sprite-descriptor table at 0x801F369C (file +0x24E84, immediately after the fourth TIM): [u32 scale][u16 tpage][u16 clut][u8 u,v,w,h][rgb top][stp][rgb bottom][tpage adder].
  • Emit: opaque POLY_GT4s from FUN_801CFBB8, sequenced by FUN_801CEFD4 (13-arm jump table at 0x801CE8E8).
  • Binding: each record's tpage / clut matches a rect the uploader wrote - 0x9A/0x7ED4, 0x9C/0x7F54, 0x0A/0x7F14, 0x0B/0x7E80.
  • Order: SCEA, Contrail, PROKION.
  • Fade: the PSX texture blend texel * colour / 128 over a vertex colour scaled by a 0..0x80 level, not alpha.

Owning page: boot.md.

The title menu's law

Status: resolved + ported on both hosts. Evidence: disassembly.

FUN_801DD35C sub-mode 0x10 (0x801DDB74..0x801DDCF4) is the two-row NEW GAME / CONTINUE menu.

  • Rows: two (andi 0x1 at 0x801DDC00). Down 0x4000 +1 / Up 0x1000 -1, cue 0x21.
  • Confirm: mask 0x844, cue 0x20. Row 0 -> mode 0x16; row 1 -> mode 0x18, stashing state[+0x200] = 1.
  • Attract countdown: 0x5DC, re-armed whenever the held word _DAT_8007B850 is non-zero and decremented by scratchpad 0x1F800393. The input block is skipped while it reads below 0x11; underflow writes _DAT_8007BA78 = 0 and mode 0x1A.
  • Entry: a cold boot reaches 0x10 through 0x11 (above).

Port: legaia_engine_title::title_overlay's executable half, driven by engine-title::title on both hosts.

FUN_801E5A08, the per-slot equip applier

Status: resolved - one routine, unreferenced. Evidence: disassembly + bytes.

"FUN_801D71F0" and its "shared armament placer 0x801E5AE8" are one 324-byte routine, FUN_801E5A08, and nothing on the disc calls it.

  • ghidra/scripts/funcs/overlay_0897_801d71f0.txt (and the _801d7210 sibling) is mis-based by 0xE818: the bytes are PROT 0897 file +0x171F0, true VA 0x801E5A08.
  • Its four class arms end on j 0x801E5AE8 / j 0x801E5AEC - intra-function jumps whose targets print correctly while the body prints low. 0x801E5AE8 is the routine's own inline placer at +0xE0.
  • Class -> equipment byte: 0 -> 0, 1 -> 1, 2 -> the weapon index (_DAT_8007B42C, 2/3/2), 3 -> 4 (an addiu v1,zero,4 in the delay slot at 0x801E5AB0). The class is bits 0x60 of equipment-table byte +7 (0x801E5A80..0x801E5A8C); a slot argument of 4 or more bypasses the class and stores at slot + 1 (0x801E5A4C..0x801E5A50, 0x801E5AE4).
  • No jal, data word or addiu materialisation of 0x801E5A08 exists in any image. The live equip confirm is FUN_801D9C14's candidate arm.

Port: legaia_engine_dev_menu::dev_equip_commit::commit_equip.

The dead Return view mode

Status: resolved - dead; the grid has 15 cells. Evidence: disassembly + bytes.

FUN_801E3F74's mode 4 ("Return", 0x801CF384) is real and unreachable.

  • The selector returns 4 only for cell 0xF (li v0,0xf / bne at 0x801E3F74..0x801E3F84).
  • Its only caller forms the cell as col + row*5 (0x801E06D0..0x801E06E0).
  • The shared stepper wraps rather than clamps, and the range is the same: col < 0 becomes 4 and col >= 5 becomes 0 (0x801E0170..0x801E019C), row < 0 becomes 2 and row >= 3 becomes 0 (0x801E01A4..0x801E01CC). The cell tops out at 14.
  • The linear seed _DAT_8007B7CC has three references on the disc, all in PROT 0899; its single writer (0x801DED2C) stores the same col + row*5.

Source: docs/reference/re-settled-threads/title-boot-overlays.md

Rendering / camera

# Settled threads: Rendering / camera

ThreadAnswerGrade
What left V-Windhash's arm-3 trail brighter than retail's?the trail was seated one tick into its pass, not on the pass's drained focus
Why

Arm 3 drains the word by the whole pass and seats the blade on the shot's focus (0x801F70FC..0x801F7398); the port seated it from the first of the pass's one-vsync ticks. A LEGAIA_RAM_AT table of arm 3 (--clut 0x770e) holds a narrow V whose sub-triangle corners the engine's pairs reach only seated from the pass's last tick; the older pairs had stood past the eye. Arm 3 .730 / .704 / .752 to .901 / .862 / .926 (cast module).

disassembly + capture
How does retail draw Kemaro's arm-17 rings 0x8000 out?in the far bucket, under the band wash
Why

On the shot's trio every corner saturates SZ; AVSZ4 links the 32 GT4s at ot + 0x3FFC, after the wash's G4s were linked there, so the wash covers them, and the table rasterised with or without them is one picture. The streak is the arm-9 spear arms 13..17 walk by handle. Arm 0x12 .825 to .982 (renderer-port).

capture + disassembly
Which frames of Ozma's closing arms lose the creature?none: the closing arms framed off retail's focus
Why

A retail table of arm 23 (ot-raster.py) links the ray ribbons' additive GT4s (CLUT 0x7F84) mostly behind the creature, each at its corners' mean, as the port's battle link does. What held arms 0x16..0x19 low was arm 0x15's exit cut, which aims at its spray's third rand() point (cast module): retail's focus x 90, the port's 0. Aimed there, seated arm 23 reads .928 for .556.

disassembly + capture
Why does Aluru's head tip show through arm 11's ray burst?the rays' rand() deal, not draw order
Why

A LEGAIA_RAM_AT recording of Vahn's Aluru (0x8E from party_basic_attack_vs_gobu_gobu) rasterised with ot-raster.py links the head tip's two GT3s (CLUT 0x7983) at bucket 89 and the additive rays crossing it (0x801F8CE4, CLUT 0x7F84) at 77..84, so they paint over it, as the port's mean-depth draw does. Each ray's spin is a rand() pick, and the engine's own stream dealt none across the tip. Seated on retail's words (--rand-track) the engine's rays cover the tip frame for frame and arm 11 reads .911 for .541, the film .942 for .853. Falsified reading: re-do-not-re-walk.

capture
Why does the engine's camera.offset_ease stand apart from retail's _DAT_8007BCAC after a door hop?a timed 4C 49 is a ramp
Why

concnow's door-hop record runs 2E 18 2E 19 4C 49 0A 00 F0 00. A non-zero ticks hands each store to the ramp pool (jal 0x8003C5F0 at 0x801E1508 / 0x801E205C); the port posted the endpoint at once, where the walk film climbs about a unit a vsync (menuctrl). Now ramped (CameraRig::offset_ramps). The endpoint is 10 - player[+0x16] at the op: 266 in retail, 234 in the engine, which reaches the op on another phase of the floor bob.

disassembly + capture
Which pose does kor_field_route_walk's frame stand on after the walk rewind?the RAM's pose of the pass two back
Why

A film of the deterministic route walk that made the state (korb2_field_card_boot, autorun_walk_route.lua, the same RAM on the same vsync) seats the image child on the pass two back - player (1680, -32, 3730), yaw 1770, eye (0, 611, 4768) - and the frame reads 1.000. The rewind composes the older target on the composer's floor (.913 to .923); the rest is a turn on the RAM's pass, and no copy of the older step exists anywhere in main RAM, so a seat from the state alone cannot reach it. retail-compare.

capture
What frames Koru's Done tail on its first pass (koru_pre_*)?the held pass of case 7, pitch included
Why

A retail film of Gala's Lightning Storm into the Done arm (probe pre-trace camera columns from koru_strip_forced) reads the displayed pass on case 7's table: pitch 4058, yaw 3598, TR z 5995, the exit pass 0x50 stepping it on to the Done arm's origin. Case 7 aims the pitch at 0 (sh zero,0x10(sp) at 0x801D58B4), so the walk back takes the pitch with the rest; holding it at the Done arm's -23 left the far crystals and mist band a few rows high, the zoom-like part. .906 to .976 (retail-compare).

capture + disassembly
What draws Koru's ice shards on the koru_pre_* frames?the stage's own crystals under the held pitch
Why

No source record: the shards stood high because the displayed pitch was the Done arm's held -23 where the frame's was -38; on the walked pitch they stand on retail's (the row above).

capture
What pose does a monster cast's frame display under a one-frame shot it holds on the second 0x2B pass?the 0x2A terminator's pose on the case-6 walk; the frames measured the instrument, not the game
Why

The pass count is 0x28's 0x14 timer (0x801E43C4) drained by each pass's step, each armed pass moves ceil(gap / 0xC) * step, and two recordings land on every component. The engine's live walk reads those rows exactly. Standing a still on that pose lifts battle_stone_zenoir_v2_fire_claw_mid_cast and the Viguro casts from about .65 to .98 and up. The earlier actor, its style and a non-turning caster's yaw are fits to each frame (the walk).

capture + disassembly
Why did the full-party Caruban Fire Breath stills miss the camera?the replay seated the breath's dearer step on the pass that had not paid for it
Why

The frame driver waits a frame out on the step that pass ran at and hands the step its cost raised to the next pass (0x80017164, 0x800171C0, 0x800171D8), so the first 0x2B pass, which draws the breath, runs at step 2 and the passes after it at 3: accumulator 64 over 16, 24, 24. Seated from the first pass the replay reached 72; held a pass, the stills read camera 1.000 and image .929 / .938 from .994 and .654 / .687. A harness seat on retail's measured step (retail-compare).

disassembly + capture
Why does Big Wave's additive sheet draw so few packets?a raised +0x42 sends the record down FUN_8002735C, whose winding test has no double-sided mask; the floor row clips what stays
Why

parts.csv holds +0x42 = 1 on the sheet and its sibling alone, and primcall.csv shows neither entering FUN_80043390. The arm's NCLIP keeps the band whose v0 and v2 coincide. See the raised part.

disassembly + capture
Why do Ozma's fans read white in retail and dark in a frame built from its packets?an arm that arms the wash replaces the frame's clear; the packets build over earlier frames
Why

A retail ordering table of arm 16 rasterised on black matches the cleared engine frame. The far bucket holds an opaque black rect except on a frame with the armed wash (FUN_8004695C / FUN_80046978), so the displayed frame is those packets over the frames before it, drained by the wash; arm 25's table has the rect back. Page: cast-module.md, renderer-port.md.

capture + disassembly
Why did battle_dohaty_nova_v2_scythe_wind_mid_cast read .860 with the camera on retail's?the displayed pose was walked back on a table built once; arm 4 builds its shot every pass
Why

Not the tornado ring. Scythe Wind's arms 3 to 5 call FUN_801D829C ahead of their hold (0x801F7CD4, 0x801F7E08, 0x801F80B0), so the RAM's table is the RAM pass's and each pass before stood the re-arm's wider share back. Two step-4 passes back that puts TR z at 2840, where the once-built walk said 2860; a scan along the table reads best there. The image child now walks such an arm on the re-arm's share (image .860 to .982) (retail-compare, cast-module).

disassembly + capture
Why does Scythe Wind's camera stand frozen through the walk?the approach leg made no case-6 call for it
Why

Arms 0 and 1 of the walking bodies call FUN_801D5854(caster, 6) per pass (0x801F79B8 / 0x801F7A5C in PROT 0951 0x5B), and arm 0 stores yaw counter 0x200. The port's approach leg held the module and made that call for 0962 alone. It now covers every walking body.

capture + disassembly
Where does Koru's 0962 charge shot set out from?the half-turned walk-in framing, after a walk a capture replay skips
Why

Arms 0 and 1 of the 0xA2..0xA4 bodies frame Koru through case 6 each pass of the walk (jal 0x801D5854 at 0x801F7C70 / 0x801F7CEC) on the style 0x70 pins to 1. A retail film from koru_pre_thunder_needle walks Koru from z = 600 to 168 over 36 vsyncs while the yaw swings onto 2168; arm 2's shot leaves from there, and the engine walks it the same way. A 0x70 capture stands Koru already in reach, so its replay aligns the shot instead (retail-compare.md).

capture + disassembly
What rewrites the prologue's CLUT rows to gold?four mode-3 CLUT-cell cyclers in the prologue effect scripts
Why

Records 10..13 of the effect-script bundles of opdeene / opstati / opurud (extraction 0748 / 0753 / 0763, no other scene): op 0x2B HSV adds (0, -0x400, 0) on two, (0x38, 0x80, 0) on two, each op 0x2C-capturing (0, 473, 320, 2) or (0, 476, 320, 34); FUN_80019D50 re-uploads every frame. In s1_newgame_field the greyers' buffers hold the disc CLUT, the tinters' the grey strip, and VRAM holds (L, (L*247) >> 8, L >> 1) for all 32 levels (L - 1 misses 29..=31). Port law and rect gate fixed (cutscene.md).

capture + disc data
Are camera-relative move-VM parts drawn where retail draws them?yes, to the pixel, on both arms
Why

In every library state a flagged part's +0x2C is the full camera applied to +0x14 on the skip arm and S_b times it under 0x400, as FUN_8001CF50 reads. Posed from the state, the hosts' kernel lands each part on that +0x2C, and puts the battle_melee_hit_spark billboards and opdeene's locked quads on retail's packets within 1.5 px, at the six-fold size; flag-free and base-scaled compositions miss (renderer.md).

capture
How far is the hosts' f32 camera rotation from retail's q3.12?sub-pixel almost everywhere
Why

Matrix elements differ by at most 2.44/4096. Over 392000 samples spread through the view volumes of 98 states, 0.77% move half a pixel or more; the median per-state worst case is 0.18 px, the worst 2.98 px (ending_vignette_fullscreen). The camera build itself is FUN_800172C0 -> FUN_80026988, ported retail-exact (renderer.md).

capture
What does FUN_80028158 build?a Legaia TMD object of GT4 packets
Why

count = packed & 0xFF, phase = packed >> 8; the plane is mode & 3, the shape (mode >> 3) & 0xF through the jump table at 0x80010BC0 (0/4/5/6/7 -> 0x80028284, 1/3 -> 0x80028310, 2 -> 0x8002833C), and mode >> 8 the texture mode (shape 0 only; modes 1-4 subdivide each column four ways). Packets carry flags 0x26, ilen 9, mode 0x3C, closed by twenty zero words; only shapes 4 and 5 call rand. The port (engine-effects::effect_default_arm) reproduces 64 of 73 captured ground-shadow blocks whole and the other 9 up to a later overwrite. effect-vm.md.

disassembly + capture
Why does map01's kind-4 puff column hold seven nodes in two groups?one spawner, two children, one drifting
Why

One spawner re-seats itself at (9152, -320 + rand % 160, 10432) while flag 0x2FA is clear; one child sets a Z velocity of -4 << 3, which only FUN_80021DF4's motion block (0x800228A0..0x80022B90) applies. The port's ambient tick runs it (engine-effects::part_motion) and holds six to eight live nodes against retail's seven.

disassembly + capture
Which Muscle Dome hub packets blend?only the row-502 palettes 0/2/6/8, additively
Why

The hub marks variant packets semi-transparent (variant 1 B + F, variant 2 B - F), but the GPU blends only STP texels. Extraction 1220's palettes are each all-STP or STP-free: row 502's sub-palettes 0/2/6/8 carry STP, the rest and all of row 503 do not. So every variant-2 packet draws opaque and no hub packet subtracts a texture; the backdrop shade FUN_801D1610 is an untextured POLY_G4 and does subtract. Pin hub_palette_stp_real (ringside-still.md). Settled for the shade: code 0x3A at 0x801D1624, tag 0x800 at 0x801D164C, draw-mode page 0x46 (ABR 2) at 0x801D16CC.

disassembly
In what units are a field attached light's extents?view space, six times world scale
Why

FUN_800195A8 adds them after FUN_8003D344 has transformed the parent through the view matrix carrying _DAT_8007BF10 = 24576 * I, so the rim radius is H * ext / vz. The retail dolk state holds extents 0x1000 and puts the rim at 201 px round (153, 93) with H = 768, vz = 15635 (script-vm.md). Settled: jal at 0x800195C8, extents added to the transformed point at 0x800195DC..E8.

capture + disassembly
Why did vell's fog not show on the native host?the effect-texture pool has to sit under the scene VRAM
Why

The fog page 0x27 selects VRAM (448, 0) (page-y bit clear), cells v 0x40..0x6F (UV rows 1 / 0 of 0x8007322C, picked at 0x8003F758 / 0x8003F7C0) of the PROT 0874 section-2 effect pool with CLUT row 473. Retail keeps that pool resident under every field scene - the cells are byte-identical in nine PCSX-Redux states from retona to vell - and loads it before the scene, so the scene wins where they overlap. A host VRAM without the pool reads every fog texel as zero and discards it; both hosts underlay the pool (field-ambient-fx.md).

capture + disassembly
What sets the field-fog cap?MAN header bits 0 and 2
Why

FUN_8003AEB0 stores MAN[1] & 1 into _DAT_8007B6A8 (0x8003AF54) and at 0x8003B6BC..0x8003B6E8 writes 0x48 into _DAT_8007BCB0 when MAN[1] & 4 or that byte is set, 0x18 otherwise. Four of the 101 scene MANs raise it: map01 / map02 / map03 (bit 0) and opurud (bit 2).

disassembly
Why did the port's vell fog run denser than retail's?a raw LCG state where retail calls BIOS rand
Why

FUN_80056798 is the BIOS A(2Fh) thunk and returns (seed >> 16) & 0x7FFF; the spawner tests low bits of it. A raw 32-bit state's low nibble cycles with period 16, so the burst gate passed on every draw of one frame in sixteen. Shaped (bios_rand_shape), the port's pool reads 19 to 35, mean 26.4, against a retail poll of 21 to 38, mean 25.9. The stale live count the spawner compares is modelled as well. Settled: thunk 0x80056798..A0 (li t2,0xa0; jr t2; li t1,0x2f); the spawner masks the draws at 0x801D6410 and 0x801D6444.

disassembly + capture
What are FUN_80024EE4's three arguments, the per-frame screen-effect push?one quad, never a family
Why

The routine builds exactly one full-display POLY_F4 from the scratchpad display rect (0x80024F68..0x80024F98). a0 is the ordering-table bucket, floored at zero by bgez s1 at 0x80024F00 and used as AddPrim(OT + a0*4) twice; a1 is the ABR blend equation, packed (a1 << 5) | 0xE into the draw-mode word at 0x80024FB0; a2 is a GP0 colour word with red in the low byte (0x80024F54) - the opposite channel order from every other kernel in screen_prim. Both hosts composite it through screen_prim::screen_effect_push_prims.

disassembly
What does the scene-entry camera reset write?six stores, and H is not one of them
Why

FUN_80025C24 is seventeen instructions: three sw seating the eye triple at 0x800840B8 / +4 / +8 to (0, -0x100, 0x4024), and three sh seating the angle triple at 0x8007B790 / +2 / +4 to (0x1B8, 0x64, 0). It never touches _DAT_8007B6F4, the camera height, so a port that resets H on scene entry is adding a store retail does not make.

disassembly
Is the field follow camera's framing one set of constants?no - retail derives it per scene and per player tile
Why

Measured across the walkable state population, a single pinned height matches 12 of 19 states, a single pitch 8 of 19 and a single yaw 1 of 19 - so the pins are one state's values. Retail's arrival handler queries the MAN section-3 zone table and hands the hit's camera-region record to the config loader, which writes the angle trio, the height and the eye trio with hold and glide rates. Camera roll is a separate matter and is genuinely zero in 51 of 51 sampled frames.

capture
Which FUN_8002C69C arm lays the post-battle report's nine-slice?resolved
Why

The routine reads the style from gp[+0x14C] (0x8007B464), indexes 0x800732A4 + style*12, and jrs the table at 0x80010D18 on descriptor byte 0. Style 0x03 is kind 0 with tile set 0 at 0x80073A00 - eight tiles, byte-identical to a 36-SPRT capture - CLUT byte 0x02 -> 0x7FC2, dx = dy = -8, rect (16, 160, 288, 42), so x runs 8..312 and y 152..210. All 782 emits arrive with ra = 0x800323EC (inside FUN_80031D00). Styles 0x01 / 0x02 are the HUD plaques (kind 3, sets 3 / 4, CLUTs 0x7FC4 / 0x7FCC). The "Gimard fight carries no report band" reading is falsified - the same capture holds 152 entries.

disassembly + capture
FUN_80058490 - a sound-driver lane?MoveImage
Why

It moves a VRAM rect to (0xE0, 0x1DC) - CLUT row y = 476 - so the table feeding it at 0x801F6418 holds VRAM x coordinates and is a CLUT map, not a cue list. FUN_801E22C8 builds the {map[id], 0x1DC, 0x10, 1} argument block at 0x801E2400. The bytes are not sound-effect ids.

disassembly
How does FUN_80043390 unpack an actor's afterimage colour?resolved
Why

Actor +0x74 is [R][G][B][mode] - byte 0 is red. The routine splits a1 low byte first into cr21/22/23 at 0x800434C8 and cr13/14/15 at 0x80043464. The ribbon literals in FUN_8005112C are a different word, reached through FUN_80048310 -> FUN_800485BC at byte 2, which is what made the two look like one field.

disassembly
Which routine draws the world-map / field ground pass?PROT 0900 ships a depth-cued / flat pair, not one emitter twice
Why

FUN_801F69EC (file +0x14, 860 B) runs GTE.dpcs at 0x801F6C44 + swc2 $22,4($t5) at 0x801F6C4C; FUN_801F6D48 (+0x370, 832 B) replaces exactly that pair with a plain sw $s2,4($t5) at 0x801F6F88 - the same loop with and without the depth cue, not a duplicate. Selector 0x801F79A0 on _DAT_8007BB4C, non-zero arm calling SetFarColor first. Both gate cell & 0x1000; the decoration pass FUN_801F7088 gates 0x2000. Rows on functions/renderer.md.

disassembly + capture
Which ordering-table slot does each screen effect take?resolved
Why

Letterbox bands at OT +0x4, sprites at +0xc, the panel at +0x10, mask borders at +0x1c. The letterbox bands therefore draw in front of geometry the mask borders sit behind, so the two cannot share a batch (port engine-effects::screen_fx). See renderer.md. Settled at the FUN_8003D2C4 link sites: 0x801F8AF8 (+0x4), 0x801F7FA8 (+0xc), 0x801F87BC (+0x10), 0x801F8344 (+0x1c).

disassembly
Does any retail shot author a non-zero camera roll?yes
Why

Eight scenes stage a reachable, executing op-0x45 slot-2 roll, from 10 units (0.9 deg) to -660 (-58 deg). details ↓

capture + disassembly
Does retail stack coincident curved shells?no
Why

Field-run display-list reads in jouine / jouind find zero screen-coincident surface groups; every surface is submitted once. details ↓

capture
FUN_80045BB4 - a function entry, or interior residue?the bank-3 kind-19 textured Gouraud quad handler
Why

It is word 12 of the bank-3 cluster-A primitive-handler table at 0x8007668C (kinds 8..19), which is what makes it reachable - no jal names it. Frameless, 1272 bytes, register-entered from the dispatcher FUN_80043390 and leaving by j 0x80043580 rather than jr ra, which is why a gap census reads its middle as two interior runs. Row in functions/world-map.md.

disassembly
How does PROT 0901's 0x801F7644..0x801F8EB4 band split into routines?nine: eight tail-call leaves plus a framed ninth
Why

The band has no prologue and no jr ra; every leaf ends j 0x80043580 and the next starts at that jump's delay slot plus four. Twenty j sites sit in the band and only eight leave it, so cutting at every j over-splits by nine - the boundary rule is the j that leaves the band, which scripts/ghidra-analysis/split-tail-call-band.py reads off the image. The ninth routine 0x801F89B8 is a different shape: four local js to its own tail and a real jr ra at 0x801F8EB4. Per-leaf sizes in functions/world-map.md.

disassembly
Which base does the world-map overlay's per-prim handler table use?0x801F8968, with words 0..7 zero
Why

FUN_80043390 forms it with lui s4,0x8020 / addiu s4,s4,-0x7698 at 0x800435F4..0x800435F8 and adds the same (group_flags >> 1) * 4 index it adds to the SCUS table 0x8007657C. Both tables have words 0..7 zero, 8..11 the four shared emitters and 12..19 the per-bank leaves, so the two paths agree kind for kind; the only difference is the alpha-bank offset s2, which the SCUS arm adds at 0x800435E4 and the overlay arm does not. Naming the first non-zero word as the base re-keys every kind by eight - falsified.

disassembly
Does FUN_8002C69C draw the post-battle report windows?yes - driven from SCUS, off the retained widget list
Why

PROT 0898 contains no jal to it, which is a true absence and a misleading one: FUN_80031D00 (SCUS) reaches the emitter with jal 0x800323E4 every frame a battle is up, walking the retained widget list. The results chrome is 36 SPRT nine-slice cells (CLUT 0x7FC2, tpage 0x1E; 4x4 corners at (160,0) / (188,0) / (160,28) / (188,28), 24x4 edges at (164,0) / (164,28), 4x24 at (160,4) / (188,4)) over the rect x 8..312, y 152..210. Body in level-up.md.

disassembly + capture
What is FUN_80019D50?the CLUT-cell HSV cycler
Why

It rotates hue / saturation / value over one CLUT cell block and pushes the result with a single LoadImage at 0x8001A030 - one palette upload per call, no primitives. It is what drives jou's pulsating flesh and the lightning; the "BGR555 cell-grid emitter" reading is falsified. Port engine-field::clut_cell_fx; see field-ambient-fx.md.

disassembly
What order does FUN_80026988 (RotMatrix) compose?Rx(vx) * Ry(vy) * Rz(vz)
Why

The sin / cos tables it walks are the pointers at 0x8007B7F8 (cos) and 0x8007B81C (sin). Composition order is the one thing a capture of a single rotation cannot distinguish, so it has to come from the instruction sequence.

disassembly
Who reads the GTE light matrix FUN_8001ADA4 writes inline?five sites, all in SCUS, all in the world-map NCC handlers
Why

A disc-wide GTE command-word census finds exactly five light-matrix consumers: NCCS at 0x800441C8, 0x800443C8 and 0x80044750, NCCT at 0x80044540 and 0x80044724 - the kind-8..11 handlers. No overlay image contains one, and NCS / NCT / NCDS / NCDT occur nowhere. MVMVA's mx field is the consumer such a census would miss, and disc-wide none selects the light matrix: 29 rotation, one colour, none light. Denominator 238 GTE command words across 84 images. details

disassembly
How often is the field view matrix built?three times a vsync, from three different callers
Why

An exec-count probe over a field run puts FUN_800172C0 at three entries on 133 of 134 vsyncs, from 0x801D0F98 and 0x801D185C in the field overlay and 0x80016678 in SCUS. Counting one caller gives "once per frame". Which of the three a given frame's geometry is drawn against matters, because on a scene-entry frame two of them read different live camera words.

capture
Is the field view's pre-multiplied matrix a pure scale?_DAT_8007BF10 = 24576 * I, zero translation
Why

Read out of a retail town01 field state with mednafen-state extract: the diagonal is 0x6000 (6.0 in 1.3.12) and the off-diagonals and translation are zero, so the uploaded rotation is six times a pure rotation and a 1x renderer reproduces the frame with the eye trio divided by six.

capture
Does anything on disc select the light matrix through MVMVA?nothing
Why

Of the 31 MVMVA words in code across 87 images, 29 select the rotation matrix, one the colour matrix and one the reserved encoding; the two libgte light wrappers at 0x8005B850 / 0x8005B8B4 have no jal site. The light matrix is consumed only by the five canonical NCCS / NCCT words in SCUS.

disassembly
How many call sites does the field view builder have?seventeen jal sites disc-wide; three run per field vsync
Why

The three that run each field vsync are the jals at 0x801D0F90, 0x801D1854 (PROT 0897) and 0x80016670 (SCUS) - each eight bytes below the return addresses a probe reports.

disassembly
What the dance count-in banner is made ofone textured quad off the hall's own texture page
Why

PROT 1230's member at VRAM (512, 0) with its CLUT at (0, 500) is byte-identical to the parked minigame state's VRAM - 16,384 of 16,384 halfwords and 256 of 256 CLUT entries - and widget 0 is the READY... cell. The numerals, GO! and FINISH! are not that widget: they belong to the sprite spawner FUN_801D3FD0, and 0x77 / 0x78 are y seats rather than widget ids (the emitter clears a2 at 0x801D2EBC / 0x801D2EEC / 0x801D2F04).

capture
The field passive-ability badge column, host against retailpixel-exact
Why

The panel's origin matches retail with dx = dy = 0 on both library states that draw one. Retail takes the anchor's X from the first projected head point and its Y from the third, so a port projecting a single point and using its pair is wrong even when the two points are close.

capture
What stages the dance widgets' second texture page at VRAM (960, 256)?boot-resident system UI; no PROT entry stages it
Why

The page is in no PROT entry: it is uploaded from PROT.DAT's unindexed head gap (0x1800..0x3C800). Two members of that gap cover what the three widget records sample, each declaring its rect in its own TIM header - PROT.DAT[0x11218], the menu-glyph atlas, origin (960, 256), 64 x 256 halfwords, i.e. texpage 0x001F; and PROT.DAT[0x1AED0], origin (976, 272), 8 x 32, whose first three rows are the CLUT ids the records carry. A per-entry sweep cannot source it. details

capture
Why does the dance floor hide the dancers' shadows in PSX mode?a depth tie the port's height lift could not break; retail draws the floor first in one slot
Why

In minigame_dance_retail_go_in's ordering table the floor quad under Noa (CLUT 0x7E80) and her blob (0x7F86) share slot 2220, floor first. The port lifted the blob's depth six units up Y, which the hall's level camera turns into under a unit of depth; the depth is now taken six units nearer the eye. The blob's own darkening is ABR 0's 5-bit average, which barely shows on that dark floor on retail either (renderer).

capture + disassembly
Which of a field frame's view builds does the drawn geometry use?the first; the last frames none of it
Why

Splitting every ordering-table link by GPU command code over three runs - including a real map01 -> town0c entry, the case the question was asked about - gives 4289 polygons: 3861 under site A (the jal at 0x801D0F90), 428 under B, and 0 under C (0x80016670) or either of PROT 0901's slot-B sites. C's whole share is attribute packets and 2D rects, so the build a frame ends on frames no scene geometry at all; a link count that includes non-geometry primitives ranks C too high. details

capture
Which routine draws a field or world-map actor's mesh?three arms of one bracket, chosen by actor[+0x42] then +0x7A
Why

Each of FUN_8002735C's three SCUS jal sites - 0x8001B594 in FUN_8001ADA4, 0x8001BD88 in FUN_8001B964, 0x80048FE4 in FUN_80048A08 - is the far arm of an lh/lhu r, 0x42(s0) then bne pair (branch sites 0x8001B454, 0x8001BC64, 0x80048EA4). The near arm calls FUN_80029888 when actor[+0x7A] is non-zero and FUN_80043390 otherwise. So the three are not three renderers for three kinds of mesh; they are one two-level choice, and the outer level is a per-actor gate.

disassembly
Does any sampled mode enter the table-driven mesh renderers?no
Why

Over 720 vsyncs across four states and three game modes - battle, field, cutscene and the slot machine - the census records 0 entries into FUN_8002735C and 0 into FUN_80029888, against 5089 hits on the +0x42 gate, every one of them reading zero, and 10621 entries into FUN_80043390. The per-prim leaf draws everything those modes put on screen. Probe: autorun_w4c_mesh_path_census.lua.

capture
Does the disc ship writers of actor[+0x42], the mesh-renderer gate?yes, three families - one dev-gated, two content
Why

FUN_80020DE0 clears the halfword at 0x80020EAC and writes 2 at 0x80020EC0 when _DAT_8007B6D0 & 2 - the world-map dev counter, booted clear by sw zero,0x3b8(gp) at 0x80015F64 and raised only by the debug menu and the pad ring. The content raisers are move-VM op 0x10, sh $v0,0x42($s2) at 0x8002342C, and field-VM 4C C2, sh v0,0x42(s5) at 0x801E26FC. Two SCUS sh rt,0x42(rs) sites inside the render brackets are not raisers - their base is the scratchpad packet block. renderer.md

disassembly
Is an actor a shipped script raises +0x42 on drawn through a bracket?yes - field placements, through FUN_8001C204 + FUN_8002735C
Why

Field-VM 4C C2 <b> stores b into the resolved actor's +0x42 (0x801E26F0..0x801E26FC); the field-op census finds 50 clean sites in nine scenes, every operand 0 or 1, several on the player. Placed actors draw through FUN_8001ADA4 / FUN_8001B964, whose far arm runs the object-effect transform FUN_8001C204 on row +0x42 - 1 of 0x80083FF8 and stages the row's +0x10 as the clip bound 0x1F800380 that FUN_80027F00 reads. The four-state census sat in none of those beats. details

disassembly
What do move-VM ext sub-ops 0x17..0x1A write?four different writes to one object-effect row
Why

Row op[2] of 0x80083FF8, stride 0x14: 0x17 writes five operands, 0x18 zeroes the angles and seeds the clip words from actor +0x16, 0x19 adds five operands (0x801D3C5C), 0x1A seats a yaw row with clip words offset by the operand vector rotated to it (0x801D3CE0). details

disassembly
How large are Viguro's arm-5 sparks?the module shrinks the effect pool's sprite scale
Why

PROT 0909's arm 0 stores the pool head's sprite scale 0x600 (lw v1,-0x42D0(v0) / sh v0,0x2(v1) at 0x801F6BC4..0x801F6BCC) and arm 0xFF puts back the init's 0xA00 (0x801F7908..0x801F7910); pass 2 sizes every child atlas w/h * scale >> 8, so the effect-0x10 sparks the creature sheds draw at 0.6 of the default size, a third of the area. PROT 0904 does the same (0x400 on arm 0, 0x800 on each arm-12 hit, 0xA00 on 0xFF). Carried by cast_module_ticks::effect_sprite_scale_store: the 0x87 film's arm 8 reads .948 from .669, arm 5 .946 from .915 (effect-vm).

disassembly + capture
Which emitter does effect render mode 4 take?all three - the move VM writes both flag bits
Why

FUN_8001ADA4's case 4 picks on actor[+0x9E]: 0x4000 -> FUN_8002A5A4, 0x2000 -> the battle overlay's ribbon builder FUN_801CFA48, else FUN_80028158. Move-VM op 0x42 (arm 0x80023F94) stores op[1] | 0x2000 (ori at 0x80023FBC, sh v0,0x1e(s1) with s1 = actor + 0x80); op 0x23 (arm 0x800237D8) is the 0x4000 sibling. Op 0x42 ships in five slot-B cast / summon programs. details

disassembly
Where does a fade block's delay and hold end, frame for frame?delay n suppresses n - 1 frames; hold 0 retires the frame after the ramp lands
Why

FUN_80020C14 subtracts the frame byte from the delay at block +0x1C (0x80020C20..0x80020C40) and returns -1 only while the result stays positive (0x80020C48..0x80020C54), so the frame that reaches zero already steps the ramp. Retail keeps accumulating and clamps on the delta's sign rather than latching on the target; the engine's fade::FadeState steps through the same transcription (fade_ramp). live-audit-triage.md

disassembly
Does any asset set an actor's render mode +0x56 to 4?no - every write of 4 is code
Why

The four sh sites that store 4 into +0x56 are move-VM arms 0x80023460 / 0x800237E4 / 0x80023F98 and 0x8004D574; no carrier holds the value. The port's move VM names the same halfword its sub-state. The +0x9E emitter selector is code-written too: no asset among SCUS, 86 overlays and all 1233 entries carries a 0x2000 / 0x4000 value for it, and move-VM ops 0x42 / 0x23 set the bits (row above; port engine-effects::effect_ribbon).

disassembly
Is the field fog half-sheet a world-space quad?a view-space billboard
Why

FUN_8003F348 folds RotMatrixX(0x400) into the base matrix (S * I, S = 6) at scratchpad 0x1F800334, FUN_8003F3FC makes the particle's view point its translation, and FUN_8003F86C RTPTs (dx0, 0, 0x80) and (dx1, 0, 0) through it: screen-aligned at the particle's depth, H * 0x80 * S / vz px tall. The capture holds the matrix as [[S,0,0],[0,0,-S],[0,S,0]], and the port's billboard puts 103 of keikoku_chest_preload's 104 walked fog packets within a pixel (field-ambient-fx.md). Settled: 0x8003F374..94, RTPT at 0x8003F8E8 over VZ 0x80 / 0 (0x8003F8AC..DC).

disassembly + capture
What does the field drop shadow draw?four textured quads over a projected 3x3 grid
Why

FUN_800460AC RTPTs three rows of three points 0x20 apart at the actor's own y, writing [SXY, SZ] pairs from scratchpad 0x1F800020; FUN_8001C394 links four POLY_FT4 (0x2E808080, tpage 0x1F, CLUT 0x7F86, the 16x16 blob at u 0xE0) at ((ΣSZ + 0xA0) >> 4) >> DAT_1F8003A4, ten slots nearer under 0x800000. Rebuilt from state actor lists the packets match retail's 12 of 12 on town01 and 4 of 4 on map01 / map03 (renderer.md). Settled: grid 0x800460EC..46140, packet words 0x8001C3E0..C40C, depth 0x8001C488..A0, flag arm 0x8001C50C..34.

disassembly + capture
Which field actors cast a drop shadow?flags with 0x01020000 and without 0x200000
Why

FUN_8001B964 0x8001BE20..0x8001BE48 tests the flags staged at 0x1F8002D0; its +0x72 == 0 early-out jumps to this gate, so an actor at render scale 0 still casts one. Retail also skips an actor with +0x10 bit 0x2, which the port does not (renderer.md).

disassembly + capture
Why did opdeene's jungle plants draw black natively?the dim ambient belongs to lit rows, not to a vertex colour
Why

The prologue's dim ambient 0x20 applies only to light-source rows. A restage keyed on a vertex colour of 0x80 - the builder's fill for lit rows, but also the authored word on the jungle's one-quad billboards - dims the billboards too, and the grade takes 0x20 to V = 2. Retail's colour words are identical at the crawl start and mid-crawl and its billboards draw as FT4 packets at (98, 94, 42); the native asset path stages the ambient by lit-row mask (host-drift.md).

capture
Does the port's battle tint pass match retail?yes, distance fade included
Why

FUN_8004A908 recomputed from RAM reproduces the stored +0x74 / +0x78 words for 258 of 266 bodies over 97 states, 54 on the distance-fade arm (a body beyond half its radius fades toward a darker copy; outdoor stages invert). The engine test measures 158 of 160 over the 58 loadable states (battle.md).

capture
What is the party body radius in battle?640; 1024 on Terra
Why

*(+0x22C)+0x58 reads 640 on 212 of 213 party seats. The seat stager FUN_800513F0 writes 0x280 and overwrites it with 0x400 when the seat's roster id is 4 (0x8005151C..0x80051538); the engine's separation pass takes the same pair (PARTY_SEPARATION_RADIUS in engine-core::world::battle::locomotion).

capture + disassembly
What camera does the Baka Fighter duel play under?the overlay's own scripted shot, not a fitted one
Why

Round setup (state 0x32, 0x801CFF34..0x801CFF7C) snaps pitch 0, yaw 0x2F8, roll 0 and the eye to (0xC8, 0x708, 0x1FE0); state 0x35 (0x801D0324..0x801D0454) spins the yaw by dt << 6 per frame past 0x1000, zeroes it and glides the eye to Y 0x898 / Z 0x3520, and the duel state 0x64 writes no camera value. A special glides through the record at 0x80070764 (consumed by FUN_80021248); the tally exit (0x801D0A38) snaps to a close-up at yaw 0x3D4. Engine: baka_duel_scene::DuelCamera, both play hosts (minigame-baka-fighter.md).

disassembly + capture
How is the Baka Fighter arena built?four placed wall models and the battle ground grid
Why

FUN_801D6D60 draws a stage model at a world point through the camera with RotMatrixY (FUN_8004629C), only at view depth >= 0x2711; the epilogue places wall model 0 four times as a room, and the depth gate drops the wall between camera and fighters. The floor is FUN_801CEB84, the battle ground-grid emitter sized 6 x 6 by the duel init. Each fighter stands at X = -/+(roster +0x44 + 200) (0x801D005C..0x801D00F4).

disassembly
Where do Ozma's middle arms part from retail once the camera matches?the module band's rand() deal, not the body
Why

On the 0xA0 Gobu Gobu film the creature's clip, cursor, stand, facing and the camera hold to the unit through arms 7..10, and paired frames show the same body under different lightning bolts and glow sprites. Seated on retail's word each pass (--rand-track, a harness seat) the arms read .890, .847, .938, .994 for .517, .370, .457, .510, and the bodies coincide; the turned-body reading was the deal's sprites over it. cast-sequence oracle.

capture
What whites out Ozma's arm 24?the frame step: arms 22 and 23 at retail's 3, then the deal
Why

Arm 24's luma runs a 32-vsync pulse on both sides, the engine's a pass ahead on the arm's clock: retail's step falls to 3 late in arm 21, its arm 23 runs 123 vsyncs, and the engine's runs 127 at 4 (the open step row). On the 1788-frame recording the arm reads .309, .365 on --retail-steps, .441 on --rand-track and .700 on both, every paired luma within four. Harness seats only.

capture
How much of Iota's arms 5..9, and of its backdrop through arm 5, is left once the sheets stop crossing?the rays' rand() deal
Why

With the cycler rows, scrolled pages and part sets agreeing (cast-module), --rand-track on the 0x93 Gobu Gobu film lifts arms 5..9 from .709, .803, .749, .737, .885 to .948, .976, .977, .976, .909, and the cast from .847 to .968. The opening ring's size and the cycler's one vsync of velocity are under what is left. Harness seat.

capture
Why are Slippery's storm clouds dimmer than retail's in arm 6?each record's rand() % 4 pick
Why

The clouds stand where retail's do on the same +0x78 ramp; dealt retail's words each pass (--rand-track) the 0x92 film's arm 6 reads .999 for .956. Harness seat.

capture

Long write-ups in this area

Does retail stack coincident curved shells?

Status: resolved (no) - Evidence: capture.

Every surface in the live ordering table is submitted exactly once, so retail has no stack of coincident shells to order.

  • Measurement: field-run display-list reads in jouind (1263 packets walked) and jouine (1016) report zero screen-coincident surface groups above a 16 px^2 area floor, with no undecoded polygon link in either state.
  • Decoder requirement: POLY_FT3 carries seven payload words. The two chains hold 46 and 45 textured flat triangles - the strand meshes jouine is built from - and a walk that skips them is blind to exactly those.
  • The one coincidence: a single mesh drawn three times in one texture family (clut=0x7F86 tpage=0x001F). That is the multi-pass semi-transparency idiom, not a mesh stack: its members share a material, and two different env TMDs would not.
  • Consequence: the coplanar kernel's same-position curved-shell residual is a property of how the port assembles a scene's env TMDs, not something retail resolves by ordering.
  • False positive 1: retail double-buffers, so ordering tables come in pairs holding frame N and frame N-1 with near-identical packet counts; merging a pair makes every surface look stacked with itself.
  • False positive 2: distant geometry projects to 1-3 pixel slivers that coincide constantly without saying anything about meshes, which is what the area floor is for.
  • Coverage limit: each read is one frame and one camera. The library's other jouine / jouind entries are byte-identical backups of these two (a library filename is the sha256 of its contents), and chitei2 is unmeasured and inherits the conclusion. The negative rests on scale: a stacked shell would double many adjacent surfaces at once, and in cave interiors the walls are the dominant on-screen geometry.
Does any retail shot author a non-zero camera roll?

Status: resolved (yes) - Evidence: capture for the census (a disc-derived executing oracle), disassembly for the factor.

Slot 2 of the op-0x45 CONFIGURE mask is the roll angle _DAT_8007B794, the third factor of the camera build FUN_80026988's Rx * Ry * Rz (it reads three angles off its argument, +0 / +2 / +4, at 0x800269F8, 0x80026998, 0x800269A4), and eight scenes stage a reachable, executing non-zero value.

  • Factor: the per-node variant FUN_8001CF50 loads _DAT_8007B794 at 0x8001CFD0..0x8001CFE8 (lui a0,0x8008; lh a0,-0x486c(a0)) and calls 0x8004638C unless the render node's +0x52 bit 0x200 is set. That site is not inside FUN_80026988.
  • Writers: none of FUN_801DAB90, FUN_801DB8EC or FUN_801DBE9C writes _DAT_8007B794; the only zeroing is the scene-entry reset FUN_80025C24 (sh zero,-0x486c at 0x80025C5C).
  • Beat shape: every rolling beat carries the full nine-slot mask - pitch, yaw, roll, the eye trio, focus X and Z, H, and no focus Y (the opdeene reading on cutscene.md). Every operand is an in-range 12-bit angle, and the beats of one shot repeat the same tilt.
scenewhat it isPROT entryrecordroll (12-bit)degrees
edstati3Ending (station gondola)826P2[0]10, 200.9, 1.8
station3Karisto Station616P2[0]302.6
map03World map (Karisto Kingdom)392P2[10]605.3
nilboaNivora Ravine638P2[33]605.3
taikuZora's Floating Castle373P2[27]-120-10.5
koroutSol Tower (entrance)534P2[3]24021.1
juui1Ra-Seru scene (1)588P2[0], P2[3], P2[4]-400-35.2
juui2Ra-Seru scene (2)597P2[0]-660-58.0
  • edstati3 is a real scene: the ending cutscene block, bundle at extraction entry 826. Its CDNAME label inherits forward as far as the battle-data packs at 863 / 864.
  • Frequency: of the 371 CONFIGUREs a control-flow walk reaches, 123 set the slot and 15 of those write a non-zero value. Roll is a minority term, not an absent one.
  • Port: Camera::roll, the shell's psx_camera_mvp / cutscene_view, the cutscene interp's tenth component, and the browser page's orbit cam.roll.

Method. A field-VM record's tail is not linearly decodable, so a decode census measures its own resynchronisation policy. The authored rolls live in partition 2 (cutscene-timeline / walk-on beat records), while a linear census walks partition 1:

InstrumentCONFIGUREs reachedNon-zero rollsWhat it measures
Strict linear (stop at first decode error)210its own blindness - it reaches none of the eight
Resuming linear (advance one byte on error)2182637 roll operands, 7 of them outside the 12-bit angle spaceits resynchronisation into data
Raw byte scan (decode at every offset)4257a "2 %" ratioits own post-hoc credibility filter
  • The resuming sweep's shortlist of "coherent" candidates - deroa, chitei2, station3, town0b, retona, nilboa, edstati3 - holds three of the eight and misses five.
  • The decider is control flow. Stepping the ported field VM (legaia_engine_vm::field::step) from each record's real entry PC, under a probe host that answers every branch predicate both ways, gives the PCs control flow can reach; executing the same records in a real World gives the subset that runs. Both find the same eight scenes, and neither reaches a roll operand outside the angle space, which marks the resuming sweep's impossible operands (26708) as data.
  • Oracle: crates/engine-core/tests/thread_camera_roll_execution.rs; the linear modes are kept as a negative record in crates/scene-formats/tests/thread_camera_roll_census.rs.

Owning page: cutscene.md.

What does field op 4C ED do to a PSX-mode frame?

Answer: it writes the dither word _DAT_8007BA66 (sh at 0x801E351C), which the frame-begin driver FUN_80016B6C copies into the DRAWENV dtd bit. Boot writes 1 at 0x8001D520.

Evidence: disassembly for the writers; capture for the word, which reads 1 in every library state, ending_vignette_fullscreen included. The census (asset field-op-census --only "4C ED" --context) finds 00 only in the ending scenes, immediately ahead of the closing 43 12 / 43 13 wipe of a vignette, with the next vignette's head raising it again. So the word is off for the wipe and the wait that follows it, not for the picture.

Port: World::toggles.draw_dither, read per frame by both hosts into PSX mode's dither gate (Renderer::set_draw_dither, the page's setDrawDither). No channel of the retail-compare corpus can see it, because no state stands inside the window.

Owning page: shading.md.

Source: docs/reference/re-settled-threads/rendering-camera.md

Measurement + corpus

# Settled threads: Measurement + corpus

ThreadAnswerGrade
Why did the image child draw battle_uru2_nurga_glare_mid_cast's near Nurga opaque?the child re-derived no ghost bits on a partly aligned pose
Why

The second Nurga holds the near-camera ghost (+0x8 = 0x83000000). The child re-ran the ghost pass only on a fully aligned camera, so it kept the replay's clear bits. In the capture band only the near-camera test sets a bit, and it reads the pose it is handed, so the child runs it there on retail's ghost pose (LEGAIA_BATTLE_GHOST_POSE): image .906 to .948, Freed's Water Crystals .967 to .993 (retail-compare).

capture
What pose does battle_garmel_songi_hyper_wave_mid_cast display?two step-2 passes back along the RAM's table
Why

The close-up accumulator reads 48 over a history whose newest entry names step 3: only the older entries add up (three step-2 passes), so the newest is the step of the pass to come. Walked back two step-2 passes on the shot's own divisor, the RAM's table stands at yaw 3971, TR z 5066, the frame a fit had found; two step-3 passes overshot onto the shot's origin. image .779 to .990 (magic_band_older_steps, retail-compare).

capture
What are the bytes past a DATA_FIELD stream's terminator?last-sector fill
Why

All 34 clean streams end inside their entry's last 0x800 sector; the trailer runs to the sector boundary and is zero, except eight bytes of residue in entry 1208 at 0xFD70. FUN_8002541C stops at the terminator. Test data_field_trailer_real. data-field.md.

capture (disc census)
Does any scene place an actor whose model lives in another PROT entry?no loadable scene does
Why

Over the 101 CDNAME blocks with a MAN, every placement with a model byte below 0xF0 resolves into its own block's TMD list; >= 0xF0 is the global head pool by design. The only exceptions are 20 placements in other7, the unused koin3 draft, whose model bytes 65..=71 run past its own list. Test field_npc_model_source_disc. data-field.md.

capture (disc census)
What do move-VM ext sub-ops 0x36 / 0x37 compare against?the live actor count; every 0x37 is a pool-headroom guard
Why

0x801D4744 / 0x801D4764 read 0x8E - *0x8007C348, the allocator's free-stack top; the library states hold the top at 14..135. All 28 shipped 0x37 sites are [2F][37][0x80..0x87][1], which jump the HALT behind them while the pool has room. Answering "142 live" everywhere halts those programs and leaves kor, korb2, dohaty, garmel and map03 a fraction of their ambient parts; the port's host reads the pool top (World::actor_pool_top) (move-vm-overlay-ext.md).

disassembly + capture
Does a shipped move program keep the camera yaw factor on a camera-relative node?yes, one: urudre1 stager record 14, word 0x0080
Why

The word sits past that record's 0x37 guard - the headroom test at 0x801D4764 that jumps the HALT while the pool has room - so the static program walk resumes at forward branch targets to reach it; the disc census pins the one carrier (renderer.md).

capture + disassembly
Where in the story does the party first enter vell (West Voz Forest)?between drake_castle_revisited and voz_forest
Why

Flag 0x489 has two disc setters, vell P1[0] (the entry) and map02 P2[13]. It is clear in every ladder anchor through dolk2_market_noa and set in every card save from PRO-01 (vozz) on, so the playthrough's first vell entry lies between those two. The spine holds it there as west_voz_forest; its anchor vell_fog_field is a door-tile poke from a Rim Elm-era state, whose flags are that state's plus vell's three entry writes, so it anchors the scene but neither the order nor the next seed (full-game-ladder.md).

capture
Does any code reseed BIOS rand?no; one kernel stream
Why

FUN_80056798 is the A(2Fh) thunk (0x80056798 li t2,0xA0, 0x8005679C jr t2, 0x800567A0 li t1,0x2F in the delay slot) and returns the high half of the kernel LCG. SCUS_942.54 carries 21 A-table thunks of that three-word shape (0x800565F8..0x8006EE24) and none loads t1 = 0x30 (srand); no li t1,0x30 sits beside a li t2,0xA0 in any of the 1234 PROT entries either. Every caller in every image draws on one kernel seed. The port models that as bios_rand_shape over one world stream (World::next_rand).

disassembly
Why do PROT 0896's jal targets miss this disc's SCUS entries?a foreign-build link
Why

Imported at its own base 0x801D4DF0, the image cites SCUS-range targets that each land on a body instruction or delay slot of this disc's SCUS_942.54, none on a frame adjust, and no other image calls them; rebasing cannot move a decoded target. They are baselined as a measure of the executable it linked against (call-target-integrity.md).

disassembly
What is PROT 0970's run at 0x801D0E9C..0x801D199C?an unreferenced AC VLC table
Why

Every word is zero or (len << 26) | (run << 10) | level; no word, jal, j, lui pair, gp or base-plus-displacement access reaches it, and the decoder reads the table FUN_801F1A00 unpacks at 0x801E0A00 (byte-accounting.md).

disassembly
What sizes the debug monitor rows and the 0972 / 0977 tables?their consumers
Why

FUN_8001C93C draws $a0 rows of 0x28 bytes (PROT 0976 hands it eleven); PROT 0971 walks twenty-two such rows inline (sltiu v0,s0,0x16 at 0x801CECE0); 0972's species records are ten of 8 bytes, 0977's hub sprites seventeen of 0x14; PROT 0896's pool is twenty-eight programs for its own interpreter FUN_801D896C (byte-accounting.md).

disassembly
Which catalogued save states carry a patched executable?measured and tagged per family
Why

SCUS_942.54 is read once at boot, so a state made on a patched disc keeps that build's code on every load. patch_taint_audit.py states compares each state's resident SCUS at the patcher's hook sites and records resident_patch per scenario; every capture-graded claim re-checked against its family stands, and w3a/registrar_sol is void (pcsx-redux-automation.md).

capture
What are PROT 0897's short data tables?window layouts, sound-test rows, a handler table, window programs
Why

27 0x1C-byte window records at 0x801F2B98 (index a3 * 28 at 0x801ECA24), 73 ten-byte sound-test rows at 0x801F2E94 (index (n + 1) * 10 at 0x801DB8BC..0x801DB8C8, not a six-byte stride), a 52-word jalr table at 0x801F33B4 indexed by actor +0x50, and fifteen window programs from 0x801F3340 run by the overlay's own thirteen-arm interpreter FUN_801E9B3C. The two counts sized by layout rather than by a bound are inference (byte-accounting.md).

disassembly + inference
What is PROT 0976's sparse table?Baka Fighter's per-fighter move table
Why

Seventeen per-fighter blocks of nine 0x60-byte move records tile 0x801D7E28..0x801DB788, pointed at by the seventeen-word table 0x801DB8B8; FUN_801D553C walks and formats them, FUN_801D57BC is the runtime reader (fighter a0, move a1 * 0x60, point count +0x1C < 8), and the name tags sit at 0x801DB7A8 (byte-accounting.md).

disassembly
What does the indexed form lui; addu; lw lo(at) hide?512 accesses, none in 0897 / 0899, no verdict moved
Why

A strict register walk completes the form 512 times over SCUS and the mapped overlays (273 SCUS, 223 PROT 0896, 9 PROT 0971). A scanner that never drops an overwritten register reports 23 / 44 phantom sites in 0897 / 0899. Both reference sweeps share the strict walk (scripts/ghidra-analysis/mips_walk.py), and re-running them over the 89 addresses the ignore list and docs call unreferenced changes no verdict.

capture
Which live ports cover only part of their routine, and which of those differ from retail?every residue ported
Why

Of 43 partial ports a 316-tag audit found, each one that changed behaviour is ported or closed without a port details ↓.

disassembly + capture
How often does a live // PORT: tag describe its routine correctly?about seven in ten
Why

316 live, dumped tags in the VM / world / audio crates read against their disassembly: 222 match, 45 misname the routine, 43 port only part of it, 6 name the wrong address. The window VM and anim VM were worst. check-port-provenance.py carries a non-entry signal from that audit; a tag naming an address that is no function entry is the shape it catches.

capture
Does the byte account cut an overlay's inherited tail the way disc-coverage.py does?yes - one rule feeds both
Why

legaia_disc_audit::inherited_tail ports the sibling fixpoint and adds the packer-buffer leg: byte k of a tail is the byte of the nearest earlier entry reaching k, searched over the whole entry. tail_cuts / tails_in feed disc-coverage.py, the attribution sweep and the byte account alike; 85 of the 87 mapped images end in a tail, 96,986 bytes, and the leg reproduces 82 of the sibling rule's 83 cuts offset for offset. It adds a non-overlay donor (PROT 0898 and 0895 end in 0894's bytes) and moves 0901. disc-coverage.md

capture
What did the framed-text walk change in the op-0x49 census?55 sites lost, 54 gained, the spine-flag negative unmoved
Why

Diffed site for site against a walk that does not frame text: every one of the 55 lost sites reads a printable-ASCII base pair (dialogue decoded as an op), and five scenes leave the census entirely; the 54 gained are real instructions behind a record's first line - 48 are the [49 03 N] / [26 delta] picker-resume ladder after a 0x1F prompt in sixteen scenes, plus two 49 09 in bylon and four sub-0x00. 0x142's only near-miss stays the same 24 kor windows at distance 3.

capture
Can a green ladder rung assert its invariant vacuously?yes - and a canonical one did
Why

A rung meant to show that re-applying an inventory Arrange leaves the bag unchanged drove Throw Out first, which emptied the bag; retail's own dispatch then swallowed the confirm, so the assertion compared two empty bags and passed. A rung's order is part of its claim: an assertion whose subject an earlier step can empty has to check the subject non-empty before it compares.

capture
Does a "not observable" reach verdict survive the next export?no - it is a link-time property
Why

Whether an address is observable at all depends on what the profile links in, so a verdict recorded against one export does not carry: the same addresses come back unobservable on the next run rather than staying converted. Read such a cell as a statement about the export that produced it, and re-run the export before planning work off it - the same law the reach audit states for the prose beside an address.

capture
Can a per-vsync save-state capture measure an envelope statistic?no - the emulator's envelope is not on emulated time
Why

PCSX-Redux runs its SPU on a thread paced by the audio device accepting samples and steps ADSR once per sample that thread produces, so a capture that writes a ~19 MiB state every vsync advances the envelope by an uncontrolled amount per captured frame. Measured: the wall-clock gap between captured vsyncs averages 169 ms against the emulated 16.67, and two captures of one state with no input agree on the voice pitch register for 5285 of 6000 voice-frames but on env_level for 404 of 1000. Anything written by the emulated CPU - a key-on, a pitch - is still comparable.

capture
Are FUN_801D14B0 and FUN_801D6710 two routines?one, linked into two overlays
Why

Both are 24 instructions and 22 of them are identical (words 2..23 of 0x801D14B0 and 0x801D6710, compared in the two images); the two that differ are the lui/lw pair materialising the gate word - 0x801D1AB4 in PROT 0977, 0x801DBF00 in the Baka Fighter image. A byte comparison reports nothing in common because the relocated pair comes first, which is what let the pair read as unrelated routines.

disassembly
Can replay-port-coverage.py's PARTIAL UNION line tell a failing ladder from one nobody exported?resolved
Why

No. Both leave no cov-*.json, and the report names them identically; a red canonical member therefore reads as a forgotten export while every row it was written to convert keeps reading never entered. Read the line against the export run's own log (reach-triage.md).

inference
What does DAT_8007BB38 hold?the id just issued
Why

FUN_80026B4C publishes it through gp[+0x820] (sw at 0x80026BBC) before the increment (addiu 1 at 0x80026BC0, stored at 0x80026BCC), so it is the last installed index and not the next free one. A walker bounded by it as a count reads one entry long.

disassembly
Can a mode-seat parity witness sample the mode word?no - an INIT mode never survives the call
Why

An INIT mode lasts one frame inside the seat's own entry call, which resolves it and hands off to RUN before returning, so no post-call sampler on either host can observe one. The witness that does work is the edge count: five on each host with the hooks in place, three without. A parity check reading a mode word instead is measuring the wrong global as well as the wrong moment.

capture
What does a reach-triage cell assert?nothing - it is prose beside an address
Why

The page audit checks the addresses a row cites, not the sentence next to them, so a cell can keep describing work a ladder has already done. Nine of eleven "not driven" rows were converted by canonical ladders whose cells were never rewritten. Treat a reach cell as a claim to re-measure, not as a measurement.

inference
Does a PCSX-Redux .sstate carry the scratchpad?yes
Why

The state carries a 64 KiB hardware blob (protobuf field 4) with the scratchpad at its own offset 0. A teien field-run state reads the object-grid pointer at +0x3EC, the camera's visible tile window at +0x3E8..EB and the floor-height ladder at +0x35C out of it. The reader exposes it as SaveState::scratchpad. Body in crates/pcsx-redux/README.md.

capture
Is the overlay corpus's uncovered-byte residue un-dumped code?sixteen of twenty runs are not code
Why

Four of the twenty ranked runs on the bytes-derived dump worklist hold real routines. Three are slot-A: FUN_801CE8EC (PROT 0973, 144 B), FUN_801CF870 (PROT 0977, 2184 B; a 1748-byte dump of it is short) and FUN_801D03C4 (PROT 0980, 636 B, of which a 500-byte dump is a short read and 0x801D05B8 a fabricated interior head). The fourth is slot-B and is what makes the count sixteen: PROT 0949's 0x801F7630 run is the body band of an eight-arm leaf table, invisible to frame matching because the leaves have no frame. The other sixteen are the image's own data tail or a neighbouring image's bytes.

disassembly
Why does every extracted overlay image end in another image's bytes?the build buffer
Why

An image's extracted extent runs past its own content into another module's code at the same file offset - a build-buffer leftover, the inherited_tail shape, cut from the image's own code denominator. The buffer is indexed by file offset, so the donor need not share a load base, and content_bytes is a sector extent, so the donor need not be longer; a same-base, strictly-longer rule reports only 66 of 83 images. The gate cuts on the packer-buffer leg (row above). Measured case: PROT 0918 0x801F9388..0x801F99D8 == PROT 0899's file +0x29B0, 1616 bytes. See disc-coverage.md.

disassembly
What is the slot-B modules' unclassified data tail?the spawn-record band
Why

[i16 model_sel][u16 reserved][move-VM bytecode] records, addressed by the consumer's own lui/addiu and handed to FUN_80021B04 / FUN_80050ED4 in $a2; +0x02 is zero in every record with no reader. 62 of 64 images carry a band - 1027 records, 114,412 bytes. PROT 0926 is a null stub; 0952's two spawn sites sit in its inherited tail and point at 0x801F8348 / 0x801F836C, two of 0951's records past 0952's own end. A shape rule misses the band: a record's bytecode carries a lui $rt,0x80xx word by accident, which in_data_segment rejects. slot-b-module-layout.md.

disassembly
Does FUN_8003E8A8 return an LBA?no - a sector count
Why

0x8003E90C is subu s0,v0,s2 over TOC[idx+3] and TOC[idx+2] and 0x8003E948 returns that difference; the start LBA is the side effect sw s2,0x8f0(gp) and the index lands at gp+0x90C. FUN_8003E800's a1 is therefore a sector count too, and FUN_8005E4D4 is (sector_count, lba, dest_buffer), not (buf, dir_entry, size). Rows on functions/asset-loading.md and functions/runtime-libs.md.

disassembly
Where is the libcd directory cache?0x801CB408
Why

FUN_8005DEA0 forms it as lui at,0x801d + sw ...,-0x4bf8(at), stride 0x2C, capped by the two slti a3,0x80 tests at 0x8005E0E0 / 0x8005E100; the routine forms no address in 0x801C4*** at all. The address 0x801C4BEC has no support in the bytes and its origin is unexplained: 0x4BEC occurs at no word in SCUS, so it is not the offset half of a lui/sw pair; the only relation the bytes carry is 0x801D0000 - 0x4BEC = 0x801CB414, which is a record's +0x0C name field.

disassembly
The asset descriptor's +0x04 wordΣ of the decompressed sizes; never read
Why

105 PROT entries carry a descriptor table, and in all 105 the header word equals the sum of the descriptors' decompressed sizes (re-measured over every extracted entry). It is dead at runtime: the walker FUN_80020224 reads the count at 0x80020288 (lw s3,0x0(s4)) and the pairs from +0x08 (0x8002029C / 0x800202A0), never +0x04; of the 69 sites across 84 images that touch _DAT_8007B85C, the one store is 0x8001E2A0 and every dereference is at +0. A "zero carriers over 1233 entries" count is a detector's, not the bytes'. See asset-descriptor.md.

disassembly + capture
record[0] + 0x5C in the player battle filesno reader exists
Why

A sweep of 518,656 words across 84 images finds 8 non-sp word accesses at that displacement and none of them consumes a record[0]: the SCUS pair 0x800532D4 / 0x800532E4 is the load-time rebase, which loads the word only to store it back. Two of the hits are one in PROT 0900 and one in 0901; an over-read entry size shows both in 0900.

disassembly
Two minigame overlays carry private copies of the two-window tile lookupresolved
Why

PROT 0972 at 0x801D617C and PROT 0980 at 0x801D3F1C each hold their own copy, which is why the same VA disassembles differently depending on which image is resident. See overlay-va-aliases.md.

disassembly
Do the feature views measure distinct features?no - twelve measured one blob
Why

A BFS from a feature root spills through the title tick into the save UI, the effect spawner and the move VM, so title-screen's anchor set was a strict subset of muscle-dome's. Localized, the same views separate: muscle-dome falls from 781 anchors to 72 and battle-action from 882 to 504. The grade is capture because the evidence is a corpus measurement, not an instruction. See port-catalog.md.

capture
What is in the SCUS_942.54 code gap the citation graph never listedresolved
Why
disassembly + capture
Why three plausible-code blocks in SCUS_942.54 can never be a function bodyresolved
Why

The BIOS kernel-patch cluster copies 0x8006EF78 and 0x8006F058 into kernel RAM, and 0x8005BBB8 is the stock PSX exception-handler prologue with no reference of any of the five forms anywhere on the disc. Each is real MIPS that executes only after relocation, so nothing calls it at its link address and no function record exists. See runtime-libs.md.

disassembly
Is the unattributable dump-extent residue a re-dumping problemno
Why

Three shapes remain and only one is dump-shaped: short windows no image reproduces at that VA, bytes in no extracted image at any VA (needs an extraction), and extents where two dumps genuinely resolve to different images (an answer). The "repaired by re-dumping" reading is on re-do-not-re-walk.md.

capture
Can the inner of two nested overlay spans be measured at allyes
Why

Address ambiguity is total for it by construction; byte attribution places most of its extents anyway and the row reports. What is structural is that it can never be resolved by address - a statement about one method, not about the image.

capture
Where does a slot-B image's highest spawn record end?its own move-VM program bounds it
Why

Walk the opcode widths to a terminator - 0x08 HALT, or an armed 0x19 / 0x1B idle loop not immediately followed by one - and round the end up to 4, because the records are word-aligned. 991 of 1027 extents come out exact and 58 of 62 image tops bounded. The residue's direction is not uniform: over the 1023 pointer-credited extents, 33 misses split 14 past the end / 13 exactly on it / 6 below. The invariant is narrower - no miss ever terminates above the end, because only a terminator produces a claim. Measurement in slot-b-module-layout.md.

capture
When does SCUS's jal 0x801F7B88 run?ordinary in-battle frames, while PROT 0920's effect budget is non-zero
Why

One Slippery cast (action 0x92, loader-B tracker 25 = PROT 0920) fires it 212 times, across phases 6..10 and 0xFF, with _DAT_8007BD71 = 0xFF - battle running - every time. Its gate _DAT_8007BDC0 has five writers inside 0920: 0x801F6BBC zeroes it, 0x801F6FEC seeds 0x204, 0x801F70CC drains 8, 0x801F712C floors it at 4 and 0x801F7B4C clears it - so it never reaches zero on its own.

capture
Is a VA that prints in two neighbouring slot-B images one routine?usually, but not always - diff the bytes
Why

0x801F81DC is two routines: a 272-byte stager in PROT 0951 and a 2040-byte applier in PROT 0910, differing at the first instruction. The other six "also in" cells around it are residue, verified pair by pair. The band shares a load base and a build buffer, so neither "same VA, same routine" nor "same VA, residue" is safe as a default.

disassembly
How long is FUN_801DD9D4?588 bytes / 147 instructions, 0x801DD9D4..0x801DDC20
Why

Every one of its seven dumps stops at 276 bytes, because the decompiler stops at the jr v0 jump table at 0x801DDA88 and the beq at 0x801DDA78 branches past it; the body ends at the jr ra at 0x801DDC18. Seven dumps agreeing is agreement about the decompiler, not about the function. The extent belongs in the shared dump header parser - a CSV-only correction leaves the dump claiming the short length and makes the whole body VA-ambiguous.

disassembly
content_bytes vs clean_copy_bytes - which is an overlay's length?the entry's sector extent is the length; clean_copy_bytes is how much a RAM capture verified
Why

On PROT 0899 they differ by 0xF174, and the shorter one measures a prefix (the menu overlay reads 99.9% covered against it); every overlay image equals its corrected PROT extent exactly (31 of 31), so content_bytes is the denominator disc-coverage uses. See disc-coverage.md.

capture
Does PROT.DAT's TOC leave gaps between entries?no - it is a gapless partition
Why

The resolver sizes an entry as the gap: FUN_8003E8A8 loads TABLE[idx+2] and TABLE[idx+3] (0x8003E8F4 / 0x8003E8F8) and subtracts them at 0x8003E90C. On the disc the 1234 LBA words from toc[2] run strictly ascending from 121 to the terminator 59206, which is PROT.DAT's own sector count, so each entry's extent abuts the next and nothing lies past the last. Any "unindexed gap between entries N and N+1" reading is the superseded over-reading entry size (prot.md).

disassembly + capture
PROT 0867's trailing 0x14000 slotsraw 4bpp PSX TIMs, not LZS monster blocks
Why

Slots 187..194 open with the TIM magic 0x10 where a block carries dec_size: CLUT to VRAM (0, 482) 256x1, page to (384, 0) 64x256 halfwords, 33,312 bytes, rest zero; row 482 sits under the monster CLUT base 484 and x = 384 is monster page origin 1. The same TIM shape sits inside PROT 0892. LZS-decoding the magic as a length "succeeds" against the zeroed ring buffer. Instrument: asset account (byte-accounting.md).

capture
summon.dat's seven unclassified slotsthe big-summon third members
Why

PROT 0893 slots 77 / 81 / 85 / 89 / 93 / 97 / 101 are the raw slot of each big-summon group: id 0x9A gives base 0xCB (slot 0x4B + 2 = 77) and id 0xA0 gives 0xE3 (slot 0x63 + 2 = 101). The consumer is the applier FUN_801F12D0's arm gated on ctx[+0x277] >= 0xCB (sltiu 0xcb at 0x801F16A0, i.e. exactly that id band): a 240-entry CLUT to (0, 486), a page from +0x1E0 to (448, 256), and 0x2188 words from +0x81E0 to *0x8007B85C + 0x44000 (0x801F16A0..0x801F1774), tiling the 0x10800 slot. See summon-readef.md.

disassembly
Do a MAN's partition offset tables tile its data region?in order, data-region-relative
Why

Record offsets are relative to data_region_offset (0x2B + 3 * N). Over every scene MAN on the disc - the type-0x03 descriptor of all 90 scene-bundle tables, LZS-decoded - the flat [P0..P1..P2] table starts at 0, is strictly ascending, and stays below u24_at_28, so the records occupy [data_region_offset, data_region_offset + u24_at_28) in table order with section 0 after them. asset account on five MANs finds the bytes between consecutive starts claimed, one byte of padding left. The one type-0x03 descriptor that fails is PROT 0874's, which is not a scene MAN.

capture
Where does an overlay image's code stop?at the word after its last jr ra
Why

Every complete MIPS body ends in jr ra, so the data segment begins after the last one; the only exception is a body the sector-granular PROT extent cut short, which still carries a RAM-page lui in its tail (PROT 0902 / 0977 / 0979). Corroborated by the slot-B frame-matched partition ending at the same word in all thirteen images. disc-coverage.py's data_floor.

inference + capture
Is a caller-site citation checkable, or only assertable?checkable - the disc word at the cited address settles it
Why

A citation of the form "called from 0x..." is true exactly when the word at that address decodes as a jal / j / conditional branch to the entry. check-port-provenance.py's site_relation reads it off the bytes, so a citation that names the wrong site fails rather than reading plausibly.

inference
Which dumps do the overlay_dance_* prints above 0x801D6818 really come from?PROT 0972, the fishing overlay
Why

PROT 0980's own content is 0x8000 bytes from base 0x801CE818, i.e. it ends at 0x801D6818; 0x801D73B8, 0x801D7D44, 0x801D7DD8, DAT_801D8610 and DAT_801D8778 all sit above that and below PROT 0972's 0xB000. Byte-checked: 0x801D73B8 is overlay_fishing_0972.bin file 0x8BA0 instruction for instruction. Rows in overlay-va-aliases.md.

disassembly
Which status effects is each enemy immune to? (community: SPD-down lands, ATK-down never)no per-monster immunity - it is the scripted-fight boost profile
Why

Seru-magic stat debuffs are the side-effect FUN_801F3D3C stages (0x801F6870, 5-20% by magic level) and FUN_801DDB30 applies per hit. In a scripted fight (ctx[+0x287]) the stager drops the effect when the target's base stat differs from the record: the boost has moved ATK / UDF / INT (never land), SPD / AGL land once, MP every hit; four casts in five are suppressed unless the enemy is weak to the element. Random encounters: everything lands. battle-formulas.md.

disassembly + capture
Which scene bundles carry a type-0x14 FLAG descriptor?28 of 105 tables, always the last slot
Why

Re-measured over every extracted entry: the FLAG descriptor appears exactly once on 28 of the 105 descriptor tables, whose counts run 4 to 7 (nine count-4, four count-5, three count-6, twelve count-7), and in every one of the 28 it is the last entry. It is not a property of a bundle's descriptor count; the "every count-4 or count-5 bundle" reading pairs two properties that are only correlated.

capture
Why do the save sub-screens read as unreached by every ladder?an entry-context gap, not a wiring gap
Why

SaveScreenFlow only ever constructs the memory-card context, so the sub-screens behind the other entry contexts have no route in even when a ladder drives the screen; the shop entry 0x801DC89C reaches three bodies by contrast. Adding a ladder does not move the rows - the constructor has to offer the other contexts first.

capture
Do battle_cursor_pose's routines have a caller?yes, all of them, in retail and in the port
Why

Every routine the module ports is reached by jal in PROT 0898: FUN_801D32BC from six sites (0x801D4010, 0x801D4128, 0x801D4750, 0x801D5690, 0x801D56A0, the round reset 0x801D8910), FUN_801D57E8 from 0x801D4414 / 0x801D4434, FUN_801D5778 from 0x801D50A0 / 0x801D50F8, FUN_801D9AE8 from 0x801D4C04, and FUN_801D5718 from eighteen. The port wires the cursor step and the three placement copies into the command session and commit log; the pool release carries REPLACED-BY:. The old "link-time dead code" verdict described unwired Rust items, not retail (falsified).

disassembly
Does the browser play page emit audio?yes
Why

Intercepting the onaudioprocess setter, town01 gives 82 non-zero blocks out of 82 and the boot chain 76 of 76, and the attract FMV plays with audio. A probe that registers its listener before the mixer installs onaudioprocess watches a handler that is later replaced and sees silence by construction - an observer placed inside the thing it observes.

capture
Why three big PROT entries carry a third of their bytes as residuethe consumers declare it - the extents are slot fill
Why

Entries 867, 893 and 894 are read by seek-and-fixed-length transfers, so the slot size is the claim, not the payload: FUN_800542C8 seeks (id - 1) * 0x14000 and reads a literal 0x28 sectors (0x80054608), and FUN_801F17F8 seeks index * 33 * 0x800 and reads 0x10800 (0x801F1948 / 0x801F1958). Each entry's extent is an exact multiple of its own slot stride - 194, 103 and 78 slots - so the bytes past a slot's content are declared fill, not unexplained data. Last-sector slack past a stream terminator is the same shape.

disassembly
The "64 runs" in the residue reportsan instrument truncation cap, not a disc figure
Why

A residue report that prints at most 64 runs per entry gives a floor: the real counts are 194, 92 and 84. No entry's largest run is capped, so the summaries read right while the totals do not.

inference
Which shipped scenes carry field-VM ops 4C EA and 4C 52?three and seventy
Why

4C EA has one carrier per kingdom map bundle; 4C 52 is the chest script's item consume, seventy sites across twenty-five scenes, one text line into each record (a walk that ends at every record's first 0x1F segment reads one and three). The instrument is asset field-op-census over every carrier, cross-checked against the independent camera-arm census; its zeros separate "no fixture drives this" from "there is nothing to drive". field-op-census.md

capture
Why do two identically-shaped toc[p + 2] expressions disagree?they use different p
Why

0x801C70F0 receives a 3-sector read of PROT.DAT from byte 0 - header included (a0 = 3, a1 = 0x801C70F0, a2 = 0x80, jal 0x8005E9A4 at 0x8003E624) - so the runtime table is the file's words, header and all. FUN_8003E8A8(a0) therefore reads RAM words a0 + 2 / a0 + 3, which are toc[a0] / toc[a0 + 1] in prot.md's header-excluded convention: the runtime index is the extraction index + 2. Checked against entry 981 - the TOC word at file 8 + (981 + 2) * 4 gives LBA 47798, and the extracted image is byte-identical there.

disassembly
Is a resolution ratio over lui pairs a base test?no - it is one-sided
Why

A base whose two high halves catch few pairs scores perfectly on all of them, so the metric ranks a base by how little of the image it explains. On PROT 0896 it reports 65 of 65 at the refuted slot-A base and 110 of 177 at the base the call graph recovers, and it cannot see the call graph at all - which is where an image's own structure is. static-overlay-pipeline.md

capture
When may a dump's filename credit an extent?only where something else in the same image confirms by bytes
Why

An extent whose printed instructions are outside the encodable grammar is unverifiable, and crediting it on the dump filename alone makes the filename the evidence - exactly the case where the dump program's base is wrong, so the extent lands at an arbitrary offset in a file that never held it (three such extents fall inside 0896). The byte-accounting walker credits such an extent only where something else in the image confirms by bytes, and reports what it declines. byte-accounting.md

capture
Can a measurement tool be stale the way its cache is?yes, and more quietly
Why

A committed sweep CSV and a "fresh" per-entry run can disagree with the stale side being the binary: a target/ built in another checkout reproduces superseded figures exactly (0970 without the zero-match rule, 0895 without the init_pak walker). A superseded documented figure reappearing is a stale instrument, not a finding about the disc. byte-accounting.md

capture
Do two host kernels paired by name do the same work?not necessarily - and an empty body pairs with anything
Why

The host-drift gate's tier-11 alias row pairs kernels by name plus a stated reason, so an empty body pairs cleanly - on record, a native {} tick_field_prop_anims against the browser's drive_npc_clips. Tier 12 answers the next question with the only evidence a source scan carries - the set of engine functions each paired body reaches, host helpers followed transitively - and states its own hole: names that are also ordinary std methods are excluded wholesale, so a divergent engine call spelled insert is invisible. host-drift.md

inference
What is PROT 0976's pointer table at 0x801D7134?eleven lines of the Baka Fighter help panel
Why

The eleven words run 0x801CE948 down to 0x801CE818, which is the slot-A base itself - they address the image's own head string pool at +0x00..+0x130, emitted in reading order over a pool the compiler laid out backwards, and the lowest word points at the leading NUL, so the eleventh line is a deliberate blank. FUN_801D6CBC walks them at 0xD px pitch inside a 240x143 frame; its one caller is the cabinet SM FUN_801CF388 at 0x801D1638, the "How to Play" arm. minigame-baka-fighter.md

disassembly
What is a scene bundle's last-sector residue?the packer's buffer - an earlier entry's bytes
Why

The bytes after a bundle's last descriptor's LZS stream are byte k of the nearest earlier TOC entry whose extent reaches k: 80,337 of 80,337 bytes across 90 of 90 bundles. The same leg closes the lzs_container, pack and bse_bank tails, and the donor can be any entry. legaia_disc_audit::inherited_tail::buffer_run; byte-accounting.md

capture
Is the packer's buffer run confined to an entry's last sector?no
Why

PROT 0976's last 0x98C bytes equal PROT 0970's bytes at the same offsets (re-measured: the same-offset suffix match stops at exactly 0x98C), longer than one sector, so the suffix is searched over the whole entry. The last-sector bound was a property of the scene bundles, whose residue happens to fit one sector.

capture
Where does a slot-B record chain stop?at eight zero bytes
Why

No consumer loop bounds the record band, and a walk that chains past the padding reads an image's zero bytes as a [model_sel 0] record on into its donor. The Rust and Python walkers both stop at eight zero bytes; twelve images' cuts depend on it, and PROT 0944's lands at 0x199C with donor 0942 on both instruments. slot-b-module-layout.md

capture
Why did PROT 0780 (edteien) rank as scene_event_scripts residue?its prescript holds two records, under the standalone count floor
Why

Its prescript holds two records, under the standalone detector's count floor, so the walker never starts - "walker tail" is the wrong verdict. The class selects the walker instead (the entry is already placed) and reads the records positionally, as the scene loader does.

capture
Is a dump that opens on data credited as code?no - the extent is refused
Why

FUN_801CF5D0 walks 3,264 bytes of PROT 0898 read-only data - two switch tables and the Seru banner pool - printing table words as lb ra, 0xNNNN(zero). An extent opening on that $zero-absolute signature is refused as code; 68 such extents in 0898. byte-accounting.md

capture
Is a #[wasm_bindgen] export visible to any cargo gate?no - it is a property of the impl block
Why

A method in the plain impl LegaiaRuntime rather than the exported block compiles, passes the wasm32 tier, cargo check, clippy and the drift gate, and is not a function in the browser - where a page try that falls back to null draws nothing (the case on record: boot_title_backdrop_draws_json). Only a headless run of the built bundle sees it.

capture
How do you find a host's call sites for a web-only name?scan [.:]name(, not .name(
Why

A method-call scan misses path calls (Type::name(), and 31 of 32 "web-only" battle-presentation names have native call sites in that form; the 32nd, packet_color::hybrid, is the page's WebGL vertex-colour stream, which the wgpu fragment shader replaces.

inference
Can a probe cross a field door without a pad ladder?yes - by tile poke
Why

A walk-on door is an exact tile match in the .MAP kind-1 trigger table, so writing the player object's position onto a door tile crosses it in about ninety vsyncs; a door that opens a Yes/No picker needs one confirm press too. It measures arrival, never locomotion. scripts/pcsx-redux/autorun_w5a_poke_walk.lua.

capture
Does any battle routine carry its own generator?no - one overlay PRNG, for ribbon geometry only
Why

A scan of SCUS and every based overlay for jal 0x80056798 finds 163 sites in PROT 0898 and 58 in SCUS, and no inline LCG constant anywhere; the battle overlay's only other generator, FUN_801D0290, feeds ribbon geometry. Every modulo checked around a draw is a signed div or a reciprocal (0x51EB851F for /100, 0x55555556 for /3), equal to an unsigned % on a 15-bit draw (battle-formulas.md).

disassembly
Which engine draws hand a raw LCG state, or a private seed, where retail calls BIOS rand?none; every port draws the world stream
Why

Every draw is shaped through bios_rand_shape. That includes the battle camera's shake pair (FUN_801D9D30, two jal 0x80056798), strike-loop yaw coin and per-art track column (FUN_8004E13C), the field follow ease's shake (FUN_801DB510), the field motion VM's draws (FUN_80038158), and the Muscle Dome damage rolls (FUN_801DD0AC). A subsystem without &mut World borrows World::rng_state and writes it back (drive_on_stream, Camera::tick_on_stream, resolve_turn_on_stream). A shake at rest draws nothing (battle-formulas.md).

disassembly
What completes a tmd_size_prefix entry's truncated TMD at load?nothing - no retail entry is truncated
Why

All 182 entries that open with a type-0 chunk header and the TMD magic at +4 hold the whole TMD chunk in their own sectors ((header & 0xFFFFFF) + 4 <= len), and asset categorize puts none in the class; no 12 KB entry carries the magic. The detector is a regression guard pinned at 0 in validation_suite.rs. scene-bundles.md

capture (disc census)

Long write-ups in this area

Which live ports cover only part of their routine

Status: resolved - Evidence: disassembly + capture. Every behaviour-changing residue of the 316-tag audit's 43 partial ports is ported, or closed without a port.

Ported, on every host that draws or drives the routine:

  • Field actors and scripts: the encounter reroll FUN_801DDF48 and its global _DAT_8007B5FC; the actor tick's move-VM step when +0x54 < 0; FUN_801DBF9C's parameter stream at its true byte offset; the entry-context park's release; the actor allocator FUN_80020DE0, which resets a reused pool slot's handler, reflection link and state words; the model setter's 4C 50 re-stage through World::npcs.models, which both hosts re-bind from, and the player-mesh rebuild for 4C 50 aimed at F8; FUN_801D1BA0 / FUN_801D1EC4's player CFlag bit 24; the system channel's clip reset on every unlocked tick.
  • Settle and warp: the settle's clip tail with the timed kind-0 warp; its 4C CE clip override _DAT_8007B6AC; op 0x22's clip-base write; the warp's 0x801DA7F0 tag (the 4C E1 text balloon, which the warp tears down) and its 0x80000 same-tile re-poll.
  • Camera: the follow camera's shake consumption and mode-5 focus; FUN_801DB510's two hold gates (the reader half - the writers were already ported).
  • Audio and text: the SFX ring's three producers; the narration roller's config block.
  • Tile board: event-cell flag writes and trigger exit (states 3 and 8), prompt and fade states, step cue, bonk, run and idle clips, octant facing. No retail scene installs a board, so these rest on disassembly.
  • Menus: Rula and Riremito, reached by the Door of Wind / Door of Light through the pause-menu session as in retail.
  • Minigames: Baka Fighter's and the slot machine's face buttons; the strip emitter FUN_801D31B0 and the dome course card FUN_801D042C, each ported whole; the dome hub's subtractive shade on all three hosts, its two waits and first-visit lines; Baka Fighter's impact pair, cameo and cell blit, and its display clip - the actor's +0x5C / +0x68 pair the clip selector plays out past the booked exchange, which all three hosts pose from (minigame-baka-fighter.md).
  • Battle flow: the initiative sweep's dead-slot refund and tie list; the escape timer's HUD; the "nobody can act" 0x1E -> 0x6E round with its ctx[+0x25] skip count; the 0x801F696C swing drift; state 0x3E's class-5 arm at 0x801E3F2C with its (rand % 2) * 2 camera draw; the commit's clip-tag ladder; the Seru absorb's kill-check gate.
  • Battle special word and passes: the special-battle word's readers (the wipe rule 0x801E6578, the arena Run arm 0x801D322C, the two result-window calls in FUN_8004E568); the 0x801F0518 flag write, the ribbon's per-clip caller and the War God Icon's per-stage bump. The shadow's +0x6A flag is the after-image bracket the engine draws; the ghost pass's gp+0x330 gate passes on every battle state and ctx[+6] comes from the flow mirror. ctx[+0x26B] follows a PCSX-Redux capture of the victory load hold - raised for its first 28 of 80 vsyncs (one capture), then idle; the loss window 0x42 draws on both hosts (battle.md).
  • Anim: FUN_800480D8's schedule lives in engine-battle-vm::battle_actor_tick, outside engine-render, so engine-core reaches it: World::battle_actor_draw_plan runs it with the tint pass per body per frame on both play hosts.

Closed without a port:

  • FUN_801CF8AC: only the +0x10 & 3 exempt early-out changes behaviour. The no-class arm is unreachable - bit 17 is never cleared on the disc and all three callers keep only & 1 of the result - and the +0x98 link is overwritten before anything reads it.
  • FUN_80020F88: REPLACED-BY scene-build mesh binding plus the live model seat.
  • FUN_8004DA00 only seeks the drive, and FUN_80064090 is unreachable (settled).

Audit rows not named here were not re-read against the fixes; start from the audit's own evidence column before trusting one.

What is in the SCUS_942.54 code gap

Status: resolved - Evidence: disassembly for the identifications, capture for the reachability split.

The gap the citation-denominated instruments cannot see is mostly unreferenced library code. Working the disc-denominated gap list to exhaustion yields about 95 function entries; a five-form reference sweep over each, across SCUS_942.54, the based overlay images and every PROT entry, splits them roughly three quarters / one fifth / three:

  • No reference of any form, anywhere (about three quarters). Ghidra builds functions from the call graph, so a routine nothing references gets no function record, no dump and no citation; only a byte-denominated worklist finds it.
  • Referenced from SCUS_942.54 (about a fifth). Mostly reached through the entry-stub / init path, or from a body whose own analysis was incomplete.
  • Referenced only from an overlay (three). Live routines whose only caller is in an image a SCUS-only analysis never sees - "zero static callers is not dead", surfacing as a coverage gap.

Four recovered entries are game logic rather than library material, written up in the function directory: the inventory count-add primitive carrying the 99 stack cap (0x80042FE8; the cap is slti v0,v0,0x64 at 0x8004302C and li a2,0x63 at 0x80043038), the CD-read retry step and subsystem mode toggle (0x8003F210 / 0x8003EDAC), and a camera preset (0x800260DC).

The reachability split rests on find-address-word-refs.py, whose negative covers static references only: a computed target assembled some other way, or a caller in an overlay never extracted, would not appear.

Owning page: disc-coverage.md.

Source: docs/reference/re-settled-threads/measurement-corpus.md

See also

A thread whose verdict no longer matches the disassembly moves back to the open list; the row here is not edited in place.