TL;DR
A monster approaching an out-of-reach target waits in a range-check poll with no movement code and no timeout; its staged “Move” animation is what actually carries it forward. A summon's staging round-trip immediately before the melee kills that animation ~12 vsyncs in, nothing re-stages it, and the fight waits forever. The patch rewrites nine redundant words inside the poll so a dead animation bounces the state machine back to its own staging state - the attacker resumes walking. Enable it with the Approach-softlock fix toggle in the 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:

State routing out of attack setup 0x14: strike, party approach, walk chain, or the fallback poll that can park 0x14 setup in reach 0x1E strike the usual case out of reach, party 0x19 wait run anim moves them 0x1E monster with tag 0x20 0x15 0x16 step loop 0x17 0x1E SM moves it - 6 of 186 monsters monster without tag 0x20 - 180 of 186 0x19 wait-in-range Move clip carries him 0x1E ...unless the clip dies: no movement, no timeout → forever
State routing out of attack setup. The bottom lane is where almost every monster melee lives - and the only lane whose forward motion depends on an animation staying alive.

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.

Distance to target versus time: a healthy approach walks under the reach line; the parked approach moves 12 vsyncs and freezes vsyncs since the approach began distance to target 0 416 1022 reach (~416, size-scaled) in reach at vsync ~28 → 0x1E strike healthy melee: Move clip playing (anim 1/1), ~19 units/vsync clip dies at ~12 vsyncs (anim pair → 0/0) frozen at 786 > reach - polls forever, battle over
Measured from the live captures: the same fallback path, ~19 units/vsync in both - until the staged clip dies. Nothing re-stages it, and state 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:

The nine-word window at 0x801E3568: stock facing recompute on the left, the re-stage guard on the right stock: facing recompute (redundant during an approach) 801e3568 lh a0,0x38(s8) 801e356c lh a1,0x34(s8) 801e3570 lh a2,0x38(s3) 801e3574 lh a3,0x34(s3) 801e3578 jal 0x80019b28 801e357c _nop 801e3580 addiu v0,v0,0x800 801e3584 andi v0,v0,0xfff 801e3588 sh v0,0x46(s3) target is static; 0x14 and 0x1E both re-derive this same size patched: the re-stage guard 801e3568 lbu v0,0x1da(s3) staged clip? 801e356c lui v1,0x8008 801e3570 lw v1,-0x42dc(v1) battle ctx 801e3574 ori a0,zero,0x14 801e3578 bne v0,zero,+3 alive → skip 801e357c _nop 801e3580 sb a0,0x7(v1) dead → state 0x14 801e3584 nop 801e3588 nop falls into the unchanged range-check call
One same-size nine-word rewrite at overlay offset +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

Get the fix
Open the in-browser ROM patcher, load your disc image (it is processed entirely in the browser tab and never uploaded), and enable the checkbox labelled “Approach-softlock fix” in the patch options, then download the patched image or a PPF patch. From the command line: 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.

See also