How to read this page
Everything here is read from the instructions and data on the disc - the USA disc throughout, plus the Japanese and French discs for the Elixir and coin-vendor sections. Two sections, the Speed Elixir one and the enemy-item one, were also observed in a running game, captured on a poked state: values were written by hand in an emulator, as each section spells out. The other three were not run. Where a section says something "should" happen, that is a prediction that follows from the code and is marked as untested. Each glitch has an address table so another reader with a debugger can check it.

Speed Elixirs and void turns

Reported as
"Stat boost overflow": stacking Speed Elixirs in battle makes the turn-order number go negative. The character stops getting turns and "void turns" appear, where the camera swings to a spot where nobody stands and nothing happens. A dead enemy can take a turn, and the game can freeze (community report)
Cause
The turn-order number rolled from Speed is kept in 16 bits, and the routine that picks the next fighter reads its best-so-far as signed, so a number of 32,768 or more can never win
Where
Key seed FUN_801DA780 and next-actor picker FUN_801DABA4 in PROT 0898; Elixir arm 0x80041464 in SCUS_942.54
Affects
The over-boosted character and every party member seated before them; all three builds read (USA, Japanese, French)
Status
Explained - read from the instructions and captured on a poked state; the freeze is not traced

This entry is about Elixirs used in battle, and about Speed in particular. A Speed Elixir or a Wonder Elixir multiplies a character's battle Speed by 1.2, and nothing stops the next one from doing it again. On the USA disc the Speed stat itself is safe - it stops at 65,535. The overflow is one step further on, in the number the game rolls from Speed every round to decide who acts next.

That turn-order number is kept in 16 bits. The routine that picks the next fighter walks the seats in order and remembers the best number so far, but it reads the remembered number as signed and each new candidate as unsigned. A number of 32,768 or more therefore beats everything when it is the candidate, and then reads as negative when it is the remembered best - so the very next seat takes the lead from it, even a seat whose number is zero.

The turn-order number one round can roll, on the 16-bit line (to scale) 0 to 32,767: normal turn order negative: never picked No Elixir: Speed 331 24 Elixirs: Speed 26,193 26 Elixirs: Speed 37,717 28 Elixirs: Speed 54,312 30 Elixirs: 65,535, pinned 0 32,768 (0x8000) 65,535 (0xFFFF) 0x46A9 (Speed 0xFFFF) captured: acts first 0xA9E5 (Speed 0x8000) captured: never acts
Each bar is the range the turn-order number can land in for one round, for a character who started the battle at Speed 331. Green lies in the half the picker reads correctly; amber lies in the half it reads as negative. Past 26 Elixirs the bar runs off the right-hand end and comes back in on the left, and at the 65,535 ceiling the effect is gone. The two marked values were captured with Speed written by hand.

Two things follow. The over-boosted character can never be picked, and neither can anyone seated before them, because each of those lost the lead to the big number on the way past. And the round still hands out one turn per living fighter, so the turns those characters should have had go to whichever later seats are left. Once those have acted their numbers are zero, every remaining seat ties at zero, and the game draws one at random without asking whether anyone is there:

  • an empty enemy slot - the void turn: the camera frames a spot with nobody in it and the turn ends at once;
  • a fighter who has already acted, who gets a second action;
  • a defeated enemy's slot, which the draw does not exclude.
Seat 1 over-boosted: the picker walks the seats left to right seat 0 party 01ED seat 1 boosted A9E5 seat 2 party 0140 seat 3 enemy 0094 seat 4 empty 0000 seat 5 empty 0000 seat 6 empty 0000 never picked every turn of the round is drawn from these seats The round as captured (Speed written by hand): four turns, four living fighters Turn 1 seat 2 attacks Turn 2 seat 3 attacks Turn 3: void turn seat 5, nobody there Turn 4 seat 2 attacks again Seats 0 and 1 hold their numbers all round and never act.
The seat row and the round are one capture: the Speed of the party member in seat 1 was written as 32,768 in an emulator, giving the number A9E5. Seats 0 and 1 never acted; the two turns they freed went to an empty seat and to seat 2 a second time.

The effect has a window. From an ordinary Speed it takes roughly 16 to 26 Elixirs on one character to open it, and a few more close it again: once Speed is pinned at 65,535 the sum wraps all the way round to an ordinary positive number and the character simply acts first.

Battle Speed at the startFirst Elixir that can lock the character outFirst that always doesElixir that reaches 65,535
20026th29th32nd
33124th26th30th
50021st23rd27th
99917th20th23rd
125416th18th22nd

Speed and Wonder Elixirs count toward the same total. Which party members lose their turns depends on where the boosted one stands: in the first seat only that character is locked out, in the second seat the first two are, and in the third seat the whole party is - and then every freed turn goes to an enemy slot, living, empty or fallen.

What was captured, and how

The mechanism was read from the instructions and then observed in PCSX-Redux on the unpatched USA disc, in a battle of three party members (seats 0 to 2, Speed 331, 364 and 310) against one enemy in seat 3. These are captures on a poked state: Speed, or the turn-order number itself, was written directly in the emulator. No Elixir was used, so the Elixir routine itself was not exercised in a running game, and nobody has yet played the 24-odd Elixirs out by hand. Commands were entered by scripted pad presses (a plain attack for every member).

What was writtenNumbers, seats 0 to 3Turns observed
nothing (control)01ED 0186 0140 0094seats 0, 1, 2, 3
seat 0 Speed 0xFFFFsum 0x146A9 stored as 46A9seat 0 acts first; an ordinary round
seat 1 Speed 0x800001ED A9E5 0140 0094seat 2, seat 3, seat 5 (empty: a void turn), seat 2 a second time
seat 0 Speed 0xFFFF and seat 1 Speed 0x800046A9 A9E5 0140 0094seat 2, seat 3, seat 5, seat 6 (both empty)
seat 0 number 0xA9E5, written at the first menuA9E5 021C 0191 0095seat 1 (already chosen when the number was written), seat 2, seat 3, seat 5 (empty)

Every round lasted four turns for four living fighters, whoever took them. Not captured: a defeated enemy taking a turn (the battle had one enemy, so no fallen seat existed) and the freeze, which has been neither reproduced nor traced.

The Japanese and French builds

The picker is the same in all three builds, instruction for instruction. The Elixir routine is not: the Japanese executable has no ceiling on it, so on the Japanese disc the boosted stat itself wraps round to a small number once it passes 65,535 - an Attack of 300 is 59,077 after the twenty-ninth Power Elixir and 5,356 after the thirtieth. The USA and French builds stop the stat at 65,535. The localisation closed the stat wrap and left the picker as it was.

What it is not

The permanent stat items - the six Waters, Honey and Miracle Water - cannot overflow anything. Each adds a small fixed amount to a 16-bit number and immediately compares the result with a ceiling (999, or 9,999 for HP and 280 for agility), writing the ceiling back if it was passed. The largest value that ever exists before the compare is 1003 (10015 for HP). The Japanese and French builds carry the same compares with the same ceilings. A status-screen figure above 999 is the stored stat plus equipment, summed for display.

The instructions

The Elixir arm (class 7, 0x80041464) computes stat + stat/5 on both halfwords of a battle stat pair and stores 0xFFFF when the sum is larger (0x800414D8..0x80041510 for Speed, actor +0x164 and +0x166). There is no "already boosted" guard. Once a round the key is seeded:

801da800  lhu a0,0x164(a1)        ; SPD
801da840  addu a0,a0,v1           ; + rand() % (SPD/2 + 1)
801da844  addiu a0,a0,1
801da84c  sh a0,0x16c(a1)         ; 16-bit store, no ceiling

The wounded bonus, the halving under Slow (0x801DA908) and Speed Chain's +0x1000 (0x801DAA08) are 16-bit too. The picker starts with seat 0's key as the running maximum and walks seats 1 to 6:

801daca4  sll v0,a1,0x10
801daca8  lhu v1,0x16c(v1)        ; candidate key, zero-extended
801dacac  sra v0,v0,0x10          ; running maximum, sign-extended
801dacb0  slt v0,v0,v1            ; (signed) max < (unsigned) candidate?
801dad60  sb v0,0x274(a1)         ; the chosen seat

A passing test makes the candidate the maximum and restarts the tie list; equality (0x801DACD8) adds the seat to the list; the result is a random member (jal rand at 0x801DAD0C). A key of 0x8000 or more always passes as a candidate and always loses as the maximum, and the equality test never matches it. The loop has no HP test, no "is this slot populated" test and no "key is not zero" test.

The turn start (0x801E2C50..0x801E2D54 in FUN_801E295C) takes the seat from ctx[+0x274] unchecked, loads the actor pointer from 0x801C9370[seat], zeroes its key and calls the camera framing routine (jal 0x801d5854). Unused enemy seats still have zero-filled actor records, so the camera frames an empty seat and the turn falls through to its end.

From the seed formula: below Speed 21,845 the key is always under 0x8000; from 21,845 to 32,766 it passes 0x8000 on some rolls; from 32,767 to 43,689 on every roll; from 43,690 to 65,534 the sum increasingly passes 0xFFFF and wraps back; at 65,535 the key is rand() % 0x8000.

The Japanese Elixir arm (0x800425F0..0x8004295C in SCPS_100.59) ends in a plain store with no compare: lhu v0,0x164(a0) at 0x80042608, addu v1,v1,v0 at 0x80042620, sh v1,0x164(a0) at 0x80042624.

The permanent items are class 6, arm 0x8004112C; the tier byte picks one of seven arms through the table at 0x800151B0, all of this shape:

800411e8  lhu v0,0x6ec(v1)      ; stored ATK (record +0x124)
800411f0  addiu v0,v0,0x4
800411f4  sh v0,0x6ec(v1)
800411fc  sltiu v0,v0,0x3e8     ; below 1000?
80041200  bne v0,zero,<exit>
8004120c  _sh v0,0x6ec(v1)      ; otherwise store 999

The item menu then rebuilds the live stats (jal 0x80042558 at 0x801D8540, PROT 0899), which clamps again at 0x80042C0C..0x80042CE0. One enemy move does wrap a stat - Jugger Power (cast 0xBA, PROT 0945) adds a quarter to ten stat halfwords at 0x801F6DA0..0x801F6E34 with no compare - but it is an enemy self-buff and unrelated to Elixirs.

AddressImageWhatEvidence
0x800402F4SCUSItem applier; class table 0x80014FA0disassembly
0x80041464SCUSElixir arm; Speed pair 0x800414D8..0x80041568, ceiling 0xFFFFdisassembly
0x800425F0SCPS_100.59 (Japan)Elixir arm, through 0x8004295C; no ceilingdisassembly
0x80041E24SCES_019.44 (France)Elixir arithmetic, with the 0xFFFF ceilingdisassembly
actor +0x164, +0x166, +0x16CRAMBattle Speed (working, base); turn-order keydisassembly
0x801DA780PROT 0898Key seed; 16-bit store at 0x801DA84Cdisassembly
0x801DA84CPROT 0898Sum 0x146A9 stored as 0x46A9; 0xA9E5 stored from Speed 0x8000capture (poked Speed)
0x801DA908, 0x801DAA08PROT 0898Slow halving and Speed Chain +0x1000, both 16-bitdisassembly
0x801DABA4PROT 0898Next-actor picker; mixed compare at 0x801DACA4..0x801DACB0disassembly
0x801DAD60PROT 0898Result store; empty seats 5 and 6 drawn, seat 2 drawn twicecapture (poked Speed or key)
0x801E2C50PROT 0898Turn start, through 0x801E2D54: seat taken unchecked, camera calldisassembly
0x801DDBFC, 0x801DE048Japanese PROT 0898 (base 0x801D05C8)Key seed; picker compare through 0x801DE080disassembly
0x801DB524French PROT 0898 (base 0x801CF0A8)Picker compare, through 0x801DB55Cdisassembly
one turn per seated fighter, less the skippedPROT 08980x801E679C..0x801E67E4: the end-of-action gate bumps the turn cursor ctx[+0x1A] and continues the round while it is below ctx[+0] + ctx[+1] - ctx[+0x25] (party seats plus monster seats, less the fighters the order dropped without acting)disassembly
0x801F69D0RAMTurns begun this round: zeroed at 0x801E2ABC and 0x801E2E08, incremented at 0x801E2C38; its one read (0x801E2B9C) is a zero test that picks a camera yaw step, so it does not hand out turnsdisassembly
a defeated enemy taking a turn; the freeze-The draw admits any seat with key 0; neither was capturedcommunity report
0x8004112CSCUSPermanent stat-up arm; tier table 0x800151B0disassembly
0x80041178SCUSLife Water: record +0x11C +16, ceiling 9999disassembly
0x800411C4SCUSPower Water: +0x124 +4, ceiling 999disassembly
0x800413F4SCUSGuardian Water: +0x126 and +0x128 +4, ceiling 999disassembly
0x80041210SCUSSwift Water: +0x12A +4, ceiling 999disassembly
0x8004125CSCUSWisdom Water: +0x12C +4, ceiling 999disassembly
0x800412A8SCUSMagic Water: +0x11E +8, ceiling 999disassembly
0x800412F4SCUSHoney / Miracle Water: +4 to eight fields, AGL ceiling 280disassembly
0x800420F4, 0x80041A9CSCPS_100.59, SCES_019.44Japanese and French permanent stat-up arms, same ceilingsdisassembly
0x8003FB10SCUSUsability check that greys an item nobody can benefit fromdisassembly
0x80042558SCUSLive-stat rebuild; clamps at 0x80042C0C..0x80042CE0disassembly
0x801F6DA0PROT 0945Jugger Power stat block, through 0x801F6E44disassembly

Verifying it

On a poked state: at the entry of 0x801DA780, write 0x8000 to +0x164 and +0x166 of the party member in seat 1. A breakpoint at 0x801DA84C shows a0 between 0x8001 and 0xC001; a breakpoint at 0x801DAD60 never shows seat 0 or seat 1 as the result, and shows an empty seat once seats 2 and up have acted. Unpoked, use Speed Elixirs on one character up to the count in the table and watch the same two breakpoints.

Still open: the freeze (a battle with one enemy already defeated and a later-seated party member in the lock-out band would show where the action state stops); and a played reproduction with real Elixirs. Related reference: item-effect table, battle action state machine, battle formulas.

Story equipment removal skips the re-check

Reported as
When the plot takes a piece of equipment, its bonus keeps working until something else re-checks equipment (community report)
Cause
The script's take-item instruction empties the equipment slot but never calls the routine that turns slots into bonuses
Where
Field script instruction 4C 52 at 0x801E1ABC (PROT 0897) and FUN_800430AC in SCUS_942.54
Affects
Nine story hand-overs of a wearable item, listed below, when the item is equipped at that moment
Status
Explained - the mechanism and the nine sites; how long the stale bonus lasts is only partly traced

An accessory's effect is not looked up when it is needed. One routine "bakes" it into each character: it reads the eight equipment slots and writes out a set of ability bits and a block of live stats. Battle, the random-encounter roll and the menus all read the baked copy, never the slots.

When the story takes an item, the script instruction first tries the bag. If the item is not there because it is being worn, it clears the equipment slot directly - and stops. It does not run the bake, so the baked copy still says the item is on.

The game does have a script instruction that writes an equipment slot and re-bakes; it is the one that grows the Ra-Seru. The take-item fallback simply does not use it.

Story takes the item op 4C 52 -> FUN_800430AC Rebuild FUN_80042558 never called Equipment slot record +0x19B..+0x19D Ability bit record +0xF4..+0x103 Live stat record +0x104..+0x11A set to 0 rebuilt Until the rebuild runs, battle and menus read the two right-hand boxes: the item still counts as worn.
The take-item instruction touches one byte. The two things the rest of the game actually reads are only refreshed by the rebuild routine, which this path never calls.
ItemEffect left behindSceneScript location (PROT / record / offset)
Yuma's Ring (0xAF)Max MP +10%Jeremi (geremi)166 / P2[16] / +0x0109
Earth Egg (0x6E)Earth GuardJeremi166 / P2[19] / +0x1056
Water Egg (0x6F)Water GuardJeremi166 / P2[19] / +0x1192
Light Egg (0x70)Light GuardJeremi166 / P2[19] / +0x12CE
Dark Stone (0x71)Dark GuardJeremi166 / P2[19] / +0x1409
Ra-Seru Egg (0xAB)Thunder GuardEast Voz Forest (vozz)104 / P2[17] / +0x01D7
Star Pearl (0xAE)INT +5%Shadow Gate (stone)175 / P1[4] / +0x018D
Nemesis Gem (0xB5)High Encounter (whole party)Jette's Fortress entrance (deroa)606 / P2[4] / +0x03D4
Fire Droplet (0xBC)Maximum APUsha Research Center (doman, variant script)401 / P1[15] / +0x1F82

The list is computed, not picked: every take-item instruction on the disc was decoded, and these nine are the ones whose item carries a wearable effect. All the others take key items or consumables and leave nothing behind.

How long it lasts

Until the next event that re-bakes. Entering any scene always does. So do using an item or magic from the menu, equipping or removing anything, Best Equipment, and buying-and-equipping in a shop. The field scene is reloaded after a battle, which would limit the stale bonus to a single battle fought before leaving the room; that part rests on the engine documentation and was not re-read. The in-field save screen does not re-bake. Whether simply closing the pause menu does is not traced.

The instructions
801e1ac4  lbu a0,1(s6)              ; item id
801e1ac8  jal 0x80042310            ; remove one from the bag
801e1adc  bne v0,v1,0x801e00b8      ; found in the bag: done
801e1ae8  jal 0x800430ac            ; not in the bag: unequip it
801e1af0  j 0x801e3628              ; exit - no jal 0x80042558

FUN_800430AC walks each party member's three accessory slots (record +0x19B..+0x19D), zeroes the first match with sb zero,0x196(v1) at 0x80043108 and returns. It has one caller on the disc.

The rebuild FUN_80042558 zeroes the ability words at 0x800425EC, walks the slots at 0x80042620..0x8004270C, applies the percentage boosts and clamps. Every reference to it on the disc is a call, and none is in the battle program. The instruction that does it properly is 0x3B at 0x801E07DC: jal 0x80042558 at 0x801E0810 with the slot store sb v1,0x75e(v0) in its delay slot.

AddressImageWhatEvidence
record +0x196..+0x19DRAMEquipment slots; the last three are the accessoriesdisassembly
record +0xF4..+0x103RAMAbility bits, one per effectdisassembly
record +0x104..+0x11ARAMLive stats (stored base at +0x11C..+0x12C)disassembly
0x80074358SCUSParty-wide ability maskdisassembly
0x80042558SCUSThe rebuilddisassembly
0x801E1ABCPROT 0897Take-item instruction 4C 52disassembly
0x80042310SCUSBag remove; returns 0x100 when absentdisassembly
0x800430ACSCUSUnequip by id, no rebuilddisassembly
0x801E07DCPROT 0897Instruction 0x3B: equipment-slot write with rebuilddisassembly
0x8003B2B8SCUSRebuild call in the scene-entry routine FUN_8003AEB0disassembly
0x80024378SCUSSave-screen driver reloading the field program with no scene initdisassembly
the nine sites abovePROT scene scriptsDecoded take-item instructions joined to the item tabledisc data
scene reload after battle-Bounds the stale bonus to one battlerepo doc

Verifying it

Equip Yuma's Ring on Vahn: 0x800847FC has bit 0x4 and the live max MP at 0x80084810 is 110% of the stored one at 0x80084826. Give the ring to Zalan. Straight afterwards the slot bytes at 0x800848A3..0x800848A5 hold no ring, but the bit and the live max MP are unchanged. A breakpoint on 0x80042558 that logs the caller shows which event finally clears it. Related reference: accessory passives, field script VM.

Enemy items aimed at an ally

Reported as
An enemy-targeted battle item can be aimed at one of your own characters. The Point Card then hangs the battle (the "Point Card softlock"); the Lippian Flute can kill an ally who stays on their feet ("Lippian Roulette"). Outcome matrix from a community report by ZetaPhoenix
Cause
The target cursor can step onto an ally. The Point Card's damage skips the HP-counter bookkeeping the end of the action waits on; Lippian's kill runs a fade that stands a party member back up at 0 HP
Where
Item window at 0x801D18F8 and the action state machine in PROT 0898; discharge arm 0x8004209C and fade arm 0x80050230 in SCUS_942.54; Lippian cast module FUN_801F6A18 in PROT 0924
Affects
The Point Card and the Lippian Flute, aimed at any party member. Not the Spikefish Flute, which hits every enemy and never opens a cursor
Status
Explained - read from the instructions, matched against the community matrix and captured on a poked state

Two items in the game are meant for one enemy and nobody else: the Point Card and the Lippian Flute. Both can be pointed at your own party, and each does something its designers never drew.

The cursor can reach an ally. When you pick one of these items, the game builds a ring of targets that starts from whoever this character aimed at last. It builds the ring first and only afterwards checks that the last target was an enemy. If the last thing this character did was aimed at an ally - any healing item or Elixir, whose cursor starts on the user - that ally is already in the ring. The cursor is moved to an enemy, but one press of Left steps back onto the ally.

The Point Card breaks the HP display. Its damage routine subtracts HP and does nothing else. Every other routine that changes a party member's HP also notes how far the on-screen number still has to count. At the end of an action the game holds, slowly turning the camera, until on-screen HP equals real HP. Here they differ and nothing is counting, so the hold never ends: the "Points returned" caption stays up, the HP bar keeps its old value and the camera circles for good.

Lippian's kill is written for a monster. It sets HP to 0, starts the target's knockdown and switches on the fade a defeated monster disappears with. On a party member that same fade is the one a revive uses: the body goes black, is put back in its standing pose and fades in again. HP stays at 0, so the character is dead in every way except how they look.

The Point Card hang is the same end state as the first of the two known battle softlocks - the HP-counter wait - reached from the controller on an unmodified disc. It is not the approach softlock described in the endless camera orbit.

What happens, case by case

Each cell says where it comes from: C a community report (ZetaPhoenix, in game), D the disassembly, P a capture on a poked state, described below.

ItemConditionLiving allyDead ally
Point Cardholds pointsBattle hangs: the camera orbits for ever. Lethal or not. (C, D, P)No hang; the points are still spent. (C, D, P)
Point Cardno pointsNothing; the turn is used. (C, D, P)Nothing. (C, D)
Lippian Flutekill roll failsNothing; the flute is used up. (C, D, P)Nothing. (C, D, P)
Lippian Flutekill roll landsHP goes to 0 but the ally stands back up. (C, D, P)The body stands up, still at 0 HP. (C, D, P)
  • The dead-ally column holds only when the item user is the first fighter to act that round. For anyone acting later the game re-rolls a dead target among the living party members, the user included, and the living-ally column applies to whoever it picks (D, P).
  • Point Card on a dead ally is not quite nothing. The whole bank, up to 9999 points, is spent, a damage number is shown and the body replays its collapse. Nothing hangs because real and on-screen HP are both 0 (D, P).
  • The Point Card itself is never used up. It leaves the bag when you confirm and is added back when the action starts. The Lippian Flute is consumed (D, P).
  • The kill roll is 1 in 128, not 1 in 126: a random number's low seven bits must all be zero. The number 126 does appear on the item, as the effect class of its descriptor, which is not a probability (D).
  • A standing character at 0 HP is dead to the rules. The command ring skips them, and when the last living member is "killed" this way the battle ends in the ordinary defeat message with the character upright (D, P).

Which items can do this

The Item window looks at two flags on the item: "enemy side" and "all targets". Only "enemy side, one target" opens the cursor whose ring is built too early.

ItemIdDescriptorTargetReaches an ally
Point Card0xFE01 00 C4 41one enemyyes
Lippian Flute0x987E 00 C4 41one enemyyes
Spikefish Flute0x997F 00 E4 41all enemiesno: no cursor is opened

Those are the only three consumables with the enemy-side flag. Two more descriptor rows carry the flute classes (7E 00 C6 41 and 7F 00 E6 41), and no consumable uses either. The game's own code names the same pair: the routine that re-aims an action whose target has died tests the item id against 0xFE and 0x98 and nothing else.

1. The target ring is built before the last target is checked ctx+0x245: head ally seat 0 (stale) ctx+0x246 monster seat 3 ctx+0x247 monster seat 4 ctx+0x248 monster seat 5 Left cursor starts here and lands here 2. The hit lowers real HP but not the number on screen real HP 188 (actor +0x14C) on-screen HP 600 (actor +0x172) left to count should be 412, is 0 (actor +0x10) The end-of-action check waits until real and on-screen HP match. Nothing is counting, so it never ends. (Example figures: a 412-point hit at 600 HP.)
Top: the ring keeps the stale ally in its first slot. Bottom: the bars are to scale for an example 412-point hit; the dashed bar is the countdown that should exist and does not.
  1. Aim at an allyA healing item or Elixir leaves an ally seat in the actor's target byte +0x1DD.
  2. Pick the Point Card or the Lippian FluteThe ring is built at 0x801D18F8; the enemy check comes after, at 0x801D1918.
  3. Press LeftThe cycler FUN_801D8D00 returns the ring head: the ally.
  4. Point Card: the hit landsArm 0x8004209C lowers HP and seeds no countdown, so state 0x51 asks FUN_801E7250 every frame and is always told "not settled".
  5. Lippian Flute: the rollOne chance in 128 at 0x801F6B14. On a miss the target is only tinted.
  6. Lippian Flute: the kill0x801F7684 zeroes HP, seeds the countdown and sets the fade; the fade arm at 0x80050348 puts a party member back in the standing pose.
The instructions
801d18f8  jal 0x801d8a88       ; build the target ring
801d1918  lbu v0,0x1dd(v0)     ; only now read the stored target
801d1924  sltiu v0,v0,5        ; a monster seat (3..7)?
801d1930  jal 0x801db8b4       ; if not: first living monster

The Magic window runs the same two steps in the right order (check at 0x801D1C8C, build at 0x801D1CC0). The builder stores the unchecked target as the ring head with sb v0,0x245(v1) at 0x801D8B30. The picker stores the cycler's answer at 0x801D2630 and its confirm path never re-checks it. At a round boundary a stored target is replaced only when it names a dead seat (0x801D8A00..0x801D8A54), so a living ally stays stored from turn to turn.

801e4084  addiu v0,zero,0xfe   ; item 0xFE?
801e40bc  addiu v0,zero,0xe
801e40c0  sb v0,0x1e8(s3)      ; applier selector = 0x0E

The discharge arm leaves at 0x800420AC when the bank at 0x800845B4 is empty. Otherwise it spends min(bank, 9999), queues a reaction clip and writes HP at 0x8004219C, clamped at 0, without ever looking at the target first. It writes neither the countdown at actor +0x10 nor the displayed HP at +0x172; the heal and revive arms of the same routine do, at 0x800408FC, 0x80040D28 and 0x800410BC. The settle check compares +0x14C with +0x172 at 0x801E72A8..0x801E72B4, and state 0x51 turns the orbit angle every frame at 0x801E6024 while it waits. On a dead target both values are 0, so nothing hangs. The card comes back to the bag at 0x801F3A58.

801f6b14  andi v0,v0,0x7f      ; kill only when the low seven bits are zero
801f7684  lbu v1,0x1f1(s3)     ; the victim's knockdown clip
801f7688  lhu t0,0x14c(s3)     ; its HP
801f7690  sb v0,0x225(s3)      ; = 2
801f7694  sb v0,0x21c(s3)      ; = 2, the defeat fade
801f76a0  sh zero,0x14c(s3)    ; HP = 0
801f76a4  sb v1,0x1da(s3)      ; stage the knockdown
801f76bc  sw v1,0x10(s3)       ; countdown += old HP

The cast module reads its victim from the caster's target byte on every tick (0x801F6A68) and never asks which side it is on. Because the countdown is seeded here, the HP number counts to 0 and the action ends normally. The fade is arm 2 of the per-actor presentation tick FUN_80050120: a non-zero +0x225 starts it at once, and when a party seat reaches black it stores +0x21C = 0, +0x1DA = 0 (the standing clip) and +0x1DC = 1 at 0x80050348..0x80050350. The revive arm of the item routine is what normally sets +0x21C = 2 on a party member (0x800410D0), after it has restored HP.

801db1e0  li v0,0xfe
801db1ec  beq v1,v0,0x801db1fc    ; Point Card
801db1f0  li v0,0x98
801db1f4  bne v1,v0,0x801db300    ; anything but the Lippian Flute: leave it
801db1fc  sltiu v0,a1,0x3         ; is the dead target a party seat?

That is the dead-target redirect FUN_801DB124. A dead party seat is replaced by a random living party seat (0x801DB210..0x801DB274). It is called from the turn picker only while the command-flow byte reads 0xFF (0x801DAF04), and the round's first fighter is picked earlier than that, from the menu half of the battle (0x801D0E8C, 0x801D0ED8). So the first fighter of a round is never re-aimed and every later one is.

AddressImageWhatEvidence
0x800845B4RAMPoint Card bankdisassembly
0x80074F50, 0x800754B8SCUSItem 0xFE record and its descriptordisc data
0x80074A88, 0x8007535CSCUSItem 0x98 record and its descriptordisc data
0x80074A94, 0x80075360SCUSItem 0x99 record and its descriptordisc data
0x801D18E0PROT 0898Item window, enemy single-target armdisassembly
0x801D8A88PROT 0898Ring builder; head store 0x801D8B30disassembly
0x801D8D00PROT 0898Ring cyclerdisassembly
ctx[+0x244..+0x248]RAMRing count, head, three more seatsdisassembly
0x801D23F0PROT 0898Target picker; store at 0x801D2630disassembly
0x801D19A0PROT 0898Ally-item arm seeding the user's own seatdisassembly
0x801DB124PROT 0898Dead-target redirect; item-id tests at 0x801DB1E0disassembly
0x801DAF04PROT 0898Turn picker's gate on the redirect for a party seatdisassembly
0x801F3A58PROT 0898Point Card handed back to the bagdisassembly
0x801E4080PROT 0898Action state 0x3E: selector override for item 0xFEdisassembly
0x801E4134PROT 0898Action state 0x3F: call into the applierdisassembly
0x8004209CSCUSPoint Card discharge arm, through 0x8004219Cdisassembly
0x801E7250PROT 0898HP-counter settle checkdisassembly
0x801E6044PROT 0898Action state 0x51 gate; orbit step at 0x801E6024disassembly
0x801F6B0CPROT 0924Lippian kill roll; flag at 0x801F7C4Cdisassembly
0x801F7684PROT 0924Lippian kill writes on the victim, through 0x801F76BCdisassembly
0x80050230SCUSFade arm; party-seat completion at 0x80050348disassembly
0x800410D0SCUSRevive arm setting +0x21C = 2disassembly
actor +0x10, +0x14C, +0x172, +0x1DDRAMCountdown, real HP, on-screen HP, target seatdisassembly
actor +0x1DA, +0x1F1, +0x21C, +0x225RAMStaged clip, knockdown clip id, presentation arm, fade-now bytedisassembly

What was captured

The probe scripts/pcsx-redux/autorun_enemy_item_ally_target.lua runs on a catalogued save state with three members on the first command screen of a battle. The cursor path is the real one: it presses Begin, opens the Item window, picks the item, presses Left and confirms. Written by hand once beforehand: the item into the first bag slot, the Point Card's points, the user's stored target (the stale ally a healing item leaves), and 0 HP plus a staged knockdown on the two members who are not the user, so that they are lying dead when the walk starts. For the flute rows the kill flag is forced, since the roll is 1 in 128.

RunLogged
The ring, every runRing head is the ally seat; Left moves the target byte from 3 to the ally
Point Card, 50 points, user on selfBank 50 to 0, HP 3437 to 3387, on-screen 3437, countdown 0; knockdown, get-up; state 0x51 to the end of the run
Point Card, 9999 points, user on selfHP 0, on-screen 3090; collapse, then standing again at state 0x51; the same hang
Point Card, 0 points, user on selfNothing written to the target; the round goes on to the monster's turn
Point Card, 50 points, dead ally, user firstBank 50 to 0; target HP and on-screen HP both 0; the body replays its collapse; no hang
Point Card, 50 points, dead ally, user not firstTarget re-rolled to the living user when the turn is picked, then the hang
Lippian, kill forced, user on selfHP 3090 to 0, countdown 3090 draining to 0; knockdown, collapse, standing; defeat follows
Lippian, kill forced, dead allyLying, knockdown, collapse, standing; HP 0 throughout; the round goes on
Lippian, roll forced to failNo HP, clip or countdown change on the target, living or dead
Card and flute countsThe card goes 3, 2 at confirm, 3 when the action starts; the flute goes 3, 2 and stays

In the hang the screenshots show the "Points returned" caption still up, the HP bar at its old value and the camera at a different angle each time. In the kill-on-self runs the user is the last living member, so the standing character is followed by the party-defeat message and the title screen.

One detail explains an older report that the character is "revived" before the freeze. A lethal Point Card hit on the user's own seat shows the character collapse and stand up again: the end-of-action cleanup tells the acting character to return to the standing pose when its current clip ends. The HP bar still shows the old value, and the battle still hangs.

Verifying it

Use a healing item on the acting character, or open and cancel its ally cursor. Next turn pick the Point Card or the Lippian Flute and press Left: a breakpoint at 0x801D2630 shows a seat of 0, 1 or 2.

  • Point Card: confirm, then watch the action state byte sit at 0x51 with its timer frozen at 0x3C, the target's +0x14C different from its +0x172 and its +0x10 zero. Writing +0x172 = +0x14C by hand releases the battle.
  • Lippian Flute: write a non-zero word to 0x801F7C4C once the cast's phase byte ctx[+0x279] has left 0. At 0x801F76A0 register s3 is the ally's actor. Afterwards its +0x14C is 0 and its playing clip +0x1D9 is 0.
  • Redirect: break on 0x801DB260. It is hit only when the user is not the round's first fighter.

Related reference: battle action state machine, item-effect table.

Open: whether a revive item works normally on a member left standing at 0 HP, and whether monsters still pick that seat; how a dead ally comes to be the stored target at command time (the round-boundary pass replaces a dead one); other builds were not read for this entry.

Coin vendor purchases past 99 (Japanese release)

Reported as
On the Japanese release a coin vendor sells an item while 99 are held; the coins go and the count stays 99 (community report)
Cause
The Japanese prize counter only asks whether the coins cover the price; the bag then adds the item and silently trims the stack back to 99
Where
Japanese prize session 0x801DE978 in PROT 0899 and bag add 0x80043210 in SCPS_100.59; USA counterparts FUN_801DC1CC and FUN_800421D4
Affects
Every coin prize row on the Japanese release; equipment bought "to the bag" in a gold shop on every release
Status
Explained - Japanese, USA and French code read; not run

At a coin prize counter the Japanese game asks one question before the Yes / No box: can the coins cover the price? It never looks at how many of the item you already hold. After Yes it adds the item and takes the coins with no further test.

The bag's add routine never refuses anything: it adds, then quietly trims the stack back to 99. So a hundredth copy costs the coins, is added, and is trimmed off again - exactly the report.

The USA build added two things in front of that. The prize list draws a row dimmed when you hold 99, and the confirm step asks "fewer than 99 held?" and buzzes if not. The French build carries both. The purchase itself and the trimming add are the same in every build.

USA and French builds (read from the code) Row dim at 99 held 0x801D5ED4 Confirm: fewer than 99 held? 0x801DC3DC..0x801DC3F0 No: buzz, nothing bought coins and stack unchanged Japanese build (read from the code) Confirm, holding 99 one test: 0x801DEB6C no held-count check Add 1, trim to 99 0x80043210 Coins - price 0x801DECF0 The trim is in every build. It only costs the player anything when nothing in front of it asks how many are held.
Both rows are read from the code of the build they name. The USA build put two checks in front of a purchase path that is otherwise the Japanese one.

Because only the coin counter was fixed, every release still has a relative of this fault in the ordinary gold shop. Stackable items go through a quantity screen capped at 99 - held, but equipment does not: the buy confirm tests only gold, and the "to the bag" row adds the item and debits gold with no held-count read between them. Buying a hundredth copy of one piece of equipment takes the gold and leaves 99. That is read from the instructions of the USA and Japanese builds (the French build is the USA code relocated) and has not been run.

The instructions

Japanese build - PROT 0899 at load base 0x801D05C8, executable SCPS_100.59. The session's call list is the USA one with a single call missing, the held-count lookup.

801deb6c  slt v0,v0,v1            ; confirm: coins < price?
801deb70  beqz v0,0x801deb88      ; can afford: open the Yes / No box
801deb78  jal 0x8002cabc          ; otherwise buzz

801deca8  jal 0x80043210          ; commit: bag add (id, 1), result ignored

80043334  slti v0,v0,0x64         ; bag add: over 99?
80043340  addiu v1,zero,0x63      ; then 99
8004334c  sb v1,-0x4037(at)

After the add the commit subtracts the price from the coin bank at 0x801DECD8..0x801DECF0. The Japanese prize-list row draw (0x801D8014..0x801D80A8) dims a row only when it is unaffordable. The whole Japanese PROT 0899 contains no signed compare against 0x63.

USA build - PROT 0899 at 0x801CE818:

801dc3dc  jal 0x80042f4c          ; held count
801dc3ec  slti v0,v0,0x63
801dc3f0  bne v0,zero,0x801dc408  ; fewer than 99: open Yes / No
801dc3f8  jal 0x80035bd0          ; otherwise buzz and stay

801dc514  jal 0x800421d4          ; commit: bag add, result ignored
801dc550  sw v1,0x464(a0)         ; coins -= price

800422f0  slti v0,v0,0x64         ; bag add: over 99?
800422fc  li v1,0x63              ; then 99
80042300  sb v1,0x1819(a0)

The USA row draw makes the same held-count lookup at 0x801D5ED4..0x801D5EF4 and picks the dim text colour at 99; those two sites are the only compares against 0x63 in the image. The French build has both, at 0x801DCEF8..0x801DCF10 and 0x801D69E4 (load base 0x801CF0A8).

AddressImageWhatEvidence
0x801DE978Japanese PROT 0899 (file 0xE3B0)Prize-exchange session, through 0x801DEE64disassembly
0x801DEB1CJapanese PROT 0899 (file 0xE554)Confirm: coins against price only, then the confirm windowdisassembly
0x801DEC5CJapanese PROT 0899 (file 0xE694)Commit: jal 0x80043210 at 0x801DECA8, coins minus pricedisassembly
0x801D8014Japanese PROT 0899 (file 0x7A4C)Prize-list row draw, no held-count calldisassembly
0x801E7A3CJapanese PROT 0899 (file 0x17474)Prize table, same record shape as USAdisc data
0x80043210SCPS_100.59 (file 0x33A10)Bag add; trim at 0x80043334..0x8004334Cdisassembly
0x80043088SCPS_100.59Held count; not called from the prize sessiondisassembly
0x801CAC14, 0x801CBFC8Japanese RAMCoin bank; bag, two bytes per slotdisassembly
0x801DC1CCPROT 0899USA prize-exchange sessiondisassembly
0x801E4518PROT 0899USA prize table: 8-byte rows of item, one-shot flag, coin price; 0x60 per vendordisc data
0x800845A4RAMUSA coin bankdisassembly
0x801DC3C0PROT 0899USA affordability and held-count checks, through 0x801DC3F8disassembly
0x801D5ED4PROT 0899USA prize-list row draw: dim text colour when 99 are helddisassembly
0x801DC4C0PROT 0899USA commit: add, debit, set the row's one-shot flagdisassembly
0x80042F4CSCUSHeld count of an itemdisassembly
0x800421D4SCUSBag add; trim at 0x800422E0..0x80042300disassembly
0x801DCEF8, 0x801D69E4French PROT 0899Front check and row dim, as on USAdisassembly
0x801DB21C, 0x801DB380PROT 0899USA gold-shop equipment buy with no held-count check ("to the bag" row 0x801DB478..0x801DB4C8)disassembly
0x801DDB14Japanese PROT 0899The same "to the bag" row, through 0x801DDB6Cdisassembly
the Japanese result on screen-Coins taken, count stays 99community report

Verifying it

On a Japanese disc hold 99 of a prize item and buy another: a breakpoint at 0x801DEB70 shows the branch to the confirm window taken, and after Yes the coin word at 0x801CAC14 drops by the price while the item's count byte stays 0x63. On the USA disc the same attempt buzzes and no box opens; forcing the branch at 0x801DC3F0 to be taken reproduces the Japanese behaviour. Still unrun on any release: holding 99 of one weapon and buying one more to the bag. Related reference: shops and the prize exchange, inventory.

War God Icon can be missed for good

Reported as
The War God Icon is lost for good if the Muscle Dome Expert Course is beaten before defeating Jette (community report)
Cause
The item and its "already awarded" flag are handed out by two different pieces of code that disagree about what clearing the course means
Where
Arena settlement FUN_801D0F60 in PROT 0977; lobby victory scene, record P2[9] of scene koin1 (PROT 543)
Affects
The Master course - not Expert as reported - won while it is still shortened
Status
Explained - from the instructions and the scene script; not run
Correction to the report
The dome's three courses are Beginner, Expert and Master. The Icon belongs to Master. Nothing in the script or the arena program ties it to the Expert course.

The prize is split in two. The item is added by the arena program, and only when the contest ended on its thirteenth fight and an "already awarded" flag is clear. The flag is set by the lobby's victory scene, which only asks: was this a Master contest, and was it won?

Early in the story the Master course is cut short to 8, 11 or 12 fights. Winning the shortened course is still a win as far as the victory scene is concerned, so it sets the flag and plays the presentation - the announcer names the Icon. But the arena saw fewer than thirteen fights and added nothing. From then on the flag is set, and the arena skips the item every time, including after a full thirteen-fight win.

The last of the three story flags that lengthen the course is set inside Jette's Fortress. That is where "before defeating Jette" comes from.

Master course, fights 1 to 13 1 2 3 4 5 6 7 8 9 10 11 12 13 item 0x378 clear: ends at 8 0x382 clear: ends at 11 0x471 clear: ends at 12 all three set: 13 fights Arena program: gives the item only on fight 13, and only while flag 0x6CB is clear. Lobby scene: sets flag 0x6CB after any won Master contest, whatever its length. A win on a shortened course sets the flag with no item. After that the 13-fight win is refused too.
Each bar is a Master course as long as it runs while the named story flag is clear. Only the full bar reaches the fight the arena program pays out on, but the lobby sets the "awarded" flag after a win on any of them.
Story flagCourse length while it is clearSet by
0x3788 fightsmap03 P2[15] and scripts in nine other scenes
0x38211 fightsScripts in eleven scenes
0x47112 fightschitei2 P1[0] only (Jette's Fortress), after a staged fight that precedes the Jette fight

The tests overwrite one another in order, so the last missing flag decides: with all three missing the course runs 12 fights.

The instructions and script bytes
801ced44  jal 0x8003ce64   ; flag 0x378 missing: length 8
801ced6c  jal 0x8003ce64   ; flag 0x382 missing: length 11
801ced94  jal 0x8003ce64   ; flag 0x471 missing: length 12

801d111c  slti a1,a1,0xd          ; round reached below 13?
801d1124  bnez a1,0x801d1148      ; yes: no item
801d112c  jal 0x8003ce64          ; flag 0x6CB
801d1134  bnez v0,0x801d114c      ; set: no item
801d1140  jal 0x800421d4          ; War God Icon (item 0xCD) x1

The arena never sets 0x6CB. A win sets flag 0x50A at 0x801D101C, and the lobby's winner scene is gated on that flag. In that scene, at record +0x0369, 76 CB tests 0x6CB and 75 38 tests the Master-chosen flag 0x538; the target at +0x03E1 begins with 56 CB, set 0x6CB. The item appears there as a name in the text only - the record contains no give-item instruction - and neither test looks at the number of fights. No instruction on the disc clears 0x6CB.

AddressImageWhatEvidence
koin1 P1[9] +0x0B6FPROT 543Master row of the course menu; sets flag 0x538disc data
0x801CED28PROT 0977Course-length overrides, through 0x801CEDA4disassembly
0x801CEE10PROT 0977Win latch store (0x801D1ADC)disassembly
0x801D1A90, 0x801D1A94PROT 0977Course index, round reacheddisassembly
0x801D0F60PROT 0977Settlement; item test 0x801D111C..0x801D1144disassembly
koin1 P1[0] +0x0181PROT 543Lobby dispatch of the result scenesdisc data
koin1 P2[9] +0x0369PROT 543The two flag testsdisc data
koin1 P2[9] +0x03E1PROT 543Set flag 0x6CBdisc data
0x80085831 bit 0x10RAMFlag 0x6CB: Icon presenteddisc data
0x800857FF bit 0x80RAMFlag 0x538: Master chosendisc data
0x800857F9 bit 0x20RAMFlag 0x50A: contest wondisassembly
0x800857C7 bit 0x80RAMFlag 0x378disassembly
0x800857C8 bit 0x20RAMFlag 0x382disassembly
0x800857E6 bit 0x40RAMFlag 0x471disassembly

Verifying it

From a save with flag 0x471 clear, win the Master course. It ends after fight 12; a breakpoint at 0x801D1124 shows the branch taken and the item add skipped; after the lobby scene, byte 0x80085831 has bit 0x10 set. The counter-test: clear that bit, set 0x471, win thirteen fights, and the item arrives. Clearing bit 0x10 at 0x80085831 should repair an affected save.

Open: whether the party can leave Jette's Fortress between the last flag being set and the Jette fight was not checked.