At a glance

Generated inputscaps and empty cases too Retail routine off the discin the R3000 interpreter Rust kernelthe routine its tag names Whole regions equal?not chosen fields one scripted rand() stream
The port catalog asks whether a routine has a tag and a caller, and the retail comparison corpus asks about what a save state happens to show. This asks whether the Rust computes what the bytes compute, over inputs no capture reached.
cargo test -p psx-retail-exec --profile dev-fast --test integration

Coverage. 765 of the 1043 retail addresses a // PORT: tag names (73.3%) are run by a differential; 554 tests run 783 retail routines in all. Generated from the tests' declarations by scripts/ci/update-progress-metrics.py, and ratcheted by differential-coverage.py --check.

Where it sits among the project's other checks, and what it cannot see, is the verification ladder.

What it can run

Integer instructions, hi / lo, unaligned loads and stores, RAM and the scratchpad. A routine with no break guard can run a div by zero as the chip does (Cpu::hardware_zero_div), since that result is what ships. COP0 carries its register moves and rfe; anything else there, or a syscall, stops with Stop::Unsupported.

The GTE is psx_retail_exec::gte: both register files with their quirks, every command, the 44-bit accumulator with its overflow flags, the FLAG saturation bits, the UNR divider and the MVMVA far-colour bug. It is written from the hardware documentation, not from the engine's GTE, so a defect the two shared could not cancel. Unit tests pin hand-worked spec values; gte_capture re-runs the RTPS / RTPT / NCLIP / AVSZ commands the PCSX-Redux state library's register files hold and lands on their outputs; gte_engine holds the engine's live divide and its light-source NCCS kernel to it with no disc.

  • Cpu::hook replaces a call, which is how rand() is scripted so both sides draw one stream.
  • Cpu::calls records the calls made, which is how a test proves it reached the arm it claims.

How to add a routine

  1. Read the disassemblyFind what it reads and writes. Seed those bytes with the shapes a game reaches, including the caps and the empty cases.
  2. Compare whole regionsA write the port does not know about is exactly what this is for, and a field-by-field test cannot see one.
  3. Assert the armsHold that the run reached the interesting arms, so a seed change cannot leave the test passing on nothing.
  4. Declare the routineOpen the test with differential!(scus(VA)) or differential!(prot(ENTRY, VA)), naming every routine it compares. A test with no declaration fails the coverage gate.

Declaring what a test runs

Every test opens with differential!(..), naming the routines it compares as scus(VA) or prot(ENTRY, VA), or with not_a_differential!("why") when it compares a table and runs nothing. Each argument is a literal or a const of the test file, so the declaration reads without compiling anything.

  • The run checks the declaration. Cpu::call notes every entry it runs, with the bytes there, in a thread-local ledger. When the test passes with the disc present, the macro's guard fails it unless each declared routine was entered and its bytes are the named image's. A wrong address, or the right VA in the wrong overlay, fails the test that carries it.
  • The count needs no disc. scripts/ci/differential-coverage.py reads the declarations and the // PORT: tags and reports how many tagged addresses a differential runs; --check fails on a test with no declaration or on coverage below its baseline, in the pre-commit hook and in CI.
  • Other tools read it. The port catalog carries a differential column and a --no-differential worklist; the provenance checker counts a differential-run address as verified.
  • The declaration can be derived. --record runs the suite with the ledger written to target/ (it holds the executable's bytes, so it is never committed), --propose names each entered routine's image off extracted/, and --apply writes the missing declarations.

The credit is per address: a passing differential proves which routine ran and that the kernel the test calls computes what it wrote, not that every tag carrying the address sits on that kernel, nor that every arm was reached.

python3 scripts/ci/differential-coverage.py --check

What it has found

Every row was a disagreement between the bytes and a port that read as finished.

RoutineWhat the run showed
Stat aggregator FUN_80042558Its grant and revoke scans had no port, behind a heading that tagged them ported: nothing put a Talisman's or a final-form Ra-Seru's summon on a spell list (glitches). The live stat window was only rebuilt for HP and MP, so an accessory's percent boost never reached the status screen's ATK.
Battle seed FUN_80053CB8The port clamped after the flat equipment bytes; retail clamps before them.
Level-up applier FUN_801E9504The stat spread was never enabled on any host, so every level-up took the curve's exact mean and drew none of retail's eight rand() calls a level. The six battle stats had no cap on the way up.
Melee resolver FUN_801EC3E4A clip's +0x7A selector inflicted nothing on a normal swing. A defender whose Stone was the raw word a cast writes still took damage. The Mystic Shield halved a plain swing where it stops it; the block breaker's quarter damage and the drain clip had no port; the shared finisher's no-damage floor was wired onto a routine that has none; the Spirit percent wraps at a byte here alone; a granted block spends the approach terms even when the verdict is overturned.
Spoils arm of FUN_8004E568The port paid EXP and gold for every seat; retail pays a seat that died and was not captured. Level-ups draw rand() ahead of the drop walk, and the port drew them the other way, so a level-up changed which item dropped. A Stoned member takes no EXP and leaves the split, a fallen member's Items Up still counts, and a level 99 member keeps earning up to the record's cap.
Monster flee roll FUN_801EC0DCIt read the monster's INT back off its record under the scripted fight's profile, even in a random encounter.
The round opening (FUN_801DA780, FUN_80051D84, FUN_801E752C)A fallen actor draws its initiative roll, and the port skipped it, shifting every later roll. The written turn order of Tetsu's spar and Cort's final form had no port. The formation roll's unguarded divide leaves the raw draw as the spread when the sides' levels match: the port neither drew nor added it, so an even fight had no ambush. A poison a cast wrote never drained, poison wore off on a timer retail does not have, and Life Grail and Magic Grail restored nothing in battle.
Action validatorThe kernel matches over every arm. Its battle host read the raw word alone, so a member poisoned by a hit had nothing a cure could target.
Bag accessors, walk probesNormalize FUN_800423E0 is one pass of two cursors, not a whole compaction: a call can pass a hole by. The wall probes FUN_801CFE4C match, except that a negative coordinate wraps through the grid's masks where the port called it a wall.
Monster picker FUN_801E9FD4A walk matching nothing drew a target and a full swing budget the retail seat never gets; the interrupt arm of monsters 0x28..0x2A, 0x7F, 0x85 cast at members who had acted; monster 0xB6 cast past its five round cases; Tetsu's post-amble was missing; a pass that failed on its target forgot the cast it had stored. Compared on the stored category itself, a later cast entry failing its MP test dropped what an earlier entry of the same walk had stored, and monsters 0x97 / 0x98 under a 0x380 status ran neither of their two category arms.
Battle items FUN_800402F4Phoenix healed a flat quarter and skipped its draw, status and gauge. Medicine and Antidote missed the raw status word, MP items restored the dead, Fury Boost landed on the dead, and a heal landed at commit, not at state 0x3F. The Point Card was not offered in battle at all.
Queue builder FUN_801EED1CAn art ran with too little Spirit and an unknown art still ran. The price now comes off the gauge at the cleanup arm 0x801E5D74, ahead of the action's own +8.
Walk step FUN_801D01B0A wall skid kept the held heading and full speed, the diagonal cut ignored the camera mode, and terrain slow and op 4C DF had no port.
Bearing FUN_80019B28The computed arctan table rounded where retail's truncates, so about half of all bearings stood one unit off; the move VM's sub-op 0x3A measured from +X and mirrored every angle.
Take-item fallback FUN_800430ACWalked the whole roster where retail walks the present party, so an absent member could lose a worn accessory.
Use-list probe FUN_8003043CThe port answered it from the catalog's effect where retail asks the validator: an MP restore lit for a fallen member, a stat seed for a member at the cap, an arts book for a party without its reader, and the Doors ignored flags 5 and 6. The Items screen's confirm could use a row the list had greyed.
Floor sampler FUN_80019278Matches on every tile of the grid, ramps included. Off it the port called the floor zero, where retail halves the sub-cell toward zero, wraps the object cell and reads the corner block on into the neighbouring map regions.
Arrange FUN_801D64A8The sort matches. The port sorted only the screen's rows and never the bag, so the next screen, the battle window and the save kept the old order.
Turn picker FUN_801DABA4A Numbed monster acted where retail's picker tail arms nothing; an unmatched walk stored a strike, so the redirect and the Counterattack coin ran where retail's stale category does neither; a cast-written Stone did not count a side down.
Passive sweep FUN_8004CE2CThe party walk had no port. Retail's melee status arms, an enemy special's poisons and a cast's raw bits land whatever the target wears, and the walk clears what the guards cover on the next pass, so a Poison, Curse or Numb Guard stopped none of them in the port. Maximum AP did nothing; the walk holds a living wearer's gauge at 100.
Motion VM FUN_8003774CThe tile walk took one step of the frame's whole distance where retail takes the frame step's count of steps, each re-reading the deltas, and it ended a leg on the frame that landed where retail ends it on the next, so every scripted walk resumed its script a frame early. The other five ops match.
Status lifetimes (FUN_801E752C, FUN_801F45A4)Run over six rounds, the port wore Sleep and the Confuse group off after three rounds, and Numb on the same count, where retail's round tick never clears them. The round-end waker read only half of the port's status word, so a tracked Numb never drew its one-in-eight wake roll. All three now last until a hit's strip, a cure, the waker (Numb) or the battle's end.
Menu window painters (PROT 0899)Logged with the ink word each leaf draw runs under: the Yes / No labels of windows 46 and 5 are teal (ink 5), not white; window 34 falls back to the item's own description past the passive bound; an open options popup greys every other row (ink 0) and marks its cursor with the hand, not a >; the Magic info window's level is the one-glyph “Lv” ligature $.
Field party HUD FUN_801D0D38The MP pair takes the MP ink law alone, so a fallen member's MP keeps its own tier; the port painted it red.
Save grid FUN_801E06C0Each cell slides from the previous cell's park slot; only the parked row and the slide frames show it.
Shop sale (Confirm path)The menu graph's Confirm sale priced per unit off the shop's stock; it now pays what the quantity stepper does, (item-table price × qty) >> 1.
Stone and Curse castsNo cast module calls the applier. Stone Circle writes no status and is a damage cast, where the port rolled Stone on every party seat; Curse and Curse All write the bit with no roll and no guard test. Glare's inline roll matched.
Ambient motion VM FUN_80038158A restamp of the requested move left the move pair an earlier op had written, where retail stores the record's byte to both words; the directional steps probed the player under the flag bit that skips the probe. Wander, home step, facing ramp, touch-woken wait and the flag, cue and bit ops match.
Encounter roll FUN_801D9E1CThe anti-repeat latch was cleared on every scene entry, where retail's one global stands across them. The counter, rate shifts, story-group walk and battle setup all match.
Status readers (+0x16E)The port holds a status word in two halves: the bits a cast writes and the tracker a hit fills. Seated the way a game reaches them, the monster picker, the turn picker and the Auto delegation went red: a Confuse the tracker held was invisible to the picker’s arms and the Counterattack roll, a tracked Stone or Numb did not veto the Auto fill, and the seed’s retarget ran twice for a raw Confuse. The HUD’s gauge loop FUN_80046A20, the limb dimming and the cast modules’ seat view had the same split. Every reader now takes the whole word.
Text width FUN_80035F04The measurer matches Font::measure at both glyph pads and disagrees with the draw layout on most strings carrying an escape. The text balloons and the world-map labels sized themselves off the layout; they read the measure now. The battle-intro name banner FUN_801D9D3C matches; on four labels that cannot sit side by side retail’s relax-and-clamp loop never settles, where the port stops at its bound.
Shop quantity steppers (FUN_801DB7F4, FUN_801DBD94)Buy and sell match the port’s sessions and the purse and bag writes, except one arm the port differs on by choice: at 99 held retail opens the buy stepper with a bound of 0, still buys the one copy the list started on and takes its gold while the bag add trims it. In play the shop’s list refuses a row built at 99 before the stepper opens, so the arm is latent there; the port refuses that buy too.
SFX cue ring and drainer (FUN_80035B50, FUN_8001698C, FUN_80016B6C)Run frame by frame with the sound library’s key calls logged, the ring, the one-shot voice cursor and the sustained run match. A sustained row with no voices keyed nothing in the port, where the drainer releases the held run before it tests the count, so the old run kept sounding.
Field Magic casts (FUN_801D9594, FUN_801D9280, FUN_800402F4)A heal cast from the pause Magic screen restored a catalog placeholder (MP × 8, or × 6 for the party) where the applier caps it at 0x100 / 0x200 / 0x400 plus the caster’s level in the spell; 122 of 125 casts wrote different records. A single-target heal on a member at full HP also charged MP, where retail’s confirm refuses it.
Field interact probe FUN_801CF9F4The port’s interact box matches. Where boxes overlap, retail talks to the last actor in list order and the port to the nearest; a slot-order pick turned the casino’s door into a talk with a neighbour, so the port keeps the nearest until a capture reads that scene’s spawn order.
Enemy Steal FUN_801F730C (PROT 0941)A party victim’s draw opens with a rand() gate the port skipped, so every enemy steal shifted the battle’s later rolls by one; a monster robbing a monster raised no line where retail composes one.
Divide split FUN_801F78B8 (PROT 0940)Only 0xAE throws the coin, where the host drew for 0x50 too; the clone takes the caster’s HP before the coin, so a weakened caster left both halves on 1 HP in the port; the weakened caster also loses its MP.
Element Change FUN_801F88EC (PROT 0964)Retail draws once and again only while the draw repeats the last roll; the host pre-drew 33 rand()s every cast.
Water Hazard FUN_801F7298 (PROT 0956)Retail draws each seat’s damage and then its Venom before the next seat; the host rolled the whole row’s damage first. The attack ring FUN_801D8A88 / FUN_801D8D00 matches.
Mushura chase (PROT 0915), Nighto (PROT 0907)The chase trades its target with rand() % 4 + 3 redrawn on a repeat, over all four monster seats, where the port drew once from the living ones; Nighto skips its resist throw for an immune target, where the host drew it. Water Hazard’s monster leg sweeps the whole row; the port struck one seat and rolled nothing there.
Ozma’s hit ladder (PROT 0934 arm 0x18), Mule’s wave (PROT 0929)The arm draws its spray’s six rand()s before the ladder rolls; the port drew them after, so every ladder hit rolled off other draws. Each ladder and wave roll is a whole FUN_801DD0AC, finisher included, but the port rolled it bare: no spell XP, no spirit. The fold also threw away what the pre-hits had banked. Mule’s wave also walks fallen seats and clears their staged clip; the port skipped them. Every FUN_801DD0AC call site in 0903..0966 is now held to an owner that runs the finisher. Kiss of Death’s arm 4 (PROT 0955) and the encounter counter’s scene-entry top-up match.
Battle records (FUN_800508DC, the death arm’s credit in FUN_8004AD80, the combo fold of FUN_801E805C)None of the four records-page words had a writer in the port, so Knockouts, Monsters Defeated, Max Damage and Max Hits read 0 for ever. The cue walker bumps Knockouts on each character cry it reaches; a monster’s death bumps the acting member’s Monsters Defeated, the word once read as a magic-rank counter (save record).
Casino coin counter FUN_801F0ADCMatches on the cells, cursor, hold, ceiling and sale; the port had no arm for its two flag calls, so the script that opened the counter could never tell a sale from a walk-off.
Separation driver FUN_80051078Retail gates each seat on its colour word +0x04, not HP: a fallen member still lying on the stage and a monster in its defeat fade push and are pushed, and a hidden body is not. It walks retail’s seven seats, so a fifth monster at seat 7 is never separated; the port walked its own compacted slots.
Anim-rate restore FUN_801E93C8The gate and the latch clear match. It restores retail’s seven seats, so a fifth monster keeps its rate; the port restored its own compacted slots, and its arts slow-motion slowed every actor where retail’s arms walk the same seven.
Cast camera FUN_801DC0A0The shots and hand-offs match. The input builder sized a slot target with the party radius where retail reads the target’s own node, and built a group from the monsters not hidden where the aim counts a monster by its colour word, so one faded out after death still pulled the centroid.
Camera-relative rotation FUN_8001CF50The port built its axis factors from rounded f32 trigonometry where retail's FUN_800461A4 / FUN_8004629C / FUN_8004638C read the truncated sine LUT through MVMVA: three cases in four were off, by up to four units of 4.12. It now builds from the LUT and matches.
The engine's register-file GTEH, DQA and ZSF read back raw where cfc2 sign-extends; MAC0 clamped where it keeps the low 32 bits; an IR3 saturation raised FLAG bit 31, which the hardware leaves out.

Several routines matched on their first run, which is a finding too: the Magic list's probe FUN_8003053C (it greys a spell row and refuses its cast), the menu stat block behind the Status page, the Equip compare and the shop preview draw (FUN_801CF5D0 / FUN_801CF650), the scene record dispatcher FUN_8003BDE0 over every such record of every scene on the disc, the camera zone ramp FUN_80037018, and the field walk-regen tick FUN_801D0B90 (the step counter, the HP / MP / AP Walk bumps with their clamps, a fallen member’s bump, and the Incense window’s wear-off edge). The battle cue router FUN_8004FE5C, the escape timer’s HUD actor FUN_801D2EBC over whole countdowns, the death arm of FUN_8004AD80 and the MP-cost leaf FUN_80035394 match. The death arm's table test leaves out row 0xBF, which holds the neighbouring table's [9, 0]: retail would add item 0 there and the port refuses it, and a second test holds that no shipped monster reaches such a row. The field follow camera's ease FUN_801DB510, its composer FUN_801DAB90 and the focus edge clamp FUN_801DAA50 match over generated parameter blocks of every camera mode. The overlay colour tween FUN_801DDC20 took its ramp progress as a word where the bytes take a halfword, which only a negative delay no shipped script seats can reach; the SCUS fade actor's tick FUN_80025000 matches, and so does a fade loaded through FUN_80020B00 and played tens of thousands of frames past its end, except at a zero duration, where the loader's unguarded divide lands the fade on its target at once and the port had divided by one. The battle overlay's two scans of a caster's spell list, the already-known Seru test and the summon return's “No effect.” pass, match but for a byte-wrapped compare no game reaches. Run over every distinct field-VM instruction on the disc for what it stores into its context, the VM wrote op 0x4B's keyframe count one byte off (+0x6D for +0x6C), kept the half-tile bit in op 0x23's tile bytes, and cleared the minigame warp's walk lock on the context running the script instead of on the player. Stepped on the real engine rather than an inert host, the seat op 4C 51 matched on every tile but the off-map hide box, which the engine ignored: the Rim Elm blackboard's tour now parks the villagers round the map as retail does, and its re-seat brings them back.

The per-art attack camera FUN_801D71B8 and the framing cases of FUN_801D5854 run against their ports in attack_camera and battle_framing: they found the art 0x1A arms' target fade unported, case 7's midpoint truncating where retail floors, case 8's Normal and scripted-fight arms missing, and the far framing's box built over fallen seats. The HUD glide stepper, the ledge-hop probe, the typewriter count and the cast dispatchers match.

With the GTE in, MulMatrix FUN_8005B3A8, the Euler builder FUN_80026988, the overworld curvature table FUN_800271A8, the drop shadow's grid FUN_800460AC and packets FUN_8001C394, and the billboard projector FUN_800195A8 match their ports.

The field-to-battle transition runs off the disc packet for packet. Its particle fields (FUN_801CFDA0, FUN_801D0370) rounded every per-frame step toward zero in the port, where retail’s shift floors. Its tile shatter (FUN_801D0D24, FUN_801D0E54, through the SCUS dispatcher FUN_80043390) drew every flying tile at full brightness: a tile that has started to move dispatches on the depth-cue bank, which fades it toward black by its progress and drops a quad wholly off screen. The swirl’s early submit FUN_801D1A20 rides the same bank, linking each quad at its deepest corner, and drops an edge-on quad even double-sided. The late swirl, which retail draws through the shatter renderer, is not carried faithfully.

The field camera build FUN_800172C0 and its single-point RTPS FUN_8003D368 match an integer port of the camera matrix. The framebuffer grab and the drop shadow had projected through the hosts’ floating-point view, which missed retail’s screen point on nearly every point by a pixel, and by far more near the eye; both now project through the integer camera. The procedural mesh builder FUN_80028158, the field frame blender FUN_8001BE80, the camera-relative glide FUN_80021248 / FUN_8002149C and the field-entry camera reset match.

The battle weapon trail runs whole: the band emitter FUN_800485BC with the billboard projector live beneath it, its sweep driver and its trigger. The emitter keeps the projector’s last corner, a view unit right and down of the control point, through the integer battle matrix; the port had projected through the hosts’ floating-point view, a pixel off on about one point in twenty, and now projects as retail does.

The op-0x45 camera mover’s tick FUN_801DC0BC stores nine of its ten pairs: focus Y holds through a glide, where the port walked it to the beat’s target, and the tick re-rolls the shake about the mover’s own pose, which the port skipped. The arming routine works on halfwords. The tween builder and the shake match.

The transition’s seeders, run with the production environment, showed its sine and cosine tables swapped, so every particle and tile tumbled about the wrong axis and the swirl’s rings were built transposed; the heading and the root were floating-point stand-ins for retail’s bearing and root. All four seeders now match given the same draws, and the draws are the game’s own: its one rand() stream, taken where the transition’s setup finds it.

The battle monster loader FUN_80055468 uploads a whole 256 x 1 CLUT row, whose last sixteen entries are the page’s first halfwords; the port wrote fifteen palettes. The party battle mesh’s splice, post-pass and texture relocation, the TMD registrar, the sprite arm and the visible-tile crop’s decoration pass FUN_801F7088 match. The PsyQ St sector ring matches over generated movies, except that a header claiming zero chunks never completes its frame in retail.

The anim-node cursor FUN_80047430, its readout ramps, the tint machine, the pose decoder FUN_8004998C, the walk among seated actors, the hop arc and the action effect stepper FUN_801DEA50 run against their ports; the cursor differential found a Slowed idle under slow motion still advancing at a fraction retail drops.

The battle side's frameless kernels run on their own too. The cast roll's scale stage FUN_801DD864 matches over every seat pairing, both weaken bits, the Spirit stance and the summon body's magic level, but its products wrap at the word where the port saturated. The group aim FUN_801DCEAC floors a spread read back as a halfword, so a spread past 0x7FFF takes the floor; the body colour pass FUN_8004A908 scales a depth-cue channel over whole words where the port truncated to 16 bits. None of the three is in a staged fight's reach. The Seru learn, the magic-level line, the swing camera height, the popup ring, the cursor step and placement copies, the Super tail replace, the learned-art check, the monster install with both boost profiles, the dead-target redirect, the arts flash ramp, the cast-start cue, the attach swap, the chip marks, the arts pager, the cue-group expander, the target banner, the bolt ribbon with its generator, the near-camera ghost pass and the clip interpolator match on their first run.

The field and menu side runs too. The CLUT-cell HSV writer FUN_80019D50, behind jou's pulsating flesh, repacks each texel with an add where the port used an or: under a white-blend amount below zero, which op 0x44's signed velocity can reach, a negative channel carries into its neighbour. Everything else matched on its first run: the system flag bank and the field VM's operand loaders, the actor node pool, the MAN record resolver over every scene, the pad's camera remap and wall-slide resolver, the walkability probe and the elevation lookup over every field map, the camera height ease, the floor-ladder oscillator, the eased move and their spawners, the camera-zone loader and re-query, the property-ramp pool, the morph envelope and clip cursor, the arc hop, the fog emitter and spawner, the world map's entry fade, the L1 map display, and the menu overlay's category check and preview mode.

The sound driver's per-frame tier runs in the interpreter too: libsnd's SsSeqCalc FUN_80062F98 with its tempo slide, volume slides, track end, transport arms, event decoder and delta-time pump; the voice chooser FUN_80066B00; the key-on volume chain FUN_80067550; the key-on and bend pitch; the SEQ slot release. The volume slides cached their pair unclamped where retail reads it back through the set's 0x7F clamp, and the ascending fast arm wrapped a halfword where retail sums 32 bits.

So do libsnd's per-voice walks, stood on one voice file with the port's sequencer: the note-off FUN_8006688C, the bend FUN_80067C1C, the CC7 / CC10 re-volume FUN_8006861C, the sequence-volume walk FUN_80067E9C, the flush's envelope read and silent history FUN_80065BAC, the explicit cue key-on FUN_80065034 and the game's BGM pause FUN_80026740. Each run seats a random record file on both sides (notes of the sequence sounding or released, voices a cue took, records another sequence owns) and compares the shadow registers and dirty bits each walk writes with the port's voices. They agree; the two differences left are a second key-off of a voice already releasing and a pause that keys off a vsync sooner under a level already zero, neither audible.

The slot-B stagers, the routine a module’s parts reach through move-VM op 0x20, run arm by arm off their images (stager_spawns, stager_states): every seat call, each seated part’s buffer, the module image, the fades and the pool spawns. PROT 0959’s arm 1 reaches arm 5’s tail by j 0x801F87A4 and seats two records the port left off; Spoon’s and Steal’s arm 1 spawn into the efect.dat pool, which no port carried; Gizam’s arm 1 stores the creature’s tint word 0x3FF00000, which the port dropped; and Evil Seru Magic’s stager arm 4 opens on Juggernaut’s salmon flash and camera shake, which its host never raised.

The capture-class cameras (capture_cameras) run every ported director’s body pass by pass, comparing each pass’s tween builder call, countdown word and hold. Water Hazard’s and Mystic Circle’s arm 0 turn the caster onto its target before framing it; Melt Spray’s and Chaos Flare’s arm 2 turn on the victim’s display pair; Stone Circle’s arm 1 cuts to the origin. Dead End Crisis’s arm-0 line-up and its restore now have a port and a differential of their own (dead_end_crisis), as does Juggernaut’s stager arm 7, which fans the Dark Eclipse rays off three rand()s.

The phase-chain tick bodies (chain_ticks) run from arm 0 to their Done against the port’s whole pass: the chain runner, the camera director on every pass, the caster pre-pass and the party-row and hit rates, over the whole context and all eight records less the bytes another owner writes. Dead End Crisis’s arm 5 holds the party at rate 2 where the port froze it, which stalled the engine’s Done band for some 1800 ticks after Final Crisis; Genocidal Cannon slows its struck seats; Megaton Press freezes a victim on its +0x1F2 clip; Hyper Lightning’s arm 7 left a pass late; and a replayed caster’s return to idle belongs to the runner. The summon tick bodies’ phase walks match but for Water Crystals, whose arm 5 is terminal.

The LZS decoder FUN_8001A55C matches over every container section on the disc. The SCUS root FUN_8005AF0C under the fishing lure counted a negative input's leading zeros where the GTE's LZCR counts leading ones. The field animator's sampler and angle lerp, the "ME" art-animation reader and codec, the VDF morph, the MAN section-0 carve and motion-script installer, the slot machine's LCG, landing search and marquee, the fishing prize gate, the coin counter's panel, the publisher-logo quads and the mode table's handlers match.

The battle routines too large to run whole run arm by arm. The Triangle arts-list driver FUN_801D3444 latches its View Hyper Arts list caption, and the latch keeps it up while a list closed just before the entry ended slides out; the port now draws it. The after-image walk FUN_80049348 divides by the anim rate with no guard, so a frozen actor casts no ghost, and a monster at rate 1 samples depth 16 where the port clamped to 15. The damage finisher FUN_801DDB30, called on its own with the two rolls, matches on the value, the Stone nullify, the strip, the rumble and the summon XP, but its Spirit fill caps a halfword, so a big hit on a tiny max HP can wrap the gauge under the cap. The Final Heal sweep FUN_801E6968 keeps the ability bit when no Lost Grail was spent; the multi-cast readout FUN_801E805C draws a five-digit row's cells fourteen rows tall; the result build FUN_801D84C0 publishes one buffer into two placement records. The side-band tick, the battle floor's grid with every packet, the per-actor draw, the per-hit numeral and the arts-entry chip build match.

The menu side runs through a window-VM harness: the interpreter FUN_801D6628 with its SCUS window-list helpers live, every program the menu overlay holds chained at random, the whole list compared node by node. The port's window model created windows with the wrong style byte, never computed the screen edge a window slides in from, and closed a sliding window from frame 0 where retail reverses the slide. With the helpers live, the small sub-screens that wait on them run frame by frame: the pause menu's ready check stepped on Left and Right and stopped at the ends, where retail wraps two rows on Up and Down and tests confirm before any move. The casino prize counter FUN_801DC1CC commits a redeem on the frame after Yes, which the port did on the press itself, and the menu's decimal writers FUN_80034B78 / FUN_80034E4C print a value wider than its field digit by digit from the pen, where six copies in the port stacked the overflow on one cell or cut it to the field.

A second pass over the field side. The move VM FUN_80023070, run over random programs, does not end a frame on an opcode past 0x46 or on 0x0B: both land on the loop's epilogue, which skips the word by the width the previous opcode left behind, where the port stopped the frame or spun. Op 0x46 stores three words where the port wrote two halfwords. Its overlay escape FUN_801D362C, every sub-op against a host answering each global from the same RAM, saves and restores the 8-byte position vector, pad halfword included, where the port carried a different word in that slot; the Bezier it calls splits each weight into whole and fraction in 32-bit words, which the port now transcribes. The property-ramp ticker walks its ramps in install order, so the newer of two ramps on one value stands; the port walked slot order. The motion VM's player box and wander fan, the party-table rebuild that seats Terra, the field actor cull and the sub-area window rebuild over every field map match.

The field side's leftovers. The fog pass FUN_8003F348 / FUN_8003F3FC / FUN_8003F86C, run record for record and packet for packet, now projects through the integer camera: through the floating-point view fewer than one sheet in twenty landed on retail’s corners, a particle behind the eye was dropped where retail draws its saturated corners, and the overworld’s cull test bent all three corners where retail bends one; a map01 capture’s 104 sheets went from 6 to 103 exact. The battle effect pool (FUN_801DE914, FUN_801DFDF0, FUN_801E0080) writes an effect’s random spawn offsets into the shared script, so a second spawn re-deals an earlier one’s children, and stands still once the battle-end byte is down; the port kept the offsets per spawn and kept walking. Of the PROT 0900 screen widgets, the image panel draws its targets, so a zero-length move shows at once where the port kept the old position, and the scripted sprite tests its opcode byte only on entry. The entity tick FUN_801DA51C blocks the player’s movement during an activation, not its own, and hands the encounter roll the BGM poller’s answer. The move VM’s sub-op 0x29 writes and ramps a rung of the floor-height ladder, which the engine had stored where nothing read it. The mask, the letterbox and the shared tween match on their first run.

A pass over the menus and the save path. The shop’s quantity spinner FUN_801DBC5C re-arms its list result on the frame it settles, so that frame never routes; the port routed on whatever the word held. The mode select’s Buy row clears only the staged item, quantity and list result, not the staged slot. The sprite-descriptor dispatcher FUN_800198E0 ends every upload by recording the texpage’s CLUT row in the table at 0x8007BEC0, which the port skipped, and wraps its strip width at 16 bits where the port saturated. The slot add FUN_80042FE8 refuses a slot past the window’s end; the first-live-monster scan FUN_801DB8B4 falls through to the fifth monster’s seat. The save composer FUN_801E1934, the equip applier FUN_801E5A08, the root picker, the Use list, the notify window, the Options popup, the card drivers, the list-row resolvers and the cold reset match on their first run.

The STR overlay runs from frame bytes to the MDEC word list. Its Iki converter FUN_801D0378 and the table’s LZSS FUN_801D0604 match the port word for word over frames of all six movies; the port’s decoder now goes through that list and a separate MDEC stage, as retail does, with the pixels unchanged. The STRv2 decoder FUN_801D070C, one-shot and resumed in chunks, its table unpacker, the play loop’s context, frame poll and slice callback, the stream open and frame pump over the nine retail cutscene slots, and the MDEC sync routines match.

So do a set of system and render leaves: the boot SPU-base seeder, the fade ramp step, the rumble actuator, the scene-name sync, the prompt line counter, the gouraud colour spread, the display mirror, the battle intro’s style pick and quad builder, the CLUT-walk shimmer stepper, the placement spawner’s model-bank select, the field-load entry step, the sustained-cue teardown and the battle XA seek-ahead.

The move-FX streak ribbon FUN_801E1D98 grows its first segment above the bottom quad whatever the baseline: the chain loop is a do-while, and the port tested first, so a ribbon whose bottom quad sat at the screen top drew one segment short. Its single-quad sibling, the cast colour wash, Swordie’s split panels and slash streak, and Deadly Rain, run frame after frame on the real GTE, match.

A pass over the format readers and the cast band. The battle-stage stream walker FUN_8001FE70 copies its lead chunk whole and type-tests only the chunks after it, so a lead of type 0x02 does not end the walk, where the port’s walk stopped on it. The boot loader’s PROT TOC read, the CDNAME table loader, the scene-entry script install over every field scene, the New Game seed, the container walkers, the capture-class trampolines, the single-target Curse, the clip morph and the battle-texture upload match on their first run.

See also