Settled reverse-engineering threads
The questions about Legaia's runtime that have an answer - and, for each one, how firmly that answer is pinned. This is the archive half of the RE register: the live hunts are on open RE threads, and the disproved readings on do not re-walk.
How to use this page
Look here when you need the answer to something, or when you are about to build on a claim and want to know what it rests on. Each row carries a one-line current answer; byte-level detail lives on the linked page, and the full working notes live in docs/reference/re-settled-threads.md.
resolved is a claim, not a warranty. That is what the evidence column is for.
What an evidence grade means
Every settled row is graded by what its own stated evidence actually rests on. Where a row cites more than one kind, it is graded by the weakest load-bearing claim - the one that breaks the conclusion if it is wrong.
| Grade | The row cites |
|---|---|
| disassembly | Instructions, addresses, opcode encodings, branch or store sequences. The strongest grade. |
| capture | A runtime capture, save state, probe, firehose, or disc-derived oracle. |
| decompiled-C | Ghidra's C output, a FUN_x(...) call signature, a Ghidra label, or a claim about store order / store count / a boolean operator with no instruction behind it. |
| inference | Reasoning 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. Most are probably right; the point is that none has been checked, and every claim falsified in the last audit wave would have graded decompiled-C. The C is a rendering: dropped register arguments, || printed as nested ifs, reordered or omitted stores, and hand-written annotations read as fact have each already put a wrong statement on these pages.
World map / kingdom bundles
| Thread | Status | Evidence | Current answer | Detail |
|---|---|---|---|---|
| World-map walk-view continent ground render | resolved | capture | A procedural heightfield (FUN_80019278 bilinearly samples the +0x4000 nibbles) with per-cell texturing from the object record +0x14..+0x16; slot-1 pack meshes are only sparse landmarks. Shipped in the engine. |
world-map |
DAT_8007C018[45..53] mid-load vertex-pool pointers |
resolved | inference | Structural: DAT_8007C018[i] is meaningful only for i <= DAT_8007BB38 (the walker counter); entries above it - including 45..53 in small field scenes - are stale carryover, never dereferenced. No per-index "vertex-pool" semantic. |
memory-map |
| Field decoration path - does it dispatch the NCC light handlers? | resolved (no field light; depth-cue only) | capture | It does not. The field decoration path never reaches the NCC light handlers - field shading is the GTE depth cue alone, with no light source applied. | renderer |
| Kingdom slot 4 - per-record semantic + consumer | resolved (read in place; attr render-unused) |
capture + disassembly | Each 8-byte record is a GTE vertex: the cluster-A handler FUN_80044c14 loads its two words into the GTE vertex registers and RTPT-transforms them, and each slot-4 body is a self-contained render packet (header + object-local vertex pool) walked in place - there is no transcode and no separate command-stream builder. kind tags body class/scope (the kind 1 bodies are byte-identical across all three kingdoms - a shared mesh library); attr is genuinely per-vertex yet the full handler-family sweep finds no reader - authoring data the render path ignores. |
world-map-overlay |
Battle / arts / level-up
| Thread | Status | Evidence | Current answer | Detail |
|---|---|---|---|---|
| Arts command-input UI decomposition (the Muscle Dome runs the standard battle input) | resolved | capture | A live dome match in the static recomp, read through the runtime's gpu_frame_dump GP0 packet ring + a same-moment full-VRAM dump, pins the input screen piece by piece: the High/Left/Right/Low chips are boot-gap widget-page hexagon pieces + baked label strips + gold diamond ends; the input bar is the tiled maroon widget bar filling with committed-command pennants at cost-wide pitch; the right-hand AP plate reads the Spirit gauge (the entry budget's only visible form is the bar); Triangle cycles a five-row-per-page learned-arts window (system-UI interior tiles under a 0x40..0x88 gouraud) whose name / arrow-string / AP columns are the SCUS arts-name table's own, in orange sub-palette 15; the green Triangle circle is its own tiny gap TIM at PROT.DAT 0x7B00. Flow pinned live: per-press budget debit + queue append, auto-end on exhaustion (0x50 → 0x5a), the Begin|Reselect chain (0x6e), Triangle inert with no learned arts. |
muscle dome · arts gauge |
| Encounter MAN sub-section layout | resolved | decompiled-C | FUN_8003AEB0 walks the MAN multi-section header; the region table is a count-prefixed array of 18-byte records (kind + tile-space bounding box + a per-region camera preset). The camera payload bytes[5..17] is decoded byte-for-byte: three mode-keyed splits on byte[5] >> 4 into the 0x8007B607..27 camera globals (loader FUN_801dbc20, builder FUN_801dab90). Residual: the mask-kind records' scratchpad side-copy (0x1F8003E8..EB) has no traced reader. |
encounter |
Endless camera orbit (Gaza 2 softlock) - the 0x19 attack-approach park |
resolved (caught live; root-caused; disc fix shipped) | capture + disassembly | State 0x14 (attack approach setup) looks up the approach-transition clip - action tag 0x20, distinct from the tag-1 "Move" locomotion loop the enemy viewer shows (that one is only played inside the walk chain this gate protects) - in the acting monster's action table (FUN_80050E2C over record+0x4C); when absent (bosses generally never walk; Gaza's parked-save table reads [00 01 02 03 04 05 0B 0E 13 0C 23 23]), the fallback drops straight into the state-0x19 range re-poll, whose SM arm has no movement code and no timeout. The staged Move clip's playback normally still closes the gap during 0x19 (~19 units/vsync, measured); in the caught parks the clip engages and dies ~12 vsyncs in (anim pair back to 0/0, frozen beyond reach), so the attack waits forever while the idle camera orbits. Trigger reproduced: a summon immediately followed by the boss's melee; the anim-driver field the staging leaves stale is pinned in the next row. Caught live by the poll-only dynarec-speed hunter (scenario battle_gaza2_park_0x19); the stall counter ctx[+0x6D4] feeds only the arms-resolver roll. Fix: legaia-patcher --approach-softlock-fix - a nine-word guard replacing the poll's redundant facing recompute: a dead approach clip bounces the state back to 0x14, retail re-stages it, and the boss resumes walking (runtime-verified on both park savestates). The sibling 0x51 HP-settle class stays injection-only (twelve-revive retail campaign; both candidate generators measured out). |
battle-action |
0x19 fallback approach drive - which anim-driver field does summon staging leave stale? |
resolved (pinned + causally reproduced) | capture + disassembly | The battle actor's anim event-flag byte +0x1DC, bit 2 (0x4) - the stage-idle-at-clip-end flag. The summon's hit stages the flinch with +0x1DC |= 4|1; when the boss's melee follows immediately, state 0x14's fallback stages the Move clip through the tick's event-path commit, which clears only bits 0-1 where the natural-end path clears bits 0-2 - so the Move clip runs with bit 2 still set and, at its first natural end, the tick stages idle over the queued clip (sb zero,0x1da at 0x80047B44) instead of re-looping. Both halves replayed causally on the parked save (clearing the flag lets the clip loop and arrive; re-arming it reproduces the kill write at exactly the first natural end). The park save reads +0x1DC == 0 because the killing commit consumed the flag - it is only visible in flight. |
battle-action |
| Super / Miracle Arts trigger chain | resolved (all 15 Supers live-executed) | disassembly + capture | Saved-chain preseed FUN_801DA34C (verbatim +0x76F → +0x1DF copy - the char-record chain uses the queue-space encoding), queue-builder FUN_801EED1C (ActionSeed 0x0C), Miracle replacement inline, Super find→tail-replace in FUN_801EF9E4 (table-driven off (actor slot, char index); find 0x801F6524 + char*65 + row*13, replace 0x801F65E8 + char*80 + row*16, first-match-wins). The queue proper is 16 bytes at actor[+0x1DF..+0x1EE]. All 30 resident find/replace strings byte-exact vs the modeled tables, and all 15 Supers live-executed via applier-entry injection, each tail-replace byte-exact; per-character library states re-checked by crates/pcsxr/tests/super_art_queue_replace.rs. |
battle-action |
| Xain "Bloody Horns"/"Terio Punch" ignore elemental guards (community mystery) | resolved | disassembly + capture | Not an element drop - a resist-ladder bypass. Capture-class casts (spell byte +0 = 'c') run per-spell modules (PROT 944..966) whose damage calls pick one of two wrappers: FUN_801DD4B0 (finisher param_5=0, resist ladder runs) or FUN_801DD6B4 (param_5=1, the whole party-defender jewel/guard block is skipped). BH (952) / TP (953) use the bypass wrapper for their main hits; enemy ESM (966) uses the respecting one (hence Cort reads as Dark). Element attribution law + live confirmation: battle-formulas. |
spell-table |
| First boss trigger → Battle | resolved | disassembly | The scripted-battle arm is the field-VM op 3E FF <formation_row>: Zeto = garmel P2[12] row 9 (lone 0x4B), Caruban = rikuroa stager P1[3] row 17 (lone 0x49) - the same record path, entered organically by the engine (World::run_boss_stager_record). DAT_8007b7fc is closed: writer-less across SCUS_942.54 + every static overlay (absolute + gp-relative + address-materialisation sweep); readers pin it as the debug forced-battle formation id - battle init FUN_80055b6c → FUN_8005567c seeds the formation cells DAT_8007BD0C+ from it, and FUN_80046A20 routes a nonzero value to its mode-0 debug-menu exit. Retail never sets it. |
battle |
| Enemy-ally charm battle softlock | resolved (both tracks fixed) | disassembly | 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 "unbounded reroll" theory is falsified (do not re-walk). 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. |
battle |
Battle-actor +0x16E bit 0x400 applier (guard-disabling status) |
resolved (exhaustive negative) | disassembly | Bit 0x400 has no retail setter: a word-level decode of SCUS_942.54 plus every static-overlay image (all stores covering +0x16C..+0x171, pointer precomputes, ori/sllv bit-set shapes, the +0x6F6 mirror, the +0x21F deferral) finds only clears. The infliction appliers (hit leg FUN_801EC3E4, cast leg FUN_801E09F8) map kinds 3/4/5/6 to bits 0x1/0x2/random-0x38/0x1000 and kinds 1-2 to the 0x380 deferral; no kind reaches 0x400. Latent content with a full consumer/cure lifecycle. |
battle |
Effect-VM pass-1 "state token algebra" (FUN_801E0088) |
resolved | capture | Fully extracted: the "state" bytes are 5.3 fixed-point wait counters, not opcodes — the lifecycle is two countdown-driven cursor walks (master spawn cadence over 14-byte pack1 records, child anim/motion over 6-byte pack0 frames). The pack1 record fields are re-pinned (anim-batch index + delay + rotated offsets/velocities; the old sprite_id/anim_flags/tail reading is superseded), the init immediates (0x1000, 0xA00) resolve as the global motion + sprite-scale scalars, and pass 2's triangular brightness envelope + random UV-mirror bits are decoded. A faithful Pool::tick port is still owed. |
effect-vm |
| Seru-magic summon visual (e.g. Tail Fire) | resolved | capture | The player summon renders as its namesake battle_data creature (Gimard 0x81 → monster 10; disc-pinned through 0x95) via the ordinary rigid TRS-keyframe battle draw - not the move-VM stager scene-graph, and not PROT 0900. |
move-vm |
summon.dat / readef.DAT side-band streaming |
resolved | decompiled-C | Extraction PROT 893/894, 0x10800-byte slots: per-special CLUT rows + texture pages + summon-creature actor records + the player art-anim "ME" archives. Action id → slot 3*(id-1) (id < 0x9A) else 4*id + 0x63; bit 7 selects the file. |
summon-readef |
| Monster steal item (Evil God Icon) | resolved | capture | Static SCUS table DAT_80077828 + id*2 (1-based monster id): [steal_chance_pct, steal_item_id] - chance first. Not in the PROT 867 record. Byte-exact vs the published steal table. |
steal-table |
| Battle face-stamp issuing site | resolved | capture | The per-frame facial animator FUN_8004C7B4 stamps eye/mouth frames every frame (action-entry tracks +0x8C/+0x98, static per-character SCUS tables, via MoveImage; Terra skipped). The sibling-pass residue is closed: FUN_8004CCD4 is not a stamp - it is the per-frame equipment mesh-variant swap driven by the entry's third track at +0xA4 (retail windows Noa-only), re-run per after-image ghost by the arts trail renderer. |
battle-data-pack |
| Per-spell magic power / multiplier | resolved (mechanism + roll ported) | decompiled-C | Summon power is battle-state-derived, not a static scalar: roll FUN_801dd0ac → affinity scale FUN_801dd864 → finisher FUN_801ddb30, all ported. Arts/specials read the 26-byte move-power table at 0x801F4F5C (PROT 0898). details ↓ |
move-power |
| Arts command sequence - independent source | resolved | capture | The SCUS arts-name table (DAT_80075EC4) glyph string is byte-exact ground truth for every art's directional command; contract tests validate the PROT 0x05C4 record decode and the curated gamedata against it. |
art-data |
| Weapon-specialty arm width | resolved | capture | Not a runtime comparison: the Arms AP cost is a per-(character, weapon) disc byte at the weapon section's swing record +0x74 (favored 0x1E / off-class 0x2A / far 0x36), copied verbatim at battle load by FUN_800557B8. |
arts-command-gauge |
| Stat growth-rate source | resolved | capture | Resolved + wired: static SCUS tables read by FUN_801E9504 - DAT_80076918 per-character {start, max, jitter, row} sub-records + three curves; gain = (max-start)*curve/0x24C0 ± jitter. Byte-validated; wired in engine-core/src/levelup.rs. |
level-up |
Character-record HP/MP/AP pair order is (max, cur) |
resolved (relabeled throughout) | decompiled-C | Three independent sources - the walk-regen tick FUN_801D0B90 (bumps +0x106, clamps at +0x104), the per-frame stat aggregator FUN_80042558 (rewrites +0x104 from base stats + passives), and the GameShark "Infinite HP" codes (write +0x106 at every character stride) - agree +0x104/+0x108/+0x10C are the effective maxima and +0x106/+0x10A/+0x10E the currents. Beware: fresh-save fixtures mask a swap (cur == max at seed). |
cheats |
| Monster stat-record archive source | resolved | capture | PROT 0867 (the monster_data block): FUN_800542C8 streams per-monster 0x14000 LZS slots; each head is the FUN_80054CB0 stat record (name, battle-TMD offset, HP/MP, six stats, rewards at +0x44..0x49). |
monster-animation |
| Monster mesh + texture pool | resolved | capture | A Legaia TMD at stat record +0x04 in each PROT 867 block; the pool at +0x08 is fifteen 16-colour CLUTs + a 4bpp page (the loader relocates CBA/TSB per slot; disc values are nominal). |
monster-animation |
| Terra slot-3 / story-flag overlap | resolved | capture | A header-size drift: the record base is game+0x3C8 (name at +0x2A7). Terra's tail benignly aliases the story-flag bitmap - she is never a savable battle-party member. No special case. |
save-record |
Battle party meshes (PROT 1204 / other5) |
resolved | capture | Battles assemble each character's mesh at setup from the player battle files' equipment-id sections (FUN_80052770 case 4 → FUN_80052FA0 → FUN_800536BC splice), with load-time TSB/CBA relocation to rows 481-483. PROT 1204 is the Baka Fighter / default-equipment sibling. |
character-mesh |
| MP-cost ability-bit priority (half vs quarter) | resolved | disassembly | Half (0x20) wins over Quarter (0x10); the formula subtracts a shifted copy: half = cost-(cost>>1), quarter = cost-(cost>>2) (pay 3/4). All engine cast paths share one helper. |
battle-formulas |
| Scripted Tetsu encounter → Battle | resolved | capture | Reaches Battle from a NEW GAME cold boot: the MAN-derived sparring carrier (town01 formation index 4, monster 0x4F) is engaged by walking to it, the interaction probe, and the dialogue-accept - no teleport, no injection; the row selection is the standard scripted-battle op 3E FF 04 in P1[10]. details ↓ |
battle |
Spine flag 0x142 (Caruban beat / dolk-dolk2 switch) writer |
resolved (disc writers + engine port + oracle) | capture | Plain field-VM 51 42 SETs in the rikuroa streaming variant MAN
(PROT 0157: P1[10..12] + post-victory P2[50], C1 = 0x142 itself - the self-latching
one-shot), re-asserted by dolk2's carrier P1[0..1], cleared by dolk P1[26]. An earlier corpus-negative
stood only because no census walked the streaming carriers (census + pins: man_variant_carrier_census_disc.rs);
the engine sets the flag organically when rikuroa P2[50] executes on the Battle→Field edge. |
world-map |
Spine flag 0x482 (Drake mist-wall) writer |
resolved (writer-less; the "direct code path" presumption falsified) | capture | No script writer exists: every census site for 0x482 is a desynced-walker alias of Shift-JIS text - the other7 "SETs" are the full-width digit run 82 54 82 4F read one byte off as 54 82, the edbalden/eddoman "CLEARs" are a repeating full-width EXIT label table, the edbylon "TEST" is dialogue with a garbage branch target. The census carries a per-site decode-coherence flag (GFlagSite::clean) and the anchor asserts all 37 sites non-clean (man_variant_carrier_census_disc.rs); motion-VM + op-0x49 censuses find no site either. The named capture then ran: a 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; 0x482 never flips, every catalogued state through the Karisto era holds it clear, and all 37 census sites stay desynced. Verdict: no writer ever fires - the presumed direct code path is falsified, the map01 P2[34..36] C1 spawn-block never latches, and wall despawn is not flag-driven. (Flag 549's identical verdict was overturned by the 4C 0xE_ width fix; 0x482 stays all-noise under the fixed widths.) Reader side is solid: map01 P2[34..36] C1 force-walk bands (header gates, not disasm). Bonus identity: other7 = a dev-leftover earlier revision of koin3's MAN (its 8 P2 records are a name-exact prefix subset of koin3's 12; no loader in any captured code). |
world-map |
CDNAME scene-window frame (raw = extraction + 2) in Scene::load |
resolved (engine converts; misattributions corrected) | capture | Engine scene windows used raw-TOC defines as extraction indices - two entries late, dropping each block's first two retail entries and bleeding in the next block's. Corrections that fell out: the .MAP is the retail block's first entry (not "two below"); "suimon == dolk2 MAN" and "rikuroa MAN = [18,70,20]" were next-block sidecars under the wrong label; "urudre1 tests 0x15E" and "0x63A has no writer" are falsified; "0x1BE = rikuroa Zeto gate" was geremi's arrival one-shot. Head blocks (defines 0/1, inside the TOC header rows) keep legacy windows. See cdname § numbering space. |
cdname |
Motion-VM (FUN_80038158) bytecode carrier + flag census |
resolved (carrier pinned; spine flags negative) | capture | The second motion VM's bytecode source is MAN tail-section 1 (FUN_8003AEB0 installs it; FUN_8003A9D4 binds records to actors; parser legaia_asset::man_motion). Disc-wide op-7/op-8 census (man-scripts --motion-flag-census): overworld walking-band choreography (map02/map03) + one town0b clear; 0x142/0x482/0x1BE and 549 appear in no stream, falsifying the "549 set by op-7 bytecode" carrier claim. Anchor test motion_flag_census_disc.rs. |
motion-vm |
| Debug-menu "STR trigger teleports + sets flags" mechanism | resolved (no per-FMV event table; dev-menu tools explain it) | decompiled-C | The FMV dispatch record is playback-only (legaia_asset::fmv_dispatch - no scene/position/flag fields) and the two direct _DAT_8007BA78 store sites are corpus-exhaustive (raw scan of all 1235 PROT entries, every addressing form). The observed teleport+flag application is the field-overlay 0897 dev-menu toolset: warp appliers FUN_801EE094 ("ON RIREMITO,") / FUN_801EE328 ("ON RULA,") for MAP CHANGE, and the EVENT FLAG editor (FUN_801DBD04, cursor over flags 0..0xFFF, SET/CLEAR at cursor). The FMV corpus states were produced by dev-menu pointer-mediated global editing (invisible to static addressing-form scans). Do not re-walk the "per-FMV event table" shape. |
cutscene |
Spawned-record player-channel (0xF8) ExecMove/HaltAcquire handshake |
resolved (engine completion model) | inference | The timeline stepper models the handshake directly: A2 F8 ExecMove arms an in-flight countdown (CutsceneTimeline::player_move_frames) and C3 F8 HaltAcquire parks at the op until it drains, then steps past by encoded width - so door-cutscene records reach their trailing 0x3F and driven hops land (jou→jouina and the full castle chain to jouinc; chapter-1 hub oracles). See cutscene § player-channel completion. |
cutscene |
| Equipment stat-bonus table - slot model | resolved (slot model + passives) | decompiled-C | DAT_80074F68 (8-byte stride) decoded from FUN_801CF650: +0 INT, +1 ATK, +2 UDF, +3 LDF, +4 SPD. The four +7 categories are the weapon/armour slots; accessories are absent. |
equipment-table |
Flag 0x63A - the vell/vozz P2[7] gate with NO script writer |
resolved (script writers exist) | capture | vell P2[7] and vozz P2[7] carry byte-identical C1=[0x63A, 0x7] gates. The "no script sites disc-wide" premise was the CDNAME +2 skew: under the fixed 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), retockin (PROT 0281 P2[7]/[8]) and edretoin (PROT 0800 P2[7]/[8]) - all late-game beats, so the vell/vozz 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. NB the old row's watch target 0x800858C7 was mis-derived; the byte for 0x63A is 0x80085758 + (0x63A >> 3) = 0x8008581F. |
world-map |
cave01 P2[16] (the 0x15D entry-key setter) - what spawns it |
closed (slot-counted spawn chain) | capture | P2[16] is spawned by 44 1E at P2[12] body +0x1C, gated by an op-0x4E slot-table compare (4E 00 50 08 00 06 00: skip while slot 0x801C6460[0] < 8); P2[12] increments that slot and is spawned once per interaction by the five creature-interact scripts P1[3..7], whose talked latches 0x161..0x165 are re-cleared so interactions repeat until the count reaches 8 (P1[2] zeroes the slot). Corrected along the way: op-0x4E subs 0..9 are all the 7-byte compare-and-skip - the "sub-5..8 absolute jump" / "sub-4 rand as next PC" readings were a collapsed decomp switch. |
world-map |
Drake Castle deep interiors (jouinc/jouind) depth decode |
resolved (door-choreography families, not story gates) | capture | jouinc's 58-record C1=[0x00F] P2 family (SJIS names J01..J58) is a busy-mutex door family - each record SETs 0x00F first / CLEARs it last, a mutual-exclusion lock, not story content; 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/0x44F/0x450 + the jouinb-local 0x461). See script-vm § door-choreography record families. |
world-map |
scene_destinations P1-table scan misses P2-only door names |
resolved (P2 pass folded in) | capture | The P2-only class is the town/dungeon exit door (a P2 door-choreography record): town01→map01 (the P1 pass alone sees zero town01 destinations), retockin→retona, geremi→map02/tower — 13 scenes / 14 destinations disc-wide. The suspected jouinb→jouina exemplar is falsified: it is P1-visible. Merged kernel legaia_asset::man_edit::scene_destinations; disc pins scene_destinations_p2_disc.rs. |
world-map |
0x4C 0x51 byte +3 = [bit7 special-model | facing nibble] vs the glide-speed interim depth & 7 reading |
closed (facing wins; two different ops) | disassembly | Raw asm settles both halves: 4C 51 case-1 consumes byte +3 only as [bit7 special-model | facing-LUT nibble] and carries no speed operand (byte +4 = move-anim id); the depth & 7 base-step selector belongs to the walk-kernel op 0x47's own third operand (FUN_8003774C; ops 0x37/0x41 use (op0>>5 & 4)|(op1>>6)). No motion-bytecode synthesis exists: the field-VM yield-class ops park their instruction pointer at actor +0x94 and the walk kernel interprets the record bytes in place. See field-locomotion § NPC glide speed. |
field-locomotion |
| dolk2/rikuroa MAN source (the "v12-embedded MAN" was an over-read) | closed (streaming carrier) | capture | 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 the extended-footprint over-read onto the next scene's base+3 bundle (byte-identical to suimon/geremi's - see scene-v12-table § over-read). The scene's real MAN is the type-3 chunk of its block's standalone data_field_streaming entry (dolk2 extraction 70, [29,73,17]; rikuroa extraction 157, [13,29,64]); the live script heap at the Caruban beat byte-matches PROT 0157's chunk. Engine: the field_man_payload streaming fallback (v12_bundle_man_disc.rs) supersedes V12Embedded for these two scenes. |
scene-v12-table |
kor-family op-0x49 flag window [0x138..0x13F] - what the 8 flags gate |
closed (Uru Mais warp-pad destination memory) | decompiled-C | Each flag is one destination row of the Uru Mais dream-shrine teleport-pad picker: the pad records (kor P2[17..20], kor3 P2[9..12], kor4 P2[4..7]) clear the window, pre-set their own row (kor → 0x138..0x13B, kor3 → 0x13C/0x13D, kor4 → 0x13E/0x13F), run the FUN_801EF014 picker, then an 8-way test ladder executes a named 0x3F SceneChange per row (4 KOR entries, 2 KOR3, 2 KOR4). Descriptor +2 = first visible row, +3 = visible row count, so the paired descriptors are the full 8-row menu vs the rows-4..7 chambers-only menu (state flags 0x136/0x137). Confirming a different row sets base + selection; picking the current row or cancelling sets nothing, so the ladder falls through to the stay-put arm. |
functions |
Battle stage backdrop: which scene_tmd_stream a scene fights in |
resolved | capture | 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. |
battle |
Battle-stage overlay band (+0x47) |
resolved | decompiled-C | FUN_800520F0 pages a per-stage slot-B overlay via FUN_8003EC70(_DAT_8007B64A + 0x47), skipped when the id is 0 - which every catalogued battle but the Tetsu tutorial reads. |
battle |
| Battle-intro tutorial boxes (Tetsu sparring fight) | resolved (machine pinned, ported and wired) | decompiled-C | The prompts live only in stage overlay 967, so porting the battle SM alone could never emit them. FUN_801F6B70 is a 91-entry jump-table hook on the flow-state byte ctx[+0x06] with just nine live slots. |
battle |
Battle command-flow byte ctx[+0x06] |
resolved | decompiled-C | The other battle SM - FUN_801D0748, the menu half, distinct from the action SM's ctx[+0x07] and overlapping its value space. Its selection band is regular decimal tens 30..120. |
battle-action |
Per-spell magic power - residual
The 0x801F4F5C table is special-attack-only (party arts take per-strike art-record power); the aux effect tables are parsed, move-FX scene-graphs render through the ported move VM, and both affinity directions are wired. Residual: live note-on wiring of the resolved sound cue; the retail draw transform of a move-VM part (the FUN_80021DF4 render tail); the live slot-7 summon's HP at roll time (modelled as spawn HP).
Scripted Tetsu encounter - resolved
Launch mechanism, carrier identity/placement, picker, and the walk-talk-accept leg are all derived from disc bytes. The formation-row selection is the standard field-VM scripted-battle op 3E FF 04 in P1[10] (MAN body 0x01B67) - the same case-0x3E direct-install arm as garmel's Zeto (3E FF 09) and rikuroa's Caruban (3E FF 11) - with row 4 = the lone-Tetsu formation. The opening carrier reposition is P1[10]'s 4C 51 15 0E 07 22 NpcRun → tile (21,14) = world (2752,1856) (bytecode-derived, not a bare constant), and the spar Yes/No is a MES option picker (0x29 open + relative-jump table, handler FUN_80038050, ported as legaia_mes::Picker::jump_target), not a field-VM opcode. Anchor rim_elm_sparring_carrier.rs.
Field / locomotion
| Thread | Status | Evidence | Current answer | Detail |
|---|---|---|---|---|
| Town/field free-movement locomotion | resolved | capture | FUN_801d01b0 (field overlay 0897): camera-remapped pad, per-frame speed, 2-unit steps gated by per-axis wall collision FUN_801cfe4c and the actor probe. Footprint probes, NPC boxes, and the interact probe are byte-reproduced in the engine. |
field-locomotion |
| Field ambient animation - what makes jou's ground pulse and the water shimmer | resolved | disassembly | Three mechanisms: the bundle type-6 CLUT-walk table (12 carriers, 9 of them field water/waterfall scenes), the ambient move-VM tree the MAN P1 placements install at scene entry (FUN_800252EC → FUN_80021B04 spawn-time first run + op-0x25 fan-out), and jou's flesh pulse = the mode-3 CLUT-cell HSV cycler: op 0x2C captures a palette rect and FUN_80019D50 rewrites it per frame with integrated H/S/V adds (lightning = flag 0x364 brightness spikes + screen flash + thunder cues). Runs in the engine and the site field-scene viewer. |
docs/subsystems/field-ambient-fx.md |
Ambient render-mode 4 - what the op-0x1E seat animates |
resolved | disassembly | A cyclic VRAM-rect scroller, and it is what makes waterfalls fall. Per fired period (+0xC6 drained by the frame step alone, without the mode-3 arm's 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 (0x80022CB8..0x80022EE0). Seventeen scenes carry one at plain scene entry; sixteen scroll upward over a texture-band rect (x >= 0x200) and tunnelc's second seat is the exception that bounds the reading - a one-row rect at (0, 508) stepping right, i.e. a CLUT row walked by the same primitive. Ported as engine-core::world::ambient::vram_scroll. |
docs/subsystems/field-ambient-fx.md |
| Master ambient record 0 - what reads the 8-byte rows | resolved (it is not a stager record at all) | disassembly | 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 - record 0 of whatever bundle is installed at 0x8007B8D0, which in field mode is the scene prescript bundle (the field asset loader stores scene buffer + 0x12800 there on every load). offsets[0] is the identical word FUN_800252EC reads for stager id 0, so "record 0" and "the runtime SFX bank" name one address. Rows are the 8-byte descriptor of the SFX table, category 3 - a variable VAB slot. |
docs/formats/sfx-table.md |
Which op-0x34 sub-3 installs fire at scene entry |
resolved | disassembly | The ones the placement spawn-prologue slice executes - the carrier is not a distinguished kind of script. FUN_8003A1E4 carries its own copy of the per-actor frame slice: 0x8003A480 gates the entire pre-run on the record's first opcode being 0x24/0x25, and 0x8003A4C4 breaks the slice after executing an opcode whose full byte is 0x21. Both are "nop" to a disassembler and only one ends the slice, which is why a dialogue-bearing placed actor opening 25 / 34 30 00 installs its ambient tree exactly like a dedicated effect actor. Ported as engine-core::man_field_scripts::scene_entry_ambient_installs, taking the unconditional prefix of that slice - an under-approximation, since a flag-gated install deeper in a record depends on runtime state. |
docs/subsystems/field-ambient-fx.md |
| town0e's morph-record installer | resolved (the census was shape-blind, not the disc) | disassembly | The install is the ordinary 0x34 sub-3 arg 0 (stager record 1) - it just rides partition-1 placement 29, a full placed actor with its own dialogue, as that record's second instruction, ahead of its SysFlag.Test 0x1A park/seat branch. A census that discriminates by script shape reports nothing here; the entry-slice rule above finds it. Residual: that the entry pre-run slice reaches it is an inference from the settled pre-run mechanism, not a live capture of this scene. |
docs/subsystems/field-ambient-fx.md |
| Scene bundle type-7 slot content (VDF) | resolved | disassembly | The scene's vertex-morph delta pack (61 bundles populated; jou = 17 sub-entries of ground-vertex deltas), installed at DAT_8007B7DC via FUN_8001FBCC and consumed by the morph stager FUN_8001C604 through move-VM morph lanes. Parser legaia_asset::scene_vdf. |
docs/subsystems/field-ambient-fx.md |
| VDF morph render substitution - what draws the staged vertices, and what arms the lanes | resolved | disassembly | Per drawn group of a part whose flags carry op-0x0A's bit 0x1000, FUN_8001ADA4 calls FUN_8001C604 (rest-pose scratch copy + weighted-delta blend FUN_8005B038 + 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's flag-gated records 69/70, town0e 10/11, jagaroom 20/21), weights ramped by the FUN_80020740 envelope under op-0x32 flags. jou arms nothing at plain entry (cutscene op 0x1F only) - its entry throb in the engine is the documented vdf_pulse enhancement. Ported to the play-window, the field-scene viewer, and the web play runtime. |
docs/subsystems/field-ambient-fx.md |
| Field collision-map source | resolved | decompiled-C | Walls are painted by the field-VM 0x4C outer-nibble-7 wall-paint ops in the scene event script; the +0x4000 byte's low nibble is a floor-elevation tier; +0x8000 is a per-tile object/attribute map. |
field-locomotion |
| Tile-board grid mode | resolved | decompiled-C | Re-scoped: a puzzle / board minigame (procedural rand-filled board, per-cell tile actors) installed by field-VM op 0x49 - not town locomotion. Open sub-questions: which minigames use it; fixed vs procedural boards. |
docs/subsystems/tile-board.md |
game_mode 0x03 = field/town gameplay |
resolved | capture | Modes 2/3 are field init/run (0x03 = in-town/on-field gameplay); the handler map is read off the disc, MAPDSIP 12/13 is world-map display, and the pause menu runs under mode 23 (CARD). |
boot |
| Scene prescript: field-VM scripts vs move-VM stagers | resolved | capture | 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 install the ambience on entry (record 0 = the master ambient record in 62 scenes) and partition-2 cutscene timelines install the per-shot ids. Installer id = record index (RAM [u16 count][u16 offsets] relocation at _DAT_8007B8D0, live-pinned vs the file bundle). The "field-VM runs a record" premise was the engine's own fallback, not retail behaviour. details ↓ |
scene-bundles |
| Engine VRAM byte-exactness for town01 | resolved (major source); minor residue | capture | Single-snapshot byte-exactness is unachievable (much of the texpage band is dynamic/residual); the oracle asserts the static mask and passes on every static pixel uploaded. Residue: the x=896..1024 character-texture region + small UI residue (the (960,256..511) part is the boot-resident system-UI band). |
|
CLUT row 510 population (env meshes' (64,510) CBA) |
resolved | capture | Row 510/511 = the flat-strip CLUT band of the boot-resident system-UI TIM bundle (the prot::timpack at raw PROT TOC entry 0, below the extraction index space; uploader FUN_800198E0 flattens every declared CLUT bank to a w*h × 1 strip). Byte-identical to the disc in every captured phase, title included; the engine pre-pass uploads the bundle (legaia_asset::system_ui_bundle), and the once-unattributed page rows 456..462 are the pack's own bare row-patch members. Residue: the exact boot walker call site. |
npc-palette |
Field .MAP PROT resolution |
resolved | capture | A scene's .MAP is the entry two slots before its CDNAME block start (define − 2), identified by its 0x12000 footprint - universal; the first in-block hit is the next scene's map. Census-pinned; engine resolver corrected. |
cdname |
| World-map CLUT cycling beyond the ocean head | closed (operand table + emitter + cadence pinned) | capture | A per-column census pins the runtime-touched CLUT cells (rows 500/506/508/509); the writers are the field overlay's script-driven CLUT-cell effects (FUN_801E4C58 / FUN_801E4794) copying from strips parked at rows 498/501..505. details ↓ |
world-map |
init_data UI-tile pages / map03 texture column |
resolved (both premises falsified) | capture | The (704, 0)/(704, 256) UI-tile rects are journey-dependent residency (overworld transit leaves kingdom-bundle content behind). The map03 column at x=576..640 is resolved: the "engine fails to upload PROT 0392" premise is falsified - a direct prepass measurement shows real terrain (only 37 stray 0x3332 cells, an old-build artifact), and PROT 0392 slot-0 is byte-identical to 0391 slot-0 which the engine already uploads. Residual (low): a map03-resident capture would fully close it. |
|
Scene-transition (0x3F door) destination indexing |
resolved | capture | 0x3F ops are partition-2 MAN records reached through the partition-2 record-offset table, making variable-length door editing safe (man_edit). The 0x3E warp is the SCUS-resident mode-24 minigame door (scene-name backup/restore). |
man-relocation |
| Intra-town (house / interior) door mechanism | resolved | disassembly | Not a scene change: the cross-context form 0xA3 0xF8 (opcode 0x23 | 0x80 into the player channel) teleports within the same scene; the partition-0 records pair fullwidth IN/OUT names. The randomizer shuffles class-preserving. |
script-vm |
| Field/town environment-geometry placement | resolved | capture | Object-local TMDs in the scene_asset_table LZS streams, placed by FUN_8003a55c from the +0x8000 object-index grid. The engine renders the town, including per-vertex-RGB props; the only residue is one missing-CLUT mesh. |
field-locomotion |
| Overworld / town entrance story-flag gating | resolved | capture | 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 carry idx = ((op & 0x8F) << 8) | operand. Disc-pinned via man-scripts --system-flag-census: map01 keikoku portals C1=[0x193] (setter vozz P1[7], the only 0x193 SET disc-wide), mist walls P2[34..36] C1=[0x482], town01 dinner chain P2[4]→550→P2[5]→551. The dinner "re-fire" is falsified: SET ops already apply in the timeline stepper, and P2[5] has no walk-on trigger. |
world-map |
Overworld story-conditional destination (dolk→dolk2) |
resolved (mechanism + port) | capture | 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. overworld_portal_sites decodes the conditional pair (ConditionalDest); the seeder resolves via World::system_flag_test. Falsifies "dolk2 is reached from a dungeon interior" (no interior lists dolk2). The 0x142 setter is pinned (rikuroa streaming-carrier script records; see the spine 0x142 row). |
world-map |
Writer of the Rim Elm opening flag (549) |
resolved - self-latching script SET; the census was width-blind, not carrier-blind | capture | 549 (0x225) is the Rim Elm opening one-shot; its writer is town01 P2[3] itself - a plain 52 25 SET at body +0x3 in the very record its C1 gates (the same self-latch shape as rikuroa's 0x142), plus the gameover_data dev-copy sibling. Reader: the C1/C2 gate evaluator FUN_8003BDE0 across town01/town0b/town0c/gameover_data. Trap worth keeping: the census had missed the SET because the preceding 4C ED op had no width in the disassembler, desyncing the walk one byte short - the earlier "direct code path, capture-only" verdict was width blindness, and the whole 4C 0xE_ sub-op width family is pinned from the retail param_2 + N advances. Anchors flag_549_reader_is_the_rim_elm_p2_gate / flag_549_writer_is_the_rim_elm_p2_3_self_latch. |
world-map |
| How an NPC's facing changes after spawn - snap vs ramp, and which writer wins | resolved (two laws; order-of-execution priority) | disassembly | Two distinct facing laws coexist; which one is visible is decided by order of execution rather than by precedence in the data. | motion-vm |
| What opens an inn stay in retail? | resolved (the premise was wrong) | capture | There is no inn trigger, because there is no inn session: 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. |
field-menu |
| Retail-vs-engine NPC + story-flag state parity across the capture library | resolved (breadth oracle) | capture | The sweep oracle field_npc_state_parity_disc.rs compares every catalogued field-mode capture against a cold engine entry with the capture's DAT_80085758 bank seeded byte-for-byte. Residuals are filed as their own rows. |
script-vm |
Entry pre-run channel slice ends on a no-mask 4C 70 wall paint |
resolved (slice-continue landed) | decompiled-C | All four nibble-7 tile-flag paints continue in the same interpreter call; the apparent Yield was the label-call idiom. The pre-run and runner break a slice only on a real yield. |
script-vm |
| What transitions retail into game over? | resolved | capture + disassembly | Retail has no mode-0x12 transition. A party wipe exits battle to mode 2; MAIN INIT FUN_8003AEB0's back-from-battle arm (gated on the DAT_8007BD60 & 0x80 survivor latch and the scripted-loss story-flag latch) stores game_mode = 0x16 (CARD INIT) at 0x8003B5D4, landing on the title screen with CONTINUE preselected - no GAME OVER art, no dedicated menu. Every store PC captured live. Mode 18/19 + PROT 0902 are an unreachable dev harness; the port's three-row session stays an engine invention. |
battle |
| Mid-visit NPC re-arrangement beats (dolk2 market crowd; garmel pre-Zeto staging) | resolved | disassembly + capture | dolk2: the swap is P2[11], spawned by .MAP fallback walk-on-trigger rows (C1=0x27C, C2=0x142) - eight CC <crowd> E3 <day> position-copy seats (op 4C nE sub-3, 0x801E3108) put P1[53..60] on the day cohort's market tiles and park the day cohort at (127,127). garmel: the Zeto stager P2[12] materializes P1[3]/P1[4] beside the player; post-battle re-entries run P1[0]'s flag-consume arms. Pinned by man_midvisit_rearrangement_disc.rs. |
script-vm |
| Region story-flag gate families (record-header C1/C2 gates) | resolved as structure (play-order residual on the open page) | capture | Field-scene progression is driven almost entirely by the C1/C2 flag lists in each partition-2 record header (evaluated by FUN_8003BDE0: C1 = one-shot latch, C2 = requires-all), which the inline opcode census cannot see. Every region's families are pinned disc-static by census anchor tests: the Sebucus dungeon spokes, the Rim Elm opening/revisit/final town-state variants, Uru Mais, Nivora Ravine, the Karisto region and castle depth, and Conkram - with the low flags 0x7/0xF recurring as scene-variant discriminators. The chapter-2 spine is engine-wired through real 0x3F scene transitions with no chapter-specific code. |
script-vm |
Extraction-0874 §2 (player.lzs) F-variant pixels |
resolved (installing event named) | capture + disassembly | Both prior premises fell: the pause walk issues zero image transfers (menu-writer falsified), and the wrap-scroll-phase reading is a frame-content coincidence. The variant is a one-shot face-frame stamp: town01 opening record P2[3] (C1-gated on the opening latch 0x225) runs field-VM op 4C 60 MoveImage (852,336,6,16) → (852,268) after the white flash - the parked alternate frame differs from the boot cell at exactly the three F-variant halfwords. Live-caught at ra 0x801E1B8C; disc ops at MAN 0x735A/0x7368 (misaligned-u16 operands, which is why aligned scans missed them). |
character-mesh |
Scene prescript dual consumer - resolution
The op-0x34 sub-3 census (scanner man_field_scripts::scene_stager_installs, oracle scene_prescript_consumer_census_disc) walked all three MAN partitions across the corpus: 78 scenes install stagers, from both the placement scripts and the cutscene timelines. The install carriers in partition 1 are dedicated effect-actor records - Shift-JIS-named "effect", whole script = install id N + infinite loop - and the town "768-byte dispatch table" at record 0 is those actors' target: the scene's master ambient stager (8-byte-periodic spawn rows), not a table. A town01 field state pins the id space: retail relocates the bundle to RAM as [u16 count][u16 offsets[count]] at _DAT_8007B8D0 with count = the file record count and record 0 byte-identical at the first offset.
World-map CLUT cycling - 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 (parser legaia_asset::clut_walk). Chain: loader FUN_8001F05C case 6 → DAT_8007B7C8; field-init FUN_801D6704 spawns one render-mode-0xB actor per entry (FUN_80024CFC, accumulator seeded 100); emitter FUN_8001ADA4 case 0xB banks acc += DAT_1F800393 and on acc >= hold issues the 16×1 MoveImage, resetting acc = 0. Live traces on all three kingdoms confirm strictly constant intervals ceil(hold/dt)*dt (9/12/21 vsyncs for holds 8/10/20 at overworld dt=3), a shared spawn epoch with independent per-entry phases, and the 18-step head cycle sourcing two extra wave frames parked before the OCEAN_ANIM_FRAME0_HEAD signature (ocean frame 12 never shown). The emitter is not the script-driven FUN_801E4C58/FUN_801E4794 family - that carries only the row-498 park one-shots/fades, whose cells the slot-5 walkers then use as sources. "Dest rows = park rows + 8" is falsified (literal u16 dest cells). Engine: WaterAnim::Walk in play-window; WORLD_MAP_CLUT_CYCLE_CELLS = the slot-5 destination fold. See world-map § Ocean animation.
Text / fonts / dialog
| Thread | Status | Evidence | Current answer | Detail |
|---|---|---|---|---|
| Dialog font extraction | resolved | capture | The glyph tile page lives at VRAM (896, 0)..(960, 256); legaia-font extracts widths + atlas from the SCUS plus any in-game save state. Kept so the older "blocked on runtime trace" framing isn't re-opened. |
dialog-font |
Inline dialog-box format (0x1F-lead segments) |
resolved | decompiled-C | NPC dialogue is an inline 0x1F-lead segment pool (the NPC's full line set, not prompt + options); the pre-segment bytes are ordinary field-VM prologue bytecode. Pager dispatch, picker jump tables, and 3-row box packing are decoded and ported. |
mes |
| Tetsu 4-option spar menu mechanism | resolved | capture | A standard 0x29 4-option MES inline picker in the partner's dialogue (immediate-labels form); confirming index 2 starts the spar, driven by the dialog SM. The engine engages the carrier only on the fight option. |
mes |
| Pause Items/Magic screens: remaining sub-flows | resolved (dim-bit residual closed) | disassembly + capture | All four sub-flows traced from disassembly and ported: the window-14 target panel (FUN_801D0520; the preview modes are the permanent-stat Water previews, superseding the "HP-restore" reading), the PAGE sprite (UI-icon 0x76), the SCUS kind-4 list kernel (FUN_80032A44 + allocator FUN_80030104), and the class-0x80..0x82 Use routes (single-target apply FUN_801D8308, Door of Light/Wind FUN_801D8A58/FUN_801D8B90, Incense FUN_801D8D94); Throw Out / Arrange likewise. Engine engine-ui/pause_screens. The 0x800 dim bit is set at build time by the SCUS content builder FUN_80030628's content-id-3 case (equipment always-dim, field-usable bit 0x2, then the FUN_8003043C applicability probe); no focus-dependent write exists - the white→grey flip is the kernel mode-4 park override. |
field-menu |
Animation
| Thread | Status | Evidence | Current answer | Detail |
|---|---|---|---|---|
| Player ANM per-record layout | resolved | decompiled-C | 8-byte header + prologue + frames × bones 8-byte entries; each is a nibble-packed signed-12-bit translation triple + three u8 rotations (interpreter FUN_8001BE80), posing R·v + T about the object origin. Decodes byte-exact corpus-wide. |
anm |
| Battle anim-id space + record[0] strike family | resolved | capture | Anim ids are entry indices (commit FUN_8004AD80; idle = 0); tags 2..5/0xB form the hit-reaction family; weapon swings are the equipment-section splice (slots 0xC..0xF) plus dynamic art slots 0x10/0x11. |
monster-animation |
FUN_80047430 caller |
resolved | capture | Live-captured: a single dispatch site, the jalr at 0x800252B4 inside FUN_8002519C (the per-frame actor-list tick iterator), which calls each node's +0x0C handler slot. The anim-node tick is an ordinary list-node tick handler. |
functions |
Record[0] +0x5C pointer + art-anim stream source |
resolved | decompiled-C | The art keyframe streams are "ME" archives in readef.DAT slots 3*char+1/+2 (ported, disc-validated). The staging is traced end to end: the initiative scheduler FUN_801DABA4 stages the acting character's group per turn (main archive resident), the battle-end arms request 3*char+2 directly (base archive = the win-pose records 1..8). +0x5C is reframed as a vestigial paired-relocation field: rebased at load alongside +0x58, but an exhaustive corpus sweep finds no reader. |
battle-data-pack |
Audio
| Thread | Status | Evidence | Current answer | Detail |
|---|---|---|---|---|
| SPU reverb live routing (C7-REVERB) | resolved | capture | Retail runs the Studio C preset, master-enabled, globally; per-voice EON send is broad (BGM + SFX alike). No per-cue enable source exists. Wired at SPU init; residual: output-depth tuning only. |
audio |
| SFX cue bank routing - the category byte selects the VAB slot | resolved | capture | A cue names its own bank. The descriptor's +4 category selects the 12-byte mixer record at 0x80091508 + category*12, whose +8 is a VAB slot id: FUN_80065034 hands it to FUN_80068b98, which repoints the current-bank globals at that slot before the program / tone lookup. Across every catalogued save state record N holds +8 == N. Slot 0 = PROT 0868 (byte-matched against a live state's VagAtr page), slot 2 = the class-2 bank PROT 0869. Worth grading rather than assuming, because staging one bank fails quietly: both banks carry a UI key map at program 0, so a category-0 cue resolved to a sibling sample - a real retail blip, twice as long and a fifth lower - and peak, duration and "did a voice key" all passed. Both hosts now stage the pinned pair out of one SPU allocator and route by category; categories 6 and 11 keep falling back to the class-2 bank while their slots are unpinned. |
sfx-table |
| XA channel map / STR demux SM | resolved | disassembly | Statically decompiled from PROT 0970 + SCUS. No channel selector exists in the STR overlay: FMVs play filter-off (each movie = one (1, 0) track); the XA*.XA clip path selects CdlSetfilter {file 1, chan} per cue (FUN_8003D764). Falsified: the "\DATA\MOV.STR multi-channel container" hypothesis; the 64-byte dispatch stride (real stride 32 B - all nine retail slots resolve, every disc movie plays, town0d/uru/jouine triggers are not vestigial); the name-first "compact MV table" phase (it is libcd's CdlFILE dir cache). The engine resolver fmv_index_to_str_filename mirrors the corrected nine-slot map and the return-scene list. |
cutscene |
| Which PROT entries fill SFX VAB slots 1 / 3 / 6 / 11 | resolved (slot 6 = 0876, slot 11 = 0889; 1 / 3 are variable banks) | disassembly | The installer names every binding: a bank reaches a slot through the pair FUN_8001FC00(raw_toc_index, …) + FUN_8001E54C(category, …), and reading the install call sites closes the map - slot 6 = PROT 0876 (field init FUN_801D6704; its programs cover 29 of the 30 category-6 descriptors), slot 11 = PROT 0889 (battle-end reward resolution FUN_8004E568; its lone program matches the one category-11 cue), while slots 1 and 3 hold the current BGM / vab_01 side-band banks re-selected at runtime, so neither has a fixed entry to name. Two laws fell out of the same read: FUN_8001D424 writes mixer +8 = record index for all 16 records ("category is the slot" is the initialiser's own statement), and slots 2 / 6 share one header buffer + SPU base - the same physical bank in two modes, never open together. |
sfx-table |
XA clip-table writer + (clip_id, chan) cue census |
resolved (writer pinned; cue census decoded) | disassembly | The 0x801C6ED8 clip-table content is pinned (34 [CdlLOC][len] slots = XA1..XA34, title-capture byte-exact vs the disc files). The filler is FUN_801CFA78 in PROT 0895 init.pak (base 0x801CE818): it sprintf-generates \XA\XA%d.XA;1 per slot and fills [BCD-MSF][size] via the ISO9660 lookup FUN_8005DBB4, called once from the init boot tick 0x801CF500. The earlier "untraceable DMA/computed write" framing was the SCUS-only sweep's blind spot - the two lui 0x801c sites in SCUS (FUN_8003D53C/FUN_8003EAE4) are the readers; the writer is overlay-resident. Cue census (callers of those readers): 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); SM state-0x6E (XA9); slot machine XA1. Full deduped census in audio; dedupe by true entry extent - PROT over-read aliases callsites into neighbouring overlays. |
audio |
| Hyper Arts fanfare selector - what audio fires when a Hyper executes | resolved (per-(char, art) coin flip over a fixed channel pair) | disassembly capture | A Hyper art fires no pool shout (its action constant sits below the shout table's lo bound). The staged-animation materialiser FUN_8004AD80, on the Hyper class byte 0x1A, fires the jingle queue with jingle_id = rand()%2*3 + base - a per-(character, art) coin flip between the fixed channel pair {base, base+3} of the character's stereo fanfare bank (Vahn XA1.XA, Noa XA3.XA, Gala XA5.XA). Super/Miracle expansions take fixed ids = the bank's generic channel 1; a Miracle's finisher adds its anim cue track. No avoid-repeat memory, unlike the shout pool. All nine Hyper rows capture-witnessed; durations reproduce the 0x800788B8 table. Engine table legaia_art::hyper_fanfare::CAPTURED_FANFARES; the arts page plays the pinned cue per card. |
battle-action |
Title / boot / overlays
| Thread | Status | Evidence | Current answer | Detail |
|---|---|---|---|---|
title.pak PROT entry |
resolved | capture | No single bundle: the title wordmark TIM is PROT 888/890, the options/config-menu bundle is PROT 899, and the title-overlay code sits in the unindexed gap after entry 899. | boot |
| Title screen mode-table PROT | resolved | inference | No such entry: a pre-mode-dispatch boot routine loads the title overlay (tick FUN_801DD35C, in the unindexed PROT.DAT gap between TOC entries 899 and 900); NEW GAME crosses into mode 2. Open sub-question: which overlay owns FUN_801DD35C - its disassembly is identical across the menu, title, save-UI and shop-save dumps, and the engine ports it twice under incompatible descriptions. Closing it needs the residency check the op-0x2F thread used. |
boot |
| Load-screen panel 9-slice geometry | resolved | capture | Retail composes the 81×29 panel from 14 textured sprites sampling the system-UI sheet with CLUT (32, 511); no interior fill - the "marbled blue" is dimmed title art through the empty frame middle. Engine renders it byte-perfect. |
save-screen |
Key-item area consumers (0x800859E8..0x80085A40) |
resolved | decompiled-C | No unguarded-index consumer: the range is inventory slots >= 72 of &DAT_80085958, read by the item-menu functions via & 0x3ff masks into 256-entry item tables (bounded). It amplifies to game-state corruption (item possession / displayed ids), not a native index-OOB chain step; the OOB write at 0x800422BC remains the only primitive. |
|
| Full-window item-add OOB reachability | resolved (write primitive real; normal play cannot reach it) | disassembly | The id store sb t0,0x1818(a0) at 0x800422BC in FUN_800421D4 is unconditional and precedes the slt/beq guard that gates only the count store, so an exhausted free-slot scan writes one slot past the window - but the retail add call sites can never present a full window. details ↓ |
memory-map |
| XP-table source + reader | resolved | capture | The static SCUS per-level delta table DAT_80076AF4, read by the level-up applier FUN_801E9504. Extracted at boot; byte-validated L2 = 365 / L3 = 730 against a captured retail level-up. |
level-up |
| Overlay identity from the disc (static extraction) | resolved | capture | Overlays are clean fixed-VA copies, so each extracts statically from its PROT entry at a jal-recovered (or anchor / pointer-resolution) base; the committed map spans the slot-A family + pinned slot-B entries. Complements runtime captures. | static-overlay-pipeline |
| SCUS recomp gap - render/GTE + boot/init clusters | resolved | disassembly | The recomp's "undocumented" render/GTE + boot bands dissolve on behavior read: entry-skew aliases of documented functions, statically-linked libgte per-op wrappers with zero callers (link residue), and a dev draw cluster (wireframe box / line / digit emitters). Genuinely new: FUN_80015E90 = main(), and FUN_8004CE30 = the per-frame battle actor maintenance pass (not a mode dispatcher). The general-game band stays open. |
boot |
| Options/menu overlay PROT entry | resolved | capture | PROT 0899 at 0x801CE818: FUN_801CF650's signature byte-matches file 0xe38; the prefix is byte-identical across menu-open saves. The earlier "0896 = menu" label is falsified. |
static-overlay-pipeline |
PROT 0896 (bat_back_dat) identity |
resolved | capture | 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, "FWIN ERR %d" at file 0x3D4). 0899 ships the English label set with zero FWIN; a signature scan finds 0896 resident in 0 of 140 states, so the USA build never loads it. details ↓ |
static-overlay-pipeline |
| Slot-A scene-overlay family beyond field/battle/menu | resolved | disassembly | Pinned from the disc: 0970 cutscene/STR plus minigame overlays 0972 fishing / 0975 slot machine / 0976 Baka Fighter / 0980 dance, each anchored at 0x801CE818. Minigame entries over-read each other (phantom-base risk). |
static-overlay-pipeline |
| "World-map / save / shop" overlay PROT entries | resolved | disassembly | Not separate entries: the world-map controller FUN_801E76D4 lives in field overlay 0897, the save dispatcher FUN_801DC6B4 + shop session in menu overlay 0899 (signature-matched). They are subsystems of existing slot-A overlays. |
static-overlay-pipeline |
Debug flag 0x8007B98F |
resolved (the MSB of the debug-mode word _DAT_8007B98C) |
disassembly + capture | 0x8007B98F has zero byte-granular references because it is the most-significant byte (offset +3, little-endian) of the 32-bit debug-mode word 0x8007B98C, and code reads the word. _DAT_8007B98C is the debug gate tested by the input dispatcher FUN_8001822c plus the resident field-overlay (0897) gates, written by the shared menu/title/save-init routine. Poking 0x8007B98F = 1 sets the MSB, so every != 0 gate reads debug active and SELECT+△ opens the debug menu. Statically pinned and runtime-confirmed. |
boot |
_DAT_8007B8C2 polarity, and its writer |
resolved (docs were backwards) | disassembly + capture | The old reading had the branch sense backwards and named the wrong loader for one arm. Every site reads the flag with lh and takes the zero arm to the host-PC debug station (FUN_8003E360 falls through to jal 0x800608F0, whose body is break 0x103), the non-zero arm to the PROT-index resolver. The writer that makes it self-consistent is gp-relative: main() (FUN_80015E90) stores the flag once at cold boot, sh v0,0x5aa(gp) at 0x80015F08, taking the constant 1 from the two-instruction leaf FUN_8003F084 - an absolute-form-only scan cannot see it, and no BSS is cleared for this executable at all (b_size = 0). |
memory-map |
| New-Game opening chain + narration roller | resolved (chain + caption + roller + prologue gold grade; far-geometry residual resolved-negative) | capture + disassembly | The opening is a five-scene chain (opdeene → opstati → opurud → map01 → town01, all master mode 3, zero input), live-probe + pixel-capture pinned; the narration is a bottom-up scrolling crawl on a child-context roller actor (FUN_80037174), the "It was the Seru." caption is a pre-rendered TIM in PROT 0749, and the camera op 0x45 map, name-entry auto-open and the eye-back depth (offset-trio slot 5, 0x800840B8) are all pinned. Both former render-fidelity residuals had one root cause: the prologue gold grade is a capture-pinned palette-space collapse (L = max(r,g,b) → (L, max(L-1,0), L>>1) applied to every uploaded CLUT + the TMDs' colour packets; the per-node depth-cue reading is falsified - do not re-walk). Engine port Renderer::set_palette_grade. The far-geometry-brightness residual closed resolved-negative: reproducing retail's scene GTE far/back-colour ambient would contradict the port's no-field-light boundary, so no engine change was warranted. |
cutscene |
| Overlay-loader index off-by-2 - remaining ripple | resolved (slot A reconciled; slot-B per-spell identity capture-pinned) | capture + disassembly | The loaded entry is extraction param + 0x37F (the in-RAM TOC is raw PROT.DAT), so every historical + 0x381 attribution was 2 high. Slot A reconciled; the player, evolved (Kemaro → 918, Spoon → 919, pinned by injected casts), high, flute (0924/0925 Lippian/Spikefish), and Cort enemy stager blocks are all capture-pinned on the linear spell − 0x79 arithmetic, each loader-B-id-confirmed mid-cast with the slot-B image a full byte-match over the entry's LBA footprint; engine mirrors corrected. |
prot |
Slot-B overlay cluster (0900..0969) per-entry identity |
resolved except 0968 (that residual is on the open page) | capture + disassembly | 0900/0901 = the default move-FX pair, 0902 = GAME OVER, 0903..0913 = player summon stagers (capture-pinned per spell id), 0924/0925 = the rare-Seru flute summons Lippian / Spikefish (loader-B ids 0x1D/0x1E mid-cast, extending the player run loader = spell − 0x79 unbroken through 927; the "other enemies' specials" guess is refuted), 0926 = the unused-0x98 one-sector jr ra stub, 0957 = summon-effect strings. The 0x4000/0x4001 render-mode nodes are statically decoded (moving particle / 3D positional sound emitter) and the classification is ported. |
static-overlay-pipeline |
Full-window item-add OOB reachability - resolved
Grade: disassembly for the full window; inference for the half-window sub-case.
The OOB write is confirmed from FUN_800421D4's disassembly: the id store sb t0,0x1818(a0) at 0x800422BC is unconditional and precedes the slt/beq guard (0x800422C8/0x800422CC) that gates only the count store at 0x80042300. When the free-slot scan exhausts the window it leaves the index == end, so the id lands one slot past the window (base + end*2 = 0x80085A58 for end=128, 0x80085B58 for end=256). The window is installed only by FUN_8004313C, which installs [0,256), [0,128) or [128,256) - never the 72-slot span an earlier note recorded.
Reachability verdict: unreachable through the retail add call sites in normal play. No add caller pre-checks room - each loads an item id and jals the helper directly (shop buy-confirm 0x801C38A4, battle-loot 0x8004F380/0x8004F608, plus the menu/save/fishing/world-map/minigame/equip-refund helpers) - so the helper's own scan is the only backstop, and it holds:
- Full window
[0,256)(installed for any party of ≥2, the normal mid/late state). The merge pass keys on the id byte (andi a3,t0,0xff@0x800421F4), so each non-zero id occupies at most one slot and0is the empty sentinel; at most 255 distinct ids occupy the 256 slots, so a hole always remains and the scan exits in-window. The OOB store is mathematically unreachable here. - Half windows
[0,128)/[128,256)(a single playable member with story flag 20 clear - a transient early/solo phase). 128 ≤ 255 so the id ceiling alone does not forbid a fill, but the real disc item population is far below 128, so the scan still terminates on a hole.
A non-add path (debug menu, cheat engine, or a crafted save seeding duplicate live ids) could still force the exit with an attacker-influenced byte - outside "normal play", which is what the thread asked. Port + machine-checkable verdict: legaia_save::retail_inventory (ItemWindow::oob_reachability, MAX_DISTINCT_ITEM_IDS, OobReachability).
PROT 0896 - resolved
Host-capstone decode of the head (extraction entry 896, located by the "FWIN ERR" bytes) identifies it as the vestigial Japanese-build field-menu / config / status overlay: a Shift-JIS config/menu/status label pool, the "FWIN ERR %d" (Field WINdow) debug printf at file offset 0x3D4, real MIPS at link base ~0x801D0000 indexing the 0x414-byte character records, and head function-pointer tables across 0x801D81C0..0x801DC700 - the same window-renderer VA family as the live English menu overlay PROT 0899, which ships the English equivalents of the identical label set with zero FWIN. A distinctive-signature scan across 140 catalogued RAM states finds 0896 resident in none (positive control: the English "Battle Voices" is resident in 10 menu-phase states), so the USA build never loads it - a vestigial JP-build overlay carried on the disc.
Rendering / camera
| Thread | Status | Evidence | Answer |
|---|---|---|---|
| Does any retail shot author a non-zero camera roll? | resolved (yes) | capture + disassembly | Eight scenes stage a reachable, executing op-0x45 slot-2 roll, from 10 units (0.9°) to -660 (−58°). The port used to drop the RotMatrixZ factor and now composes it. details ↓ |
Camera roll - retail authors it
Slot 2 of the op-0x45 CONFIGURE mask is the roll angle _DAT_8007B794, the argument FUN_8001CF50 hands to RotMatrixZ (0x8004638C) as the third factor of Rx · Ry · Rz. The engine used to compose pitch and yaw only, on the assumption that retail shots rarely roll.
Eight scenes roll. Every one of these beats carries the full nine-slot mask - pitch, yaw, roll, the eye trio, focus X and Z, H, and no focus Y - every operand is an in-range 12-bit angle, and the beats of one shot repeat the same tilt, which is what an authored Dutch angle looks like.
| scene | what it is | PROT entry | record | roll (12-bit) | degrees |
|---|---|---|---|---|---|
edstati3 | Ending (station3) | 826 | P2[0] | 10, 20 | 0.9, 1.8 |
station3 | Karisto Station (late) | 616 | P2[0] | 30 | 2.6 |
map03 | World map (Karisto) | 392 | P2[10] | 60 | 5.3 |
nilboa | Nivora Ravine | 638 | P2[33] | 60 | 5.3 |
taiku | Muscle Dome | 373 | P2[27] | -120 | −10.5 |
korout | Field (korout) | 534 | P2[3] | 240 | 21.1 |
juui1 | Juggernaut interior 1 | 588 | P2[0], P2[3], P2[4] | -400 | −35.2 |
juui2 | Juggernaut interior 2 | 597 | P2[0] | -660 | −58.0 |
The two biggest tilts sit inside the Juggernaut, and the smallest opens an ending cutscene - which is where a canted camera is exactly what an author would reach for.
Roll stays a minority term: of the 371 CONFIGUREs a control-flow walk reaches, 123 set the slot at all and 15 of those write a non-zero value - roughly a third, then about one in eight. That is why "rarely" survived as long as it did. Rare is not never.
Why it took execution. A field-VM record's tail is not linearly decodable, so a decode of the corpus has to pick a resynchronisation policy, and the policy is what it ends up measuring. Three instruments disagreed:
| Instrument | CONFIGUREs reached | Non-zero rolls | What it was really measuring |
|---|---|---|---|
| Strict linear (stop at first decode error) | 21 | 0 | its own blindness - it reaches none of the eight |
| Resuming linear (advance one byte on error) | 2182 | 637 roll operands, 7 outside the 12-bit angle space | its resynchronisation into data |
| Raw byte scan (decode at every offset) | 4257 | a "2 %" ratio | its own post-hoc credibility filter |
The resuming sweep's own shortlist of "coherent" candidates - deroa, chitei2, station3, town0b, retona, nilboa, edstati3 - scores three hits out of seven and misses five of the eight real ones. That is its signal-to-noise stated as a number: half its picks are data, and it cannot see most of what is there, because the authored rolls live in partition 2 (cutscene-timeline / walk-on beat records) while the linear census walks partition 1.
The decider is control flow. Stepping the ported field VM from each record's real entry PC, under a probe host that answers every branch predicate both ways, gives the set of PCs control flow can arrive at; 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 is what identifies the resuming sweep's impossible operands (26708 is not an angle) as data.
Two corrections fall out. The parenthetical "zeroed in the field-camera build path" on the slot-2 row was wrong: none of FUN_801DAB90, FUN_801DB8EC or FUN_801DBE9C writes _DAT_8007B794, and the only zeroing is the scene-entry reset FUN_80025C24. And edstati3 is a real scene - the ending cutscene block whose bundle sits at extraction entry 826. Its CDNAME label inherits forward as far as the battle-data packs at 863/864, and that is what made it look like a decode artefact rather than a scene.
Grade capture for the census (a disc-derived executing oracle) and disassembly for the factor itself - 0x8001CFD0..0x8001CFE8 loads _DAT_8007B794 through lui a0,0x8008; lh a0,-0x486c(a0) and calls 0x8004638C unless the render node's +0x52 bit 0x200 is set.
Measurement + corpus
Questions about the instruments rather than about the game. They are settled rows like any other, and they are on this page because a measurement has no oracle: a number it prints is believed on the strength of its explanation.
| Thread | Status | Evidence | Answer |
|---|---|---|---|
What is in the SCUS_942.54 code gap the citation graph never listed |
resolved | disassembly + capture | Mostly one thing: routines nothing references. Ghidra builds functions from the call graph, so a routine with no reference gets no function record, no dump and no citation - invisible to any worklist derived from citations. details ↓ |
Why three plausible-code blocks in SCUS_942.54 can never be a function body |
resolved | disassembly | 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. |
| Is the unattributable dump-extent residue a re-dumping problem | resolved (no) | capture | 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 (which needs an extraction), and extents where two dumps genuinely resolve to different images (which is an answer). |
| Can the inner of two nested overlay spans be measured at all | resolved (yes) | capture | 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. |
What is in the SCUS_942.54 code gap
Status: resolved.
The gap is what the citation-denominated instruments cannot see, and it is mostly one thing. Working the disc-denominated gap list until it stopped yielding produced roughly 95 function entries; a five-form reference sweep over each, across SCUS_942.54, the based overlay images and every PROT entry, splits them about three quarters / one fifth / three:
- No reference of any form, anywhere. The class only the bytes can find, and the majority of what was left.
- Referenced from
SCUS_942.54- mostly 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 sits in an image the SCUS-only analysis never sees - the standing "zero static callers is not dead" trap, surfacing as a coverage gap.
Four recovered entries are game logic rather than library material: the inventory count-add primitive carrying the 99 stack cap (80042FE8), the CD-read retry step and subsystem mode toggle (8003F210 / 8003EDAC), and a camera preset (800260DC).
Grade disassembly for the identifications, capture for the reachability split - the latter rests on the reference sweep, whose negative is a statement about static references only: a computed target assembled some other way, or a caller in an overlay never extracted, would not appear.