The endless camera orbit - anatomy of a retail softlock
Since 1998, players have reported Legend of Legaia battles that simply stop: mid-fight, usually against the Gaza rematch, the action never comes and the camera circles the party forever. It occurs on every regional release and under every emulator. This page documents the defect from live-capture data: the per-vsync state and animation traces that localize the stall, the call chain that routes a monster melee into a poll with no movement code, and the staging timing that kills the animation that poll depends on. The fix - a nine-word in-place rewrite that lets the game's own staging state repair the approach - ships in the browser ROM patcher.
Localizing the stall
The visible symptom - an endlessly orbiting camera - carries no diagnostic information of its own. The battle round driver (FUN_801D0748) steps the camera's idle azimuth sweep every frame, unconditionally, without consulting the action state machine; an orbiting camera is what any stalled Legaia battle looks like, whatever stalled. The component that stops advancing is the per-action state machine, FUN_801E295C in the battle-action overlay (PROT entry 898, load base 0x801CE818).
Two independent live captures localize it. The instrument is a poll-only PCSX-Redux probe - no breakpoints, so the emulator runs at recompiler speed during ordinary play with periodic savestates - that flags any action state held for thousands of vsyncs. Both captures park in the same state: 0x19, the attack approach.
The call chain: four ways out of an attack
Every physical attack enters approach setup, state 0x14. Its first operation is the range check FUN_8004E2F0(attacker, target) - 0 means “within reach” (a size-scaled radius, ~416 units for a boss-sized actor; party members use the static table DAT_80078870 = {256, 384, 1024}). From there the arm routes four ways:
The decisive branch is the walk gate. Out of reach, a monster attacker needs its approach-transition clip - action tag 0x20 in the per-monster action table (record+0x4C, scanned by first byte via FUN_80050E2C):
801e3260 li a1,0x20 ; approach-transition tag
801e3268 jal 0x80050e2c ; scan the action table
801e327c bne v0,0xff,0x801e32b4 ; found -> 0x15 (walk chain)
801e329c li a1,0x1 ; NOT found: stage tag 1 (the Move loop)
801e32a4 jal 0x80050e2c
801e32ac j 0x801e32c4 ; -> 0x19, the wait-in-range poll
801e32b0 _sb v0,0x1da(s3) ; (delay: staged clip -> actor+0x1DA)
A roster sweep over the monster archive (PROT 867) puts numbers on it: 180 of the game's 186 monsters have no tag 0x20 - bosses included - so the fallback lane is not an edge case, it is how nearly every monster melee approaches. (All 186 do carry the tag-1 “Move” locomotion clip - the float or walk cycle the enemy viewer shows.)
An approach the state machine doesn't perform
The two approach mechanisms differ in one decisive property. The walking states 0x15–0x18 contain the state machine's own stepping code - a monster in that chain is moved by the engine, guaranteed to arrive. The fallback state 0x19 contains none: it re-derives facing, re-polls the range check, and on failure bumps a counter (ctx+0x6D4, whose only reader is an interference-roll modifier, not a timeout). All actual movement comes from the staged animation's playback: the tag-1 Move clip slides the actor along its facing at a measured ~19 units per vsync.
Position captures of ordinary play show this working every time: each healthy boss melee starts out of reach (438–1,078 units against a ~416 reach - standard formation spacing is out of reach; the models are large enough that it reads as adjacent on screen) and the Move clip walks the boss in over 28–67 vsyncs. The path fails only if the animation stops, and no code can observe that it stopped: 0x19 stages the clip once, in 0x14, and never again.
0x19 has no timeout.The trigger: a summon's staging round-trip, then the melee
The second capture logs the actor's animation pair - staged clip (+0x1DA) versus playing clip (+0x1D9) - every vsync, which pins what kills the clip. The frame-exact onset:
v26536 summon cinematic: boss staged off-arena to (0,-2048),
his damage-reaction clip plays (pair 2/2), restored, pair -> 0/0
v26867 summon action ends
v26873 boss melee starts DIRECTLY next - 0x14, target ~1022 away -> fallback
v26876 0x19, pair 1/1: Move clip ENGAGED, sliding ~19 units/vsync
v26888 pair 0/0: clip DEAD after ~12 vsyncs / ~236 units
frozen at ~786 (> 416 reach) -> polls forever = the softlock
Summons relocate every combatant for the cinematic - the boss is parked ~2,500 units off-arena and restored - and his hit-reaction clip plays while he is staged. When his very next action is the melee, the freshly staged Move clip terminates almost immediately: the staging round-trip leaves the animation driver holding stale state (a frame cursor or clip-length latch - the one detail still open), so the new clip reaches its end condition in a dozen frames. Any intervening action resets whatever went stale: the same capture session shows a summon → another combatant's turn → melee running the same clip 64 vsyncs and 1,260 units to a clean hit. The sequence reproduces on demand: cast a summon, stall until the boss's melee is the next action, and the approach parks.
The trigger's shape matches the 25 years of field reports. It clusters on late-game bosses - walk-less monsters with contact attacks, in long fights where summons are routine. It afflicts every region, since nothing in the chain is localization-touched. And it appears random, because it requires the summon → melee adjacency and an out-of-reach target in the same round - common conditions, but not every round's.
The trigger is CPU-core-independent in emulation: the same recipe parks under both of PCSX-Redux's CPU backends - the recompiler and the cycle-stricter interpreter - with an identical onset shape, so the race is not an artifact of loose dynarec timing. It is sensitive to when the summon's staging restore lands relative to the melee, which is downstream of CD-drive latency; an emulator that preloads the disc image or reads ahead asynchronously schedules that restore differently and may rarely or never land in the window. That spread - frequent on one emulator, stubborn on another, occasional on hardware - is what the timing dependence predicts.
The fix: teach the poll to re-stage
Two candidate fixes exist. Retargeting the walk-gate fallback straight into the strike chain (one word - attack from the current position) closes the softlock but is rejected by the roster data above: out-of-reach is the norm, so it would remove every monster's approach walk, not just the broken case. The shipped fix addresses the defect itself - the dead clip that nothing re-stages.
No free space is available for new code: every verified-dead byte run in the executable is occupied by other patch features, and the one remaining candidate region is a runtime buffer that all 55 savestates in the capture library show live. The guard therefore replaces nine redundant words inside the poll itself. State 0x19 re-derives the actor's facing every frame - but the target does not move during an approach, the facing is computed at staging, and the strike arm recomputes it on entry. Nine words with no observable effect, exactly where the guard needs to live:
+0x14D50. No injected routine, no reclaimed dead space, no new behavior.The guard reads the staged-clip byte each poll. While the clip is alive, nothing happens - healthy fights are byte-identical, because the write never executes. If the clip is dead while the poll still fails, the guard sets the state byte back to 0x14. That single write is sufficient because the staging logic already exists in retail: the 0x14 arm re-runs next frame, recomputes the facing, re-checks the range, re-stages the Move clip, and the attacker resumes walking. If the clip dies again, the guard fires again - a stuttering but monotonic approach that always arrives. The same bounce protects a party attacker whose run clip dies, and the only shape it cannot rescue - a monster with no Move clip at all - does not occur: the roster sweep finds zero.
Verification is layered. A disc-gated oracle proves the edit is surgical: exactly the nine words change, byte-deterministic, idempotent, refused on an unrecognized image. The runtime proof is hands-off replays of both live-caught park savestates with only the nine words poked into RAM: in each, the guard fires exactly once, retail re-stages (pair back to 1/1), the boss walks in at ~19 units/vsync, the strike lands, and the round completes.
Apply the fix to your disc
legaia-patcher randomize --input DISC.bin --approach-softlock-fix (see the patcher reference).
The fix is seedless, composes with every other patch option, and the Balanced / Full Chaos presets enable it by default.