Falsified RE readings - do not re-walk
Every reverse-engineering project accumulates readings of the bytes that looked right, were believed, and turned out wrong. This page indexes Legaia's: what each reading claimed, the verdict, and the reasoning - why it was convincing and what killed it. Check here before committing to a reading that looks obvious.
What a falsified reading is, and why it is kept
A reverse engineer looks at a routine or a table and forms a story about what it does. Most stories survive the disassembly or a runtime capture. The ones here did not - but each was plausible, usually backed by real evidence read the wrong way, and the shape of the trap recurs in other subsystems. Rows are terminal: new evidence moves a row back to open RE threads, never edits it in place. The answered questions are on settled threads. Each row opens to its full reasoning - why it convinced, what killed it - and the longer cases sit under Long write-ups in this area. Every area, row and write-up is rendered from docs/reference/re-do-not-re-walk.md when the site is built.
Three traps behind most rows
- VA aliasing. A bare virtual address is not an identity. The game swaps different overlays into the same RAM window (
0x801CE818for slot A,0x801F69D8for slot B), so a dump labelled by address can be a different function entirely. See the overlay band. - Ghidra's collapsed switch. A jump table's arms render in the decompiled C as bare
breaks or fakeFUN_xcalls, inventing opcode semantics the raw table does not have. The cure is the disassembly, never the C (Ghidra workflow). - The over-reading entry size. A PROT entry's size is the sector gap to the next entry. An older expression read past that end, so a "field at offset K" was really the next entry (PROT.DAT).
World map / kingdom bundles
Kingdom bundles and the slot-4 animation bank.
| The reading that looked right | What is true |
|---|---|
The monotone 2048-entry ramp at SCUS file 0x5FCC0 is the world-map fog LUT | it is the arctan tableWhy it looked right, and what killed itPlausible: a content scan for "starts at zero, climbs to |
FUN_801E3E00 is a world-map atmospheric fog-RGB actor tick whose +0x74 is the per-kingdom haze | it is the attached light's keyframe scriptWhy it looked right, and what killed itPlausible: the word sat in an overworld actor's tick slot in one capture and |
The overworld ground's far colour comes from 0x1F800398 or a script | a literal 0x100 per channelWhy it looked right, and what killed itPlausible: the emitter reads its colour from that scratch word and sets no far colour itself. |
| The overworld walk camera is its own pose, sliding between two zoom anchors | it is the field zone cameraWhy it looked right, and what killed itPlausible: two resident overworld states held two different pitch / depth pairs, and the top-view controller has a zoom input. The overworld is a mode- |
A kingdom scene reaches ClutWalkAnim::Ocean | it is the arm for a bundle whose slot-5 CLUT-walk table fails to parseWhy it looked right, and what killed itPlausible: the arm is named for the ocean and lives on the kingdom path, so a ladder into |
| Slot-4 → cluster-A converter site | falsifiedWhy it looked right, and what killed itThere is no slot-4 → cluster-A converter. The cluster-A pool ( |
| World-map outline / coastline reading | falsifiedWhy it looked right, and what killed itVisual inspection plus the slot-4 record-semantic work refuted the "world-map overlay outlines / coastline wireframe" interpretation. The replacement guess - "small object-local 3D meshes" - is falsified too: slot 4 is the scene's animation bank (below). Treat any future "kingdom border lines" claim with suspicion. |
Walk-view decoration layer = walk-bit (0x1000) cells plus a FLAG_MESH_DRAWN test | the gate is 0x2000 aloneWhy it looked right, and what killed itPlausible: every tree / prop cell carries both bits, and the one family the walk-bit sweep wrongly admitted (record 408, a wall down every river) happened to lack flag |
FUN_80024E08 performs the 0xF0 model-id split | the split is in its callersWhy it looked right, and what killed itPlausible: the resolver is the routine that turns a model id into a pool pointer, so the id space's one discontinuity looks like its business. It does no splitting. Two callers do - |
FUN_80026B4C is the TMD pointer table's only writer | PROT 0976 writes it directlyWhy it looked right, and what killed itPlausible: the registrar is the only routine that installs an entry, and every install path does go through it, so "only writer" and "only installer" predict the same thing everywhere an install happens. |
Long write-ups in this area
Slot 4 is a GTE vertex pool walked by an unpinned "cluster-A command stream"
Falsified by disassembly, with a live state agreeing.
The reading: each 8-byte slot-4 record is a vertex (i16 x, y, z, attr) in an object-local pool, indexed by a command stream nobody could find, drawn in place by the world-map render library; attr render-unused.
Why it looked right: FUN_80044C14, a real per-kind primitive handler, does load two words into the GTE vertex registers exactly that way, and a capture showed the slot-4 window read in place with GTE-shaped return addresses.
What is true: there is no stream. _DAT_8007B888 (the type-0x05 buffer) has six references image-wide, none in a render path; the entry's field boundaries fall on nibbles (three 12-bit translations, three 8-bit angles); attr is the Y / Z rotation, read every frame; and the capture's return addresses sit inside the animated-mesh renderer FUN_8001B964, not a prim handler. The attr-is-unread register-width argument swept a function that never sees these bytes. See re-settled-threads.md.
The world-map slot-4 landmark meshes are the consumer of the lit prim handlers
Falsified twice over.
The renderer's kinds 8..11 are the only handlers with an NCC* light op, and the presumed consumer was the kingdom bundles' slot-4 "landmark meshes". Slot 4 is the scene's animation bank, not a mesh library; and exec breakpoints on all four handlers over two kingdom overworlds, a field scene and a battle return zero while the overworld's real handlers (PROT 0901's eight replacements) fire in the same run. A kingdom overworld never enters the SCUS prim-dispatch family at all. See re-settled-threads.md.
Op 4C 12 is the scene-entry screen fade
Falsified by a reference scan.
Every scene's arrival arm drops the op's word to 0 and ramps it back, which reads like a fade from black, and the port staged it as a multiply over the whole field frame. Its one reader disc-wide is the fog particle update FUN_8003F3FC; retona_field_card_boot holds the word at 27 over a full-brightness frame. See cutscene.md.
The field runs no hardware light source
Falsified by a frame.
The reading rested on a town01 sweep that sampled only the kind-11 body and on a GTE-opcode census that found the light handlers' consumers but not who reaches them. The dispatcher selects by flags >> 1, so every TMD group with flags 0x10..=0x17 goes through NCCS / NCCT; cave01's walls are all such groups, and its retail frame is the light's. A white back colour (town01's op 4C 8A) is what made the town's lit rows look baked. See renderer.md.
Battle / arts / level-up
Battle load, the action state machine, damage, the HUD, Muscle Dome, boss softlocks.
| The reading that looked right | What is true |
|---|---|
The Hyper Art speed lines are dealt afresh each pass off rand(), so no seat can match them | a static fan the replay seated elsewhereWhy it looked right, and what killed itPlausible: the rays differed between retail's two framebuffers, and the battle draw does deal |
| Ozma's arms 7..9 draw an orb dark where the port draws it orange, so the port has the orb's blend mode or CLUT wrong | a blink phaseWhy it looked right, and what killed itPlausible: the same disc, the same seat, another colour. In a retail recording's RAM the orb is the opaque |
| A full-party fight's summon flash-in opens about five vsyncs late in the port because retail spawns it earlier | spawn and delay matchWhy it looked right, and what killed itPlausible: the Zenoir and Gimard films from |
A four-slot event list reads a byte no disc data decides, and it reads 0 | the disc decides it, and most fights read non-zeroWhy it looked right, and what killed itPlausible: the index is the low byte of the entry's RAM address, the block "moves with the fight", and the first two fights measured read |
The juggle window overruns 32 * beat / (speed * rate) because the clip cursor carries a sub-frame fraction across the commit | the commit zeroes the cursorWhy it looked right, and what killed itPlausible: a measured window ran 22 vsyncs against a computed 20, and a 12.4 cursor has a fraction to carry. |
The anim commit's +0x1F7 test is a counter / guard (parry) window | it seeds the sound-cue cursorWhy it looked right, and what killed itPlausible: it sits in the commit beside the reaction-entry compares and reads the juggle byte, so it read as a timing window for a counter. The store it gates is |
FUN_801EFE44 is a battle camera-bounds walk (min / max X and Z over the actor table) | it recentres the formation and rewrites every included pairWhy it looked right, and what killed itPlausible: its first half takes extents over the actor table, the shape a camera-bounds helper has, and it runs at the action seed beside the framing calls. The second half subtracts the box's centroid from each included slot's |
A monster action entry's +0x04 / +0x08 are runtime keyframe pointers, zero on disc, sampled as per-bone curves | 1-based indices to two clip-morph lanesWhy it looked right, and what killed itPlausible: the loader writes both words, and the battle draw's |
Battle status bit 0x400 has no retail setter | PROT 0955's Kiss of Death miss arm sets itWhy it looked right, and what killed itPlausible: a word-level sweep of |
| The loss window is a pause-menu window descriptor | a SCUS placement recordWhy it looked right, and what killed itPlausible: it opens and closes like a pause-menu window. |
The battle pose decoder unwraps angles as FUN_8001D088 does | the guards differ at half a turnWhy it looked right, and what killed itBattle tests |
The battle ground shadow's +0x6A skip flag is an unmodelled divergence | the port already drew what retail drawsWhy it looked right, and what killed it
|
The ghost pass's recompute misses come from the driver's gp+0x330 gate | the gate passes on every frameWhy it looked right, and what killed itThe byte is the battle-load stage |
| A second Rot replaces the first limb | limbs accumulateWhy it looked right, and what killed itPlausible: one status word per actor reads as one slot. The applier ORs into |
Op 3E FF makes a scripted fight unescapable | the formation row's header byte doesWhy it looked right, and what killed itPlausible: most scripted fights refuse Run. The arm ( |
_DAT_8007BAC0 is a scripted-fight no-gold flag | it is the special-battle wordWhy it looked right, and what killed itPlausible: it zeroes gold. It is non-zero only in an arena leg and the two Ra-Seru-forbidden fights - a boss row leaves it alone - and it also zeroes EXP and suppresses drops, steals, absorb, spell XP and monster flee. |
The battle ground shadow is FUN_80028158 case 1 | mode word 1: shape 0 in the XZ planeWhy it looked right, and what killed itPlausible: the call passes |
FUN_8004DC68 is a target-highlight dim measured from the acting actor | a near-camera ghost passWhy it looked right, and what killed itPlausible: it sets semi-transparency bits on bodies near one point during actions. The point is on the camera's view axis - the focus pulled back by |
| Party entry 8 is a recover clip whose negative speed is a backstep | the downed kneel, root speed 0Why it looked right, and what killed itPlausible: it follows the knockdown and the files carry negative root speeds. Entries 7 and 8 have speed |
+0x1DC bit 3 (the root-motion latch) is set by the knockdown's tag-4 chain | by the tag-8 commitWhy it looked right, and what killed itPlausible: the latch holds a downed body still. The tag-8 commit raises it ( |
FUN_801E7320 picks a monster target as rand % count + party_count | + 3Why it looked right, and what killed itPlausible: the engine compacts monsters after the party. Retail's pool slots are fixed - monster |
| The battle-intro label's X is a GTE projection the renderer owns | the monster's battle world XWhy it looked right, and what killed itPlausible: the label sits over its monster on screen. |
| The Baka Fighter impact pair draws no sprite, and runs on the ambient effect runtime | template A is the 0x4000 sprite arm; the duel seats its own partsWhy it looked right, and what killed itPlausible: templates B are stage meshes and the ambient runtime seats move-VM records. Template A's op |
+0x90 / +0x92 / +0x94 are the move VM's tween sources | the per-tick rates of +0x78 / +0x72 / +0x7AWhy it looked right, and what killed itPlausible: ops |
ctx[+0x25] is the battle's round counter | it is the round's skip count; the round counter is +0x28AWhy it looked right, and what killed itPlausible: a byte cleared at every round's start reads like a counter of rounds, and a census of |
ctx[+0x16] has one writer, the War God Icon refill | two: the refill and a per-stage bumpWhy it looked right, and what killed itPlausible: the refill at |
0x801F696C's 0x801F0518 store belongs to FUN_801F0450's auto-combo tail | it heads the auto-fill armWhy it looked right, and what killed itPlausible: the routine was known as the auto-combo assembler and its tail was undecoded. The store is the first instruction past the |
The auto-combo pool arm halves its AP budget under record +0xF8 & 0x800 | the halving is the art tail's Spirit costWhy it looked right, and what killed itPlausible: |
The art insertion tail's first draw is rand() % 10 | rand() % 5Why it looked right, and what killed itPlausible: the divide is the |
0x801F696C's Miracle writer is at 0x801EF5A8 | the store is 0x801EF5B8Why it looked right, and what killed itPlausible: the Miracle arm's |
FUN_801F138C pays a captured monster's item into the bag | it is FUN_801DABA4's dead-slot Item refund, at a phantom VAWhy it looked right, and what killed itPlausible: the credit reads |
The commit-confirm Reselect returns to the first party member | the last member that can actWhy it looked right, and what killed itPlausible: "reselect" reads as starting over. By |
| Retail pre-picks the arts target | state 0x5A is a target cursorWhy it looked right, and what killed itPlausible: the port committed an art straight to its default target. |
| The Begin / Reselect confirm follows each member's arts entry | one party-wide screen after the last able memberWhy it looked right, and what killed itPlausible: the port drew it after every arts entry. It is raised once, from any command, when |
| The in-battle steal is a command, or rolls on the hit | it rolls on the kill, once per strike chainWhy it looked right, and what killed itPlausible: the steal table is per-monster like the drop table, and "steal" reads as an action a character takes. The roll lives in |
The steal latch ctx[+0x27] is set once per battle and has one writer | the action SM clears it at every strike chain's exitWhy it looked right, and what killed itPlausible: a byte scan for a store to |
| Hub arms 4 / 5 / 6 draw the ROUND banner when a dome leg opens | course card + title art; the banner is arm 0x15 onlyWhy it looked right, and what killed itPlausible: the envelope has the banner's shape and timing. Arms 4 / 5 call |
FUN_801DD4B0 is the physical-damage wrapper and FUN_801DD6B4 the spell one | the other way roundWhy it looked right, and what killed itPlausible: the two wrappers are near-identical and sit next to each other. The stat each loads decides it: |
battle_map01_frog_strike's left Lippian is a body dimmed by the solo sweep, and leaves the ordering table at a non-zero word | near-camera ghost: opaque until the pass reads a pose in rangeWhy it looked right, and what killed itPlausible: the capture holds |
FUN_801E93C8 re-arms the arts gauge / FUN_80046870 is a charge gauge | an anim-rate restore; a cooldown top-upWhy it looked right, and what killed itPlausible: both run around the art commit, where a gauge would be re-armed. Neither touches the AP gauge's fields. |
ctx[+0x28B] has four writers, none in the battle overlay | five; the fifth is the tick's own clear in PROT 0898Why it looked right, and what killed itPlausible: the four raises in |
| Which pass draws the ringside still is unsettled on the retail side | FUN_801D00F8, two POLY_FT4 packets at texture pages 0x106 / 0x109, measured liveWhy it looked right, and what killed itPlausible: the still's loader ( |
Spine flag 0x142 is set by six records | two are story writers; four are menu rowsWhy it looked right, and what killed itPlausible: all six decode as clean |
The cast-arm countdown drains by the scratch[0x37D] * scratch[0x393] product | the multiplier is per armWhy it looked right, and what killed itPlausible: one arm does drain by that product, and a second that agrees makes it look like the band's rule. Each arm carries its own multiplier of the frame byte at |
| PROT 0950's arm 6 is frame-gated like its siblings | it has no gate; it ends on countdown expiryWhy it looked right, and what killed itPlausible: twelve neighbouring arms in the band gate on a frame count, and a run that stops two ticks short of expiry looks like a gate firing. The arm tests nothing; the countdown runs out. The image's real fault is a different arm walking the actor table past a one-monster seat fill. |
| PROT 0905 (Vera) leaves the HP restore to the band's shared heal fold | it writes HP itself, as a second ownerWhy it looked right, and what killed itPlausible: every other healing module routes through the band's one heal fold, so a module that also has the amount looks like a caller. Vera stores the restored HP in its own tick as well, so a port that runs both applies the heal twice per frame. With a single owner, |
PROT 0952 carries no spawn record because its spawn sites load $a2 from a saved register | the sites are not 0952'sWhy it looked right, and what killed itPlausible: a pointer arriving in a saved register does defeat the |
The slot-B spawn record's +0x02 is a flags word | reservedWhy it looked right, and what killed itPlausible: |
Both gates of the 0x801E6218 block jump to 0x801E6814 | they are conditional branchesWhy it looked right, and what killed itPlausible: the block's two exits do reach the same tail. They are |
FUN_801D65F8 is a positional SFX helper | it is a VRAM blitWhy it looked right, and what killed itPlausible: it takes a small packed argument and is called from the Baka Fighter round SM, where a positional cue would fit. It blits a |
| No dome round raises the magic bit, so magic is not forbidden on the Master course | FUN_801CEA6C seeds the word per courseWhy it looked right, and what killed itPlausible: the |
| A cast is an AP spend, so the dome pennant's AP budget pays for one | a cast costs MPWhy it looked right, and what killed itPlausible: every other dome command is an AP spend, and the pennant geometry is linear in AP cost. The cast path |
A slot-B tick body that sweeps the whole actor row is a never-kill clamp, and only 0x801F85A8 / 0x801F8D64 sweep one | three more sweep it, and they can killWhy it looked right, and what killed itPlausible: the two known whole-row bodies both clamp, so "whole row" and "clamped" look like one property. Three tick bodies - |
Past its sltiu bound, a capture-class tick body returns Done | it returns BusyWhy it looked right, and what killed itPlausible: an out-of-range phase index looks like a terminal state. The saved register is seeded |
| PROT 0955's four bodies are the band's only stat-block writers | eight images write the blockWhy it looked right, and what killed itPlausible: 0955 is the module whose whole choreography is buffs, so it looked like the band's designated stat writer. Also writing |
0x801E6218 is an unported multi-cast sweep | it is latched, and already portedWhy it looked right, and what killed itPlausible: a documented sentence named the address as remaining work, the shape a worklist row has. It is a latched arm the port implements as |
The eleven player-Seru arms off 0x801CF4EC are data stagers | they are tick bodiesWhy it looked right, and what killed itPlausible: the cast-module page's per-entry verdicts say "data" for PROT 0903..0913 - but those verdicts describe each module's stager, and the |
| The battle AI's companion pick reads the acting actor | it reads seat 0 and writes seat 1Why it looked right, and what killed itPlausible: every other per-actor routine in the file takes the actor it acts for. |
Selectors 0x10..=0x83 of the battle applier are a stat-up / status-clear / queue-end / item family | 116 of them are the epilogueWhy it looked right, and what killed itPlausible: a 132-slot jump table with decoded arms at the bottom reads as a large opcode space with an undecoded upper half. The table at |
| A learned skill is inserted at the head of the displayed list | both list writers insert in orderWhy it looked right, and what killed itPlausible: a head insert is the cheap implementation and would explain "newest first" in a menu. The party-slot learned-Arts list ( |
FUN_80043264 scans all eight equipment slots | threeWhy it looked right, and what killed itPlausible: the routine sits beside the equip helpers and the block it indexes is eight slots wide. Its counter starts at |
0x801F7D34 is a body of PROT 0943 (cast_curse) | PROT 0942, cast_power_upWhy it looked right, and what killed itPlausible: the dump is named |
SCUS's jal 0x801F7B88 runs only when a battle ends mid-cast | it runs on ordinary in-battle framesWhy it looked right, and what killed itPlausible: the call sits in a teardown-shaped neighbourhood, and no corpus state has its gate up. The arm requires |
The battle exit (the 0x66 escape teardown / the results sequencer's +336 frame) is a white-out | a fade to blackWhy it looked right, and what killed itPlausible: the fade template ramps black -> white, and the ramp's end colour reads as the screen's. The template is kind |
| Retail plays a victory fanfare BGM when a battle is won | the battle theme runs through the results; the only jingle is cue 0x50 on a level-upWhy it looked right, and what killed itPlausible: |
The party leader always strikes the victory pose (no battle-overlay store writes a seat into ctx[+0x13]) | the acting slot posesWhy it looked right, and what killed it
|
| A strike recording's low cue score is wrong cues | it is the dealWhy it looked right, and what killed itPlausible: plain strikes read about |
A heal's voice 0x20C is late | the seat's idle phaseWhy it looked right, and what killed itPlausible: it reads |
A party melee's whole sound is the XA27 channel-4 sting (0x10C) | an ordinary swing is the XA30 grunt; the sting needs _DAT_8007BD84 != 0Why it looked right, and what killed itPlausible: the sting is the loudest cue on a strike, and the decompiled C flattens the fork into "grunt first, sting dropped behind its CD read". |
Move-VM op 0x2F extension dispatcher - per-overlay copies? | one copy, field overlay 0897 onlyWhy it looked right, and what killed itPlausible: the call VA is fixed, and every slot-A overlay loads there. Every other mapped slot-A overlay and the title overlay carry unrelated bytes at the call VA and no JT at |
"FUN_801F3894 spirit/magic damage roll" (state-0x3D chain caller) | VA-aliased dumpWhy it looked right, and what killed itPlausible: a dump exists at that entry and its body is a damage roll. The |
A level-up refills the live HP / MP pools (the captures' "settle" phase at +0x106 / +0x10A) | the settle write is the battle-end resync; both currents stand stillWhy it looked right, and what killed it |
| A streamed signature-attack cast module cannot run from a party slot because "a party actor has no monster block" | it has a first-class equivalentWhy it looked right, and what killed it
|
| An art whose attack camera films flat has the wrong arm, so the fix is to select a better-choreographed one | every arm is timed for a ~20-frame swing, and not one is spareWhy it looked right, and what killed it |
FUN_801D5854 case 6's 0x801D5CFC arm is the per-action party framing, gated on DAT_8007BD71 == 0xFE as "the in-battle state" | 0xFE is the battle-END signal; a running fight takes the 0x801D64C4 arm for everyoneWhy it looked right, and what killed it |
The Done band (0x50 / 0x51) is idle for the camera - keep it on the far framing so the "per-action close-up" does not own half the fight | retail re-arms case 6 / 8 per category there; the close-up was the wrong armWhy it looked right, and what killed it |
| Navmesh / per-scene navigation data | falsifiedWhy it looked right, and what killed it
|
Op-0x4E sub-ops 4..8 "absolute jump" / "rand -> next PC" readings | all sub-ops 0..9 are the 7-byte compare-and-skipWhy it looked right, and what killed it |
801d58f0 / 801d63b0 as single shared port blockers | VA-aliasing artifactWhy it looked right, and what killed itPlausible: a bare-VA catalog key aggregates every overlay's references into two top blockers. The two addresses host different code in different overlays (byte-verified: 80/228/124/308/1 B and 208/1036 B across 0897/baka/cutscene/debug-menu/fishing/slot/dance). They are tracked per overlay as |
A monster's after-image ghosts (FUN_80049348) fire on any non-idle clip tag | the gate is two record bytes, +0x77 and +0x87Why it looked right, and what killed itPlausible: the party gate is committed slot |
Charm battle softlock = unbounded reroll in FUN_801E7320 | cannot spin from any reachable stateWhy it looked right, and what killed itPlausible: the reroll loops are unbounded in isolation. Every reachable caller state has an exit: the scheduler |
Gaza 2 0x51 park: clamp asymmetry as a standalone retail generator | amplifier only; its exhibit was a phased mid-action stateWhy it looked right, and what killed it |
Gaza 2 0x51 park: the Final Heal revive lands "at the worst possible moment" (mid-drain) | on the Gaza 2 move set (12/12 revives found the accumulator already drained)Why it looked right, and what killed it |
| Muscle Dome as a card battle with a per-fighter "score out of 108" | it is a 4-turn battle; the readout is the opponent's HP percentageWhy it looked right, and what killed it |
| Muscle Dome awards a Seru on a win | a leg pays nothing; a contest pays casino coinsWhy it looked right, and what killed it |
FUN_801DBC30 blits the party panels' name plate | its page + CLUT resolve to the etim red cross-out XWhy it looked right, and what killed it |
| The retail party HUD carries HP / MP gauge bars | no bar primitive in either readout's packet runWhy it looked right, and what killed it |
Screen-element kinds named by what sits at their seat (0x32/0x33 = "the roster panels") | naming by seat named the wrong recordWhy it looked right, and what killed it |
| The battle message banner (and every class-0 frame) has no interior fill | a POLY_GT4 marbled fill covers the frame; only a SPRT-only sweep misses itWhy it looked right, and what killed it |
FUN_801E2524 / FUN_801E2650 are a full-screen flash / fade ramp | they are the Arts announcement bannerWhy it looked right, and what killed it |
The battle per-actor draw FUN_80048A08 runs 35-64x per frame during a summon | once per live actor per rendered frameWhy it looked right, and what killed it |
| The slot-B cast band applies damage with one shape, seat-0 hardcoded | true of PROT 0958 / 0959 / 0960 onlyWhy it looked right, and what killed itPlausible: the seat-0 write is the whole damage path in the three Delilas modules. The band splits: the capture-class ticks read the caster's own |
| PROT 0927 can never kill, so it needs no death path | its sweep cannot; its tick doesWhy it looked right, and what killed itPlausible: the sweep |
| The two slot-B row sweeps (PROT 0927, 0966) subtract no HP and only stage clips for a tick | both subtract, capped at HP - 1Why it looked right, and what killed itPlausible: they are called stagers, they run from the move script rather than the phase machine, and "never kills" reads as "never damages". |
Actor +0x1DC is never incremented in the cast band, so it is purely a bitfield | the caster's is bumpedWhy it looked right, and what killed itPlausible: the victim-side writes are all |
Battle ctx[+0xD] is two independent bits, and bit 1 drops TR.y by 0x100 | a compare ladder over 1 / 2 / 3, and a literal 0x400Why it looked right, and what killed itPlausible: the arms compose exactly as two bits would for the values the game writes, and |
_DAT_801F69D0 is the counter-attack flag, and its reader hands out the round's turns | a turns-begun counter with one cosmetic readerWhy it looked right, and what killed itPlausible: it sits beside the counter latch |
ctx[+0xD] variant 2 stamps a 0x400 camera roll | it is the translation TR.y, not a rotationWhy it looked right, and what killed itPlausible because variant |
The Spirit halving flag is a battle-actor +0x16E bit | it is the character record's +0xF8 bit 0x800Why it looked right, and what killed it
|
A dome direction swing takes its damage from FUN_801E09F8 -> FUN_801DD0AC | that chain carries a move-power index, and the dome's rows are zeroWhy it looked right, and what killed itThe chain is real and it is the monster-special damage path, so a dome swing entering it looks like the answer. What travels is an index into the move-power table, and the dome's four direction commands map |
^H is an exception to the element-badge caret bijection (Cort has no badge letter) | the map is a zero-exception bijectionWhy it looked right, and what killed itThe census that produced the exception was over shipped monster names, and no shipped name happens to carry |
battle_gimard_tail_fire_a/_b are frames of a party Seru summon | the enemy Gimard's Tail FireWhy it looked right, and what killed itThe acting-actor plaque top-left reads |
FUN_801DBF9C (the 0x29 party trigger) applies the spell's outcome | it stages the anim stream and the summon sub-routeWhy it looked right, and what killed itNo store in it reaches HP, MP or a target; it writes |
FUN_801DC0A0(actor, id) stages the cast clip | it is the cast-effect driverWhy it looked right, and what killed itThe summon band calls it with |
"The port never latches the art id, so actor[+0x1DB] reads 0x00 all fight" | it latches 0x01, then 0x0D, then 0x0CWhy it looked right, and what killed itThose are the rolled arm swings a basic Attack queues, and a retail mid-swing Attack state reads the same band. |
"The port's 0x51 Done-band residency is unbounded" | bounded at ctx[+0x6D8] = 0x3C, as retailWhy it looked right, and what killed itThe 60-70 frames a sample shows is one action's countdown plus the HP-bar settle freeze; a multi-action sample sums several of them. Counting frames in a band without splitting them by action reads a per-action budget as a park. details |
| "The sparring tutorial reopens the command session, so the cursor cannot move" | the cursor walks 0..5; a waiting prompt box parks the tickWhy it looked right, and what killed itThe session is reopened only on a rejected resolution. What pins the cursor on screen is retail's own |
| An action returns its combatants to their authored formation seats | retail leaves them on the ground the action ended onWhy it looked right, and what killed itPlausible: the formation is authored, so a home seat reads as the rest position. Two library states of one solo fight read the authored formation 1600 apart; two later ones read the same pair ~300 apart and both far off it, with every actor's |
Staged ids 0x10 and 0x1A alone install at dynamic slot 0x11, every other art-bank id at 0x10 | 0x10, 0x1A and every art constant >= 0x1B install at 0x11; only the base ids 0x11..=0x19 take 0x10Why it looked right, and what killed it |
| PROT 0910 writes no HP, because its tick body carries no damage wrapper | the wrapper is in its calleeWhy it looked right, and what killed itPlausible: the tick's own extent holds no |
A module's phase stores are its sb writes at a literal 0x279 displacement | most store through a formed pointerWhy it looked right, and what killed itPlausible: the phase byte is |
| The cast band uses two damage-clamp shapes, and every tick body takes the kill-capable one | three shapes; seven tick sites take the thirdWhy it looked right, and what killed itPlausible: a census that windows each |
| The wrapper's return is the damage a module applies | for PROT 0910 (it is shifted first)Why it looked right, and what killed itPlausible: every other body in the band stores the wrapper's return through its clamp unchanged. |
| Nighto's resist roll forks the module's phase | only the kill roll doesWhy it looked right, and what killed itPlausible: the arm rolls twice and ends in two different phases, so pairing the second roll with the second phase is the economical reading. The |
Monster record +0x20 is a per-monster instant-death immunity byte | it is a double-width texture-page flagWhy it looked right, and what killed itPlausible: three summon ticks read it before an instant-death roll and force the resist when it is set, and 37 of 186 records carry it - bosses plus the Evil Fly / Death Wings / Demon Fly family. Its primary reader is the model upload at |
PROT 0941's 0x51 Steal deals damage like its band neighbours | zero damage wrappersWhy it looked right, and what killed itPlausible: the band's arms are overwhelmingly damage bodies, and an enemy Steal that also hurts is an ordinary design. Its outcome is an inventory consume through |
PROT 0943's MP-pair writer is the routine at 0x801F69D8 | that VA is the body's head tableWhy it looked right, and what killed itPlausible: |
PROT 0940's 0xAC arm blanks the caster's +0x0C | it blanks seat 3's action queueWhy it looked right, and what killed itPlausible: |
The arena's pre-test seed of 0x8007BAC0 is zero | it is 1Why it looked right, and what killed itPlausible: the word is zero before the arena runs and the seeding arms fire only on a set story flag, so the unflagged path reads as "leave it alone". |
| The dome tally screen draws its four lanes, then the totals | the HP accumulator sits between lanes 2 and 3Why it looked right, and what killed itPlausible: a scoreboard that lists its lanes in order and totals underneath is the expected shape. Retail's six rows are |
The player-Seru wrapper sites pass the caster seat in a1 | for the player half (a1 is a baked 7)Why it looked right, and what killed itPlausible: the capture half does load it - |
PROT 0941's Steal floors its bag draw while the battle context's +0x11 reads 4 | the gate is DAT_8007BD10[1]Why it looked right, and what killed itPlausible: |
0x8007B83C is FUN_8001E890's == 2 gate | that word is the game modeWhy it looked right, and what killed itPlausible: the routine does compare a word against 2, game mode 2 is the scene-load mode, and the mode word takes that value across a load at the right moment. The compared word is |
PROT 0943's 0x40 and PROT 0944's 0x53 fault before their first tick | the body ticks; the fault is downstreamWhy it looked right, and what killed itPlausible: the emulator pauses on an unmapped read with no module frame on the stack, in every post-turn state of the corpus. Both arms stage clip |
Battle context +0x276 is a per-module gate | it is the side-band applier's stageWhy it looked right, and what killed itPlausible: the summon modules test it before their head cue. Its writers are the applier SM and two battle routines; the modules only poll it, so it is open by construction when a cast wants it. Nothing on the disc writes it from a tutorial flag, and a port that does silences the melee sting in exactly those battles. |
| The SPU's slow cast-voice release is an envelope defect | it is the reverb tailWhy it looked right, and what killed itPlausible: a one-second decay after key-off is what a wrong ADSR shift sounds like. The envelope is tick-exact - shift |
0x1F80037D is a second per-frame byte | it is the game-speed rate scalarWhy it looked right, and what killed itPlausible: the mode-INIT core reset reloads it in the same breath as |
FUN_801DD4B0 is a damage path of its own | it is FUN_801DD0AC's non-summon arm, term for termWhy it looked right, and what killed itPlausible: it is a separate entry with its own frame. Its arithmetic matches the shared kernel's non-summon arm instruction for instruction - driving both against one seat returns 251 against 251, so no capture separates them. |
"Move 0x36" and "move 0x37" are move-VM sub-opcodes | they are actor[+0x1DF] action idsWhy it looked right, and what killed itPlausible: the move VM has a dense opcode space and both numbers fall inside it. They are entries in the battle action queue, a different id space with a different dispatcher; the move VM's arms at those numbers are unrelated. |
_DAT_8007B64A has no writer | the field entity tick writes itWhy it looked right, and what killed itPlausible: an absolute-address sweep over |
The arm at 0x801E3DD8 sets ctx[7] = 0x3E, and a spell can reach it | the arm is state 0x3D, and only an item opens itWhy it looked right, and what killed itPlausible: the arm's own exit store is the next state, so reading the store as the arm's identity is off by one. The jump table settles it: base |
The battle selectable scans test an action-state byte where 4 means removed or done | it is a seat indexWhy it looked right, and what killed itPlausible: the scans sit beside the action state machine, run per seat, and compare a per-seat byte against a small constant - a state enum by shape, with |
FUN_801D0748 is the Muscle Dome's match state machine | it is the round SM every battle runsWhy it looked right, and what killed itPlausible: every capture that catches it is a dome one, five differently-prefixed dumps of it carry the dome label, and its arms are the arms a dome round needs. The routine has exactly one |
No draw site exists for the dome panel still at VRAM (384, 0) | the emitter never materialises 384Why it looked right, and what killed itPlausible: 33 sites disc-wide form |
0x801F90DC is retail-unreachable, so its port owes no host | five slot-B images reference itWhy it looked right, and what killed itPlausible: the address sits in a group of wiring rows that are retail-unreachable. Two |
The Arts banner selector ctx[+0x28B] is raised in the battle overlay, and an overlay sweep finding no writer means there is none | all four writers are in SCUS FUN_8004AD80Why it looked right, and what killed itPlausible: the banner's reader |
The three ctx[+0x28B] raises are alternatives - one per starter kind | they run in sequence; the side-array pick overwrites the seat flag's writeWhy it looked right, and what killed itPlausible: three stores of three constants in three arms of one routine read as a selection. |
FUN_801D84C0 builds the four battle party-name panel labels, measuring each with FUN_8003CBF8 | it builds the four battle-result messages; FUN_8003CBF8(buf, 0xC1, 1) locates the name escapeWhy it looked right, and what killed itPlausible: the four buffers are text, one per display slot, and the routine pairs with a panel opener. Resolving the pool strings its two arms copy and append ( |
FUN_801D32BC is a turn-order choice - retail's cursor order over initiative | it is the command window's member cursorWhy it looked right, and what killed itPlausible: it steps a seat index over living actors, which is what a turn order does. Its six call sites are the round reset |
The six baka_fighter_chrome NOT WIRED anchors are the duel's digit strips | they are the keyframe-editor band and sprite passesWhy it looked right, and what killed itPlausible: the digit strips were the visible gap and the cluster sits in the same image. The anchors are |
| ... and the six-anchor row's "sprite passes" | they are model passesWhy it looked right, and what killed itPlausible: the anchor names say "sprite" and the rows sit beside |
A clobber of the resident PROT 0874 pack trips FUN_8001E890's checksum | the sum is over a VRAM read-back, not RAMWhy it looked right, and what killed itPlausible: the routine re-sums the container and reloads on a mismatch, so corrupting the container looks like the way to fire it. Four XOR'd words of the resident copy left the sum byte-identical ( |
| A monster drop is granted per dead enemy at the record's chance | at most one item per battleWhy it looked right, and what killed itPlausible: each record carries its own item and chance byte, so a per-record roll is the natural reading. |
FUN_801E91E8 is the Miracle-command token lookup | it is the already-learned-Seru checkWhy it looked right, and what killed itPlausible: it returns a 1-based position in a per-character byte list gated on |
A set ctx[+0x269] routes the Done band into the capture cinematic 0x68..0x6B | it is the absorbed Seru, taught in the same actionWhy it looked right, and what killed itPlausible: the byte is set only when a Seru is involved and the capture states sit next door. The Done band reads it at |
State 0x3E's arm at 0x801E3F2C is a Spirit arm | it is the class-5 gauge-extension item's armWhy it looked right, and what killed itPlausible: it bumps the spirit gauge and sits in the state the table labels "Spirit - fire". The arm is taken on committed effect class |
The battle action SM's writes to 0x8007B790 / 0x8007B792 / 0x800840BC are a screen shake | they are the orbit camera's pitch, yaw and eye YWhy it looked right, and what killed itPlausible: they are short nudges in the run and spell-exit states, and the failed-run message wobbles the view. The words are the GTE rotation trio's pitch and yaw and the eye trio's Y ( |
_DAT_8007B888 / _DAT_8007B840 are Baka Fighter's sprite archives, and FUN_801D49E8 is a mirrored sprite pass with a yaw at +0x78 | ANM clip banks, and the special's afterimageWhy it looked right, and what killed itPlausible: the rows sit beside |
Baka Fighter's round-start cameo is a party model, scene model 0 | scene model 3, the ring girlWhy it looked right, and what killed itPlausible: the prototype record's |
Baka Fighter's block +0x0C is 0 idle / 1 windup / 2 committed | strike state: armed / landed / consumedWhy it looked right, and what killed itPlausible: three values on a move's lifecycle read as its phases. The resolver's arms require |
A battle ui_element(id, 0) raise is an efect.dat script spawn | it is a HUD screen elementWhy it looked right, and what killed itPlausible: several ids coincide with effect-script ids. |
| Retail shows an art-learned text banner | falsifiedWhy it looked right, and what killed itPlausible: a learn is the kind of event a text line announces. The build loop marks every accepted art ( |
FUN_801D5778 launches the commit log off-screen at round start | a ring exit or return launches itWhy it looked right, and what killed itPlausible: the log disappears around the round's start. The flow tail table at |
| The Auto command replays the saved command string | the Auto flag rebuilds the queueWhy it looked right, and what killed itPlausible: |
| The intro banner dedups a monster name by overwriting one glyph | it drops a character and appends * NWhy it looked right, and what killed itPlausible: the label keeps its visual width. The routine truncates the instance letter and |
The magic-capture fade 0x6F arms no framing, so its camera walks the table 0x6E left on its own increment | it re-arms case 6 every passWhy it looked right, and what killed itPlausible: walked back on the re-arm's widening share, |
Battle action state 0x00 runs once per battle | every roundWhy it looked right, and what killed itPlausible: the formation arm reads like battle setup. Begin sets flow |
| The purple band across the party Tactical Arts captures is drawn outside the ordering tables (another OT, a DMA chain, a screen effect, an art spawn) | a ghosted body in the tablesWhy it looked right, and what killed itPlausible: the band moves between the framebuffers and the packets at one of its pixels were cave wall. Rasterised whole, the tables carry it as textured ABR- |
Long write-ups in this area
The scripted boost profile is "the international release's" profile for every fight
Tempting reading. The battle loader FUN_80054CB0 boosts an enemy's ATK / UDF / LDF / INT as it installs the record, choosing between two profiles on ctx[+0x287]. A live Gaza capture reproduces the flag-set profile (ATK x5/4, UDF/LDF x2, INT x9/8) byte-for-byte and the curated bestiary matches it for 120+ enemies, so it reads as "the profile the US/PAL build uses" for every fight.
Why it is wrong. ctx[+0x287] is the scripted-fight flag (a formation row's non-zero header byte). Gaza is a scripted fight, and the bestiary is authored from boss-profile numbers. Every random-encounter save state carries +0x287 == 0 and the other profile (ATK x1, UDF/LDF x7/4, INT x5/4): a world-map Gobu Gobu with record 17/15/14/10 fights as 17/25/24/12.
What is true. The profile is per fight. battle_stats() is the flag-set (scripted) block and MonsterRecord::battle_stats_random the flag-clear one; see battle.md.
FUN_801F3D3C installs a queued-magic follow-up routine
Tempting reading. FUN_801F3C34 / FUN_801F3D3C are a "queued-magic follow-up" latch: the installer picks a record out of 0x801F6870 by [actor class][level band], stores its byte 0 as a follow-up id and its word 1 as a routine pointer at 0x800775B4, and the reader stays silent while a follow-up is pending.
Why it is wrong. The table index is not a class - it is the summon record's element byte ((*0x801C9358)[+0x1D]), the byte the affinity scale reads. The word is a banner string pointer: the table's strings name the stat and the percent, and the reader's own install value 0x801CFA20 is the text "No effect.". The "seven-entry jump table the dump does not cover" is the per-element base-vs-record compare inside the same function.
What is true. Byte 0 is the percent the damage finisher's per-element switch shaves off the target on every hit - the Seru-magic element debuffs. Mechanism: battle-formulas.md.
The dynamic-slot rewrite was never "0x10 and 0x1A only"
Tempting reading. The decompiled C of FUN_8004AD80 shows two 0x11 assignments, which read as the whole set: staged ids 0x10 and 0x1A install at dynamic slot 0x11, every other art-bank id at 0x10.
Why it is wrong. The slot register s2 is written in delay slots, which the C folds away:
_li s2,0x10under the0x1Atest at0x8004B720is the default;0x8004B76C(the0x1Aarm) and0x8004BB58(the0x10test) set0x11;- the art-constant arm - entered for every staged id
>= 0x1Bat0x8004BB5C- sets0x11in the delay slot of its name-width call (jal 0x80035f04 ; _li s2,0x11at0x8004BBBC..0x8004BBC0) before the install at0x8004BC4C.
A live Tri-Somersault capture agrees: +0x1D9 reads 0x11 under 0x27, 0x1F and 0x2B, and 0x10 under the 0x19 starter.
What is true. 0x10, 0x1A and every art constant >= 0x1B install at 0x11; only the base ids 0x11..=0x19 take 0x10 (resolve_staged_anim). The +0x1D9 == +0x1DA equality checks compare slot numbers, so the slot an id lands on decides whether the SM sees its clip as committed.
A level-up is not a heal
Tempting reading. A multi-level capture triplet's third frame writes the live current-pool cells (+0x106 / +0x10A) right after a level-up, which reads as a refill (capture_observations::char_level_up) - every level-up a free full heal.
Why it is wrong.
- Disassembly.
FUN_801E9504stores to exactly eleven addresses: the record window'shp_max/mp_max, its six battle stats, the displayed-level byte and its actor-table mirror, and two globals. No live-window cell is among them, and the routine's onlyjalis the BIOSrand. Identical in both dumps of the routine. - Capture. The single-level
noa_levelup_*triplet reads Noa at164/182HP and16/16MP going into the fight and164/221/16/21once the L2 -> L3 level-up has settled in the field. Both maxima move by the growth amount; neither current moves.
What is true. The +0x106 / +0x10A write is the battle-end resync of the live pools, not a grant (level-up.md). A write that lands near an event is not a write by it; a frame-window capture cannot separate the two, only the routine's store set can.
Record +0x9C is not a magic-rank counter
Tempting reading. A save pair taken across a magic rank-up moves record +0x9C from 9 to 10 beside the spell's XP and level bytes, which reads as a rank counter.
Why it is wrong.
- Disassembly. A monster's death arm in
FUN_8004AD80bumps+0x9C(sw 0x664off the save block at0x8004B1F4) for the party seat holding the action, and Jedo's instant-kill arm bumps it once per monster it fells. The records pageFUN_801ED710prints the word under Monsters Defeated. No rank-up path writes it.
What is true. The pair's cast felled a monster. The rank lives in the spell-level array +0x161.. (save-record.md).
The flash ramp is the Arts announcement banner
Tempting reading. FUN_801E2650 scales a percent into grey, replicates it into RGB, picks GP0 0x2C or 0x2E, and emits quads whose extent is driven by a level byte. On the arithmetic alone that is a full-screen flash / fade overlay walked by a "brightness level".
Why it is wrong. The quads are textured. Every arm writes texpage 0x27 = (448, 0) under CBA 0x7703; decoding that page at 4bpp through that sub-palette shows the emitter's three 24-tall rows are the words SUPER, HYPER, and MIRACLE + NEW, directly above the DAMAGE / HIT / TOTAL labels on the same sheet. The second quad's texel rect is fixed for every position and reads ARTS!!.
What is true. The four ctx[+0x28B] values compose NEW ARTS!! / HYPER ARTS!! / MIRACLE ARTS!! / SUPER ARTS!!, each as two halves sliding in from opposite screen sides to a per-banner seam. ctx[+0x28C] is the slide's clock, and the four "layers" are a ghost trail behind the moving word. A routine that emits textured primitives is not characterised until its texels are decoded. Geometry: battle-action.md; sheet layout: effect.md.
The battle message banner has no interior fill
Tempting reading. A walk of the rim_elm_gimard_seru_capture_after and noa_levelup_banner display lists finds the class-0 border sprites and the glyph run and nothing else inside the frame rect, so the scene shows through the banner - and through the battle-intro enemy-name labels, which share the frame.
Why it is wrong. That walk keeps only SPRT packets. The same two states carry, ahead of the border, a run of opaque gouraud textured quads (POLY_GT4, code 0x3C) over the whole frame rect: widget record 3's 32x32 blue-marbled patch at texels (128, 0) on CLUT (32, 511), tiled in 32-pixel columns from the frame origin, grey 0x40 at the top edge and 0x88 at the bottom. The emitter is FUN_8002BDC4, called for every class-0 node from 0x8002D7E8, and each state's framebuffer shows the navy fill under the text.
What is true. A gold border over a blue marbled interior. A primitive-type filter on an ordering-table walk is a claim about which primitives exist; "nothing else" needs every GP0 code checked. Geometry and the band law: battle.md.
A kind named by its seat can name the wrong record
Tempting reading. Name each undecoded +0x0E kind value by the surface it sits under: 0x0303 "full-width message rows", 0x0404 "framed windows", 0x2B2B "the status bar", 0x32/0x33 "the roster panels".
Why it is wrong. The three roster-panel placement records (6, 78, 79) carry kind 0x07; 0x33/0x34/0x35 are sibling kinds that add the level / status marker on top of the same panel chain. The kinds converge - 0x33's chain hops +0x0E and then walks into 0x08 -> 0x09, the panel plate 0x07's chain ends on - so a seat-based name cannot separate two records that draw the same pixels, and no further capture can either.
What is true. The kind is a table index: 0x800732A4 + kind * 0x0C (FUN_8002C69C at 0x8002C7A0). The other seat names survive the decode. Resolution: re-settled-threads.md.
FUN_801DBB8C is not the party readout's registration
Tempting reading. The battle overlay's FUN_801DBB8C registers one retained SCUS text actor through FUN_8003541C, stashes the handle at _DAT_801F4E0C, and sits among the party-panel build and teardown leaves - so it reads as the readout's own registration, the actor the arts input parks at y = 230.
Why it is wrong. Its one caller is FUN_801D0748 at 0x801D1660, on the ring's 0x28 -> 0x50 arm, immediately after FUN_801D388C(9) has built the arts-entry screen. The arguments are (0, 0xC, 0, -146, 36, 138, 144, 3): a 138x144 box parked one screen to the left.
What is true. It registers the arts list window the Triangle page slides in. The party readouts are placement records 7 and 6 / 78 / 79, opened by the sub-draw script through FUN_801D8DE8 like every other chrome element. A registration is identified by the rect it registers and the transition that calls it, not by the leaves it is compiled beside.
The item window shows the pill
Tempting reading. Retail's item-use action shows the full-width pill (captures/tetsu_idle), and so does the ring the window opens from, so "pill while the item window is up" is the obvious interpolation.
Why it is wrong. The sub-draw step the 0x28 -> 0x3C arm runs (FUN_801D388C(5), 0x801D13F0) is 06/0 4E/0 4F/0 07/1: the roster panels come back up and the bar parks.
What is true. The bar returns at the window's target step (0x64, step 0x18), re-pointed at the member the cursor names. The magic window (step 7) has the same shape. A menu state's surfaces are a table row, not a neighbour's: read the step, not the frames either side of it.
A monster strike's displayed focus needs another endpoint
Tempting reading. battle_korb2_aluru_strike walks back from the table to focus (276, 320), and the frame scored .946 on it. A fit of the frame gave yaw 2188 but focus (256, 304), which reads as an arm on an endpoint near (117, 107) that no walk-clip pose reaches.
Why it is wrong. The child never stood on (276, 320). The alignment keeps the replay's focus plus retail's gap to its endpoint unless the two endpoints agree, and the replay's case 7 read a body pair a pass newer than the arm did. Stood on outright, the walked-back pose reads .999.
What is true. The table is the pass's own in the strike loop, so the walk back is retail's pose (RetailBattle::stand_on_displayed_pose). A fit that runs through the alignment measures the alignment.
The magic chip's gate reads the weapon byte
Tempting reading. FUN_80053CB8 writes ctx[+0x25F + member] after an lbu at +0x760 off 0x80084140 + (char_id - 1) * 0x414, and 0x80084140 + 0x760 is the live record's +0x198 - the equipment byte the save-record table names weapon_id. So "the element chip is live when a weapon is equipped".
Why it is wrong. player_steal_skeleton_pre has the gate at 1 with +0x198 = 0 and +0x199 = 1. The store at 0x80054270 is the second arm, reached only for char_id == 2 (the beq v0,a3 at 0x800541E4 on DAT_8007BD10[member]) - Noa's byte, not the weapon rule.
What is true. The first arm (0x800541E0..0x80054218) reads +0x761 = record +0x199, the Ra-Seru slot, and in twenty-nine states the gate equals that byte's non-zero test. One lbu in a two-arm predicate is not the predicate; check a reading against a state whose bytes disagree with it.
FUN_801DBC30 is not the battle name plate
Tempting reading. FUN_801DBC30 sits in the battle overlay next to the party-name panel's open and teardown leaves, takes an (x, y), and lays down one 0x40 x 0x10 textured quad: a fixed-size strip at a caller-supplied seat, in the function group that builds the name buffers. Its x-8 bias even seems to explain the panels' 8-pixel text inset.
Why it is wrong. tpage 7 resolves to VRAM page (448, 0) and CLUT 0x7704 to (64, 476) - the etim effect page, not the system-UI sheet the battle chrome samples. The texel span (0, 96)-(63, 111) decodes out of a battle VRAM dump as the red cross-out X, the mark retail lays over a command chip the actor cannot pick; the same rect is pinned under that name for the Muscle Dome's forbidden Item chip.
What is true. The name plates are 3-slice runs off the resident system-UI sheet's page (896, 256), and the party readout draws no bar at all. A primitive builder is identified by the page and palette it samples, not by the neighbourhood it is compiled into. See battle.md.
The dome victory caption is not a prize
Tempting reading. FUN_801D8DE8 case 0x59 composes a victory line out of a per-character label from the table at 0x801F4DFC plus a spell name from the shared spell-name table at ctx[+0x269] + 0x80 - the player Seru-magic block. Read alone that is an award message: a won dome leg grants a Seru.
Why it is wrong.
- The table is shared.
0x801F4DFCis the battle-family per-character label table, byte-identical across the battle-action, magic-capture, magic-level-up and dome overlays, and the composer is the ordinary cast-caption builder reached by any cast in any battle. Its presence in the dome overlay is residency (as with the0x801F4D34/0x801F4B8Csibling tables). - The arena has no capture writer: there is no
record_captureanalogue anywhere in the overlay.
What is true. The whole reward path in PROT 0977 is FUN_801D0F60: it settles the score tally and, once per save on the Master-course final fight, hands over item 0xCD. The tally is paid by the shared minigame-exit routine FUN_80026018 into the casino coin bank 0x800845A4, saturating at 9,999,999. Before crediting anything a message mentions, find the writer of the thing credited. See minigame-muscle-dome.md.
Muscle Dome was never a card battle
Four readings, each wrong for a different reason.
- "A hand of four cards." Tempting:
FUN_801d388ccase9builds four slots in ado { } while (< 4)loop, which reads like a deal. True: the four slots are the four d-pad directions, always command ids0xC..=0xF, each carrying that fighter's own AP cost. Nothing is drawn, discarded or reshuffled; the arena is an ordinary battle whose command string is bounded by AP, and its presentation is the standard battle command cluster. - "A score of
hp * 0x6C / max." Tempting: the compiler rendersx 100as a shift-add chain -sll 1(2x),addu(3x),sll 3(24x),addu(25x),sll 2(100x) at0x801d0f38..0x801d0f4c- and stopping at the fourth instruction yields 25, folding the wrong pair0x6C(108). True:x 100. A multiplier read off a shift-add chain is correct only if the whole chain is consumed; Ghidra's C prints* 100, and a second dump of the same code at another load base (overlay_0896_801f04b0.txt) reproduces it. - "Rendered in phase
0x6e, per fighter." True: the computation lives in the phase-0x14arm;0x6eonly re-stamps the two globals0x14wrote. The record read isDAT_801c937c- actor-table index 3, the first enemy slot - so there is one number on screen, the opponent's. The match SM contains exactly two ratio computations and both are thatx 100. - "Four turns is the whole dome leg." Tempting: the arm draws a
Turns Left / HP Leftstrip (format string at PROT 0898 file offset0x0):4 - ctx[+0x28a](the shared battle turn counter, bumped byFUN_801e295ccase0xff) and the first enemy's HP percentage. True: the arm is gated on*(u8*)0x8007BD0C == 0xB6, and0x8007BD0Cis the four-slot monster-id formation cell, not a battle-type byte. The dome stages its opponents into that cell out of a 29-round table topping out at id0xAA, so no dome round reaches the strip; it belongs to monster0xB6, Koru, whose four-turn timed kill the curated boss table records independently. A byte compared against a small constant is not a mode tag until its writer is found. See minigame-muscle-dome.md.
A separate widget must not be folded into this one: FUN_801d8de8 is the shared battle status plate (dumped under ten overlays), drawing each fighter's own HP/MP cur/max numerals from +0x172/+0x14e and +0x174/+0x152. It computes no percentage and is not dome-specific.
Op-0x4E sub-op family - every sub-op 0..9 is a compare
Tempting reading. The decompiled C shows bare-break arms for sub-ops 2..9, which read as "absolute jump" (5..8) and "rand -> next PC" (4).
Why it is wrong. Those arms are Ghidra's collapsed switch: each raw loader ends j 0x801e0b40 / j 0x801e0b3c with the operand pointer staged in the delay slot (the same class of trap as the label-call idiom).
What is true. The raw 12-entry jump table at 0x801CEE30 (field overlay, PROT 0897 file +0x618) routes every sub-op 0..9 to a value loader that joins the shared 7-byte compare-and-skip continuation at 0x801E0B40:
| sub | loader | state value |
|---|---|---|
| 0 / 1 | 0x801E0A40 / 0x801E0A70 | char-record HP / MP (cur, max) pair - the only scaled form (max * arg >> 8) |
| 2 | 0x801E0AC0 | char level byte +0x130 |
| 3 | 0x801E0AEC | party gold _DAT_8008459C |
| 4 | 0x801E0AFC | BIOS Rand() & 0xFF - a random-chance branch |
| 5..8 | 0x801E0B0C | slot table 0x801C6460[sub - 5] (s16; the read side of the 4C CA/CB/CC slot writes) |
| 9 | 0x801E0B34 | coin bank _DAT_800845A4 |
Sub-ops 10/11 keep the 9-byte u32 gold/coin form; 12..15 fall through (PC += 7). Ports: field_disasm::decode_subops (single 0..=9 compare arm), engine-vm field/step/flow.rs and FieldPartyInventoryHost::char_level / slot_table_read. cave01's P2[12] spawn gate is the live sub-5 exemplar.
Gaza 2 0x51 park - the two falsified generators
Two "ordinary play" generators of the 0x51 HP-readout desync, both falsified. What is true instead is in re-settled-threads.md.
Clamp asymmetry as a standalone generator.
- Tempting:
FUN_801EC3E4has two overkill clamps (accumulator vs displayed bar at0x801EDB70, live HP vs itself at0x801EEA10), and a plain capture shows "live HP 266 / bar 0 / zero accumulator". - Wrong: the clamps can only disagree when the bar already lags live HP at action start, and the previous party-targeted action's own
0x51settle wait guarantees it does not. From a synced start, credits exceeding the starting bar also exceed starting HP, so both sides floor together (a consistent kill). The exhibit is per-strike phased crediting - paired stores0x801EDB40/0x801EDB58credit the action total and the accumulator per strike while live HP commits once at0x801EEA10- a transient that closes with a death commit ~90 vsyncs later. - True: the asymmetry is an amplifier only. A per-frame watchpoint cannot tell an absorbing desync from the inside of a healthy multi-strike resolution; only survival past the action's commit and settle wait counts.
The Final Heal revive "at the worst possible moment".
- Tempting: the assigning seed (
0x800410BC) is real and the discard arithmetic stands, so a revive landing mid-drain - the killing hit credits the whole bar, the readout is mid-drop at state0x50- would strand the readout. - Wrong: credits land per strike early in the resolution;
0x50arrives after the remaining targets resolve and effects tear down; and the quarter-step drain empties any accumulator within ~35 rendered frames. Three captures (autorun_gaza2_acc_discard.lua, Lost-Grail-armed party, no harness HP / readout / accumulator writes, ~84k vsyncs) drive twelve retailFUN_801E6968revives across cast-path, kernel-path, single-target and party-wide kills: every assign hits+0x10 == 0, with margins of 143-280 vsyncs.
The cast-module blocker was named wrong
Tempting reading. A streamed signature-attack module "stages the caster's monster-block entries by raw index, and a party actor has no monster block", so it cannot run from a party slot.
Why it is wrong. FUN_8004AD80 resolves actor+0x1DA down two arms - monster seats through DAT_801C9348[slot-3]+0x4C, party seats through DAT_801C9360[slot] - and the indices PROT 960 stages resolve on both. The module's one monster-block access is a hardcoded seat-0 write to a single clip's root-motion field, unrelated to who is casting.
What is true. Three other things block it:
- That seat-0 read walks past
magic_countinto words the loader never fixed up, so a seat-0 monster withmagic_count <= 13- Che Delilas has 12 - turns the pointer into a bare offset and the following store lands a halfword in PSX kernel RAM. Retail never trips it because the module is only reached with Lu (16 entries) in seat 0. - Battle state
0x70re-enters the module every frame and advances only on a zero return, with no timer and no bail-out. One of its four phase gates needs a clip of at least 23 keyframes, which Gala's party index0x0Dfails at 17 of 19 equippable section-2 ids. - The damage call and both HP writes are hardcoded to actor slot 0, not the chosen target.
A structural-sounding impossibility ("X has no Y") stops the search before the real failures are enumerated. Full account in randomizer.md.
The attack camera was never an arm choice
Tempting reading. FUN_801D71B8 dispatches the per-art attack camera through three per-character jump tables. The arms visibly differ in choreography - one commits a single framing for the whole swing, others change shot two or three times - and 37 of the tables' 54 slots point at a bare return. So an art that films flat has a poor arm, and the fix is to point its slot at a richer one.
Why it is wrong.
- Every arm is timed, not merely shaped. A shot change is an
sltiimmediate against the animation cursoractor[+0x22C][+0x68], which counts sixteenths of a keyframe. The arm span carries thirteen such tests with immediates64/97/112/144/160/176/192/224/240/272- keyframe 4 through 17, sized for retail's ~20-frame swings. On a 46-100 frame clip any arm finishes its choreography inside the wind-up and holds one shot for the rest of the move. - Not one arm is spare. The 37 dead slots all point at one shared epilogue (
0x801D828C), a return. The 17 live slots reach 13 distinct arms, no arm is live in more than one character's table, and each of the 13 is already some art's camera. A retarget can only alias an arm another art uses, and a re-time then mistunes that art too.
What is true. The arms admit re-timing in place: scale an arm's immediates and its shots land at the same beats of the longer swing. Where a host art's own arm carries no cursor test there is nothing to scale, and the move is a swap of two slots inside one character's table - it leaves the set of live arms unchanged and the borrowed arm reachable from exactly one slot, which makes re-timing it safe. The arms differ in shot count and agree on clip length, so the choice axis is orthogonal to the defect; empty slots in an index are not slack in what it indexes. Full account in randomizer.md.
The case-6 party arm is the battle-over framing
Tempting reading. FUN_801D5854 case 6 forks at 0x801D5CF4 on DAT_8007BD71 == 0xFE and at 0x801D5CFC on ctx[+0x13] < 3. The fork is keyed on a party seat, the 0x801D5CFC arm reads the acting actor's anim id (actor[+0x1DB]) through a per-character script - eye prescale(0x500) straight behind the actor at -5 x actor[+0x3E] - and 0xFE beside 0xFF reads as one live state beside another. So it is the per-action party framing, and everything else takes 0x801D64C4.
Why it is wrong. DAT_8007BD71 is the battle-end signal. Its writers are the action SM's 0x5A wipe scans (0x801E65D8 party wipe, 0x801E6674 monster wipe, beside the cause in _DAT_8007BD2C), the 0x66 escape teardown (0x801E5A94, right after ctx[7] = 0x67) and the capture-effect module (0x801F7318); SCUS 0x80056014 zeroes it at battle init, and the effect-VM walker (FUN_801E0088) runs only while it reads 0xFF. Twelve battle save states - five Begin/Run prompts, the arts-input close-up, the tutorial open, two mid-strike frames and three ctx[7] == 0x19 approach parks - all read 0xFF.
What is true. During a fight the 0x801D5CFC arm is unreachable for anyone; the anim band its script keys on (0x11..=0x18) is the win-pose band, so it is the end-of-battle framing (ActionFraming::battle_over, false while a fight runs). The three 0x19 parks with Gaza acting confirm the 0x801D64C4 arm byte-exact: TR (0, 0x500, prescale(ctx[+0x6D0])), yaw ctx[+0x6DA] - actor[+0x46], focus the negated +0x34/+0x38 pair. Applied to a running fight, the wrong arm puts the eye 2048 projection units behind the actor - inside whichever combatant has closed to melee range - with ndc.y past -2 for the other actor. See battle.md.
The Done band is not idle
Tempting reading. FUN_801E295C's Done band (0x50 cleanup, 0x51 fade-down) is where a fight rests - an auto-resolved fight spends about half its frames there - so treating it as "an action is executing" keeps the per-action close-up up for half the fight. Hence: the band is idle, and belongs on the far framing (case 9), where the formation and the idle orbit live.
Why it is wrong. The close-up it argues from is the battle-over arm applied to a running fight (see above). With the in-fight arm, case 6 sits at prescale(ctx[+0x6D0]) - 4915 for a 0xC00 depth - with both combatants in frame, while the far framing over a formation collapsed by a melee sits at its 0x800 floor, 3276: closer than the arm it stands in for.
What is true. Retail's 0x50 / 0x51 arms fork on the category (0x801E5E90..0x801E5EF4, 0x801E5FC0..0x801E6018): Run -> orbit, Attack -> case 8, party slot over a dead target -> case 8, else case 6, re-armed every pass. zora_glare_petrify_post (ctx[7] == 0x51) reads case 6's pose on the caster and evil_medallion_rage_battle (ctx[7] == 0x0A, a round's first actor) reads the prompt's far framing. See battle.md and re-settled-threads.md.
The battle-intro banner is raised from a top-seated 0x0303 placement record
Falsified by capture.
Tempting reading. The intro banner is one of the placement records that park at (16, -24) and live at (16, 14) with kind 0x0303 - records 67, 69..75, 89, 101, 102 - with the runtime overwriting the disc width with the measured enemy name and sliding the element down from the park seat. Every other battle-HUD element does come from that table through FUN_801D8DE8, which forwards the record field for field and glides it park-to-live, and record 68 really does get its width overwritten at runtime (disc w = 0, spawned at the measured name width).
Why it is wrong. FUN_801D9D3C places the intro labels itself with immediates and never reads the table.
What is true. The width is a call argument, not a table write, and there is no slide: the labels appear and vanish at one seat. See battle.md and re-settled-threads.md.
0x801CFA48 is a mid-function citation aliased to another overlay
Falsified by disassembly.
Tempting reading. The world-map per-actor render dispatcher's 0x2000 arm targets FUN_801CFA48, a mid-function address inside FUN_801CF88C in the menu / battle-action dumps - VA-aliased to some other overlay's routine, so no clean dump exists to port from. Slot-A overlays share base 0x801CE818, and overlay_0897 dumps do print bodies at nearby VAs that are label artifacts.
Why it is wrong. 0x801CFA48 opens with addiu sp,sp,-0x70 in overlay_battle_action_0898.bin and in no other extracted overlay image, and the routine's 12-word signature occurs in exactly one image on the disc.
What is true. It is the lightning effect-ribbon emitter (THERNDER1 in PROT 0973's dev harness), resident only with the battle overlay. The artifact is the neighbouring name FUN_801CFB94 - a branch label inside this routine colliding with a real entry in PROT 0970. See battle-action.md.
_DAT_8007BD84 is a mode word the melee kernel branches on
Falsified by disassembly.
Tempting reading. The melee kernel tests the word against zero and forks on it, so it reads as a mode flag; and "the grunt's s7 latch always passes".
Why it is wrong. Its only non-zero writer disc-wide is PROT 0940's Cort "Mystic Shield" stager at 0x801F7678, storing a FUN_80021B04 return; FUN_8004CE2C dereferences it at +0x10 / +0x56 / +0x72 and clears it when it fires cue 0x10D. The two SCUS writers only zero it. The latch does not always pass either: s7 must equal actor[+0x1F3], and only one of fourteen reaching definitions loads it.
What is true. It is an effect-instance handle; callers that read it as a flag are testing the handle for null. See re-settled-threads.md.
The slot cabinet is in neither the art pack nor any prim a traced slot function emits
Falsified by a second read of the container.
Tempting reading. PROT 1200's TIM list holds no cabinet art, and no slot function emits a large untextured quad - both true.
Why it is wrong. PROT 1200 has three descriptors; the TIM list is only descriptor 0.
What is true. Descriptor 1 is a 2160-byte untextured TMD that is the cabinet, spawned as an ordinary actor by the slot init and drawn by the shared TMD renderer - which is why no slot function emits it. See minigame-slot-machine.md.
The slot's DAT_801d4134 * 0x10 is a sub-row landing nudge
Falsified by disassembly.
Tempting reading. rand % 5 scaled by 0x10 reads like a fraction of a reel row (rows are 0x100 apart), added so a stop does not look mechanical.
Why it is wrong. The product is a row index into the 16-byte-stride table at 0x801D3630.
What is true. The table's words choose which of the five paylines a forced stop lands its target on; a forced target is not always on the middle row, and there is no visual offset. See minigame-slot-machine.md.
A phase-gated effect draw is the candidate for the arena's object-1 dust decal
Falsified by disassembly.
Tempting reading. The decal is absent from a mist-free arena capture, so an effect draw gated on a battle phase puts it there.
Why it is wrong. No effect path touches it.
What is true. Object 1 is ordinary backdrop geometry that the SCUS battle loader FUN_800513F0 trims from both backdrop actors' part lists when _DAT_8007B64B is zero; the mist-free capture is the default, not a phase gate. See minigame-muscle-dome.md.
ctx[+0x26] is a boss phase counter and ctx[+0xD] is a dead store
Falsified by disassembly.
Tempting reading. ctx[+0x26] has one increment site (0x801E6D3C) in the Cort form-change arm, so it counts boss phases; ctx[+0xD] has "no port field and no reader".
Why it is wrong. ctx[+0x26]'s unload reader 0x801E61B4 passes it as a UI element id, and its only assignment anywhere is 0x65, the level-up banner. ctx[+0xD] has three readers inside FUN_801D5854.
What is true. ctx[+0x26] is a UI element id; ctx[+0xD] is the per-action camera angle variant. Both in re-settled-threads.md.
0x801E3A20..0x801E3A64 is the Miracle continuation
Falsified by disassembly.
Tempting reading. The block sits at the end of the strike stream and re-arms a counter, the shape of a Miracle Art's continuation.
Why it is wrong. s5 is ctx + 0x11, so 0x5(s5) is ctx[+0x16], and the guard chain (ctx[+0x13] < 3, record +0xF4 & 0x2000, counter zero) names the War God Icon.
What is true. It is the Attack x2 second pass - the refill that lifts ctx[+0x16] from 0 to 1. It is one of that byte's two writers; the stage site's bump at 0x801E37AC..0x801E37BC is the other.
The Miracle marker is armed by an input recognizer (FUN_801E91E8's caller)
Falsified by disassembly.
Tempting reading. ctx[+0x25F + slot] gates the Miracle path, and FUN_801E91E8 reads it, so that routine's caller is the recognizer that arms it.
Why it is wrong. +0x25F has one sb in the corpus, in FUN_80053CB8 at battle-actor seeding, from the Ra-Seru equipment byte.
What is true. No input recognizer writes it. FUN_801E91E8's caller is the killing-blow Seru absorb; it stages the absorbed Seru into ctx[+0x269], which the Done band teaches (port field absorbed_seru).
DAT_8007BD10 is a per-slot control-mode byte
Falsified by disassembly.
Tempting reading. A per-seat byte compared against 4 to pick out an AI-driven member reads as a control mode.
Why it is wrong. Three routines index character records with it as 0x80084140 + (byte - 1) * 0x414 (0x801EF344, 0x80053CEC, 0x801E39CC).
What is true. It is the slot -> roster character id table. "== 4 = an AI-driven member" is right in effect - character 4 is the AI companion - and wrong about the mechanism.
The spell record's +0x01 effect-class byte is undecoded
Falsified by this repo's own source (inference), with the bytes confirmed against the disc (disassembly).
Tempting reading. Nothing turns the spell record's +1 byte into a module, so it reads as undecoded; and the player Seru-magic block "all shares cat = 0x32 / sub = 0".
Why it is wrong. legaia_game_tables::spell_names::SpellEntry::sub_class reads +1 beside +0, and World::spell_table_sub_class serves it to the battle host. By SCUS_942.54, 0x83 Vera is 0x00 / 0x03 and 0x89 Orb is 0x01 / 0x04; only the nine enemy-side spells are 0x32 / 0x00.
What is true. The byte is decoded; it is the sub-id the capture-class dispatcher keys on (see the slot-B readings below and spell-table.md).
The slot-B band: four readings the whole-band dump overturned
Falsified by disassembly.
| Reading | What is true |
|---|---|
"No hard-coded jal into the capture-class band was found" | The sweep looked for a table keyed id - 0x81; the capture-class dispatcher FUN_801F2160 keys on the spell record's +1 sub-id and jumps through 0x801CF56C |
"0957's trampoline 0x801F9BA8 is reached by nothing" | It is arm 22 of 0x801CF56C, at 0x801F233C |
"A slot-B module is two functions and has no internal jal" | 196 framed functions across the 64 images, 25 of them with internal calls; the two-function shape holds for the first thirteen only |
| "PROT 0965 is a shifted sibling of 0967" | An over-read past the entry's real size; 0965 is the Doomsday module |
Function extents in the band come from frame matching, not from counting prologues against jr ra words - a frameless leaf, an early jr ra, or a jr ra word in a data tail breaks the count. See cast-module.md.
The summon draw runs 35-64 times a frame
Falsified by capture.
Tempting reading. During a player Seru-magic summon the battle per-actor draw FUN_80048A08 fires 35-64 times per frame - a figure from a real exec-breakpoint run - and a summon has many mesh groups, so it reads as a per-part driver walking them.
Why it is wrong. On the same catalogued state (scripts/pcsx-redux/autorun_enemy_move_render_path.lua, gimard_burning_attack, 400 vsyncs) the draw never exceeds 2 per rendered frame - Vahn solo against one monster. The same probe reads 6 in a 3-vs-3 and 2 in a 1-vs-1.
What is true. One call per live actor per rendered frame; the group walk is inside the call. The move-VM / FUN_801F7088 hypothesis stays retired. A budget or scheduling argument built on the larger figure is off by more than an order of magnitude; see effect-vm.md.
Audio / sound driver
Sound driver, SPU transfers, audio-level globals.
Long write-ups in this area
FUN_8003DE7C(1) waits out the last voice clip's span
The poll drains gp+0x91C by the frame step and answers busy while it is positive (0x8003DF24..0x8003DF34), so it read as "the drive is still reading the last clip", and the port declined every battle voice requested inside the previous clip's span. That arm is the development host's: it is reached only while 0x8007BA70 != 0 (beq v0,zero,0x8003DEE4 at 0x8003DE90, the second test at 0x8003DF04). On the retail path the poll reads the stream queue, gp+0x988 (0x8003DF70..0x8003DF78), which a CD-XA one-shot never sets, and the starter stores its span to gp+0x918, not 0x91C. Every PCSX-Redux cast film reads 0x8007BA70 = 0 and gp+0x91C = 120, the stream load's timeout, through clips that play. A voice inside a sounding clip replaces it. What the clip does hold is the drive word 0x8007BC20 (memory map), which the shout fallback and the block grunt read.
zeto_call_wave_mid_cast's +5 dB is not a volume path
The real-PCM track channel reads the state +5.09 dB louder in retail with correlation 0.9995, which reads as a level fault in the score's volume chain (sequence volume, VAB program and tone volume, SsSetMVol, the CD input). It is not. In one-second windows retail is +2 to +16 dB louder over the recording's first six seconds only, where the cast's own cues sound, and within 0.03 dB of the engine in every window after; the channel renders the score and nothing the cast keys, and the state has no cue voice sounding at its first sample (no voice record owned 0x21), so --cue-voices changes nothing. The level is the cast's CD-XA voice (XA11 channel 0) in a whole-window energy ratio, and the cast channel, which renders it, reads +0.01 dB.
Retail does not park a key-on behind an SPU transfer
FUN_8006B854 forks on _DAT_8007AF38 & 1 into shadow accumulators instead of the KON register, and the word reads like a transfer-busy flag, so a retail key-on trailing the port's by a vsync or three (a key-on census on rugi, the extra key-on on dolk2_market_noa, one held note in theeder_summon_mid_cast) read as a mask parked behind a busy DMA. The word is libspu's event-queueing switch and its only store in any image is SpuInit's zero, and a PCSX-Redux watch of the calc and every KON write finds each KON inside the calc that staged it (a key-on is never deferred). Modelling SPU transfer timing would move no key-on.
The cue funnels' de-dup word is not dead
0x8007B724 read as -1 for the whole of a mode, because the search for its writers looked for the absolute address: the mode init's sw v0,-0x48dc(at) turned up and the drainer's sw s0,0x40c(gp) did not. The same search read that s0 as a mixer record pointer, which is the one-shot arm's move s0,v1; the sustained arm branches past it with the ring id still in s0. So the word holds the sustained cue the drainer keyed (the repeat rule). A global a gp-relative store can reach needs both forms searched.
A flee's silent steps are not the 0x800478C0 gate
The cue walk's gate in FUN_80047430 reads 0x8007BD71, and a flee is close to the battle's end, so the silence read as that byte falling below 0x11. A breakpoint on FUN_800508DC through state 0x65 reads the byte at 0xFF and the walker running on clip 1, against a track of zeros: the flee staging cleared the record (settled).
The battle track does not start during the spin
Reading. The intro tick's phase 2 is "load and start battle BGM": its FUN_800567A8 call and battle bgm %d string name the track, so the port started the theme there, two frames into the transition, under a test that said so.
Why it is wrong. FUN_800567A8 is a debug print. Phase 2's real call is FUN_8001FC00(0x36F + set, 5, ..), a bundle read. The install and attach are the completion arm's (0x801CF91C..0x801CF930), beside its mode-0x14 store, and the init keys the field track off (0x801CEB58..0x801CEB70). A capture of a walked fight reads the battle sequence open and playing on the mode-0x14 vsync, 139 after the mode-8 store, with only cue voices between (timeline).
| Thread | Verdict | Why |
|---|---|---|
| A track's residual against retail PCM is unmodellable sub-vsync jitter | falsified (three defects, then 0.999) | Plausible: a random 0..=12 sample offset on each flush reproduced the size of the residual, and the emulator's cycle counts looked out of reach. A per-note fit reads a straight line in pitch and another in the flush's staged voices, under a sample of scatter: nothing about it is random. |
| The BGM tail banks must be dropped on every track restage | falsified (a track change closes neither) | Plausible: a restage reloads the score, so dropping the banks with it reads as hygiene. FUN_800243F0's stream arm never calls FUN_8001FF58; slot 11 closes in the field init. A director that drops on every load is the defect. |
Op-0x35 sub-op 3 resumes the BGM and sub-op 4 stops it | falsified (3 pauses, 4 re-attaches and replays from the top) | Plausible: the legacy labels read that way and a 3-then-4 pair looks like resume-then-stop. The arm table at 0x801CEE00 (PROT 0897, indexed sub - 1 behind sltiu 0xB) gives 3 = set pause bit 1 + FUN_80026740, 4 = clear bit 1 + FUN_80026478. Following the legacy labels silences the score at each of the 64 sub-op-4 sites on the disc. |
A scene-local BGM id loads PROT current_scene + 6 + id from the scene's own block | falsified (it loads global slot 2) | Plausible: FUN_800243F0 forms exactly that index and stores it. It is only the change-test index; the load arm overwrites the index it loads with *(0x8007BC64) + 2 (0x800245A4..0x800245BC). Retail stages no scene bank. |
The credits bank overwrites every resident bank below 0x6C810 | falsified (it spans 0x1010..0x641C0) | Plausible: VAB 10 opens at slot 0's base. By its size it covers slots 0 to 3 and stops short of slot 4 / 7 at 0x65010. |
FUN_8001FF58 releases a SEQ slot | falsified (it closes a VAB slot) | Plausible: the teardown op that calls it also stops a sequence, and slot numbers look alike across the two tables. The routine reads the resource record's +0x8 VAB id and calls FUN_80068C80 (SsVabClose), clearing +0xB in the delay slot. |
| A cue whose category's slot is closed falls back to the class-2 bank | falsified (it is silent) | Plausible: a fallback bank is what a single-bank port reaches for. The drainer tests the slot's +0xB enable byte and skips the cue (0x80016CE4..0x80016CEC); the VAB close zeroes it. |
| The Muscle Dome's hub holds the class-2 bank | falsified (it holds the field bank; slot 2 is closed) | Plausible: the dome's rounds run the battle frame driver, which loads PROT 0869. A retail state at the hub (mode 0x19) has slot 2 closed and slot 6 open over PROT 0876's header, the bytes the field states hold (audio.md). |
| The engine keys on about half retail's key-on rate | falsified (a pairing of the track's opening bars against a mid-track window) | Plausible: 0.244 against 0.488 comes off the key-on rate over equal-length traces. A trace only as long as the retail window has nowhere to slide; aligned (engine frame 3111 / 3112) the ratios are 1.148 and 1.060 (audio.md). |
The engine keys on about 15% more often than retail (ratio 1.148) | falsified (292 key-ons against 292, counted exactly) | Plausible: it is the residual left once the windows are aligned. It counts envelope edges, which a capture reads off a host-clocked SPU, and it aligns on sounding pitches. A breakpoint census of SpuSetKey against the engine's per-voice key-on counters, aligned on key-on timing, agrees on every count (audio.md). |
| Retail BGM voices sound at about twice the engine's volume | falsified (a mednafen read-out scale, plus a real 107 vs 100 gap) | Plausible: a mednafen state's (Voices[n].Sweep[0]).Current reads about 2.3x an engine voice volume on the same notes. Mednafen keeps a sweep's Current at the register doubled ((Control & 0x7FFF) << 1; the states' own Regs shadow reads 0x0850 where Current reads 4256), so psx_mednafen::spu halves it. The remaining 1.15x is the sequence volume: retail runs BGM at SsSeqSetVol 107 against a velocity scale of 100; the port carries it as RETAIL_BGM_SEQ_VOL (audio.md). |
FUN_80035B50 is a delay queue | falsified (a round-robin ring of four) | Plausible: a scheduler with an enqueue is the obvious port shape for a cue producer. Retail writes the next slot with no free-slot search and a fifth cue replaces one; the delay is set by the sibling FUN_80035BAC. |
| The port sustains about half again retail's sounding voices, so its release tail runs long or it keys fresh slots | falsified (both explanations, and the statistic) | Plausible: the comparand (envelope level, on the track the save holds) looks sound, so the figure reads as a real residual. Two instrument defects sit under it. The windows are not aligned - the retail capture pairs with engine frame 3111, not frame 0 - and the emulator steps its envelope on an audio-paced thread, so a per-vsync capture's envelope statistics move with host speed: two captures of one state disagree on env_level for six of ten voice-frames. |
The native BGM director's stop leaves the resume gate open | falsified (it was writing one of two pause representations) | Plausible: a resume that does not resume looks like a gate nobody closed. Two representations of "paused" exist; a stop that writes only one leaves unwritten the half the resume consults. |
The blocking bank for the Seru cast voice is XA27 / XA28 / XA29 | falsified (fifteen other files) | Plausible: those three are exactly the clip slots the dispatcher's 1 / 3 / 5 remap produces, a real but unused corner of the table. The cue id is hardcoded in each cast module's own image - 62 of 64 - and the ids that occur resolve to XA7, XA9..15, XA18..20, XA22, XA23, XA25 and XA34. |
The XA cue-duration table has 0x40 entries | falsified (0x110) | Plausible: 0x40 covers every arts-shout id, and the reader's one comparison is against 0x100 rather than a length. Measured off the executable the table runs to index 0x10F, with interior zero runs (0x37..0x40, 0x78..0x88, 0xE8..0x10A) that make a short read look like the end. The reader bounds nothing above 0x100, so a truncated table silently drops every cast cue. |
| The sequencer's pause freezes sounding notes | falsified (it keys them off) | Plausible: a pause that resumes where it stopped is a freeze in every other sequencer, and the play cursor really is preserved. The script's pause is sub-op 3 (FUN_8006275C raising slot flag 0x2); the per-tick FUN_80062F98 sees the flag and calls FUN_800638D8, which releases every voice the channel owns through FUN_800684CC. No script word resumes from the kept cursor: sub-op 4's FUN_800628F0 resets it to the start for every mode, and sub-op 2 is a stop and rewind (FUN_800641EC). |
FUN_80058490 is a sound-driver lane, so the table feeding it is a cue list | falsified (it is MoveImage) | Plausible: it is called with a small id from a compact table in a battle context, the shape of a sound-cue dispatch, and the values sit in a plausible cue range. It moves a VRAM rect to (0xE0, 0x1DC) - CLUT row y = 476 - so 0x801F6418 holds VRAM x coordinates, a palette column. |
FUN_80068D94 as "SsSepOpen / SEP loader" (with FUN_80068B98 as "SsSeqOpen") | falsified (it is the VAB-open head) | Plausible: it validates a magic, reads a count at +0x12, SsSpuMallocs and patches a pointer table - the shape of a SEP/track loader, with the magic read as 'VAP'. The compare is 0x564142 against word >> 8 plus low byte 0x70 - pBAV, the VAB magic - and +0x12 is ps. The "per-track pointer table" is the ProgAtr table receiving the program -> packed-tone-page rank map (vab.md). Correct roles: audio.md. |
The entry that matches "[u32 format == 2][u16 spu_addr[256]], every address >= 0x8000" is monster.snd | falsified (it is summon.dat; monster.snd is a multi-bank VAB two entries away) | details ↓ |
Op-0x35 sub-op 9 is a queue, triggered by the next scene entry | falsified (it is a start behind an asset-load barrier) | details ↓ |
_DAT_8007B910 is the live screen brightness | falsified (it is the live audio level) | Plausible: the cell ramps down during a summon and back up when the action ends, and a summon does visibly dim the screen. All 26 dumped read sites end in a volume setter, none in a draw primitive. The dim rides the separate accumulator _DAT_8007B440 (FUN_801ED308 -> the wipe emitter FUN_8003479C); the two ramp together. Answer: re-settled-threads.md. |
FUN_800684CC keys a voice off by VAB id | falsified (it keys off by owner halfword) | Plausible: a routine that takes an id and stops voices reads as a bank teardown, and SsVabClose is the libsnd call with that shape. The argument is the owner halfword seq | track << 8 that key-on stamps at 0x8006661C, so it stops the voices one sequence's track owns. Passing a VAB id stops whatever shares the number. |
| The scene-VAB parse offset is why the audio oracle never converges | falsified (those scenes carry no bank at all) | Plausible: a parse handed offset 0, where no entry on the disc begins with the magic, fails every time - a good explanation for an oracle that converges on none of its candidates. The nineteen scenes the oracle qualifies carry no VAB entries, so the figure does not move when the parse offset is right. |
FUN_801EA9B0 cycles the BGM index | falsified (it plays the row; cycling is FUN_801E9F64) | Plausible: the dev menu's BGM CALL row shows a number that moves, and the routine is the row's action dispatcher. The arm at 0x801EACBC..0x801EAD20 reads the cursor _DAT_801F2E90, indexes the 10-byte sound-test rows at 0x801F2E94 and installs the row's global id - the fourth disc-wide writer of _DAT_8007BAC8, not a "world-map region change" writer. Stepping the cursor is the input half, FUN_801E9F64. |
A mednafen save reports nearly every voice non-Off because nothing keys them off wholesale | falsified (there is no Off phase; the predicate was talking) | Plausible: every snapshot reports 20 to 24 of its 24 voices live, so a rule asking an engine frame's mask to be a superset looks unsatisfiable and 0 converged reads as a fact about the comparand. ADSR.Phase has no Off member - it is 0 Attack through 3 Release, and a key-off parks a voice in Release - so the predicate counts every voice the state ever keyed. Against ADSR.EnvLevel != 0 retail holds 4 to 9 audible voices against the engine's 4 to 8 (settled). |
The dev sound-test table's row i carries global BGM id 2000 + i | falsified (one id is missing from the run) | Plausible: the 71 rows open at 2000 and the sound-test track numbering is 2000 + i everywhere else. The rows carry 2000..=2043 and then 2045..=2071: sound-test track 44 has no row, so above the gap the cursor and the id it installs differ by one. Deriving an id from the cursor names the wrong track for 27 of the 71 rows. |
Retail's per-voice reverb mask is 0xC081, routing voices 0, 7, 14 and 15 | falsified (that word is SPUCNT) | Plausible: the trace field is named reverb_mode, the value is stable across every frame, and 0xC081 reads as a sparse voice mask. The PCSX-Redux SPU-ports blob is the hardware window 0x1F801C00..0x1F801DFF verbatim, so offset 0x1AA is 0x1F801DAA = SPUCNT: enable, unmute, reverb-master, CD audio. The real EON two words earlier reads 0x00FFFFFF - all 24 voices - on every frame of the same capture (settled). |
| The engine routes no voices through the reverb tank | falsified (both shipped hosts do; the oracles did not) | Plausible: an engine-side trace reports EON = 0 on every frame, from the engine's own code. Both hosts build their SPU through StreamResampler::new -> set_retail_reverb; a trace or PCM oracle that builds a bare Spu measures an engine nobody runs. An oracle that constructs its own subject has to construct the shipped one. |
| The engine runs about half retail's concurrent voices | falsified (two different pieces of music) | Plausible: mean 4.83 against 9.78 over the same scene name, twice, is the shape of a real deficit. A save's scene does not decide what it is playing - _DAT_8007BAC8 does. The retail town01 capture holds 2000, the overworld track (the save walked in from the world map), while the engine trace plays the 2016 the scene's own prescript selects. Same track, comparable stretch: 9.67 / 18 against 9.78 / 19 (settled). |
| Retail doubles about one note in six across two voice slots | falsified (slots-per-note is the arrangement's, not a side's) | Plausible: retail 1.158 against the port's 1.002 looks like an allocator difference. On a track-aligned pairing the ratio reverses - retail 1.002, the port 1.037 to 1.061 - so the statistic belongs to the piece. Nor is the doubling town SFX in the retail window: a second capture in the same town, on the same walk, reports 1.002. |
The nilboa duel states read BGM 4096 | falsified (they read 2009; the 4096 states are the ending ones) | Plausible: most states carry 2000..=2068, so a 4096 in the corpus reads as an anomaly belonging to whichever states were being opened. 4096 = 0x1000 is the park sentinel both streaming slots share; it appears where a save has no field track owed - the endings (settled). |
FUN_8001E54C's types 2 / 0xC are VRAM rect uploads, and cases 1 / 3 decode LZS | falsified (SEQ installs and VAB transfers; no LZS arm) | Plausible: the installer is reached from scene loads that also upload textures, and a staging copy looks like a blit. Type 2 detaches and closes the SEQ player, copies the payload into the slot's staging buffer and opens it with SsSeqOpen through FUN_80026410; 0xC opens the fixed record 0x800705AC; 1 / 3 are FUN_8002630C. All 90 disc streams with a score past offset 0 carry it as a type-2 chunk (settled). |
FUN_801D4A60 parks on a BGM request / ack pair | falsified (the side-band sound-bank pair) | Plausible: the guard at 0x801D4B58 polls a request word against an acknowledge, the BGM handshake's shape. The pair is _DAT_8007BABC / _DAT_8007BAA0, the side-band bank the engine runs as World::audio.sound_stream (field-locomotion.md). |
Every music_01 entry is a self-contained [VAB][SEQ] pair | falsified (75 of 81 are) | Plausible: every entry that carries a track is a pair. The installer walk over all 81 finds slot 72 a score with no bank, 76..79 one-sector fills and 80 a bank with no score (settled). |
FUN_8003EAE4 streams a whole XA clip | falsified (it only seeks) | Plausible: it takes a clip id, sets the XA drive-state word and is called on every battle action that voices a line. It issues CdlSetloc then CdlSeekL (li a0,0x15 at 0x8003EB68), installs no callback and issues no read, and its callers store 0 into _DAT_8007BC20 straight after; the XA ring is installed only by FUN_8003D53C (0x8003D714 / 0x8003D72C). |
FUN_8004DA00 arms on ctx[+0x276] != 0 | falsified (zero passes) | Plausible: a non-zero byte reads as an "enabled" gate. beq v0,zero,0x8004DA60 at 0x8004DA50 continues when the byte is zero and sends a non-zero byte to the -1 latch store - the same polarity as FUN_8004FCC8. |
| The field init's slot-10 load is a bank of ending-scene sound effects | falsified (it is the credits theme) | Plausible: slot 10 sits among the SFX slots and the load runs only in two ending scenes. The arm starts a sequence from the loaded score itself and latches the BGM loader off; see settled. |
.MAP and .PCH in the sound-driver string cluster are PsyQ SoundArtist intermediates | falsified (they are the field map and the walk-on trigger sidecar) | Plausible: the strings sit between .dpk and .pac beside bse.dat, so the run reads as one sound driver's extensions. Each is referenced once, by the field-file loader FUN_8001F7C0 (0x8001F834, 0x8001F8CC), whose retail branch reads the scene block's map, .PCH and prescript in one 0x28-sector call (sound-driver.md). |
Op-0x35 sub-op 9 was never a queue
Tempting reading. Field-VM op 0x35 sub-op 9 stashes a BGM track for a later trigger, and scene entry is that trigger. The op sits next to the pause / resume / stop control words, a scene's entry script never uses it, and its arm opens by comparing two globals - which reads as "is a slot free yet?".
Why it is wrong. The arm at 0x801E0224 compares *0x8007BAB8 (the index the resolver produced) against *0x8007BA9C (the index actually loaded). The mismatch branch goes to 0x801DEE4C, which is move s8,s4 - the dispatcher's restore-PC idiom, so the script re-runs the same instruction next frame. That is a wait on the asynchronous asset load. When it clears, sw v0,-0x4538(a1) writes *0x8007BAC8 = id, the identical store sub-op 1 makes. Sub-op 11 (_DAT_8007BA9C = -1) is the barrier's arming half.
What is true. Sub-op 9 is sub-op 1's select plus a wait and sound-flag bit 0, and it is what a cutscene changes music with mid-scene. A scene-corpus BGM sweep cannot see the difference: it runs only prescripts, which emit sub-op 1 exclusively. The audible symptom of the queue reading is a cutscene that plays silent while its score starts over the next scene the player walks into. The opposite reading - the select is the start - is wrong too: bit 0 holds the selected track off until the record's own 0xA commit, so starting it at the select plays it over the outgoing score's fade. Full arm and the port's routing: script-vm.md.
The 256-slot SPU-address run that was really a CLUT
Tempting reading. A [u32 mode == 2] header followed by 256 u16s all >= 0x8000 identifies a packed monster sound bank, and the one entry matching it is h:\mpack\monster.snd. 0x8000 is the boundary an SPU sample address clears once the reserved low region is skipped, the leading 2 reads as a format word, and the matching entry's CDNAME label names the sound cluster.
Why it is wrong. The match is summon.dat (extraction 893, summon-readef.md): header word mode 2, then 0x200 bytes of BGR555 CLUT with the STP bit forced on every non-zero entry, which sets bit 15 of all 256 halfwords. The values repeat (0x8000 0x8000 0x8000 0x8000 0x8001 0x8001 ...), and SPU sample addresses are strictly increasing - a monotonicity check rejects it where a threshold check cannot.
What is true. monster.snd is extraction 891: FUN_8003E104 does li v0,0x37d (raw TOC 0x37D = extraction 891) beside the h:\mpack\monster.snd path string (see ghidra/scripts/funcs/8003e104.txt). Entry 891 is a 206-bank multi-VAB archive, so the monster SE bank is a multi-bank VAB, not a bespoke address table; attributing the vab_multi_bank class to "the level_up cluster" reads the same CDNAME +2 shift off an extraction filename (cdname.md). The monster_sound_bank class is kept and pinned at zero matches so the shape stays named. A threshold predicate over a fixed-size run identifies a shape, not a format; where the shape encodes an ordering (addresses, offsets, LBAs), assert the ordering.
bse.dat: three readings the gp+0x678 trace overturned
Falsified by disassembly.
| Reading | Why it looked right | What is true |
|---|---|---|
bse.dat record +4 is a u32 v taking only 0 and 2 | Reading +4..+7 as one word; +5..+7 are zero in every retail row | +4 is a u8 category (the VAB-slot selector) plus three bytes no reader touches; "0 and 2" describes the authored defaults, and the cue router rewrites the byte per cue |
bse.dat is loaded once at sound-init and held for the session | FUN_8001FA88 has the shape of a sound-init routine and also loads the per-set .dpk | Its one caller on the whole disc is battle init FUN_800513F0; the bank reloads per battle, and the buffer slot 0x8007B8D0 is repointed at every field load |
The bse_bank detector's "u32 at +4 under 0x100" gate is a value bound | It reads as a range check on the trailing field | A category byte can never exceed 0xFF, so the bound can only fail on a non-zero trailer - it is a zero-trailer test |
The consumer looks untraced because every reader forms 0x8007B990 with lui + lw, a pair the five-form address scan does not accept. Details: re-settled-threads.md.
bse.dat carries a second record family with a resident consumer
Falsified by disc bytes.
Tempting reading. Bytes repeat on a fixed stride inside the loaded buffer, and a matching run in a second entry looks like a shared footer - a second record family whose consumer is still to find.
Why it is wrong. Both runs are the same builder's fill inside VagAtr tone rows belonging to a different file left in the sector: 888's tail equals 886's and 1063's; 1062's equals 1056's.
What is true. There is no second family and no consumer. The companion reading - that FUN_8001FA88's dev branch loads PROT 0x37A, tying the .dpk name to the sound_data2 family - fails the same way: that is the retail branch, and 0x37A is bse.dat. See re-settled-threads.md.
Title / boot / overlays
Boot order, the title screen and overlay identity.
| The reading that looked right | What is true |
|---|---|
The title-screen overlay capture interleaves PROT 0899 options data, PROT 0897 menu helpers and a separate title overlay over 0x0E818..0x25000 | one image: PROT 0899 at 0x801CE818Why it looked right, and what killed itPlausible: the capture is genuinely mixed content, and 0897 and 0899 share slot A's base, so short fingerprints and address ranges placed overlapping regions. Comparing every 256-byte block of |
FUN_801D362C is a cutscene dialogue routine | the move VM's 0x2F extension dispatcherWhy it looked right, and what killed itPlausible: it sits among routines a cutscene exercises. Its only reference is the move VM's op- |
The narration crawl's geometry is a per-scene table, with 18 px line spacing in opdeene | a config block seeded by the script; pitch fixed at 16Why it looked right, and what killed itPlausible: a pixel capture fits each scene's window and speed, and |
| The title menu's third row opens Options | the title menu has two rowsWhy it looked right, and what killed itPlausible: the port's |
| The native window draws the boot Options screen twice, and its tests read the unframed copy | it drew once, unframed; no test read itWhy it looked right, and what killed itPlausible: two copies of one screen is a common host-drift shape. The defect is a single unframed draw: a bare session painted at a fixed pen that never reaches the framed screen. |
| Mode 18 has no hand-off store, so the front-end chain skips it | 0x801CE8DC in PROT 0902Why it looked right, and what killed itPlausible: a three-form scan for stores to the mode word finds one for every other INIT handler and none for this one. The store is |
The retail title screen runs under game mode 0x10, which leads load / new-game -> field | the title's own mode is the card mode 0x17Why it looked right, and what killed itPlausible: |
| The Muscle Dome is not an OTHER-game mode | for the hub, true for a roundWhy it looked right, and what killed itPlausible: a dome fight is an ordinary battle under the battle mode. The dome hub - the contest screen between fights - is PROT 0977, sub-id 5, game mode 24, the OTHER-game mode. |
A mode-change edge clears three gp words | fourWhy it looked right, and what killed itPlausible: three of the four sit adjacent at |
The system-UI TIMs sit in an unindexed gap ahead of init_data, loaded by a SCUS boot routine through FUN_8005D9A0, and both halves stay resident | they are raw TOC entry 0, init_data itself; init.pak's FUN_801CECD0 loads it; raw entry 1 is the GAME OVER overlay'sWhy it looked right, and what killed itPlausible: the extraction index starts at raw entry 2, which the +2 label shift files as |
init.pak's title.pak string is only a debug-print referent | a dev-branch load operand; its retail twin is extraction entry 0Why it looked right, and what killed itPlausible: |
Title sub-mode 0x10 selects between a prompt and a menu | it is one stateWhy it looked right, and what killed itPlausible: the screen visibly changes between a "press start" prompt and a two-row menu. Retail's |
Long write-ups in this area
The title sub-mode word lives at 0x801DD920, and 0x02 is a screen a player can see
Falsified by disassembly, with a cold-boot capture agreeing.
Tempting reading. FUN_801DD35C's init writes sub-mode 0x02, so 0x02 is the cold-boot screen, and the word it writes is at 0x801DD920, the address the dump prints on that line. 0x02 really is a complete two-row menu (rows at y 107 and 120, confirm mask 0x44, advancing to 0x14).
Why it is wrong. 0x801DD920 is the instruction address of the sw v0,0x204(a2) that performs the store, with a2 = 0x801F0000 from a lui four instructions earlier. And the 0x02 store is overwritten with 0x11 whenever the entry word _DAT_8007BB00 is non-zero, which the boot init.pak raises unconditionally at 0x801CEB84.
What is true. The sub-mode word is 0x801F0204, and a per-vsync cold-boot poll never observes 0x02. See re-settled-threads.md.
The attract sequence can be armed from any title sub-mode
Falsified by disassembly.
Tempting reading. The attract countdown at 0x801EF16C is a state-struct field, and several arms of the tick touch state-struct words, so any idle sub-mode lets a global timer fire.
Why it is wrong. The countdown is decremented, and its underflow writes _DAT_8007B83C = 0x1A, only inside the extent 0x801DDB0C..0x801DDD94; every other arm reaches the shared epilogue without touching it.
What is true. That extent is sub-mode 0x10 AttractIdle and nothing else. The preceding sub-mode 0x11 spends a different accumulator (_DAT_8007BAB4) and hands to 0x10, which makes the two look like one timer in a capture.
There is exactly one master-mode-2 writer, at 0x801DFC00
Falsified by disassembly.
Tempting reading. 0x801DFC00 is in LaunchGame (0x06), the NEW GAME exit, and a single "leave the title into the field" writer is the shape the mode graph suggests.
Why it is wrong. LaunchFade (0x16) writes the same master mode at 0x801DFAFC on the load route (state[-0xEA8] == 1).
What is true. There are two writers, one per route, and both arms clear _DAT_8007BB00 as they go. The second is the CONTINUE path - the one a save-file boot takes.
The title slider state[-0xEB4] is clamped to [0, 0x2C]
Falsified by disassembly.
Tempting reading. 0x2C appears as a bound in both of the slider's arms, and a value bounded above by 0x2C with a natural floor of 0 reads as a range.
Why it is wrong. The decreasing arm subtracts frame_scalar << 3, then slti v0,v0,0x2c / beqz: a result below 0x2C is forced back up to 0x2C (0x801DFC78..0x801DFC98). The increasing arm adds the same step and forces anything at or above 0x2D back down to 0x2C (0x801DFCA4..0x801DFCC4). There is no 0 floor, and the seeds the graph writes are 0x100 (0x801DD88C, 0x801DE094) and -0x16 (0x801DECB8) - both outside the supposed range.
What is true. Both arms converge on the single value 0x2C from their own side: the cell is a settling animation parameter, not a bounded slider position.
The title screen is loaded before the mode table is consulted
Falsified by disc bytes and main()'s disassembly.
Tempting reading. None of the 28 mode-table rows carries a name resembling "title", so the screen runs ahead of the dispatcher from its own pre-mode-dispatch boot load.
Why it is wrong. The one pre-loop overlay load in main() is 0x8001612C jal 0x8003ebe4 with a0 = 0, which is extraction 0895 (init.pak, the publisher logos) reached by mode 16 READ.
What is true. The title overlay PROT 0899 is loaded by mode 22 CARD (0x800258B4, a0 = 4) and its tick runs as a spawned actor under mode 23. See re-settled-threads.md.
FUN_801DD35C lives in an unindexed PROT.DAT gap between entries 899 and 900
Falsified by TOC arithmetic and a byte search.
Tempting reading. The tick's bytes sit past where entry 899 seems to end and before 900 begins, so they occupy a gap the TOC does not index.
Why it is wrong. An entry's size is the sector span to the next, so the TOC partitions PROT.DAT without gaps (899 ends at sector 47301, where 900 begins). The coordinate comes from the superseded entry-size expression.
What is true. The tick's 48-byte prologue occurs exactly once on the disc, inside entry 0899 at file +0xEB44.
FUN_8003EAE4's flags are consumed by an untraced CD driver
Falsified by disassembly.
Tempting reading. The routine stores three flag cells and nothing visible reads them, so a CD driver outside the dumped corpus must.
Why it is wrong. gp+0x910 and gp+0x890 have no reader anywhere in SCUS or the 1233 PROT entries - every access is a store.
What is true. The CD-callback sequencer is FUN_8003D764, and it reads none of the three cells; it dispatches on gp+0x928, which only FUN_8003D53C writes and which FUN_8003EAE4 merely reads as a no-op entry gate. See re-settled-threads.md.
0x801CE9C0 is an entry point in no image, so mode 16 is a stripped dev path
Falsified by disassembly.
Tempting reading. SCUS FUN_8002612C (mode 16) jumps to 0x801CE9C0, which sits mid-way through the debug-menu overlay's FUN_801CE97C, so the mode is a retail-stripped path jumping into slot A blind.
Why it is wrong. It is VA aliasing at the shared slot-A base: the debug menu is only one tenant of that address.
What is true. 0x801CE9C0 is a clean function entry in PROT 0895 (init.pak, file +0x1A8), the publisher-logo pass that mode 16 exists to run. See boot.md.
FUN_801CE9C0 draws the publisher logos
Falsified by disassembly.
Tempting reading. The routine handles all four logo TIMs, so it is the logo draw.
Why it is wrong. Its four FUN_800198E0 calls are LoadImage wrappers over rects it forms from each TIM's header, and the routine contains no primitive emit.
What is true. It uploads them. The quads come from the sprite-descriptor table at 0x801F369C, emitted by FUN_801CFBB8 (settled).
SCEA unfolds as a 2x2 grid of 32-row strips
Falsified by disassembly.
Tempting reading. A (2, 2) strip grid fits the visible content, and it is pixel-equivalent on screen.
Why it is wrong. The fit is to the pixels, not to the descriptors retail emits.
What is true. Retail's descriptors 2 and 3 are two 64-row halves of the 256x128 TIM, and PROKION's halves are 127 rows, not 128.
Containers / placeholder slots
PROT entries, placeholder slots and stream layouts - most born of the over-reading entry size.
| The reading that looked right | What is true |
|---|---|
0x02018B0C is the magic of an on-disc effect bundle with a 28-entry schema-offset table | a type-0x02 DATA_FIELD chunk header on a pack of 29 TMDsWhy it looked right, and what killed itPlausible: the word sits behind a |
PROT 0897's 0x801F2E94 table has a six-byte stride | 73 ten-byte sound-test rowsWhy it looked right, and what killed itPlausible: the bytes repeat on a short period. The consumer indexes |
PROT 0897's 0x801F3340 run is pointer tables | window programsWhy it looked right, and what killed itPlausible: the run is full of in-image words. They are eight-byte |
| PROT 0945 loses nothing to the donor cut | it loses a credited pointer like the other fiveWhy it looked right, and what killed itPlausible: its donor routine's three spawn calls resolve to nothing when read at the call. |
| PROT 0967 carries a 2,992-byte un-dumped code run | 92 bytes of one leaf, a 1,757-byte prompt pool and 1,143 bytes of PROT 0966's tailWhy it looked right, and what killed itPlausible: the shape classifier calls the window |
The PROT 0898 head block carries a switch jump table from 0x9B4 | nine runs of jump-table words below 0xDF8, not oneWhy it looked right, and what killed itPlausible: one run of in-image VA words after a string pool reads as one table. Two of the nine runs have a bound |
| PROT 0970 is the world-map top-view debug image | it is the STR / MDEC FMV overlay; the top-view image is 0981Why it looked right, and what killed itPlausible: both are slot-A images at the same base, and both sit in a CDNAME block whose labels inherit forward. 0970's own operands are the STR play loop's - the decode context at |
| PROT 0974's 10 KB of readable text is a string blob | an 81-record 0x84-stride rosterWhy it looked right, and what killed itPlausible: the run is legible text with separators, and the text is Japanese in a USA-build image, which suggests a foreign build on top. The loop operands in |
PROT 0975's 0x801D4138 run is a jump table, or data, or a function interior | it is PROT 0972's codeWhy it looked right, and what killed itPlausible: each reading fits one property of the run - no prologue, no |
FUN_8001E890's own frame keeps the registrar away from a foreign block | the checksum guards the other buffer, on the other armWhy it looked right, and what killed itPlausible: the routine re-sums PROT |
| A rebuilt PROT 0874 with a different section-0 decoded size can be hand-built to reproduce the wild read | the container is not constructible that wayWhy it looked right, and what killed itPlausible: the symptom shows on a rebuilt container, and "make the section bigger" is what a size-sensitivity test looks like. The LZS decode is length-driven by the descriptor and |
| A scene bundle's descriptor offsets can fall outside the entry | all 668 are insideWhy it looked right, and what killed itPlausible: a format that stores absolute offsets into a streamed container produces exactly that. The out-of-range offsets are measured against the over-reading entry-size expression; against the corrected extent every descriptor in all 102 tables lands inside its own entry. |
PROT 0874's meta[1] (0x2CBA0) is a VDF tail offset past the LZS payload inside the entry | the entry is 0x19800 bytes; 0x2CBA0 is 78 KB past its endWhy it looked right, and what killed itPlausible: the battle loader does walk a flat |
The dome panel still is uploaded as 320x64 rows stepped down in y | four 64x256 columnsWhy it looked right, and what killed itPlausible: a 320-wide destination and a y-stepped upload is how the neighbouring |
The scene_v12_table load path reads the .MAP, the .PCH and efect.dat | that is the dev branchWhy it looked right, and what killed itPlausible: the three-file read is in the routine, and nothing in it is marked dev. The fork is on |
Both record[0] + 0x5C accesses live in PROT 0900 | one is PROT 0901Why it looked right, and what killed itPlausible: the two hits sit close together in a window read as one entry. One is past 0900's real end and belongs to 0901 - the entry-size over-read. A sweep of 518,656 words across 84 images finds no reader of that displacement on a |
There is no jal at 0x801F78D0 in the world-map overlay | the test read the delay slotWhy it looked right, and what killed itPlausible: the address holds no call, which is a clean negative. The call is at |
| Pochi-fill slots are stale mastering scratch, and some parse as valid TIMs | every slot is one 2048-byte sector; 0 of 266 carry a TIMWhy it looked right, and what killed it |
The world-map kingdom bundle is PROT 0085 / 0244 / 0391 | it is 0086 / 0245 / 0392Why it looked right, and what killed it |
The battle-form character pack holds seven atlases inside PROT 1204, the last truncated, with CLUT row 496 skipped | eight whole atlases in PROT 1205; 496 is the eighth, not a gapWhy it looked right, and what killed it |
The title TIM ships as three multi-bank duplicates in PROT 0888 / 0889 / 0890 | one copy, in 0890 at 0x14228Why it looked right, and what killed it |
scene_tmd_stream entries can hold two or more concatenated sub-streams (the "two-list" shape) | one stream per entry; 0 of 182 hold a secondWhy it looked right, and what killed it |
| The stage backdrop renders as half a bowl because bytes are missing - mirror it to recover them | the half is authored; 182 of 182, and nothing is unreadWhy it looked right, and what killed it |
| ...and therefore nothing completes it, so drawing a second copy is a regression | retail links two backdrop actors; the second carries a per-stage transformWhy it looked right, and what killed it |
| PROT 0968 is a 4 KB module (pointer-table head, 10/11 self-pointers, 2+8 spawn calls) | its own content is 2600 bytes; the rest is stale bufferWhy it looked right, and what killed itPlausible: the entry really is 2 sectors. Only file |
The literal 0x801F69D8 in SCUS_942.54 is a cross-image reference naming 0968's loader callsite | it is the slot-B base constantWhy it looked right, and what killed itPlausible: it is the only literal-word hit outside the shared-base band, so an aliasing argument cannot dismiss it. It is the SCUS global |
Battle DAT_8007BD0C == 0xB5 at 0x801E6D04 is a test on the Lapis Wave spell id | it is the formation monster id - CortWhy it looked right, and what killed itPlausible: spell |
The Muscle Dome INTERVAL screen is the (384, 0) 320x256 still | reversed (true on a re-entered hub; the first visit draws a live render)Why it looked right, and what killed itA first-visit intermission is |
init.pak carries five logo TIMs | fourWhy it looked right, and what killed itPlausible: five plausible TIM headers parse out of the region. The fifth sits inside logo 3's pixel data, where image data produces a header-shaped run. Counting parseable headers counts images only if each parse consumes what it claims. |
The type-0x14 FLAG descriptor is a property of every count-4 and count-5 bundle | 28 of 105 tables, at every count from 4 to 7Why it looked right, and what killed itPlausible: every count-4 and count-5 bundle in a small sample carries the FLAG slot last. Across all 105 descriptor tables the FLAG slot appears on 28, whose counts run 4 to 7, and in all 28 it is the last entry. The position is the rule, not the count. |
urudre1's 584 unexplained bundle bytes are sector padding | 562 of them are high-entropy residueWhy it looked right, and what killed itPlausible: the run is smaller than a sector, sits at the end of the entry, and the pack's payload hashes identically to the standalone copy either way. The payload claim holds exactly: the pack's last member's TIM closes on byte 343,480, the same SHA-256 as |
| The PROT 0898 head is nine jump tables | twenty-two; two more sit above itWhy it looked right, and what killed itPlausible: nine runs of in-image VA words is what a scan of the bytes shows. Tables that abut with no pad word merge into one run; read off their dispatches - each |
| A scene bundle's last-sector residue is its walker's unread tail | it is the packer's bufferWhy it looked right, and what killed itPlausible: the bytes sit right after the last stream the walker parses. Byte |
| The packer-buffer run is confined to an entry's last sector | PROT 0976's tail is 0x98C bytes of 0970Why it looked right, and what killed itPlausible: every scene bundle's run fits its last sector. Searching the suffix over the whole entry reproduces 82 of the sibling rule's 83 cuts. |
| PROT 0970's 3,152-byte run above the init flag is uninitialised data, or code | two MDEC command packets and the register-pointer blockWhy it looked right, and what killed itPlausible: the image has a real |
FUN_801CFCDC stages MDEC output rects into 0x801D0D5C / 0x801D0D9C | it uploads the quant tablesWhy it looked right, and what killed itPlausible: two sixteen-word destinations filled from a caller pointer look like a rect pair. They are the luma and chroma matrices of the quant packet behind the header at |
PROT 0899's 3,552-byte zero run at 0x1EB28 is uninitialised data | inter-asset fillWhy it looked right, and what killed itPlausible: a long zero run inside an overlay usually is |
| PROT 0967's prompt pool has no consumer | twenty-eight strings formed at forty-one sitesWhy it looked right, and what killed itPlausible: no literal pointer word names it. The module's own code forms each string address with |
A slot-B image never calls itself with jal | PROT 0967 doesWhy it looked right, and what killed itPlausible: it holds across the cast band, which is why function extents there are recovered by frame matching. 0967 reaches its frameless leaf |
| PROT 0900 and 0901 are shifted copies of one image | an old entry-size artifactWhy it looked right, and what killed itPlausible: a byte comparison of the two matches. It compares 0901 with itself through the over-reading entry size; 0901's own content ends at file |
PROT 0901's inherited tail starts at 0x26B0 | 0x252A, donor 0900Why it looked right, and what killed itPlausible: |
PROT 0780 (edteien)'s residue is a scene-event-script walker's tail | the walker never startedWhy it looked right, and what killed itPlausible: the class is right and the bytes are unclaimed. Its prescript holds two records, below the standalone count floor, so a content-only walker refuses the entry; the loader reads the records positionally, and so does |
tmd_size_prefix entries are meshes truncated on disc whose tail a loader streams or zero-fills | no retail entry is truncatedWhy it looked right, and what killed itPlausible: a leading size word larger than the entry, with every object range inside it, reads as the prefix of a larger resource. On the corrected extraction all 182 entries that open with a type- |
Long write-ups in this area
Assets named by the entry the over-read window started in
Status: falsified - each asset is where it always was; only the (entry, offset) name for it was wrong
Tempting reading. An asset is recorded as "PROT N offset K" because a scan positioned on entry N finds it there, and each such name comes with corroboration:
- The kingdom bundle "at
0x1800of entry 85" has a table there, with the right count and the right first descriptor offset. - The battle-pack atlases have a consistent stride from a consistent base, a truncated last member is normal at the end of a container, and the missing CLUT row reads as a deliberate gap in a 490..497 run.
- The title TIM's three "duplicates" are byte-equal, which is what a multi-bank duplicate looks like.
Why it is wrong. Under the superseded entry size a reader positioned on entry N sees entries N+1, N+2... The coordinate is right about the disc - start_lba(N)*0x800 + K is where the bytes are - and wrong about which entry owns them. Entry 85's 0x1800 is entry 86's offset 0, and the block layout (.MAP / v12 header / prescript / bundle) makes entry 85 the prescript. 0x25804 is 1204's own length plus 4, i.e. entry 1205 offset 4; "seven" and "truncated" are both where the window stopped. The three title-TIM arithmetics resolve to one absolute offset.
What is true. The kingdom bundles are 0086 / 0245 / 0392; the battle-form pack is eight whole atlases in 1205, CLUT row 496 being the eighth; the title TIM is one copy in 0890 at 0x14228. Byte-equality between two (entry, offset) pairs shows duplication only once the pairs resolve to different absolute offsets, and a member count is a property of the framing (a chunk chain, a descriptor count), not of where a buffer ends. Corrected coordinates and the two invariants that hold them: prot.md.
Pochi-fill slots as stale mastering scratch
Status: falsified - the corrupting pages came from the next entry, reached through an over-reading size expression
Tempting reading. Reserved-but-unused filler holding leftover bytes from an earlier master is an ordinary thing to find on a PSX disc, and the hazard has a reproducible exhibit: two 64x256 pages uploading to framebuffer (768,0) and (832,0) erase a ground atlas on every run, with the sweep positioned on a pochi slot.
Why it is wrong. Every one of the 266 Class::PochiFiller entries is exactly one 2048-byte sector of fill, and none carries a parseable TIM header. There is no stale image in a pochi slot to upload.
What is true. The corrupting pages belong to the scene_tmd_stream entry that follows the pochi slot; a sweep reaches them through the entry-size expression that spans into neighbouring entries (prot.md). An over-reading reader makes the next entry's bytes look like the current entry's content, and a symptom that reproduces every time confirms only that something is wrong at that step, not which entry owns the bytes. Slot contents: pochi.md.
Concatenated sub-streams in a scene_tmd_stream entry
Status: falsified - one entry holds one stream; the "second sub-stream" is the next PROT entry
Tempting reading. 0006_town01 is two concatenated [chunk0 TMD][type-0x01 TIM chunks][terminator] sub-streams, the second at 0x14000 with its own leading TMD 0x2c20 and TIM chunks at 0x16c24 / 0x1ee48. The second block opens on a 0x800 boundary after zero padding and carries a valid Legaia TMD followed by two well-formed type-0x01 chunks. The walker FUN_8001FE70 returns param_1 + 1, just past the terminator, which reads as the hook a sector-indexed "multi-sub-stream caller" would use. The shape reproduces exactly on the town0b and town0c clusters.
Why it is wrong. Entry 0006 is exactly 0x14000 bytes, so the second block is PROT entry 0007, whose own leading TMD is 0x2c20 and whose tail chunks are at 0x2c24 / 0xae48 - the recorded offsets minus the length of entry
- The replication does not test the shape: those clusters are four-entry
runs of one layout (TMD bodies 0x383c / 0x2c20 / 0x2998 / 0x3af8, two 0x8220 TIM chunks each), so every over-read spills into a sibling of the same shape.
What is true. Across the corrected corpus, 0 of 182 scene_tmd_stream entries hold a second sub-stream and 0 yield a post-terminator chunk; there is no multi-sub-stream caller to find. A structural feature that only ever appears at the end of a buffer is a claim about the reader's bounds until it is shown somewhere that is not immediately before another entry of the same class. sub_streams and WalkSource::Continuation stay in scene_tmd_stream.rs as regression detectors, with disc-gated coverage in crates/disc-audit/tests/scene_tmd_stream_real.rs. Corrected layout: scene-bundles.md.
"The backdrop shell is drawn once, so no completion exists"
Status: falsified - retail draws the shell twice. The authored half is real; "therefore nothing completes it" does not follow
Tempting reading. Any scene_tmd_stream entry opens in a mesh viewer as half a bowl - a sky dome, a distant mountain ring and a far ground ring, sheared off along a plane through the origin. Two readings follow, and both are wrong:
- A whole map got halved, so find the missing bytes.
- The file holds a complete half and the runtime links one background actor, so no completion exists and drawing one is a regression. This one has two supports. A
Ry(180deg)duplicate plants a second village wall acrosstown01's open-Xside, which in retail is open sea. And a four-angle stage-battle capture set reads the distant mountains as covering "44-81% of the horizon columns, not a ring".
Why it is wrong.
- Missing bytes. The half is authored and nothing is dropped. Over object 0, all 182 entries put at most 8% of the shell's X or Z extent on the far side of
X = 0/Z = 0(widest0.079,0048_vell); the open side is-Xin 129 entries,-Zin 49 and+Xin 4, never+Z, the side the party is seated on. Every one of the 378 objects hasvert_top + n_vert * 8 == normal_topexactly, and the parsed body accounts for the whole declared chunk0 size: no unread vertex block, no second primitive list, no second sub-stream. - One actor.
FUN_800513F0registers the backdrop TMD once and allocates two actors from the same descriptor; the second carries a transform. - The
town01wall.town01(stage id 4) is on the mirror list: retail completes it by reflecting in the YZ plane.Ry(180deg)is the right transform for its siblings0006/0009and the wrong one for0007/0008. The artifact falsifies one transform on one stage, not completion. - The 44-81% figure. It measures band thickness against a 9-18 px threshold, and the ring's height varies from a few pixels to ~45 across the arc. Measured for presence of a mountain band above the horizon, the four angles read 98 / 100 / 100 / 100% of columns.
- The captures refute a single copy. Project
map01's drawn objects through each capture's exact camera (yaw_DAT_8007B792, pitch32,TR = (0, 1280, 7680),H = 256, all read from the save state): one copy covers 100 / 71.9 / 100 / 99.7% of the 320 columns, two copies 100% throughout. Three yaws cannot separate the models. Capture b, at yaw 334.7deg, can: one copy leaves columns0..89with no mountain geometry, and retail has a mountain band in 90 of those 90.
What is true. Retail draws two copies of one authored half-shell, the second under a per-stage transform. Mechanism, evidence and the per-stage table: battle.md. Two neighbouring things this is not:
- The
+0x10mesh puzzle - walk-visible.MAPcells naming a pack mesh no layer draws. That family is0x0011; stamping it draws a wall down every river. What keeps it out is the0x2000draw gate its cells never carry, not theFLAG_MESH_DRAWNbit - see the world-map table above. - The site's assembled map view or the engine's field renderer. Those exclude
scene_tmd_streamentries and build the scene from the environment mesh pack plus the.MAPplacements (crates/web-viewer/tests/field_scene_assembly.rs).
Before a capture statistic closes a thread, state the reading it would refute and check that the samples can tell the two readings apart: a file sweep is silent about the runtime, and a statistic pooled over angles hides that only one angle carries information.
"Field-pack" was never a format
Falsified by disc bytes.
Tempting reading. Magic 0x01059B84 opens a Legaia-specific bundle - a 97-entry strict schema, a byte-identical ~91 KB template block shared by every carrier, and the per-scene payload in a preamble ahead of packed TIMs/TMDs. The word occurs exactly once on the disc, the table after it is regular, and two carriers do share tens of kilobytes byte-for-byte.
Why it is wrong. The word is a DATA_FIELD chunk header, (TIM_LIST = 0x01) << 24 | 0x059B84, and 0x059B84 is town01's payload length - unique because every carrier's length differs. The "schema" is that chunk's container-formats::pack table [u32 count = 96][u32 word_offset[96]] (0x60 is the count), every member a PSX TIM; the clusters' "byte sizes" are word deltas. The shared block is town01 and town0c carrying the same three leading Rim Elm atlases. The "preamble" is the superseded over-reading entry size reading the block's prescript and asset table ahead of the real entry.
What is true. It is a scene's texture pack behind a chunk header. A carrier sits at raw-TOC +4 of its CDNAME block, loaded by FUN_800255B8 / FUN_8002541C. See field-pack.md.
The prescript's "per-scene secondary header" is the next entry
Falsified by disc bytes.
Tempting reading. After a scene_event_scripts prescript, a small (count, descriptor[count]) table sits at the next 0x800 boundary, alternating (type, size) and runtime-buffer offset pairs - a per-scene secondary header.
Why it is wrong. For 99 of the 101 carriers the first 0x800 boundary at or past the last record is already at or past the entry's end - there are no bytes there to be a header. The over-reading entry size appends the neighbour, as with the .PCH "+0x800 prescript" and the pochi "stale TIM" readings.
What is true. For all 101 carriers the next PROT entry begins with exactly that table at its own offset 0 (87 scene_asset_table, 14 the count-4 form). See scene-bundles.md.
PROT 0892: a 12 MB LZS container, or a truncated DATA_FIELD stream
Falsified by disassembly and disc bytes.
Tempting reading. Two readings of one entry: a 12 MB container whose content is unpinned, or a truncated DATA_FIELD stream whose final chunk the runtime continues by DMA. The second matches the stream detector's own criteria.
Why it is wrong. The 12 MB figure is the superseded toc[p+5] - toc[p+3] + 4 span (5,977 sectors). The three "leading chunks" are a container-formats::pack's header words (2, 3, 0x208B) read as chunk headers, and the "over-large fourth header" is a word inside member 0's pixels.
What is true. The entry is 33 sectors, 67,584 bytes, and retail walks it as a pack (FUN_8002574C, 0x8002581C..). See re-settled-threads.md.
PROT 1221 / 1222 have no loader, because no image names raw TOC 0x4C7 / 0x4C8
Falsified by disassembly.
Tempting reading. A sweep of every image for the literals 0x4C7 / 0x4C8 as a load index finds no loader, so the entries are unloaded.
Why it is wrong. 0x4C8 is never a literal anywhere; the index is s0 + 0x4C7 with a runtime s0 (addiu a0,s0,0x4c7 at 0x801F6C3C in PROT 0978). A literal-only sweep cannot see a computed index - the same blind spot as gp-relative addressing.
What is true. They are the dome's int.tim / int2.tim stills, loaded by PROT 0978.
Field / locomotion
Field overlay: locomotion, move-VM opcodes, the prologue grade, phantom function entries.
| The reading that looked right | What is true |
|---|---|
Placed .MAP records 1..=3 are protagonist / NPC ids whose mesh comes from the shared pool | disassembly and captureWhy it looked right, and what killed itPlausible: the field pool's head is the five party / savepoint meshes, and the low ids look like a reserved band. |
FUN_8001EBEC patches groups {0, 3, 5} on equip bytes +0x196 / +0x199 / +0x19B, one table for both roles | capture and disassemblyWhy it looked right, and what killed itPlausible: the routine does copy two short stack tables, and the old reading fits one small triple. The tables are SCUS |
4C 32 clears 0x80085718..0x80085918, party and inventory state | disassemblyWhy it looked right, and what killed itPlausible: 512 bytes beside the bag. The loop base is |
korb3 parks on a modal timeline at pc=0x0144 and edlast's helper spins on a jump-to-self | the ladder printed the entry script's pcWhy it looked right, and what killed itPlausible: the ladder report prints a parked pc for each scene. That pc is the entry script's, not the pad holder's. In korb3 the holder is |
| Critical-path rung 5 reopens with a route avoiding keikoku's inner doorways or a flag seed | falsifiedWhy it looked right, and what killed itPlausible: a route round the beat bands, or a seeded flag, would skip the turn-back. |
| Circle casts in retail fishing | Cross / Square cast; Circle abandonsWhy it looked right, and what killed itPlausible: Circle is the port's cast binding and a confirm on many PSX screens. The cast start ( |
| Fishing menu rows 2 / 3 snapshot the points bank | they seed the tackle cursorWhy it looked right, and what killed itPlausible: the rows open the point-exchange screens. They copy the lure index |
| Only cutscene-class records lock the player's locomotion | every stepped record doesWhy it looked right, and what killed itPlausible: the port locked the pad only under a modal timeline, and a helper record looks like background work. The per-actor tick steps every spawned record with |
Motion-VM ops 0x37 / 0x41 are TranslateY / TranslateX | one compass-walk armWhy it looked right, and what killed itPlausible: two ops that each move an actor along one axis. Both land in one arm of the walk kernel |
A2 F8 01 is map01's cave-mouth walk-out | it picks the walk clipWhy it looked right, and what killed itPlausible: it sits where the walk starts and names the player channel. It only selects the clip; the move is |
| Critical-path rung 5 (pad-walking the Ravine out of another door) clears | retail turns the player roundWhy it looked right, and what killed itPlausible: the rung passes when keikoku |
The motion-pause kick FUN_8003C9AC pauses NPCs while a dialogue runs | it is a standing-clip requestWhy it looked right, and what killed itPlausible: it fires when an interaction engages and reloads |
+0x10 & 2 in FUN_8003BC08's height arm is a freeze an actor sets on itself | it is the visibility cull's bitWhy it looked right, and what killed itPlausible: the arm skips on it and nothing in the motion ops writes it. |
| The NPC glide rate is "pad-held × 6" | the frame-step scalar × 6Why it looked right, and what killed itPlausible: |
FUN_801D79E8 is a draw / unlink / movement-init helper that emits glyph cells | the per-actor visibility cullWhy it looked right, and what killed itPlausible: the two dumps named after the address show glyph-cell calls and a digit field. Neither holds this routine: |
| Rula / Riremito are dev-table-only handlers with no player-facing installer | the Door of Wind / Door of Light run themWhy it looked right, and what killed itPlausible: no word or |
| The travel arts scan a visited-map table | the resident CDNAME define tableWhy it looked right, and what killed itPlausible: the scan looks up a map number and prints |
| The Door of Light returns the party to the tile it last stood on | the region record's tripleWhy it looked right, and what killed itPlausible: from a cave the destination is next to the entrance. It is |
The entry banner is FUN_801EE5D4's panel script 0x801F32B4 | the MAN loader spawns itWhy it looked right, and what killed itPlausible: the actor reads the scene name |
0x800228B0..0x80022B80 is the anim tick's dispatch-5 handler | it is the default motion block that arms 3 and 5 skipWhy it looked right, and what killed itPlausible: the range follows the dispatch compare ladder. Arms 3 and 5 both branch past it ( |
kor5's 0x619 is set by a talk body, or never by retail's entry | a spawn-section writeWhy it looked right, and what killed itPlausible: a cold entry sets it, a post-chain capture holds it clear, and a probe watching a retail entry sees no set. |
FUN_801D1EC4's warp timer walks the actor to its destination, and the crossing tile is (0x8007BDD8, 0x8007BDDC) | the timer only counts; the crossing tile is BDC8 / BDCCWhy it looked right, and what killed itPlausible: read from a dump printed at the phantom VA |
FUN_801D1EC4 is the clip base's writer, on four arms | the pad step writes it every frame; FUN_801D1EC4 on one armWhy it looked right, and what killed itPlausible: four |
Player +0x5C is a dash counter and +0x10 & 0x1000000 an interact request | the clip id, and the party-bank selectWhy it looked right, and what killed itPlausible: the controller reads |
| The player walks in place through a kind-0 warp | the player idlesWhy it looked right, and what killed itPlausible: the pad controller is skipped for the warp, so the last clip it picked looked like it would stay bound. The system channel stores base |
| Holding run never changes the player's clip | the capture held the wrong buttonsWhy it looked right, and what killed itPlausible: a locomotion capture held a run and saw the walk record throughout. It held Square and Circle; the run mask is |
FUN_801d5718 is the field's walkability sampler, and the ignore list's 801d5718 a grid copy | the sampler is FUN_801D56C4; 0x801D5718 is an interior addressWhy it looked right, and what killed itPlausible: |
A failed op-0x43 acquire skips the op | it waits and retriesWhy it looked right, and what killed itPlausible: advancing past the op keeps a script moving. A failed acquire branches to |
| The inn conversation ends because the re-acquire fails once the window has closed | it ends at the last page's closeWhy it looked right, and what killed itPlausible: the record's tail loops back over the acquire, and a refusing acquire leaves the caller halted. In |
*(_DAT_801C6EA4) + 8 is a modal-window flag | it reads 0 through a whole conversationWhy it looked right, and what killed itPlausible: it gates the dispatcher's halted-target early-out, which a conversation needs skipped. Across both |
FUN_801D1344 in the world-map overlay is different code from the dialog overlay's | one routine of PROT 0897Why it looked right, and what killed itPlausible: the dumps carry different capture labels, and the world map reads like its own overlay. The world map is a subsystem of PROT 0897; the |
The play page and the native window walk town01 along different paths | both stop at (2588, 2898) under one pad scriptWhy it looked right, and what killed itPlausible: a native reference frame disagrees with the page. |
Op 0x3E with op0 < 100 is the field interact that arms an actor's interaction script | it installs a scripted battle, the same body as op0 = 0xFFWhy it looked right, and what killed itPlausible: |
FUN_801E58A8 seeds a list's row count | it picks an actor's anim clipWhy it looked right, and what killed itPlausible: |
FUN_801D25EC's chained second actor is an emitter | a release watcherWhy it looked right, and what killed itPlausible: a second allocation after a motion helper reads like a particle trail (the port routine keeps the name |
kor5's P2[5] needs about 17,500 vsyncs to write 0x436 | 3,336 after 0x464 clearsWhy it looked right, and what killed itPlausible: a probe run stalls that long without the write. Its |
| Nothing frees the tile board's cell buffer | teardown state 0xE frees itWhy it looked right, and what killed itPlausible: the per-scene control-block reset zeroes |
4C 86 sites are talk records that install the mirror on an interact press | they install from the spawn prologue at scene entryWhy it looked right, and what killed itPlausible: the records are player-raised and sit next to talk scripts. All ten sites run before the record's first |
A tile poke cannot cross the world map, and kor5's P2[4] / P2[5] never spawn | both walk; the pokes landed during the movement lockWhy it looked right, and what killed itPlausible: pokes made while the player is locked fail, and a crossing under the lock is consumed, not deferred. A poke after the lock clears dispatches P2[4] at once, and |
0x8007C348 + 4*i is a per-channel actor pointer table | a free-stack index, seven list sentinels and the player slotWhy it looked right, and what killed itPlausible: index 7 lands on the player. |
Motion-VM ops 0x37 / 0x41 chase a target along one axis | an eight-direction compass walkWhy it looked right, and what killed itPlausible: each moves the actor toward something, and a one-axis reading produces plausible walks. The step comes from the compass table at |
_DAT_1F800384 is a packed camera word | the scratchpad region box x0, z0, x1, z1Why it looked right, and what killed itPlausible: the camera path reads it every frame. PROT 0901 restamps its low two bytes at |
Field-VM 4C 14 is seven bytes, like every other sub-op of its nibble | it is eightWhy it looked right, and what killed itPlausible: outer nibble 1 advances |
4C 87 registers a callback, and both it and 4C 9F park the script until it fires | both retire, both advanceWhy it looked right, and what killed itPlausible: |
4C 9F and 4C 87 sweep the same handler | they sweep different onesWhy it looked right, and what killed itPlausible: the two arms are the same five instructions. The VA each forms differs - |
FUN_801E573C is a six-axis rotation setter, and its +0x90 is the source | both ends were backwardsWhy it looked right, and what killed itPlausible: the spawner's argument order puts the executing context first and the resolved actor second, and six |
| The reflection callback has no spawner on the disc | the spawner is FUN_801E573CWhy it looked right, and what killed itPlausible: a reference scan for |
A context's +0x10 player bit means the context carries the player's pose | it means the player raised the recordWhy it looked right, and what killed itPlausible: the bit is set on exactly the contexts a player-facing beat runs in, so "this context is the player" gives the right answer wherever one actor is involved. Every shipped |
An all-zero op-0x34 sub-0 operand is a ramp target (fade to black) | it clears the effectWhy it looked right, and what killed itPlausible: the arm's other operands are an RGB target, so all-zero reads as "target black", which is also what a fade-out looks like. The arm stores zero to the live-actor cell instead ( |
The renderers read an effect_tint ramp, so the fade's two representations have one live reader each | neither had a readerWhy it looked right, and what killed itPlausible: a ramp is the representation a renderer would naturally consume. No renderer reads such a ramp, so swapping one representation for the other repairs nothing - neither has a reader. The measured beat is a |
| A Rim Elm house door is a scene change | an intra-scene warpWhy it looked right, and what killed itPlausible: the screen fades, the camera cuts and the player is somewhere else, as in every scene change. The scene name word does not move - |
Flag 0x5D6 has no script writer | koin4 P1[15] self-latches itWhy it looked right, and what killed itPlausible: the script census, a native flag-helper sweep, the move-VM ext sub-ops and the motion-VM census all come back clean. The census walk stops at the record's first |
The port's FIELD_DEFAULT_VIEW_WINDOW is the window retail stamps on scene entry | it is a later region'sWhy it looked right, and what killed itPlausible: the value is read off a live walk, so it is a real retail window - but not the entry one. A poll across a |
Nothing in 84 images references 0x801D5C08 or 0x801D5D60 | both are template handler wordsWhy it looked right, and what killed itPlausible: an all-forms reference sweep returns clean. Run |
| teien's hedge-base cells are filled by an unpinned kind-2-cell draw channel | retail draws nothing there eitherWhy it looked right, and what killed itPlausible: the hedge rows carry object-grid bit |
FUN_801DA390 eases a camera yaw | a vertical offsetWhy it looked right, and what killed itPlausible: the routine is a two-input ease inside the camera controller, and a yaw is the camera quantity most often eased. |
Actor +0x16 is a heading / a facing / a footing byte | it is Y, and it always wasWhy it looked right, and what killed itPlausible: three consumers of the field each suggest a different quantity from their own context. Nothing on the disc masks |
The dance step markers are the gap in field_actor_plan | a different poolWhy it looked right, and what killed itPlausible: the markers are per-cell field actors and |
FUN_801D6058 handles a cutscene element | a field-overlay template, spawned onceWhy it looked right, and what killed itPlausible: it is reached from a spawn table and its |
Insn::extended carries the field VM's op-0x43 sub-op | it is a cross-context target markerWhy it looked right, and what killed itPlausible: the field is set on exactly the instructions that have a sub-op, so a census keyed on it returns a plausible site list. The value is |
| The fishing bring-up rewrites the lure index | the rod indexWhy it looked right, and what killed itPlausible: two nearby routines each probe a small band of bag items and each rewrite a persistent index, so one description covered both. |
The .PCH sidecar's +N fixup words are filled in at runtime | nothing writes themWhy it looked right, and what killed itPlausible: the header is a runtime-fixup header and the words are zero on disc, which is what an unfilled fixup slot looks like. All 97 on-disc tables carry zero there and no writer exists, so the words are reserved, not deferred. |
| "~270 undumped field-overlay functions" (recomp dispatch-entry seed list) | not a function inventory gapWhy it looked right, and what killed it |
| Rim Elm's reachable south-gate band force-walks the player through the wall | the record is five inert bytesWhy it looked right, and what killed it |
| Scene-bundle type-6 descriptors are "all small placeholders" | 12 are walker tablesWhy it looked right, and what killed itPlausible: the modal slot is a 4-byte |
Move-VM loop op 0x19 "retires past itself (size 2), loops back to the saved PC" | both halves inverted by the C renderingWhy it looked right, and what killed itPlausible: the decompiled C renders it that way. The raw arm ( |
Field-VM op 4C nE sub-3 "syncs the resolved actor's position to the active camera" | copy direction invertedWhy it looked right, and what killed itPlausible: the handler tail ( |
| Extraction-0874 §2 F-variant pixels are written by a pause-menu-path uploader (and then: are a parked wrap-scroll phase) | twiceWhy it looked right, and what killed itPlausible: 6 of 6 pause captures hold the variant, and its 3 words equal row 273's content, which reads as a +2-row scroll park. The whole pause walk issues zero image transfers (DMA2 chain-walk + GP0 PIO hook) and plain field saves carry the variant; the strip is not shift-invariant and the wrap-scroll installer ops never fire across the s2 -> s3 flip window, so the row-273 equality is coincidence. The writer is the town01 opening record's one-shot |
Field-VM op 0x43 sub-3..6 is a sound register ramp: four target values, a ticks duration and a curve | on all four counts (it is a camera-register zone ramp)Why it looked right, and what killed it |
Prologue gold grade = per-node +0x74/+0x78 depth-cue crush | grade is a palette-space collapse; the nodes carry no IR0Why it looked right, and what killed itPlausible: |
FUN_801DD784 is a cinematic letterbox | it is the scene shutter blackoutWhy it looked right, and what killed itPlausible: the tick eases two full-width bars in from the top and bottom of the screen, as a letterbox does. The bars do not stop: they meet in the middle and hold, and the template it ticks is |
0x801F27EC is the fade family | it is one rung of the scene floor-height ladderWhy it looked right, and what killed itPlausible: the address ticks a small monotone value toward a target, the shape of every fade in this overlay, and sits in the band the fade actors are allocated from. The tick writes |
FUN_801CFF3C is a second spawner for the bar template 0x801F2858 | it is FUN_801DE754 printed 0xE818 lowWhy it looked right, and what killed itPlausible: the dump writes |
0x801D44CC is a per-dancer facing script | it is the step-marker mesh flipbookWhy it looked right, and what killed itPlausible: it is called once per dancer per frame and reads the dancer record, so a facing update fits. It indexes the marker actor's |
FUN_801D414C runs on both dance edges and stages other1 | one edge, and it stages nothingWhy it looked right, and what killed itPlausible: the routine sits between the hall's enter and leave paths and touches the globals both do. It runs on the exit edge only, and it is a restore - it puts back what entering the hall displaced. Nothing in it names |
_DAT_8007B880 is the dance pad latch | falsifiedWhy it looked right, and what killed itPlausible: the word changes every frame while a player is dancing, as a latched pad word does. The judge reads the ordinary per-frame pad edge words; |
0x801D518C holds the literal other1, so the dance hall is other1 | it is a BSS saved-caller name slot; the venue is other7Why it looked right, and what killed itPlausible: the cell holds an |
FUN_80019D50 is a BGR555 cell-grid emitter | it is the CLUT-cell HSV cyclerWhy it looked right, and what killed itPlausible: it walks a rectangular region of BGR555 halfwords and rewrites each one, a cell-grid emitter's inner loop. The halfwords are palette entries: the routine rotates hue / saturation / value over one CLUT cell block and pushes the result with a single |
The world-map controller calls FUN_801D362C (the move-VM 0x2F extension dispatcher) directly | one reference disc-wide, and it is the move VM's own armWhy it looked right, and what killed itPlausible: the world map animates through the same overlay-resident helpers the extension sub-ops wrap, so a direct call is a short path. The address has one reference on the whole disc - SCUS |
Actor +0x8A bit 0 enables the scripted motion VM | it suppresses itWhy it looked right, and what killed itPlausible: a per-actor byte tested at the top of a VM tick reads as an enable, and the actors that move do carry a non-zero byte. The gate is a |
Motion op 0x06 echoes the pad | a home-relative one-tile wanderWhy it looked right, and what killed itPlausible: the arm reads a live input-shaped source and writes a direction, which is the shape of a follow-the-player op. It draws |
Motion op 0x0C is a glide channel | it fades a tint and a draw modeWhy it looked right, and what killed itPlausible: the op schedules a two-word ramp on the actor, and the neighbouring ops ramp position. The words are |
| The field player is seated by the cold-entry arm only at New Game | every ordinary scene change takes itWhy it looked right, and what killed itPlausible: the arm is gated on a word a fresh boot leaves zero, so the gate reads as a boot-time discriminator. |
Motion ops 0x0F, 0x15 and the wander 0x06 have authored carriers | a census over every MAN finds noneWhy it looked right, and what killed itPlausible: the ops are decoded, dispatched and ported, and a decoded op with a table slot reads as a used op. Over all 573 MAN tail-section-1 variants, |
_DAT_8007BACC has no writer, so the recentre-window arm is unreachable | premise, surviving conclusionWhy it looked right, and what killed itPlausible: a five-form reference sweep returns nothing. There is a writer - |
FUN_801DBE9C's zone query is how retail picks a camera region | that leg is dev-onlyWhy it looked right, and what killed itPlausible: the arrival actor does run the query, load the hit and install the miss defaults, a complete and self-consistent camera account. The query sits behind |
FUN_801DBC20 writes the live camera globals | it writes the parameter blockWhy it looked right, and what killed itPlausible: it is the routine a camera-region record is handed to, and the globals change after it runs. It writes |
| Nibble-3 sub-8 and sub-D are one helper with a sub-op discriminator | they call different routinesWhy it looked right, and what killed itPlausible: the two arms share the tile arithmetic exactly, and one host hook keyed on the sub-op byte gives the right answer for sub-8. Sub-8 is |
[4C C4] is a sub-tile broadcast | it is a camera-zone query at a named tileWhy it looked right, and what killed itPlausible: the caller passes two coordinate bytes somewhere. The arm at |
[4C CF] is a position broadcast | it is the script camera-focus overrideWhy it looked right, and what killed itPlausible: the arm resolves each operand byte to a world coordinate, a tile centre or zero, as a position broadcast would. The values go to |
_DAT_8007B628 / _DAT_8007B62A have no writer | six sh in one field-VM armWhy it looked right, and what killed itPlausible: an absolute-word reference scan returns nothing for either address. Both forms are |
| The port's compass residual is its pad quantisation | it is the scene's camera offset through pitchWhy it looked right, and what killed itPlausible: a pad remap quantised to four cardinals does produce heading error, and zeroing the camera focus moves the failing figure. At orbit |
| Fifteen scenes raise the camera re-query flag | eightWhy it looked right, and what killed itPlausible: fifteen scene MANs carry a word whose low five bits decode to |
0x801EFE7C is a third octant derivation on the tile board | a restoreWhy it looked right, and what killed itPlausible: it stores into |
| A black frame from a name-hijacked scene load says the scene is dark | the entry path is black, not the sceneWhy it looked right, and what killed itPlausible: rewriting a door's inline name does load the named bundle - the load trace's VAB, BGM and packet count all move with it - so a frame taken afterwards looks like a frame of that scene. A hijack into |
The 0x4C outer dispatch bound is tested at 0x801E0C44 | one instruction laterWhy it looked right, and what killed itPlausible: the |
The two 0x4C error arms share a preamble | a tailWhy it looked right, and what killed itPlausible: both print through |
The fishing water class is read after the walk-grid probe reports the 0x4000 bit | two unrelated reads of the same pointWhy it looked right, and what killed itPlausible: both reads happen once per frame in the same tick, both take the lure's position, and |
The Baka Fighter editor's actor record starts where its callback was cited from, or the band is five records from 0x801D7618 | twice (eight, from 0x801D75DC)Why it looked right, and what killed itPlausible: a record's |
0x801D918E is the fishing lure's z | it is the lure's heightWhy it looked right, and what killed itPlausible: the cell sits between the lure's x and a third halfword, so it reads as the second of a planar pair. The store is |
| The port's camera visible-tile window is seeded at scene entry | it was seeded once, at camera constructionWhy it looked right, and what killed itPlausible: retail stamps the default at scene entry and then one of two writers replaces it. A default stamped once in |
resolve_field_slide cannot be wired because its rests are pinned on the non-sliding stepper | both pinned legs are slide-neutralWhy it looked right, and what killed itPlausible: a resolver that widens a held direction moves where a player rests against a wall, and wall-press oracles pin those rests. At both pinned rest positions the resolver returns exactly the held cardinal, so the oracles sit where the two models agree. Retail's slide fires in ordinary free-roam - 15 widenings in 276 calls down one town wall - and the port's locomotion step calls the resolver (settled). |
| The scene-authored octant word is read only inside the field overlay | its primary readers are in SCUS_942.54Why it looked right, and what killed itPlausible: the writer set is entirely field-overlay, and so are the two readers an absolute-word scan finds. The SCUS pad remapper |
Retail's cold entry into conc runs P0[34] / P0[36], whose body clears flag 0x6DE | those are walk-on trigger scripts a cold entry never reachesWhy it looked right, and what killed itPlausible: a fresh engine entry executes them and the clear is in their bodies. A write watch across retail's card-boot entry sees the clears come from |
| Seeding a system script's position anchor once at scene load is equivalent to reading the player each time | the script install resets it to the originWhy it looked right, and what killed itPlausible: the player does not move during a load, so one seed looks sufficient. The script install resets the ctx- |
The juui1 walk is blocked by the pad ladder's budget | it is blocked by a record gateWhy it looked right, and what killed itPlausible: a two-scene pad walk is long and encounters break it. |
4C D8's model operand is a slot in the global TMD pool, and balden's 99 / 100 / 109 / 110 are simply not in it | it is a scene-bank indexWhy it looked right, and what killed itPlausible: |
| The morph block is a fixed-pitch slab of records | retail walks it with three pitchesWhy it looked right, and what killed itPlausible: a record array is normally walked with one stride. The spawner's size sum steps |
FUN_8001FA00 seeds a cutscene sprite list | its one caller seeds the fog-particle pool's free stackWhy it looked right, and what killed itPlausible: an identity index list under a top index is a generic free-list seed, and the name was picked from a neighbour. Its only |
0x801F21B4 is one twelve-row probe table whose later rows have no caller | three tables, each with a consumerWhy it looked right, and what killed itPlausible: the 192 bytes share one row shape and the locomotion reads only the first four rows. Three consumers form three bases - the actor probes at |
| A talk acquire's face-at bind is always the conversation's own actor | it names any actorWhy it looked right, and what killed itPlausible: in the captured inn talk the bind |
Op 0x42 mode 1 tests a screen mode | it tests the held padWhy it looked right, and what killed itPlausible: |
FUN_801DB510 returns at once while 0x8007B606 is clear | it pins the focus and runs the shake tailWhy it looked right, and what killed itPlausible: the byte is the follow camera's enable, and a disabled camera doing nothing is the natural reading. |
FUN_80046870 is a brightness / fade ramp, and gp+0x2E8 a frame cooldown | it is the Incense window, counted in walk ticksWhy it looked right, and what killed itPlausible: the routine adds |
| Menu window 8 is a generic notify window whose template nothing stages | the art-learned noticeWhy it looked right, and what killed itPlausible: no |
FUN_801DD330's 0x30 is an init word and its 1 a slot selector | a window id and an exit sub-screenWhy it looked right, and what killed itPlausible: small constants passed to a picker read as flags. |
*(u16*)(_DAT_801C6EA4 + 8) means "no modal window / dialog open" | a re-entrancy bracket around one callWhy it looked right, and what killed itPlausible: the motion gate reads it and dialogue is the obvious modal state. Every writer is a set-1 / clear-0 pair around a single call - |
Move-VM op 0x13 spawns a render-mode-6 keyframe-mesh child | it sets draw kind 4Why it looked right, and what killed itPlausible: the op stages a child-looking block. It writes |
The move-VM's anim-block stores below +0xAC land where their offsets say | six arms wrapped their offsets to 8 bitsWhy it looked right, and what killed itPlausible: each store's displacement reads as an actor offset. Six arms of |
PROT 0923's ribbon node packs +0x9C = 0x040C (cap 12, total 4) | the disc stores a plain cap; the engine's glide wrote the restWhy it looked right, and what killed itPlausible: the value is what the ported decoder reads back after a tick. A summon translation glide applied to every node treats |
The effect ribbon's trig tables are 1 << 13 fixed point | 1 << 12, and _DAT_8007B7F8 is the cosine viewWhy it looked right, and what killed itPlausible: the lateral offset shifts by 13. The tables are the standard |
No shipped scene script issues op 0x43 sub-0x12 | falsified (nine ending scenes do, twelve sites) | Plausible: a widget census that counted only the spawn and tween sub-ops never listed 0x12, the VRAM rect copy. asset field-op-census --only "43 12" finds twelve clean sites over eleven MAN carriers - every ending vignette but edlast - each 43 12 00 00 00 00 40 01 E0 00 00 02 00 00 directly before the 43 13 panel that shows (512, 0), so the copy is what stages the photo. |
|---|---|---|
| Nine scenes ship developer flag-setting menus | falsified as a total (it is the text-named floor) | Plausible: nine bundle MANs label their pickers as flag menus. The streaming carriers hold more - rikuroa P1[10..=12] and dolk2 P1[1] are pinned by spine_flag_writers_surface_in_the_carrier_census, plus dolk P1[26] - and the arm classifier debug_flag_menu_arm marks 2,500 flag sites over 74 scenes, so nine is a lower bound, not a count. |
Long write-ups in this area
Op-0x43 sub-3..6 as a timed sound-register ramp
Tempting reading. FUN_8003C6A4 scales four byte operands * 0x80 + 0x40 and stores two trailing halfwords, which reads as "four targets, a ticks duration and a curve". The destinations are unnamed 0x8007Bxxx globals and the op's sub-dispatcher neighbours do sound work.
Why it is wrong. The tick decides it. The descriptor at &DAT_80074304 carries handler 0x80037018 in its +8 word, which FUN_80020DE0 copies to the new actor's +0x0C. That routine reads +0x88/+0xC8 and +0x8A/+0xCA as an AABB over the player's position, +0x80/+0x84 as two endpoints, and +0x8C as the destination store width (1 = sb, 2 = sh, 3/4 = sw). Nothing counts down. The spawn and the tick are two halves of one actor, not two separate gaps.
What is true. It is a camera-register zone ramp: the byte operands are a tile rectangle, the two halfwords are the ramp's endpoints, and the register lerps on the player's Z as he crosses the zone (and back when he returns). The four destinations are field camera-configuration registers the field-overlay camera composer reads into the camera descriptor: B60C pitch, B610 yaw, B614 eye-space Z, B618 GTE H. _DAT_8007B60C's 0x1B8 default is the pitch FUN_80025C24 seeds at field entry, which identifies the register. Details: script-vm.md, motion-vm.md.
The reachable band's record force-walks the player through the wall
Tempting reading. Record 10 covers the only band a player can stand in; its timeline runs for two frames and the player moves +8 in z (one locomotion step) before it ends. So the record force-walks the player through the sealed band and then runs the 0x3F, which would also explain how retail crosses a wall that blocks ordinary locomotion.
Why it is wrong. Record 10's entire body is 21 21 26 FE FF: no walk op and no 0x3F. The two-frame end is the choreography-wrap rule acting on a Nop + JmpRel-to-self park, and the +8 is one frame of the player's own pad locomotion, visible only because the modal-timeline install stops it.
What is true. The 0x3F lives in record 0, on the other band, and that band is opened by a collision paint rather than crossed by a scripted walk - see Rim Elm's south gate. A timeline that ends without doing the thing is evidence about the runner only if the record contains the thing: disassemble the record first.
270 undumped field-overlay functions (recomp dispatch-entry seeds)
Tempting reading. A PSXRecomp runtime capture of the slot-A overlay window over a boot-to-town session yields ~312 "call targets" in the 0x801CC000+0x29000 band, ~270 of them absent from ghidra/scripts/funcs/ and functions.md - a large undumped-function backlog for PROT 0897.
Why it is wrong. Triage of every address against the disc overlay images and the captures' own resident bytes fails the premise on three independent axes:
- They are dispatch entries, not call targets. The capture records every PC where the recomp's dispatcher entered interpretation: indirect-call targets, but also return sites (the instruction after a
jal+ delay slot), interrupt-resume PCs (mid-loop, weighted by hot loops) andjr-table case labels. Against the resident image only ~1/4 of the entries are call-shaped; the rest sit mid-function or mid-loop. - The PC tables span overlay generations; only the byte snapshot is coherent. PCs accumulate across the whole session (title, FMV, menus, field), so the list mixes title-overlay, cutscene-overlay (0970) and menu PCs with field ones. One source capture's resident bytes match the disc 0897 image at only ~16% (a title-era occupant); dozens of listed PCs land inside 0897's data head (debug strings + pointer tables); and two entries marked as already known resolve to the cutscene overlay's STR dispatch
FUN_801CEA3Cand the actor-VM jump table0x801CED70- another overlay's function and a data address. - No image claims them as functions. A sweep of all mapped slot-A overlay images plus the slot-B field library for prologues / static
jaltargets at the listed addresses yields two coincidental hits (both in the never-resident slot-machine image) and a handful ofj-target labels.
What is true. The list is not a function inventory and the gap it implies does not exist. A seed list from a recomp's interpreter dispatcher needs per-hit resident-image resolution (for example a mode-gated dirty_exec_hot window) before any identity claim, and a "new function" claim needs a prologue or a static-call witness in the image that was resident. The undumped-code question for 0897 is answered by the port-catalog dashboard.
FUN_801F12D0 read from the overlay_0897 dump
Tempting reading. 0x801F12D0 falls inside more than one overlay's load window, so the routine has dumps under several labels, and the readef / summon applier's slot sequencing can be read from ghidra/scripts/funcs/overlay_0897_801f12d0.txt. Its tail jal 0x801daba4 is close enough to the real control flow to look right.
Why it is wrong. That dump is a mid-function fragment: it opens at 801f12d0 lw v1,-0x6c84(v0) with no addiu sp,sp,-N in the window, yet closes restoring s0-s3 and ra from a frame it never established. Its 47 instructions hold none of the slot-streaming logic - no +0x277 base-slot read, no bit-7 file test, no base+2 / base+3 staging arms.
What is true. Read overlay_muscle_dome_801f12d0.txt: 330 instructions, a proper prologue, the bit-7 test at 801f1644 and both staging arms. For any VA several overlays map, the instruction count in the dump header is the first filter, and callee-saved restores with no matching save plus a missing prologue is the fragment test. Corpus-wide picture: dump-corpus-integrity.md.
A second stage-id writer "at 0x801FD514 in the 0897 band"
Tempting reading. The base-tag-less dump overlay_0897_xxx_dat_801fd150.txt shows a routine at 0x801FD150 that writes _DAT_8007B64A mid-battle and scans records for 0xE7. It is internally coherent - clean prologue, sane guard logic, real (base-independent) SCUS jal targets - so it reads as a new function with an unknown "0xE7 record scan".
Why it is wrong. The overlay-local jumps give it away: j 0x801E6A7C / j 0x801E6CE8 point 0x16000 below the "function", and are intra-function only at the real base. The store's word pair 24020003 a082b64a occurs in no PROT entry but 0898, at file 0x18510 = 0x801E6D28 under the tagged base.
What is true. The writer is real but neither new nor at that coordinate. The printing re-keys to FUN_801E6968, the Lost Grail Final Heal sweep (0xE7 is the Lost Grail item id, consumed from the accessory slot); the writer is its tail arm, store at 0x801E6D2C. It is listed in overlay-va-aliases.md and the shifted-alias table of battle-action.md. A dump without a base tag proves content, never coordinates (dump-corpus-integrity.md); before naming a function in an aliased band, grep the reference indexes for the re-keyed address as well as the printed one.
0x801D84B4 is inter-function padding
Tempting reading. The VA is alignment nop in every overlay that maps it, so no routine lives there. It is padding in the fishing, dance, debug-menu and slot-machine extractions (17 consecutive nop) and in the baka-fighter image (32, with one stray sllv zero,zero,zero), and the one dump that resolves an entry here is the field overlay's, whose header reads entry=801d8308 - interior.
Why it is wrong. None of that is about the field overlay's bytes. In overlay_field_0897.bin at base 0x801CE818, jr ra at 0x801D84AC with addiu sp,sp,0x20 in its delay slot closes the predecessor, and a six-instruction leaf follows. The field image carries exactly one jal 0x801D84B4, and two base-tagged field-overlay dumps hold the same seven-word body.
What is true. The leaf stores master game mode _DAT_8007B83C = 0x16 (22, CARD INIT), raises the entry-context word _DAT_8007BB00 = 1 and returns - the overlay-local twin of the SCUS scripted game-over trigger FUN_8003C7EC. A padding verdict is per image: slot A holds a different overlay per game mode, so nop in four of them says nothing about the fifth.
FUN_801dfb10 is a scripted player-turn state machine
Tempting reading. A dump at 0x801DFB10 decodes to a coherent routine - a player-input lock, a per-frame +0x16 angle rotation, a story-flag 0xb gate - and the described behaviour is accurate, so nothing in the write-up looks wrong.
Why it is wrong. No routine exists at 0x801DFB10. The address is a phantom of the overlay_0897_xxx_dat import's +0xE818 base error. The printed VA is interior in every image that covers it: the fall-through of bnez v0,0x801dfb28 in the battle overlay, a branch label in the field overlay, and the delay slot of jal 0x8003ce64 in the menu overlay.
What is true. The bytes are field (0897) 0x801EE328, the world-map ON RULA travel-art actor, documented and ported under that VA. The overlay_0896 batch prints the same routine at 0x801E8B10 (its +0x5818 delta), and the two phantoms resolving to one VA is the check that pins it - see phantom-print-index.md.
A scene with no 0x3F in its MAN has no door
Tempting reading. The chapter-1 frontier ladder's per-partition walk finds no 0x3F in uru / urudre1..3 and nothing in jouine, its tile sweep fires no transition, and 160 executed record bodies reach no scene change. Three instruments agree, so those five scenes are sealed and may be treated as one-way.
Why it is wrong. Each instrument is right only about what it measures:
uru's0x3Fis(2, "MAP03"), upper-case, which a lower-case-only label gate rejects.- The others sit
0x2DC(urudre1) /0x124C(urudre2) /0x2034(urudre3) bytes into record bodies, past inline0x1Ftext the fall-through walk desyncs on;jouine's FMV op is at0x1A8F. - The tile sweep stops at 48 deduped gate-1 tiles, while
urucarries 118 with its exit band at positions 63..66. - A 24-tick post-step budget cannot reach a tail behind 300+ frames of explicit waits.
What is true. All five scenes have exits, carried by the scene's .PCH sidecar; uru's fires live. jouine has no 0x3F because its exit is the FMV hand-off 4C E2 08, on the FMV dispatch table. Ask the .PCH before calling a scene sealed. See the uru / mais chain and jouine exits.
A frontier-ladder scene that will not walk is a scene or a seat problem
Tempting reading. Twenty-eight consecutive scenes of the chapter-1 closure report zero driven tiles, so either those scenes or the engine's scene-entry path (the destination case fold, the wait-discounting timeline cap) is broken.
Why it is wrong. Three isolation builds give byte-identical results with either change reverted, and each "broken" scene walks on a fresh host.
What is true. One player-actor bit is latched: tower's ledge-hop steering lock, leaked across a scene change. The failure is ordered by closure position, not by scene. Before blaming a scene, walk it first in the sweep order and alone. See re-settled-threads/field.md.
Field-VM op 0x45 sub 0xC0 returns the operand s16 as the next PC
Tempting reading. The decompiled C renders the arm as "APPLY ... then absolute jump": the s16 operand looks like the next PC.
Why it is wrong. Retail's arm at overlay_0897 0x801DF210 exits j 0x801E3624 with addiu s8, s8, 4 in the delay slot, and the s16 at operand+1 goes to FUN_801DE084 as the apply trigger - the same call the CONFIGURE arm makes with its s16 at operand+2. The sibling arms advance the same way (0x40 by +0x14, 0x80 by +2).
What is true. Sub 0xC0 is a four-byte instruction that falls through. The field VM stores no absolute PC anywhere, so every control-flow field shifts with its record and man_edit may resize a record containing this op. Under the jump reading every record whose trigger is 0 restarts from byte 0 - which makes urudre2 read as one-way and four keikoku records do nothing - and the op looks like an unrelocatable AbsoluteRef.
Op 0x43 subs 0/1/A/B resume at an operand s16
Tempting reading. The arm carries three s16 operands, and one reads as a resume PC, giving instruction widths of 5 / 9 bytes.
Why it is wrong. The sub table at 0x801CEDA8 vectors subs 0/1/A/B to 0x801DF384, whose shared exit 0x801DF5B4 is j 0x801E3624 / addiu s8, s8, 8 (plus +2 at 0x801DF534 for sub >= 0xA).
What is true. The three s16s are FUN_801D25EC arguments; the sub-A/B +7 is negated into the target's Y (0x801DF524..0x801DF530). Instruction widths are 8 / 10 bytes (9 / 11 extended).
No overlay function lives below 0x801CE818
Slot A begins at 0x801CE818; a "function" printed below it belongs to no runtime image.
Tempting reading. An undocumented address in the 0x801C0164..0x801CE000 band is an overlay-resident function awaiting a doc entry. Overlay code is usually described as living "at 0x801C0000+", and dumps in that band disassemble cleanly, carry function-shaped prologues and epilogues, and are filed under overlay_<label>_801c....txt names.
Why it is wrong. Every occupant of the slot-A overlay window bases at 0x801CE818 and every slot-B occupant at 0x801F69D8 (see static-overlays.toml), so no extracted overlay image contains any VA below 0x801CE818. The measurement agrees: disassembling every extracted image at its mapped base and asking which of those addresses is a jal target, a j target or an instruction boundary returns nothing for the whole band.
What is true. They are printed addresses from imports based at 0x801C0000, and the true VA of each is printed + delta. The deltas are constant per import, so the band resolves mechanically (dump-corpus-integrity.md tabulates them):
+0xE818into the field (0897) or menu (0899) overlay;+0xD018into fishing (0972), through the 0971 over-read tail;+0x9818into dance (0980);+0x5818for theoverlay_0896_*family.
Worked examples, each documented under its real address: 0x801C6FEC is the fishing reel tug-of-war FUN_801D4004; 0x801C56B4 is the hooked-fish handler FUN_801D26CC; 0x801C2704 is menu-overlay FUN_801D0F1C.
The same failure extends above 0x801CE818, where the printed address lies inside a real overlay's span and cannot be rejected on range alone. There, resolve the dump's bytes to an image and offset, and separately ask whether the printed VA is a jr ra-preceded boundary in any image. A VA that is only ever a j or branch target is an intra-function label, not a port site. A printed address is evidence about the import; which image, which offset and whether it is a function at all are answered from the extracted image at its mapped base.
Rendering / camera
Readings about the draw path - including one where the instrument measuring a defect was producing it.
| The reading that looked right | What is true |
|---|---|
| Aluru's head tip shows through arm 11's ray burst because the engine's depth test draws the body in front of the additive rays where retail's ordering table links them after it | retail links the rays nearer, and so does the portWhy it looked right, and what killed itPlausible: hiding every arm-11 record leaves the dark triangle, and Ozma's rays raised an order question of the same shape. Retail's table of an arm-11 frame links the tip at bucket |
The far framing takes over at the end-of-action gate 0x5A, between two actors of a round | 0x5A re-arms the last actor's case 6 / 8, 0x801E6428Why it looked right, and what killed itPlausible: |
Per-primitive painter order cannot be the field's rule, since mean depth hides casino_ticket_counter's cabinet and doman's floor | the trial left out two rules and overrode the groundWhy it looked right, and what killed itPlausible: a pass that keyed every primitive on its mean broke those frames while |
noaru_field_card_walk's bowl turns on the wrong yaw, since fitting the part's angle lowers the frame's error | the capture's quad walkWhy it looked right, and what killed itPlausible: the texture sits up to |
The wall panels' moving art in the dance hall is a VRAM scroller or a MoveImage strip | a mesh flipbookWhy it looked right, and what killed itPlausible: the ambient tree carries both, and retail's VRAM holds extra art. The page under the panels is the same in the engine's VRAM; the actors at those cells carry tick |
The field near cut runs in w units, not SZ = 6 * w | a capturing emulator's polygon limitWhy it looked right, and what killed itPlausible: a |
rikuroa_field_card_walk's missing bottom-left floor is a field near-reject rule (a quad-size or near-cut test the port lacks) | the capturing emulator draws a quad the console would notWhy it looked right, and what killed itPlausible: the missing polygon sits at the lens, beside the near wall quad the emulator drops. The retail table holds it, its mean |
The black slabs over taiku2's arrival pan are a port defect (a missed near reject, a stub mesh or an unbound colour stream) | retail draws themWhy it looked right, and what killed itPlausible: they appear only while a scripted camera grazes the stairs, the scene's pack stubs most room meshes, and a missing colour stream is a known trap. They are pack slot 63's six flat-black quads (colour word |
Ground record +0x1C / +0x1D hold the tile's page and CLUT | the signed V / U spanWhy it looked right, and what killed itPlausible: a doc line named them so, and the emitter reads them beside the tile. |
FUN_8001CF50 builds the field / cutscene camera rotation | a per-node camera-relative rotationWhy it looked right, and what killed itPlausible: it rebuilds a rotation from the camera globals |
FUN_8001D088's counter _DAT_8007BD28 and journal 0x800891A8 are bookkeeping that does not affect the pose | they drive a re-blendWhy it looked right, and what killed it
|
| Every field prop clip takes the plain, unscaled step | 119 of 549 posed props carry the gateWhy it looked right, and what killed itClip byte |
| The port spawns two kind-4 nodes on map01 where retail holds seven | a probe that never ticked the ambient treeWhy it looked right, and what killed itPlausible: the count was measured on the port. The probe saw only the nodes the scene-entry first run leaves; with the ambient tick running the port holds six to eight. What the count hid was the motion block, without which the drifting puffs stay at their spawn point. |
| Nearer terrain hides about a fifth of the overworld fog's light | about 1%Why it looked right, and what killed itPlausible: an opaque-coverage count over the ordering table finds that share in front of the fog. Accounted per primitive, 18.6 of those points are two screen-space sprite families at the top of the frame (CLUTs |
The overworld continent's ordering-table key is the mesh leaves' max(SZ) >> shift / AVSZ choice at gp-0x2D1 | the ground is FUN_801F89B8, keyed (max SZ >> 5) + 14Why it looked right, and what killed itPlausible: the mesh leaves do choose their key that way, and the landmarks sit on the continent. The ground cells are drawn by the bulk emitter |
| Hub variant-2 packets subtract and variant-1 packets add | the GPU blends only STP texelsWhy it looked right, and what killed itPlausible: the emitters mark variant 2 ABR 2 ( |
| A field attached light's extents are world units | view space, six times world scaleWhy it looked right, and what killed itPlausible: they sit beside a world-space offset in the spawn record. |
The field fog sheets sample VRAM (448, 256) | (448, 0)Why it looked right, and what killed itPlausible: page |
The fog cap 0x48 is a debug value | it is every overworld's cap, from the MAN headerWhy it looked right, and what killed itPlausible: retail field scenes sit at |
| The port's fog runs dense because the spawner compares a stale live count | the stale count is retail's; the cause was the random numbersWhy it looked right, and what killed itPlausible: the render walk zeroes and recounts the live word, so the spawner does compare last frame's count. The excess comes from handing the spawner a raw LCG state whose low nibble cycles with period 16, where retail's BIOS |
FUN_801D31B0 is shared by the dialog, cutscene and world-map overlays and reads its instruction operands | one field-overlay caller; the operands are paddingWhy it looked right, and what killed itPlausible: capture dumps labelled with those modes carry it. They are 0897-hosted modes; the one reference on the disc is the |
The strip slab's +0x18..+0x1E are UV bounds | box half-extent and wobbleWhy it looked right, and what killed itPlausible: sub-ops |
The battle VRAM regions retail holds and the engine leaves empty (CLUT row 491, rows 500 / 506, row 484 at x = 240, the (960, 0) block) are battle uploads the port misses | pre-battle residue no battle draw samplesWhy it looked right, and what killed itPlausible: every retail battle state carries them. They predate battle init and vary with the route: row |
0x801D32F8 / 0x801D3444 / 0x801D3748 are move-VM extension sub-handlers | interior addressesWhy it looked right, and what killed itPlausible: they are listed beside |
| The page may skip "sky" meshes because they read as a wall from the follow camera | neither the native window nor retail filters themWhy it looked right, and what killed itPlausible: the full-map viewer needs the filter. On the play page it removes 45 scenes' matching draws, among them the opening crater shell, leaving the Seru tableau in a navy void. |
| The page's yellow strips in the battle command phase are the billboard outline pass | the retained field ground heightfieldWhy it looked right, and what killed itPlausible: the outline is flat and bright. |
ScreenTintPush::kind selects which of retail's screen-effect quads is pushed | FUN_80024EE4 emits exactly one full-display quad; kind is the ordering-table bucketWhy it looked right, and what killed itPlausible: the spawner passes three words and names the first |
FUN_80029888 zeroes the GTE light block | it writes the far-colour trio from registersWhy it looked right, and what killed itPlausible: the routine is three back-to-back |
| The field follow camera's pitch, yaw and height are scene-invariant constants | each is a per-scene, per-tile outputWhy it looked right, and what killed itPlausible: one save state pins all three, and a second scene often agrees because neighbouring regions share a camera record. Over the walkable state population the pinned height matches 12 of 19 states, the pitch 8 and the yaw 1. Retail's arrival handler queries the MAN section-3 zone table and hands the hit's camera-region record to the config loader. |
| The koin4 coplanar sliver is a residue below the detection floor | the port's own repair pass makes itWhy it looked right, and what killed itPlausible: the scene's other coplanar families clear and the remaining strip is tiny. The strip is exactly one |
FUN_801D629C is a per-fog-particle actor | it is the spawnerWhy it looked right, and what killed itPlausible: it runs per frame, reads the player's tile and touches particle state. It maps the tile to a MAN section-4 region record and pops one record from the pool at |
The fog draw emits two GP0 line packets, command 0x9000000 | one textured quad; 0x09 is the packet's word countWhy it looked right, and what killed itPlausible: |
_DAT_80089118 is the camera focus Z and _DAT_80089120 the X | the other way roundWhy it looked right, and what killed itPlausible: both are written the same way by the same routine and scroll by the same step, so the arithmetic fits either labelling. |
FUN_80026F50 is the field view builder | it is another mode'sWhy it looked right, and what killed itPlausible: it is the same five-call view-build shape over the same globals. It folds the ROM-constant base matrix at |
FUN_80025C24 only zeroes the camera eye trio | it writes three different valuesWhy it looked right, and what killed itPlausible: the first store is |
| The field view matrix is built once a frame | three times a vsyncWhy it looked right, and what killed itPlausible: one caller and one count from it, and a per-frame view build is what a renderer normally does. Three callers enter it - two in the field overlay, one in SCUS - on 133 of 134 sampled vsyncs. On a scene-entry frame two of the three read different live camera words, so "the frame's view matrix" is not a single object. The row "built three times every vsync" below refines the count. |
FIELD_CAM_DEPTH cannot be derived, only calibrated | it falls out of the scaleWhy it looked right, and what killed itPlausible: the constant was fitted to make one scene's framing match while the composed eye trio was not fed to the view. The eye trio is the eye-space translation, in GTE units the base matrix does not scale, so a renderer drawing at |
The dance count-in numerals are animated by FUN_801D2D98 | that routine draws the banner; FUN_801D3FD0 spawns the numeralsWhy it looked right, and what killed itPlausible: the count-in is one visual event, and |
| The field view matrix is built three times every vsync | two or three times per field frame, and the first site is optionalWhy it looked right, and what killed itPlausible: one site entered on 133 of 134 consecutive vsyncs reads as "every frame". Over 1800 captured vsyncs of a world-map-to-town entry only 749 carry a build at all; of those 389 run the full three-site order and 313 only the last two. A static town scene splits 504 / 396 the same way. The builder runs per field frame. |
| The last build before the draw wins, so the trio's last reader is the frame's camera | the last build frames no geometry at allWhy it looked right, and what killed itPlausible: the last write before a read is the one a read sees - true of the GTE registers, and silent on when primitives are emitted. Ranked by ordering-table link, 16810 of 31046 fall under the first build and 14 under the last, a count that mixes attribute packets and 2D rects with polygons. By GPU command code, of 4289 polygons over three runs 3861 are under the first build, 428 under the second and none under the last (settled). |
| The builder's two extra call sites are in a resident field-render module | they are PROT 0901's world-map bracketWhy it looked right, and what killed itPlausible: the capture records them returning into the slot-B window, and slot B does host render code. Exactly one statically extracted overlay image holds |
Each landmark TMD passes once per frame through FUN_8002735C | the near arm takes every oneWhy it looked right, and what killed itPlausible: that renderer walks the per-mode descriptor table, and the dispatcher's case 5 names it - behind a gate. On |
FUN_80029888 is reached whenever actor[+0x7A] != 0 on the overworld | 0 of 5089 gate hitsWhy it looked right, and what killed itPlausible: |
Nothing on the disc raises actor[+0x42], the mesh-renderer gate | three writer familiesWhy it looked right, and what killed itPlausible: 5089 gate reads across four states and three game modes are all zero - a statement about the sample. The allocator |
Move-VM ext 0x19 / 0x1A are writes of five operand words into the "world struct" | 0x19 adds; 0x1A computes a yaw rowWhy it looked right, and what killed itPlausible: |
FX_OUTLINE draws the page's yellow strips | it is off on both hostsWhy it looked right, and what killed itPlausible: the strips are flat untextured quads, which is the outline pass's look. |
The engine's fade::FadeState is the retail fade ramp | a target-latching model disagrees on a ramp's last framesWhy it looked right, and what killed itPlausible: both model the one |
Actor render mode 4 (+0x56) is set from an asset, so a census of carriers finds its users | every write of 4 is codeWhy it looked right, and what killed itPlausible: other render-mode values arrive in data. The four stores are move-VM arms |
A fog half-sheet is a world-space quad 0x80 units tall | a view-space billboardWhy it looked right, and what killed itPlausible: the particle has a world position and a world quad lands at the right place. |
| The overworld fog reads brighter than retail's because of its colour law | the colour matches packet for packetWhy it looked right, and what killed itPlausible: a brighter frame suggests a brighter colour. Every one of the 104 fog packets in |
FUN_800271A8 builds a depth ramp for a GTE reset | the overworld screen-Y curvature tableWhy it looked right, and what killed itPlausible: it fills a |
FUN_800460AC is a GTE NCDS billboard helper, and FUN_8001C394 draws a 2x2 gouraud grid | an RTPT grid projector and four textured quadsWhy it looked right, and what killed itPlausible: the pair runs GTE ops and emits a grid. |
opdeene's black plants come from the sepia law or from retail's rewrite missing their words | a native-only restage keyed on colourWhy it looked right, and what killed itPlausible: 1565 of the scene's prims carry colour words the grade takes to black. Retail's words are identical at the crawl's start and middle and its billboards draw at |
FUN_801E0080 is a battle-arena sprite scatter over unparsed disc pools | it is the effect-VM walkerWhy it looked right, and what killed itPlausible: two script-driven pools and a quad emit read as a self-contained emitter. The disc's one call ( |
FUN_801CF754 is a render cull | it is the field contact broad phaseWhy it looked right, and what killed itPlausible: a |
ctx+0x272 is a battle scene-teardown byte, and gp+0x9F5 a debug byte | falsifiedWhy it looked right, and what killed it
|
| No shipped move program keeps the camera yaw factor | urudre1 stager record 14 writes 0x0080Why it looked right, and what killed itPlausible: a disc-wide census over every op- |
No store writes the render-mode-4 selector bits +0x9E & 0x6000 | falsifiedWhy it looked right, and what killed itA census of stores misses the move VM's own: op |
FUN_801D6910 / 693C / 6968 / 6994 build a vertex packet | they fill a camera-glide recordWhy it looked right, and what killed itPlausible: four small setters writing consecutive halfwords. The record is |
Baka roster record +0x44 is a spawn Y | it is the stand-offWhy it looked right, and what killed itPlausible: it sits among position-shaped fields. The duel init places each fighter at X = -/+( |
Baka state 0x6D is the match-result state | it is the secret opponent's tally variantWhy it looked right, and what killed itPlausible: it follows the duel. The ordinary tally exit is |
Measurement readings
Claims about the project's own instruments. A count can be right while the story attached to it is wrong.
Falsified claims about the instruments, not about the game. Each was a plausible reading that shaped what work looked worth doing.
| The reading that looked right | What is true |
|---|---|
The slot machine's at-rest symbols are rand() history, because the tick calls func_0x80056798 before state 0 builds the strips | the LCG runs from a literalWhy it looked right, and what killed itPlausible: the port's strips missed retail's and a BIOS |
The kor walks' camera misses are a wrong position-dependent yaw and height in the follow compose | the live pose is mid-easeWhy it looked right, and what killed itPlausible: yaw and eye Y both miss by a position-shaped amount on every Sol Tower stair walk. The engine's seat equals the state's own staging descriptor |
FUN_801DD088 is reached by a PC-relative branch, so it may be an interior label | the one branch hit is another image'sWhy it looked right, and what killed itPlausible: the reference sweep reports one branch to |
| A retail battle load raises a first-chance exception in PCSX-Redux | only a patched image doesWhy it looked right, and what killed itPlausible: one such exception is on record, and the runner's isolated profile zeroed the exception mask on that reading. With the mask armed on a staged retail image the same field-to-battle load reaches battle mode clean; the recorded exception is |
PROT 0896's jal misses are a mis-based dump | a foreign-build linkWhy it looked right, and what killed itPlausible: the |
| A Done-arm capture's displayed camera cannot be recovered, because the strike band's table the arm overwrote is not in the RAM | the step back is reconstructableWhy it looked right, and what killed itPlausible: the Done arm |
battle_map01_frog_strike's first clip at strike cursor 2 means the walk's lock spent a swing | the pass after a commit reads the next byteWhy it looked right, and what killed itPlausible: a first swing elsewhere reads cursor |
The 0x2A chain-exit fix broke Tail Fire, since both frames fell with it (.982 to .975, .978 to .970) | the old score rested on the errorWhy it looked right, and what killed itPlausible: the fix is the one commit that moved them. Its pair is retail's: accumulator |
gimard_burning_attack's brighter frame (luma 61 against 55) is a light or a layer the port misses | PCSX-Redux's ditherWhy it looked right, and what killed itPlausible: the gap is even over the frame and larger than the corpus's. Retail's own ordering table rasterised under the console's rule reads |
| An after-flip capture's whole displayed frame is one pass behind the RAM | only the draw tick's state isWhy it looked right, and what killed itPlausible: the double buffer's phase is readable from RAM, and on |
rim_elm_zoom_intro's sky and cliff miss is the displayed frame showing an older camera than the RAM | one placed object's floor heightWhy it looked right, and what killed itPlausible: the state is a scripted camera glide and its back buffer is half drawn, with the sky quad standing where the finished frame shows cliff. The glide is at |
The Baka print of FUN_801D0F1C matches the menu dump for its first thirty-nine instructions | sixty-threeWhy it looked right, and what killed itPlausible: one branch among them differs, which reads as the end of the match. That branch's operand differs by exactly the image delta, and operand for operand the two agree through the sixty-third instruction ( |
Writing 0 to _DAT_8007B458 selects Yes on the name-entry confirm | the frame still shows NoWhy it looked right, and what killed itPlausible: it is the confirm's cursor word, and the interpreter driver writes it. On the recompiler path the S3 re-shoot takes, the frame after the write still shows the cursor on No; the driver keys presses on the name-entry actor's own sub-state instead ( |
A reference sweep may pair a lui with any later memory access until the register is reloaded by another lui | 6,015 of 23,200 pairs were falseWhy it looked right, and what killed itPlausible: it is how the idiom usually reads. A register overwritten by an unrelated instruction in between still pairs, so |
| PROT 0897 / 0899 hold 23 / 44 indexed-form accesses the byte account cannot see | they hold noneWhy it looked right, and what killed itPlausible: a Python scan reports them. It is the lax pairing of the row above. |
FUN_801D84B4 dumped from PROT 0972 / 0976 is code | padding followed by dataWhy it looked right, and what killed itPlausible: a dump exists under that label and the byte account credits it by name - 22 KB across the two images. It has no prologue and no |
0x801C9688 / 0x801C2B2C are PROT 0897 relocation copies of the world-map emitters | they are FUN_801D7EA0 / FUN_801D1344 printed 0xE818 lowWhy it looked right, and what killed itPlausible: the bytes match the emitters exactly, which is what a copy looks like. They are one routine each, printed under a mis-based dump. |
0x801F90DC is a Baka Fighter item-acquisition caption | a mis-based print of the menu overlay's item-info panel FUN_801D0F1CWhy it looked right, and what killed itPlausible: the operands check out, and a waiver that checks only operands lets the tag stand. Every attribution row for that VA is |
PROT 0929's run has no spawn site and slot-B $a2 records are always formed directly | a lui above a branch with the addiu in the call's delay slot; also register copies and switch-arm delay slotsWhy it looked right, and what killed itPlausible: the layout walk finds every other record by a direct |
| The fog rows need a composing ladder that does not exist | it exists, green and unlistedWhy it looked right, and what killed itPlausible: rows that sit unconverted across exports usually lack a ladder. The ladder is in the tree and green but absent from the export's list. Check the fixture directory before costing a new fixture. |
| A composed-overworld ladder would convert the three field effect handlers | none of the three has a production constructorWhy it looked right, and what killed itPlausible: the three ticks look like ordinary field effects, so composing their scene reads as the missing step. No production path constructs an actor on any of them - the gap is a constructor, not a ladder - and a port tag that does not disclose that makes the rows read as merely unentered. |
sell_quantity_draws_for has no native call site | it has oneWhy it looked right, and what killed itPlausible: a grep reports nothing. The grep ran through a |
| The page never replays a camera snap, so the snap beats are a page gap | the feature was dead on both hostsWhy it looked right, and what killed itPlausible: a side-by-side read finds the beats on one host and not the other, the shape of a real drift row. The native arm watched |
| The Muscle Dome hub's title art is drawn only by the page | both hosts draw itWhy it looked right, and what killed itPlausible: the constant lives in a page-facing module and the native hub is thinner. Both hosts reach the same builder; what differs is which frames each draws it on. |
| The minigames page gates the catch HUD on a phase the engine does not have | retail gates on one word, and the port matchesWhy it looked right, and what killed itPlausible: the page has an extra predicate the native path lacks, and an extra predicate is usually the drift. Retail gates the depth readout and the tension bar on the single word |
| A reach-triage cell that says a row is not driven is a measurement | it is prose next to an addressWhy it looked right, and what killed itPlausible: the rows carry addresses and a checker runs over the page, so the cells look audited. The audit resolves the addresses a row cites and says nothing about the sentence beside them: nine of eleven "content not driven" rows were already entered by a canonical ladder. Re-measure a reach cell before planning work off it. |
| A mode-seat parity check can compare the two hosts' mode words | an INIT mode never survives the call that enters itWhy it looked right, and what killed itPlausible: both hosts expose a mode word. The seat's entry call resolves an INIT mode and hands off to RUN before returning, so no post-call sampler on either host observes one; and the word one host exposes is a front-end flag rather than the mode index. The witness that works is the edge count. |
The dome's INTERVAL tally ignores the pad, so its bypass flag DAT_801D1AB4 is never raised | the tally arm latches it on pad & 0xF4Why it looked right, and what killed itPlausible: the flag has one reader ( |
The two 24-instruction tally helpers at 0x801D14B0 and 0x801D6710 are different routines | one routine, linked into two overlaysWhy it looked right, and what killed itPlausible: a byte comparison of the two extents reports nothing in common. The relocated |
| PROT 0900 ships the same ground emitter twice | a depth-cued / flat pairWhy it looked right, and what killed itPlausible: the two bodies are near-identical for hundreds of bytes, sit in one image, and are chosen between by a single global. The difference is two instructions: |
| A slot-B module carries un-dumped code above its frame partition | it is the spawn-record bandWhy it looked right, and what killed itPlausible: the uncovered run scores as plausible MIPS, because a record's move-VM bytecode contains a |
| PROT 0943 and 0961 hold a second region of their own code above the record band | it is the donor's residueWhy it looked right, and what killed itPlausible: a dump prints framed bodies at |
0x801F99D8 is where a slot-B module's content ends | it is base + 0x3000Why it looked right, and what killed itPlausible: several images stop having recognisable content there, which looks like a structural boundary. It is the end of the four 12 KB images in the band; images of other sizes end elsewhere. |
0x801C4BEC is the libcd directory-entry cache | 0x801CB408Why it looked right, and what killed itPlausible: the address appears in dumps and is in the right region for overlay-adjacent scratch. It is the offset half of a |
engine-dialog::dialog ports FUN_8001FD44 | it implements nothing of itWhy it looked right, and what killed itPlausible: the address is a real routine in a related area, and the tag gates do not check the pairing. |
A PCSX-Redux .sstate carries main RAM only | it also carries a 64 KiB hardware blobWhy it looked right, and what killed itPlausible: a reader that exposes main RAM and nothing else reads as the format's whole surface. The blob holds the scratchpad at its own offset 0 (file |
| The port-tag checkers read a tag's continuation lines | they read only the opening lineWhy it looked right, and what killed itPlausible: the tag format documents a wrapped list. The correct rule is narrow - a following comment line continues the list only when the text so far ends in a separator and the line starts with an address token. Measured when the rule landed it changed 0 of 879 addresses, where the naive "read to the first blank line" rule would add 47 addresses nobody claimed. |
| The dump-extent attribution CSV lags the corpus | it does notWhy it looked right, and what killed itPlausible: attribution lag is a real, documented failure of the neighbouring instrument. The CSV is current; the number that looked like lag has another source. |
The disc-coverage report's excluded dumps are "typically the ones that report 0 instructions and hold only decompiled C" | zero of them reported 0 instructionsWhy it looked right, and what killed itThe files that do report |
| The inner of two nested overlay spans "cannot be repaired ... no amount of dumping moves it" | falsifiedWhy it looked right, and what killed itAddress ambiguity is total for the inner span - every extent in it falls in both by construction. Byte attribution places most of those extents in one image or the other, and the row reports. The starting point of a measurement was mistaken for its limit. |
| The unattributable residue "is repaired by re-dumping, not by extracting another overlay" | falsifiedWhy it looked right, and what killed itRe-dumping repairs almost none of it. What remains is windows a few instructions long that no image reproduces at that VA, bytes in no extracted image at any VA (which needs an extraction), and extents where two dumps genuinely disagree (which is an answer). Count a residue from the artifact, not from its class names. |
0x8005BA38 is "not a function - the dump reports size=1 bytes, 0 instructions" | it is a complete RotTransPersWhy it looked right, and what killed itThe routine is 11 instructions: load |
| "About 2 % of retail camera beats set a non-zero roll" | the figure was the scan's own filterWhy it looked right, and what killed itThe number comes from a byte scan - decode an op- |
| An over-strict header regex is one instrument's bug | every instrument had its ownWhy it looked right, and what killed itA private header regex per tool, over a corpus that spells all four header fields several ways, makes each tool silently reject a different subset of real dumps and report them as a corpus deficiency. One shared parser replaces them; see |
| A zero-reference SCUS function is a safe code cave once the five-form address scan and hours of live probing clear it | for FUN_800605C8 (boot-live)Why it looked right, and what killed itThe libapi VBlank-tier slot has no static reference of any form and every live probe runs clean with it overwritten - yet a cold boot parks at boot mode |
PROT 0977 0x801D1EF0 is data past the last jr ra, carrying no function | it is the arena settlement bring-upWhy it looked right, and what killed itA sector-granular PROT extent truncates the body, so a missing |
SCUS 0x80045CB4 is interior to a body 11 128 bytes back and nothing references it | entry FUN_80045BB4, 256 bytes back, referencedWhy it looked right, and what killed itIts address is word 12 of the bank-3 primitive-handler table at |
overlay_0897_xxx_dat_801f138c.txt is a routine at 0x801F138C | it is FUN_801DABA4 printed 0x167E8 too highWhy it looked right, and what killed itIts absolute |
| The highest spawn record's end is unbounded, because a move-VM walk lands within 4 bytes of it in only about three quarters of images | the near-miss was the missing alignment stepWhy it looked right, and what killed itPlausible: a rule that is right most of the time and off by a few bytes the rest reads as an approximation of no rule. The records are word-aligned, so the walk's end rounds up to 4 - after which 991 of 1027 extents are exact and 58 of 62 tops are bounded. |
| A build-buffer tail can only come from an image sharing this one's load base, and only from a longer one | both restrictionsWhy it looked right, and what killed itPlausible: a donor at the same base makes the printed addresses line up, and a shorter donor cannot reach a longer image's end. The mastering buffer is indexed by file offset, so five cast-band images end in the menu overlay's code and the game-over image ends in the world-map renderer's; and |
| An uncovered run in an overlay image is a code gap | the same measurement classifies most of them otherwiseWhy it looked right, and what killed itPlausible: a run of bytes no dump covers is the definition of a gap, and ranking runs by size is the obvious worklist. The instrument also shape-classifies each run - |
| An image's frame partition bounds its own content | a partition can hold a donor's whole functionWhy it looked right, and what killed itPlausible: frame matching recovers real function extents, and a function that starts and ends inside this image's bytes looks like this image's. PROT 0949's partition holds |
| A VA that appears in two neighbouring images names one routine measured twice | compare the bytesWhy it looked right, and what killed itPlausible: the band's images share a load base and a build buffer, so the same VA printing in two of them is usually residue. |
| Correcting a function's extent in the attribution CSV corrects the corpus | it orphans the bodyWhy it looked right, and what killed itPlausible: the CSV is where extents are read from. The extent is produced by the shared dump header parser and regenerated from it; a CSV-only edit leaves the dump still claiming the short extent, so the whole body becomes VA-ambiguous between the two lengths. Fix the parser, then regenerate. |
| A decompiled function's dump covers its body | for FUN_801DD9D4 (all seven dumps stop at 276 of 588 bytes)Why it looked right, and what killed itPlausible: seven independent dumps agree on a length. The decompiler stops at the |
| A feature view's Port % is a property of the port | the ignore list sat on both sides of the fractionWhy it looked right, and what killed itPlausible: a percentage that moves when work lands is what a progress figure should do. With ignored rows counted in the numerator and the denominator, the headline also moves when an ignore row is added and nothing about the port changes - |
| Every slot-B record-end miss stalls below the measured end, so the rule cannot over-claim | half falsified (the direction is not uniform)Why it looked right, and what killed itPlausible: a walk that dies on a halfword it cannot decode intuitively stops early. An independent walker over the 1023 pointer-credited extents finds 990 exact and 33 misses: 14 walk past the end before dying on a non-opcode halfword, 13 stop exactly on it and 6 stop below. What survives is the property the consumers need: no miss ever terminates above the end, because only a terminator - |
| A backward scan of a function finds every source of the value it stores | the value can arrive through a pointer argumentWhy it looked right, and what killed itPlausible: for a register-formed constant the scan is exhaustive. |
| An open-ended CDNAME block's range is safe to use as a length | it reserves the rest of the address spaceWhy it looked right, and what killed itPlausible: every other block in the map is bounded by the next |
asset overlay scan's recovered base is a vote | when the image offers one prologueWhy it looked right, and what killed itPlausible: the instrument is a vote over recovered prologues and reports a winner. On PROT 0981 it answers |
| The browser play page emits no audio | an observer-order defectWhy it looked right, and what killed itPlausible: the probe reads zero in every block on every scene, which is what silence looks like. The listener was registered before the mixer installed its own |
| A dump whose window matches the image confirms coverage there | a nop encodes 0x00000000Why it looked right, and what killed itPlausible: a byte-identical window is the strongest attribution evidence there is. A window of |
| PROT 0896 holds no strings to anchor it | its head is a label tableWhy it looked right, and what killed itPlausible: base fitting found no base and no Shift-JIS code. The head is a Shift-JIS label table and the image carries a format string unique on the disc, so the entry has identity evidence whether or not a base fits its address-forming pairs - the two questions are separate. |
| A port's eye-Z figure quoted against retail names a state that exists | one of the pair had no stateWhy it looked right, and what killed itPlausible: a pair of numbers reads as one measurement, and the free-roam half reproduces exactly. There is no |
| PROT 0981 is a monster-test harness | it is the overworld map display, game mode 0x0DWhy it looked right, and what killed itPlausible: the entry's CDNAME label reads |
0x801CE9C4 is a 324-byte routine | it is a jr $v0 arm inside FUN_801CE850Why it looked right, and what killed itPlausible: the corpus prints a body at the address and the bytes are real. The tick at |
0x801D388C hosts a Muscle Dome routine as well as the battle flow state machine | one routine; the dumps differ only in prefixWhy it looked right, and what killed itPlausible: two dumps with different overlay prefixes at one VA is the signature of slot-A aliasing. PROT 0977 is |
| The browser play page's frame path has no early-out | the early-out is in the page's JavaScriptWhy it looked right, and what killed itPlausible: the Rust runtime's |
0x801CF344 may be a phantom print from a mis-based dump | it is PROT 0897 dataWhy it looked right, and what killed itPlausible: the |
| The native minigame step runs fishing venue actors and dance count-in spawns the browser has no twin for | both reach the browserWhy it looked right, and what killed itPlausible: the waiver was written from the native side's call list, and a kernel named in one host only is the ordinary shape of drift. The dance count-in spawns reach the browser. The waiver's replacement list - the dance sequence-clear spawns, the Baka round chrome and the effect-pool ageing, all on the ground that only the native window owns |
| ... and its correction named the effect pool as the one reason | again (the dance spawns were never the pool's)Why it looked right, and what killed it
|
| ... and the fishing venue actors did reach the browser | they were native-onlyWhy it looked right, and what killed itOnly the native window ticked the venue actors until the kernel became |
| No load base makes PROT 0896 self-consistent | 0x801D4DF0, on the call graphWhy it looked right, and what killed itPlausible: a window slid across every address the file's |
| PROT 0974's uncovered run is a pointer table | sparse .bssWhy it looked right, and what killed itPlausible: the run is long, low-entropy and sits where a table would, and "pointer table" is a residue class this corpus carries. It is 81 % zero with a single RAM word in it - an uninitialised data segment, as a code image's tail usually is. The zero fraction is the cheapest test of a residue class. |
The 0x2399C run in PROT 0897 is a halfword table | it is the field overlay's data segmentWhy it looked right, and what killed itPlausible: its head is a table - the locomotion probe footprints. The segment is file |
| A frame kernel paired by name does comparable work on both hosts | an empty body pairs with anythingWhy it looked right, and what killed itPlausible: the host-drift gate's tier 11 pairs a step on each host under a written reason. A native |
engine-core has no host hook for field-VM op 0x34 sub-0 | the hook is liveWhy it looked right, and what killed itPlausible: a port tag said so. |
| The port's field direction ring is byte-identical to retail's | retail's is u32[8]Why it looked right, and what killed itPlausible: the eight values agree. The remapper reads |
| 32 battle-presentation names are web-only - the native host never calls them | 31 have native call sitesWhy it looked right, and what killed itPlausible: a |
| The play page has no Records dev-menu page | it has one, from the same builderWhy it looked right, and what killed itPlausible: the page's menu shows no Records row at the moment compared. |
battle_cursor_pose's routines have no caller anywhere - link-time dead code | every one is called, in retail and in the portWhy it looked right, and what killed itPlausible: the module's Rust items were once referenced nowhere outside their own file, so no linked binary carried them and a reach export could not see them. That is a statement about the workspace, and it was written up as a five-form sweep over the disc images. The disc says otherwise: PROT 0898 calls |
A ring cue keys a frame step after its push, because retail's drainer FUN_80016B6C runs at the end of the frame's work | on the cast channel (the cues read worse held back)Why it looked right, and what killed itPlausible: the drainer does run after the frame's game logic, and the port drains on the tick that opens the frame. Holding each drained cue |
audio-pcm's summon casts run step 2 because the summon arm rows are missing | the replay's seeded step outranks themWhy it looked right, and what killed itPlausible: the cast channel's Big Wave runs |
| Summon voices land late against Mednafen because the pre-module band is long | on the PCSX-Redux films (the band matches to the vsync)Why it looked right, and what killed itPlausible: Theeder's and Swordie's voices sit |
Generalises to: a measurement instrument has no oracle, so a number it prints is believed on the strength of its explanation. Check the explanation against the files, not against its own plausibility - several rows above are a correct count with a wrong story attached, and the story is what directs effort.
Long write-ups in this area
Two overlay lengths that were the neighbour's sectors
Falsified by TOC arithmetic.
| Reading | Why it looked right | What is true |
|---|---|---|
arena_init (PROT 0977) own content is about 0x4800 bytes | The file the superseded entry size produces is that long and disassembles cleanly to the end | The entry is 0x3800; the extra 0x1000 is PROT 0978's two sectors |
The battle overlay (PROT 0898) is 0x28800 of 0x29800 bytes, with a diverging 0x1000 .bss tail | A RAM capture matches the first 0x28800 and the tail differs, which is what .bss does | The entry is 0x28800; the diverging tail is PROT 0899's first two sectors |
0x801D2784 is PROT 0979's battle-intro transition tail | The dump is labelled 0979 and 0979 is the battle-intro overlay | The bytes are PROT 0976 (Baka Fighter). The two images are byte-identical from file 0x3C68 to the end of the smaller one, so no attribution sweep separates them - the operands do: the routine reads 0x801DBED8..0x801DBEF0 and calls 0x801D6710, all past 0979's own 0x4000 and inside 0976's 0xE000, bracketed by 0976's documented emitters 801D6480 / 801D6770. Slot-A residue rule: a byte range shared by two images belongs to the one whose addresses it names. |
0x801DDA90 and 0x801DDB44 are two slices of one loop, neither a function entry | Both look like fragments - no prologue, each ending in a j to a shared tail | Right for 801DDB44 only (it is 0x24 into slot 4's arm, at the j 0x801DDBC8 and its delay slot). 801DDA90 is slot 0 of the eight-entry jr table at 0x801CEC40 that FUN_801DD9D4 dispatches through, and that table word is its only reference on the disc. A frameless routine reached only through a table is still a routine. |
FUN_801E59B0 gives its two trig tables two different angle indices | The C renders two subscripted loads with different-looking expressions, and sin at angle with cos at angle + 0x400 is the textbook rotate: vec[0] * t1[angle] + vec[1] * t2[(angle + 0x400) & 0xFFF] | One index, both tables, components swapped. The instruction stream computes i = (angle + 0x400) & 0xFFF once at 0x801E59B0..0x801E59BC and reuses it at 0x801E59C8 and 0x801E59E4: the body is (vec[1] * t_a[i] + vec[0] * t_b[i]) >> 12. 0x8007B81C and 0x8007B7F8 are table pointers the routine lws, not the tables. |
| The PROT 0898 entry tables resolve the slot-B attribution residue | The three link-time tables are the right instrument for reachability and map the whole band | They close 6 extents / 48 bytes, and ten of fifteen table-derived attributions name a different module than the bytes do. The residue is a byte-denominated ambiguity between images that share bytes; a table that names an entry says nothing about which image the bytes at that entry belong to. |
A disc-coverage --check floor regression means coverage was lost | A ratcheted floor going down is a regression for every other gate in the tree | It is attribution lag. The gate's denominator is the disc and its numerator is what the attributed dump corpus claims: a new, not-yet-attributed dump raises the denominator before the numerator, so the percentage falls while the corpus grew. Re-attribute, then re-read the floor. It is not a worktree artifact, and re-running in the main checkout does not clear it (disc-coverage.md). |
801D84C0 is wired because panel_anchors is called | The address appears in a tag on a module whose exported function is called from a live host | The bucket is a property of the anchor, not of the address. The // PORT: tag carrying 801D84C0 sits on panel_labels, a different item, and the live-audit walks from the tagged item. Reading liveness off a neighbouring symbol keeps an inert port's audit green. |
The world-map overlay's per-prim handler table is based at 0x801F8988 | The first non-zero word of the table is there, and the eight words before it are zero | FUN_80043390 loads 0x801F8968: lui s4,0x8020 / addiu s4,s4,-0x7698 at 0x800435F4..0x800435F8, plus the same (flags >> 1) * 4 index it adds to the SCUS table 0x8007657C - which has the identical shape, words 0..7 zero and 8..19 populated. Re-basing by the zero prefix shifts every kind by eight. |
The first two rows are readings of files cut with the superseded entry size; crates/asset/data/static-overlays.toml records both corrections. The general law is on prot.md; the measured consequence - the TOC is a gapless partition - is on re-settled-threads.md.