Status rolls, jewels and boss immunities
Six battle oddities with one thing in common: a rule that looks general to the player is a special case in the code. A status that rolls every frame, seven boss attacks that walk past the jewels, debuffs the USA disc can never land on a boss, a boss family that forgot to be immune to instant death, a summon that leaves one boss's body in pieces, and a "boss flag" that protects nothing outside a scripted fight.
SCUS_942.54). Two sections go further: the Tough Love counts are also measured in a running game, on a poked state that the section describes, and the boss-debuff section also reads the Japanese (SCPS_100.59) and French (SCES_019.44) builds. The Zenoir shatter section is captured in a running game on forced formations with an injected cast. The jewel, Berserker and Evil Fly sections are static analysis only. Where a number is predicted from the code rather than proven by it, the text says so. Addresses are given so anyone with a debugger can check them: the main executable loads at 0x80010000, the battle overlay (PROT entry 0898) at 0x801CE818, and the cast and summon modules (PROT entries 0903 to 0966) at 0x801F69D8. The reports and questions come from the community.
Tough Love and Love You roll their status every frame
- Reported as
- Tough Love and Love You attempt to apply the status they cause seven times during the animation, making it nearly guaranteed (community report)
- Cause
- A hand-written routine flips a 50% coin on every battle frame from the first hit to the end of the animation: 20 flips for Tough Love and 80 for Love You at the battle's normal pace, fewer when frames drop, never under 10.
- Where
- Per-frame battle actor pass
FUN_8004CE2CinSCUS_942.54, arm0x8004D0A4..0x8004D120 - Affects
- Noa only, these two arts only; random encounters only
- Status
- Explained - counted in a running game, on a poked state (see How it was measured)
Noa's Tough Love and her Super Art Love You do not roll for their status when they hit. The normal status on hit slot of an art is empty on both. Their effect is written by hand into a routine that runs once per battle frame.
From the frame in which the first blow lands until the animation runs out, 20 keyframes later, that routine flips a coin every battle frame and puts the confusion status on Noa's target whenever it comes up heads. Nothing records that the coin already came up heads and nothing checks a resistance.
In ordinary play that is 20 flips for Tough Love and 80 for Love You. The count is not quite a constant, because the flip is made once per battle frame while the animation advances by a fixed amount of time per battle frame. A battle normally shows one battle frame every two video frames (60ths of a second). When the screen is busy the game drops to one in three, or at worst one in four; the same animation is then covered in fewer battle frames, so fewer flips fit. The table gives the normal row first.
How many times it rolls, and the final chance
| Art | Battle pace | Coin flips | Flips that decide the outcome | Chance the status is on at the end | A miss is |
|---|---|---|---|---|---|
| Tough Love | normal: one battle frame per 2 video frames | 20 | 20 | 99.9999% | 1 in 1,048,576 |
| Tough Love | busy screen: one per 3 | 14 | 14 | 99.994% | 1 in 16,384 |
| Tough Love | slowest the game allows: one per 4 | 10 | 10 | 99.90% | 1 in 1,024 |
| Love You | normal: one battle frame per 2 video frames (computed, not captured) | 80 | 68 | all but certain | far below 1 in a trillion |
| Love You | busy screen: one per 3 | 54 | 46 | all but certain | 1 in 70 trillion |
| Love You | slowest the game allows: one per 4 | 40 | 34 | all but certain | 1 in 17 billion |
| the reported figure | - | 7 | - | 99.22% | 1 in 128 |
Each flip is exactly one chance in two, so after N flips the chance is 1 - 0.5^N. The two flip columns differ only for Love You, and for one reason: every hit wipes the status again, so the flips made before its fourth and last hit cannot decide anything. Tough Love has a single hit and the flips start on it, so all of them count.
+0x78): 4 on Tough Love, 2 on Love You. It is fixed on the disc and nothing in play changes it; it is why Love You makes four times as many flips. The frame step in the table above is a different thing and belongs to no art: it is the game's own frame-skip counter (0x1F800393), rebuilt every frame from how long the last sixteen frames took to draw. It is 2 in a normal battle and 3 while the screen is busy, the same for every action on screen. No mod or exploit is needed to change it - lag does.
So the report is right in kind - a repeated roll, close to unmissable - but the roll follows frames, not hits, and the count is never seven. 10 is the fewest either art can make. Another figure that circulates in the community, about 13, is the Tough Love count in a battle that is dropping to one frame in three.
The status makes the monster aim its own action at its own side. It has no timer: it ends when the monster next takes damage. That is why it only sticks when the art is the last hit the monster takes - a later swing in the same chain removes it. The whole effect is switched off in scripted fights, which covers the bosses.
Step by step
- The art records carry no status: the selector byte at entry +0x7A is 0 on Tough Love (clip key 0x29) and Love You (clip key 0x2D). Byte +0x76 is 1 on both, so a queued follow-up never cuts the animation short.
PROT 0864 art bank, records 19 and 33 - Once per battle frame the actor maintenance pass runs. It is the last call of a resident per-frame tick.
jal at 0x8004DC4C -> FUN_8004CE2C - If the acting character is Noa and her current clip key is 0x29 or 0x2D, the pass continues.
0x8004D0A4..0x8004D0C4 - It requires that a strike has already fired (strike counter non-zero) and that the fight is not scripted.
0x8004D0CC, 0x8004D0DC - It draws one random number and continues only when the low bit is 0: one chance in two. There is no latch.
0x8004D0EC..0x8004D0F8 - Unless the first monster is id 167, it ORs 0x380 into the target's status word.
0x8004D100..0x8004D120 - Next frame the same block runs again. The strike counter is zeroed only when the next animation is installed, which for these arts is the end of the 44-keyframe clip.
0x8004B064, 0x80047A48..0x80047A60 - Any damaging hit on the monster clears the status group again - Love You's own later hits included.
0x801EDA5C, 0x801DE2EC
The instructions and the count formula
8004d0a4 lw v0,0x22c(s0) ; animation node
8004d0ac lw v0,0x4c(v0) ; the clip record being played
8004d0b4 lbu v1,0x77(v0) ; clip key
8004d0b8 li v0,0x29 / beq ; Tough Love
8004d0c0 li v0,0x2d / bne out ; Love You
8004d0cc lbu v0,0x1f4(s0) / beq v0,zero,out ; a strike has fired
8004d0dc lbu v0,0x287(a2) / bne v0,zero,out ; not a scripted fight
8004d0ec jal 0x80056798 ; rand()
8004d0f4 andi v0,v0,0x1 / bne v0,zero,out ; one chance in two
8004d100 lbu v1,0x9f4(gp) ; li v0,0xa7 ; beq out
8004d110 lhu v0,0x16e(s1)
8004d118 ori v0,v0,0x380
8004d120 sh v0,0x16e(s1)
s0 is the acting actor (from ctx[+0x13]), s1 its target (from the actor's +0x1DD), a2 the battle context.
The animation cursor counts sixteenths of a keyframe and advances by (frame step × speed × rate) >> 1 per battle frame (0x80047730..0x80047764). The art commit sets speed to 2 when ctx[+0x243] is set and 4 otherwise (0x8004BB64..0x8004BBA4): Tough Love runs at speed 4 with rate 4, Love You, staged as a Super Art, at speed 2 with rate 2. That is 8 × step per frame for Tough Love and 2 × step for Love You.
The window opens in the frame the cursor reaches 384 (keyframe 24, the first strike) and closes in the frame it reaches 704 (keyframe 44, the end of the clip). The animation driver runs before the actor pass, so the strike frame itself rolls and the closing frame does not. The count is the number of frames with the cursor in [384, 704): 320 / (8 × step) and 320 / (2 × step), rounded up. Love You's last hit is at cursor 432, so its deciding flips are 272 / (2 × step). At step 3 Tough Love gives 13 instead of 14 when the cursor does not land on 384 exactly.
The frame step lives at 0x1F800393. The frame driver FUN_80016B6C keeps the draw time of the last sixteen frames, in scanlines, in a ring at 0x80084098 and takes the slowest: under 0xF1 is step 1, under 0x1FF step 2, under 0x2D1 step 3, and 4 only when the newest frame alone ran past that (0x80017098..0x8001715C). A per-mode floor is applied afterwards; the battle's floor is 1. The playback rate is a separate byte, entry +0x78 of the art record.
How it was measured
The counts come from a running game (PCSX-Redux, retail disc) with breakpoints on the pass entry, on 0x8004D0F4 where the roll is read, on the status write, and on the strike counter. They are captured on a poked state, which is weaker than played input: in a random encounter where Noa is on the first swing of an auto-executing chain, the unconsumed tail of her action queue (actor +0x1DF) is rewritten so that the game's own queue consumer stages Tough Love, or Love You behind the Super Art starter. The art commit, animation, strikes, damage, roll and status write are then the game's own code. Where the table says forced, the frame-step byte was also overwritten.
| Art | Frame step | Flips | Flips at or after the last hit | Heads in that run (luck) |
|---|---|---|---|---|
| Tough Love | 4 (the emulator's own pacing) | 10 | 10 | 7 |
| Tough Love | 3 (forced) | 14 | 14 | 6 |
| Tough Love | 2 (forced) | 20 | 20 | 8 |
| Tough Love, two swings queued after it | 4 | 10 | 10 | 3 |
| Love You | 4 (the emulator's own pacing) | 40 | 34 | 26 |
| Love You | 3 (forced) | 54 | 46 | 28 |
The last column is not a property of the art. It is how many of that one run's flips happened to come up heads, each of which writes the same status bits again; it is there to show the write really happens more than once, and another run of the same art gives a different number. The first run happened to write the status seven times out of its ten flips. That is chance - the other Tough Love runs wrote it 6, 8 and 3 times - and not a count the code makes. In the run with two swings queued behind Tough Love the animation still played to its end, the status was on when it finished, and Noa's next swing cleared it. In the Love You runs each later hit took the status word from 0x380 back to 0 and a following flip set it again.
Which row a player gets depends on the frame step. Battle save states from a timing-accurate emulator show what a console holds: of 76 states that are in a battle, 57 hold step 2, 18 hold step 3 (summon close-ups, cast modules and other effect-heavy moments), one loading frame holds 1 and none holds 4. Step 2 is therefore the normal case. None of the 76 is taken inside either of these two animations, so which of the two steps Love You's own effects produce is inferred. Step 4 is what the capture emulator runs at on its own, a property of its timing and not of the game, which is why the measured table starts at 10 and 40.
What else it affects
Never fires in a scripted fight or when the first monster is id 167 (Lapis). The status lasts only until the target's next damage, from anyone. No other player art inflicts any status: across the four party art banks the selector byte is only 0, 1 (Vahn's fire arts) or 2 (Gala's Thunder Punch), and 1 / 2 only tint the target. A monster's physical attack, by contrast, rolls its status once per strike.
Addresses
| Address | Image | What | Evidence |
|---|---|---|---|
0x8004CE2C | SCUS_942.54 | Per-frame battle actor pass; entered once per battle frame | disassembly, capture |
0x8004D0A4..0x8004D120 | SCUS_942.54 (file 0x3D8A4) | Noa's clip-key arm: gates, roll and status write | disassembly, capture |
0x8004D0EC | SCUS_942.54 | The rand() call of the 1-in-2 roll | disassembly |
0x8004D0F4 | SCUS_942.54 | Where the roll is read; one execution per flip | disassembly, capture |
0x8004DC4C | SCUS_942.54 | Call to the pass, the last call of the per-frame tick FUN_8004DA00 | disassembly |
entry +0x77 | PROT 0864, art bank at decoded +0x5A30, records 19 and 33 | Clip keys 0x29 (Tough Love) and 0x2D (Love You) | disc data |
entry +0x7A | PROT 0864, same records | Status selector, 0 on both arts | disc data |
entry +0x76 | PROT 0864, same records | 1 on both: no early cut after the last strike | disc data |
entry +0x78 | PROT 0864, same records | Playback rate: 4 (Tough Love), 2 (Love You). Fixed per art; not the frame step | disc data |
actor +0x1F4 | RAM | Per-clip strike counter; raised at 0x801EECE8, zeroed at 0x8004B064 | disassembly, capture |
actor +0x16E bits 0x0380 | RAM | Confusion (AI-delegation) group the arm sets | disassembly |
ctx +0x287 | RAM | Scripted-fight flag; non-zero disables the arm | disassembly |
0x8007BD0C | RAM | First formation monster id, compared with 0xA7 | disassembly |
0x801EDA5C | PROT 0898 | A damaging physical hit clears status mask 0x0F80 on the defender | disassembly, capture |
0x801DE2EC | PROT 0898 | The same clear in the magic damage finisher | disassembly |
0x80047730..0x80047764 | SCUS_942.54 | Animation cursor advance: frame step x speed x rate / 2 | disassembly, capture |
0x80047A48..0x80047A60 | SCUS_942.54 | Clip-end test against the stream length | disassembly, capture |
0x80047940..0x80047948 | SCUS_942.54 | Early-cut veto on entry +0x76 | disassembly, capture |
0x8004BB64..0x8004BBA4 | SCUS_942.54 | Art commit: speed 2 or 4 from ctx +0x243 | disassembly, capture |
0x1F800393 | RAM (scratchpad) | Frame step, 1 to 4 | disassembly |
0x80017098..0x8001715C | SCUS_942.54 | Pacer that sets the frame step; frame-cost ring at 0x80084098 | disassembly |
Every capture on this list is from the poked state described above.
Verifying it
Count executions of 0x8004D0F4 while Noa uses either art in a random encounter: that is the number of flips, and v0 & 1 == 0 marks the ones that apply. Read 0x1F800393 in the same frames to see which row the run belongs to. A write watch on the target's +0x16E shows each Love You hit clearing 0x380 and a later flip restoring it.
Open questions
- The frame step held while these two animations play, measured on a timing-accurate emulator or a console. Battle states in general hold 2, and 3 on a busy screen; no state inside either animation is on hand. Every possible value gives 10 or more.
- A played confirmation, with the art entered from the arts menu instead of a rewritten queue.
- Whether a strike that misses still opens the window.
- Which piece of code raises
ctx +0x243for a Super Art; the resulting speed is measured, its setter is not named.
Background: the battle hub page (per-frame actor pass) and art records.
Seven boss attacks ignore the defensive jewels
- Reported as
- Bloody Horns, Terio Punch, Megaton Press, Plasma Strike, Blazing Slash and Guilty Cross ignore the damage reduction of the defensive jewels (community report)
- Cause
- These attacks use a physical (ATK-based) damage wrapper that tells the shared finisher to skip its jewel block; it reads as intended.
- Where
- Damage finisher
FUN_801DDB30and wrapperFUN_801DD6B4, PROT 0898; six cast modules - Affects
- Seven attacks: the six reported plus Bull Charge
- Status
- Explained
The elemental jewels do not change the damage roll. Their halving is one block at the top of the routine that finishes every magic and special-attack hit, and that block is skipped when the routine's fifth argument is non-zero.
Boss cinematic attacks reach that routine through one of two small wrappers. One passes 0, so jewels apply. The other passes 1, and it builds its damage from the attacker's ATK against the target's upper and lower defence instead of from INT. The reported attacks are exactly the ones whose code calls the second wrapper - plus one more, Xain's Bull Charge, which shares its code with Terio Punch.
This reads as intended design rather than a slip: the second wrapper is a physical-damage formula, and an elemental guard has nothing to say about a physical hit. What the game never does is tell the player which boss attacks are in that class. The Rainbow Jewel's all-element guard is skipped too; defending still halves these attacks.
Step by step
- The finisher reads its fifth argument from the stack.
0x801DDB74 - For a party defender, a non-zero fifth argument branches over the whole jewel block.
0x801DDBD4 - The skipped block tests All Guard (damage x3/4) and then each element's guard against the attacker's element (halve).
0x801DDC0C, 0x801DDCA8, 0x801DDD3C ... - The guard halve sits after the skipped block and still applies.
0x801DE094 - Of the three callers of the finisher, only one stores 1 as the fifth argument.
0x801DD824 in FUN_801DD6B4 - That wrapper adds the attacker's ATK and never reads INT.
0x801DD738 - The call to that wrapper appears at thirteen places, in six cast modules and nowhere else.
word 0x0C0775AD
The instructions
801ddb74 lw a3,0x40(sp) ; fifth argument
801ddbd4 bne a3,zero,0x801de094 ; set: skip everything below
801ddc0c lw v0,0x6c0(v0) ; andi 0x10 ; All Guard: damage x 3/4
801ddca8 lw v0,0x6bc(v0) ; and 0x20000000 ; Earth Guard vs an earth attacker: halve
801ddd3c lw v0,0x6bc(v0) ; and 0x40000000 ; Water Guard vs a water attacker: halve
801de094 lbu v1,0x1de(s1) ; li v0,4 ; guard halve, after the skipped block
801dd824 li v0,0x1 ; FUN_801DD6B4
801dd828 jal 0x801ddb30
801dd82c sw v0,0x10(sp) ; ... passes 1
The two words tested are the character's accessory-passive bitfield; passive 0x1D + element is that element's guard and 0x24 is All Guard. The other two callers (0x801DD478, 0x801DD678) store zero in the same delay slot.
Every call to the bypass wrapper on the disc
| PROT | Call sites | Power | Spell id | Attack |
|---|---|---|---|---|
| 0944 | 0x801F6AD8 | 0x38E | 0x37 | Guilty Cross |
| 0952 | 0x801F7948 | 0x1D0 | 0x5C | Bloody Horns |
| 0953 | 0x801F7410 | 0x274 | 0x5D, 0x5E | Terio Punch, Bull Charge |
| 0958 | 0x801F74EC, 0x801F7854, 0x801F7CA4, 0x801F80E4, 0x801F8754, 0x801F88D8 | 0x30, 0x38 x3, 0x40, one computed | 0x79 | Blazing Slash |
| 0959 | 0x801F71E8, 0x801F7A90, 0x801F7EBC | 0x80, 0x80, 0x30 | 0x7A | Megaton Press |
| 0960 | 0x801F8168 | 0x1C0 | 0x7B | Plasma Strike |
What else it affects
The complete set is seven attacks: Guilty Cross, Bloody Horns, Terio Punch, Bull Charge, Blazing Slash, Megaton Press and Plasma Strike. No other code on the disc sets the bypass argument. Users are Cort, Xain, the three Delilas siblings and Gaza. Because the wrapper reads ATK, the boss's INT does not affect these attacks either.
Addresses
| Address | Image | What | Evidence |
|---|---|---|---|
0x801DDB30 | PROT 0898 (file 0xF318) | Damage finisher | disassembly |
0x801DDBD4 | PROT 0898 | Branch that skips the jewel block when argument five is set | disassembly |
0x801DDC0C | PROT 0898 | All Guard test (passive word +0x6C0, bit 0x10) | disassembly |
0x801DDCA8, 0x801DDD3C | PROT 0898 | Earth Guard and Water Guard tests (passive word +0x6BC) | disassembly |
0x801DE094 | PROT 0898 | Guard halve, still applied | disassembly |
0x801DD6B4 | PROT 0898 (file 0xEE9C) | Bypass wrapper; li v0,1 at 0x801DD824, stored at 0x801DD82C | disassembly |
0x801DD738 | PROT 0898 | The wrapper reads attacker ATK (+0x158) | disassembly |
0x801DD4B0, 0x801DD0AC | PROT 0898 | The two finisher callers that pass zero | disassembly |
0x801F7EE8 | PROT 0944 | Spell-id test: 0x37 goes to the bypass routine 0x801F6A04 | disassembly |
0x801F7B54 | PROT 0952 | Spell-id test: 0x5C goes to the bypass routine 0x801F7118 | disassembly |
0x801F69FC | PROT 0953 | Single routine for both 0x5D and 0x5E, no id test | disassembly |
0x801F8664 | PROT 0960 | Spell-id test: 0x7B goes to the bypass routine 0x801F74E4 | disassembly |
0x800754C8 + id*12 | SCUS_942.54 | Spell record: class 0x63 at +0, module index at +1 (PROT = 935 + index) | disc data |
record +0x1D | PROT 0867 monster record | Attacker element the jewel block compares | disassembly |
Verifying it
Break on 0x801DDBD4 as an attack lands and read a3: 1 for the seven attacks, 0 for any other enemy cast (Neo Star Slash, from the same module as Plasma Strike, is a good control).
Astral Slash is a melee hit
Astral Slash shares its module with Bloody Horns, and its routine calls none of the three wrappers. It stages clip 0x0A on Gaza, and the ordinary melee kernel rolls that clip's four beats, so a matching jewel halves it through the kernel's own copy of the guard block, keyed on Gaza's light element. The same block covers every plain monster swing: an elemental monster's ordinary attack is halved by the matching jewel too.
Background: battle formulas, accessory passives and the spell table.
USA bosses cannot have ATK, INT or DEF lowered
- Reported as
- In the US version, bosses' ATK, INT and DEF cannot be reduced with spell effects, due to the way it is programmed (community report)
- Cause
- The debuff only lands if the boss's base stat still equals its disc record, and the USA difficulty boost changes the base before the fight starts.
- Where
- Debuff stager
FUN_801F3D3C(PROT 0898) against the loader boost inFUN_80054CB0(SCUS_942.54) - Affects
- Every scripted fight on the USA disc; ATK, DEF and INT only. Not the Japanese or French discs, which have no boost
- Status
- Explained
Levelled Seru magic carries a side effect by element: fire lowers ATK, earth lowers DEF, thunder lowers INT. There is no boss-immunity flag for these. Two unrelated pieces of code collide.
The first is a rule for scripted fights: lower a stat only if it is still at its original value. The code checks that by comparing the monster's live base stat with the number in its disc record. Once a debuff lands the base has moved, so each debuff works once per boss fight.
The second is the USA difficulty boost. When a scripted fight loads, the USA executable raises ATK, defence and INT above the record values - and writes the raised numbers into the base as well. From the first frame the base no longer equals the record, so the debuff code decides the stat was already lowered and shows No effect.
Speed and MP are not boosted, which is why those two side effects still work on bosses.
The boost is in the USA executable only. The Japanese (SCPS_100.59) and French (SCES_019.44) loaders go from the stat copy straight to the next piece of work, and both carry the same base-versus-record check in the debuff code. On those discs a boss starts with base equal to record, so the first attack-down, defence-down or intelligence-down lands, and a second one of the same kind is refused. The code is read in all three builds; the on-screen result on the other two discs has not been watched in a running game.
Step by step
- The stager runs once per cast, returns for a spell below level 3, and in scripted fights applies a four-in-five suppression roll.
FUN_801F3D3C, 0x801F3DE0, 0x801F3DEC - It jumps on the summoned creature's element.
table at 0x801CFA2C - In a scripted fight the element's arm loads the target's live base stat and the same stat from the monster record.
e.g. 0x801F3F38 / 0x801F3F3C - If the two differ, it returns without staging anything.
0x801F43F0 - At battle load the monster loader copies each record stat into both the current and the base halfword.
0x8005516C..0x8005520C - For a scripted fight the USA loader then raises ATK by a quarter, doubles both defences and raises INT by an eighth, writing both halfwords.
0x80055234..0x800552A8 - The Japanese and French loaders have no such block: the stat copy is followed directly by the spell-list loop.
0x80057014 (SCPS_100.59), 0x80055B1C (SCES_019.44)
The instructions
; FUN_801F3D3C, earth arm (PROT 0898)
801f3ec8 lbu v0,0x287(v1)
801f3ed0 beqz v0,0x801f4414 ; random encounter: no compare
801f3f38 lhu v1,0x15e(v1) ; live upper-defence base
801f3f3c lhu v0,0x14(v0) ; the record's upper defence
801f43f0 beq v1,v0,0x801f4414 ; equal: stage the debuff
801f43fc addiu s0,s0,1 ; different: blocked
801f4414 bnez s0,0x801f4484 ; blocked: return
; FUN_80054CB0, scripted-fight boost (SCUS_942.54)
80055234 lbu v0,0x287(v0) ; beq v0,zero,0x800552ac
80055248 ATK += ATK >> 2 ; sh 0x15a / sh 0x158
80055264 UDF += UDF ; sh 0x15e / sh 0x15c
80055280 LDF += LDF ; sh 0x162 / sh 0x160
8005529c INT += INT >> 3 ; sh 0x16a / sh 0x168
What each element compares
| Element | Arm | Live base | Record field | Stat | Boosted on USA? |
|---|---|---|---|---|---|
| earth | 0x801F3EBC | +0x15E | +0x14 | upper defence | yes |
| water | 0x801F3FA4 | +0x156 | +0x0E | AGL | no |
| fire | 0x801F407C | +0x15A | +0x12 | ATK | yes |
| wind | 0x801F4164 | +0x166 | +0x1A | SPD | no |
| thunder | 0x801F424C | +0x16A | +0x18 | INT | yes |
| dark | 0x801F4334 | +0x152 | +0x10 | MP | no |
The same code on the three discs
| Build | Loader: what follows the stat copy | Debuff stager | Shared compare | Scripted-fight flag |
|---|---|---|---|---|
USA SCUS_942.54 | the boost block 0x80055228..0x8005530C, then the spell loop at 0x80055310 | 0x801F3D3C, PROT 0898 at base 0x801CE818 | 0x801F43F0 | ctx +0x287 |
France SCES_019.44 | two byte clears, then the spell loop at 0x80055B1C | 0x801F468C, PROT 0898 at base 0x801CF0A8 | 0x801F4D40 | ctx +0x2A7 |
Japan SCPS_100.59 | two byte clears, then the spell loop at 0x80057014 | 0x801F7394, PROT 0898 at base 0x801D05C8 | 0x801F79BC | ctx +0x1E7 |
The French stager matches the USA routine instruction for instruction once addresses are masked. The Japanese one is a different compile with the same level gate, suppression roll, jump table (0x801D1880), stat pairs and shared compare. The USA block also holds a second arm for random encounters (both defences plus three quarters, INT plus a quarter), which the other two discs lack as well; it never shows in the debuffs because the check is skipped outside scripted fights.
Zeto, record against the USA in-battle base
| Stat | Record | USA base | Debuff |
|---|---|---|---|
| ATK | 108 | 135 | blocked |
| Upper defence | 95 | 190 | blocked |
| INT | 117 | 131 | blocked |
| SPD | 66 | 66 | lands once |
Published stat lists, this project's curated table included, print the boosted boss figures for every enemy, so the number in a list is the USA base and the record value is the lower one.
What else it affects
The boost could only leave a stat unchanged when ATK is below 4, upper defence is 0 or INT is below 8. Checked against every formation row on the disc, no monster on a scripted row meets any of those, so ATK-down, DEF-down and INT-down never land in any scripted fight on the USA disc. Still possible there: SPD-down once, AGL-down once, MP-down on each cast that survives the suppression roll. In random encounters the compare is skipped and the debuffs stack.
Addresses
| Address | Image | What | Evidence |
|---|---|---|---|
0x801F3D3C | PROT 0898 (file 0x25524) | Debuff stager | disassembly |
0x801CFA2C | PROT 0898 | Seven-entry element jump table | disc data |
0x801F3EC8 | PROT 0898 | Earth arm reads the scripted-fight flag; clear skips the compare | disassembly |
0x801F43F0 | PROT 0898 | Base-versus-record compare shared by every arm | disassembly |
0x801F6870 | PROT 0898 (file 0x28058) | Debuff percentages 5/10/15/20 by element and level band | disc data |
0x801F6960 | RAM | Staged percentage; zero leads to the No effect banner | disassembly |
0x801DE648..0x801DE8EC | PROT 0898 | Finisher tail applying the debuff to both halfwords | disassembly |
0x80054CB0 | SCUS_942.54 | Monster stat loader | disassembly |
0x80055234..0x800552A8 | SCUS_942.54 | Scripted-fight boost: ATK +1/4, defences x2, INT +1/8 | disassembly |
ctx +0x287 | RAM | Scripted-fight flag | disassembly |
record +0x12, +0x14, +0x18 | PROT 0867 monster record | ATK, upper defence, INT | disc data |
0x800552AC..0x80055300 | SCUS_942.54 | Random-encounter boost: defences +3/4, INT +1/4 | disassembly |
0x80055B1C | SCES_019.44 | Spell-list loop directly after the stat copy: no boost block | disassembly |
0x80057014 | SCPS_100.59 | Spell-list loop directly after the stat copy: no boost block | disassembly |
0x801F468C, 0x801F4D40 | French PROT 0898 (base 0x801CF0A8) | Debuff stager and its shared compare | disassembly |
0x801F7394, 0x801F79BC | Japanese PROT 0898 (base 0x801D05C8) | Debuff stager and its shared compare | disassembly |
0x801D1880 | Japanese PROT 0898 (file 0x12B8) | Seven-entry element jump table | disc data |
Verifying it
In a boss fight cast a level-3 or higher fire spell and break on 0x801F43F0: v1 is the boosted base (135 for Zeto), v0 the record value (108), the branch is not taken and 0x801F6960 stays zero. Write the record value into the actor's +0x15A and cast again: the debuff lands. On a Japanese disc the same break is 0x801F79BC, where a0 and v0 are equal on the first cast.
Open questions
- A boss debuff landing on a Japanese or PAL disc has not been watched in a running game.
- Which target-byte value each player spell uses for group casts is not enumerated; the arms treat value 8 differently.
Background: battle formulas (Seru-magic side effects) and the battle hub page (the loader boost and its regional comparison).
Every Berserker can be killed outright
- Reported as
- All Berserker bosses can be insta-killed (community report)
- Cause
- Instant-death summons treat a monster as a boss by a texture-size byte in its record, and that byte is 0 on all three Berserkers.
- Where
- Nighto module PROT 0907 (
0x801F6BF0), Aluru module PROT 0916 (0x801F6D44); monster record byte+0x20 - Affects
- Berserker ids 77, 173 and 174; no other story boss
- Status
- Explained
The monster data has no instant-death immunity field. The two player summons that can kill outright, Nighto and Aluru, decide whether the target is a boss with a makeshift test: the fight is scripted, and one particular byte of the monster record is non-zero.
That byte is a graphics setting. It tells the model loader that the monster's texture needs a double-width page of video memory. Nearly every large boss has it set, so it works as a stand-in for boss - except for Berserker, whose texture fits in a normal page. Its byte is zero, the test fails, and the summon rolls its ordinary chance.
Nighto lands 9 / (19 - level) of the time (50% at level 1, 90% at level 9) and a landed Nighto kills one time in eight, otherwise it confuses. Aluru lands 8 / (17 - level) of the time - every time at level 9 - and a landed Aluru always kills.
A third summon shows the byte's blind spot was noticed somewhere: Zenoir uses the same test to keep its shatter effect off bosses, and lists the three Berserker ids by hand. Nighto and Aluru never received that list. Zenoir's list is about how a dead monster is drawn, not about damage, and nothing in the code says its author knew the kill rolls shared the gap (below).
Step by step
- Nighto's routine reads the scripted-fight flag; in a random encounter it goes straight to the roll.
0x801F6BF0 - In a scripted fight it loads byte +0x20 of the target's monster record.
0x801F6C18 - Non-zero: the target resists. Zero: fall through to the roll.
0x801F6C20 - The roll is rand % (19 - spell level) < 9.
0x801F6C28..0x801F6CA8 - A separate rand % 8 picks the outcome: 0 sets HP to zero, anything else sets the confuse bits.
0x801F6B50, 0x801F7E58, 0x801F7F50 - Aluru repeats the gate, rolls rand % (17 - level) < 8 and sets HP to zero.
0x801F6D44, 0x801F6D70, 0x801F7E14 - Zenoir's module adds an explicit test for monster ids 0x4D, 0xAD, 0xAE - the three Berserkers. It guards Zenoir's shatter effect, not damage (next section).
0x801F8218..0x801F8230
The instructions
; Nighto, PROT 0907
801f6bf0 lbu v0,0x287(a1) ; scripted fight?
801f6bf8 beqz v0,0x801f6c28 ; no: roll
801f6c18 lbu v0,0x20(v0) ; the target's record +0x20
801f6c20 bnez v0,0x801f6cb8 ; non-zero: resisted
801f6c28 jal 0x80056798 ; rand()
801f6c70 rand % (19 - spell level)
801f6ca8 slti v1,v1,9 ; below 9: it lands
; Zenoir, PROT 0908
801f8218 lbu v1,-0x42f4(v0) ; first formation monster id
801f821c li v0,0x4d / beq ; 77
801f8224 li v0,0xad / beq ; 173
801f822c li v0,0xae / beq ; 174
Every monster on a scripted formation row, with record byte +0x20
| Id | Monster | HP | +0x20 | Nighto / Aluru |
|---|---|---|---|---|
| 19 | Red Piura (tutorial) | 75 | 0 | exposed |
| 20 | Black Piura (tutorial) | 85 | 0 | exposed |
| 73 | Caruban | 1024 | 1 | immune |
| 74 | Viguro | 1280 | 0 | exposed |
| 75 | Zeto | 5000 | 1 | immune |
| 76 | Songi | 888 | 1 | immune |
| 77 | Berserker | 5000 | 0 | exposed |
| 128 | Golem | 640 | 0 | exposed |
| 136 | Songi | 12888 | 1 | immune |
| 137 | Van Saryu | 13245 | 1 | immune |
| 138 | Dohati | 17200 | 1 | immune |
| 139 | Xain | 9511 | 1 | immune |
| 144 | Kemaro | 1400 | 0 | exposed |
| 156 | Puera | 1600 | 0 | exposed |
| 162 | Gi Delilas | 10000 | 1 | immune |
| 163 | Che Delilas | 12000 | 1 | immune |
| 164 | Lu Delilas | 9500 | 1 | immune |
| 165 | Gaza | 12000 | 1 | immune |
| 166 | Gaza | 15000 | 1 | immune |
| 168 | Rogue | 28200 | 1 | immune |
| 169 | Zora | 22422 | 1 | immune |
| 170 | Jette | 34567 | 1 | immune |
| 171 | Caruban | 4096 | 1 | immune |
| 172 | Caruban | 8192 | 1 | immune |
| 173 | Berserker | 10000 | 0 | exposed |
| 174 | Berserker | 15000 | 0 | exposed |
| 175 | Tetsu | 9999 | 1 | immune |
| 179 | Songi | 48000 | 1 | immune |
| 180 | Cort | 50000 | 1 | immune |
| 181 | Cort | 65535 | 1 | immune |
| 182 | Koru | 20000 | 1 | immune |
What else it affects
Of the monsters met in scripted fights, 22 have the byte set and resist; nine do not. Three of the nine are the Berserkers (5000, 10000 and 15000 HP); the other six are ordinary monsters and capturable Seru fought through a scripted row (the two tutorial Piuras, Golem, Viguro, Kemaro, Puera). No other story boss is exposed.
Lapis shows that the byte alone protects nothing. Its byte is 1, but its only formation row does not mark the fight as scripted, so the test is never consulted and Lapis rolls like a random encounter. The scripted-fight flag reads 0 for the whole Lapis fight in a capture on a forced formation (the Lapis row's bytes written over another scripted fight's row as it loads); see the Lapis page.
Jedo is no third route against a Berserker. Its module (PROT 0931) looks at the scripted-fight flag alone: clear, it sets every living enemy's HP to zero (0x801F8814); set, it deals rolled damage (0x801F8438). A Berserker fight is flagged, so Jedo only deals damage there.
Addresses
| Address | Image | What | Evidence |
|---|---|---|---|
record +0x20 | PROT 0867, slot (id-1)*0x14000 | Double-width texture flag, reused as the boss test | disc data |
0x801C9348[slot-3] | RAM | Pointer to the target's monster record | disassembly |
0x801F6BF0, 0x801F6C18 | PROT 0907 | Nighto's gate: scripted flag, then record +0x20 | disassembly |
0x801F6C28..0x801F6CC4 | PROT 0907 | Nighto's roll; resist word 0x801F853C | disassembly |
0x801F6CE4..0x801F6D28 | PROT 0907 | Extra one-in-three failure when Gala casts | disassembly |
0x801F6B50..0x801F6B7C | PROT 0907 | Outcome draw rand % 8 into 0x801F8534 | disassembly |
0x801F7E58 | PROT 0907 | Kill: HP set to zero | disassembly |
0x801F7F50 | PROT 0907 | Confuse: status bits 0x380 set | disassembly |
0x801F6D44, 0x801F6D70 | PROT 0916 | Aluru's gate | disassembly |
0x801F6DE4..0x801F6E1C | PROT 0916 | Aluru's roll; resist word 0x80209010 | disassembly |
0x801F7E14 | PROT 0916 | Kill: HP set to zero | disassembly |
0x801F81F0..0x801F8230 | PROT 0908 | Zenoir's gate plus the Berserker id list (shatter effect only) | disassembly, capture |
0x801F87DC, 0x801F8814, 0x801F8438 | PROT 0931 | Jedo: flag test, kill store (flag clear), rolled damage (flag set) | disassembly; capture on a forced Lapis formation with an injected cast |
ctx +0x287 | RAM | Scripted-fight flag | disassembly |
Verifying it
In a Berserker fight break on 0x801F6C20 during a Nighto cast or 0x801F6D78 during an Aluru cast: v0 reads 0 and execution continues into the random call. In the Zeto or Xain fight it reads 1 and the resist arm is taken. A level-9 Aluru should kill a Berserker on every cast.
Open questions
- Nighto and Aluru have not been cast on a Berserker in a running game; the result is read from the test and the three records.
- Instant-death sources other than these two summons and Jedo are not surveyed.
Background: the battle hub page (monster record layout).
Zenoir leaves a lone Golem in pieces
- Reported as
- What is special about Zenoir against Berserker? (community question, following the Berserker write-up above)
- Cause
- Zenoir shatters the monsters it kills. The last monster of a scripted fight is never faded away, so Zenoir skips bosses with the texture-byte test plus a hand list of the three Berserkers. The list misses the Golem and the lone Puera.
- Where
- Zenoir module PROT 0908, phase
0xCloop0x801F81C8..0x801F8264; shatter rendererFUN_80029888/FUN_80029DD8; grey stamp inFUN_800480D8 - Affects
- The Golem (id 128, Rikuroa) and the lone Puera (id 156); the Berserkers are protected by the list
- Status
- Explained - captured in a running game on forced formations with an injected cast
When Zenoir kills a monster, the monster does not just fade. Its model breaks into its own triangles, which spin and fly outward while the body fades to black. In an ordinary fight the fire covers most of it and the shards are gone with the fade.
The last monster of a scripted fight is different. The game never fades it away: it keeps drawing the body at full colour so it stays on stage until the victory scene. A shattered boss would leave a lit cloud of shards hanging over the stage, so Zenoir skips bosses. It uses the same makeshift test as Nighto and Aluru - a scripted fight and record byte +0x20 set - and, because that byte is 0 on Berserker, adds the three Berserker ids by hand.
The list stops there. Two more lone scripted monsters have the byte at 0: the Golem fought in Rikuroa and the lone Puera. A Zenoir kill on either leaves a lit cloud of shards on the stage until the battle ends.
What the shatter is
The effect is one number per actor, +0x1BA, which the draw copies to the body's +0x7A. While it is non-zero the body is drawn by a separate renderer that treats every triangle on its own: it finds the triangle's centre, spins the triangle about that centre by v × 16 4096ths of a turn, and moves it outward to 1 + v / 128 times its distance from the body's centre. Zenoir raises v by 32 a tick until the body's fade runs out, about 1000 in the capture, where each shard is almost nine times as far out as it started. The same renderer, with other settings, is the field's vanish effect for scripted characters and the way several summon creatures appear and leave.
Step by step
- Phase 8 of the cast deals the damage.
0x801F76CC(FUN_801DD0AC) - Phase 0xC visits each monster seat whose HP is now 0.
0x801F81C8..0x801F81E8 - It skips the seat in a scripted fight when +0x20 is set on the targeted victim's record (the same record for every seat, so in a two-monster scripted fight the targeted one decides for both).
0x801F81F0..0x801F8210 - It skips the fight when the first formation id is 77, 173 or 174 (the Berserkers).
0x801F8218..0x801F8230 - Otherwise it adds the step to +0x1BA; the battle draw copies it to the body and routes the body through the shatter renderer.
0x801F8254,0x800480A4,0x80048EAC - A killed monster fades to black and stops being drawn - unless it is the lone monster of a scripted fight, which is stamped grey and drawn on.
0x800481D4..0x800482DC
How it was measured
Captured under PCSX-Redux on the USA disc from a Rim Elm battle with Vahn alone: the formation row was rewritten as the battle loaded, Zenoir was injected into Vahn's queued action and the target's HP set to 1. Every fight here is forced and every cast injected.
| Forced fight | Scripted | What happened |
|---|---|---|
| Queen Bee + Killer Bee | no | Both bodies shatter; the shards fade to black and vanish |
| Golem alone | no | Shatters and fades to black |
| Golem alone | yes | Shatters; its colour reads 0x00808080 (the grey stamp) from the first shattered frame and the shards stay lit over the stage until the victory camera |
| Berserker alone | yes | The list test runs every tick and the store never does; the body stays whole |
| Berserker alone, id hidden from the list test | yes | Shatters into a lit cloud of green and purple shards |
Every lone scripted fight
| Id | Monster | +0x20 | Body after a Zenoir kill |
|---|---|---|---|
| 19, 20 | Red / Black Piura (tutorial) | 0 | would shatter and stay; Zenoir cannot be cast there |
| 77, 173, 174 | Berserker | 0 | whole (the hand list) |
| 128 | Golem (Rikuroa) | 0 | shards stay lit |
| 156 | Puera (lone row) | 0 | shards stay lit (predicted, not run) |
| others | Zeto, Xain, Cort, Songi and the other bosses | 1 | whole |
What else it affects
Only Zenoir and Jedo shatter monsters. Jedo shatters every monster it kills in a random fight and skips the effect in every scripted fight, so it never meets the problem. The other summons that use the renderer apply it to their own creature. Scripted fights with two monsters (Viguro, Kemaro, the paired Puera) fade normally, because the grey stamp needs a lone monster.
Addresses
| Address | Image | What | Evidence |
|---|---|---|---|
0x801F81C8..0x801F8264 | PROT 0908 | Shatter loop: dead seats, boss test, Berserker list, +0x1BA add | disassembly, capture |
0x800480A4 | SCUS_942.54 | +0x1BA copied to the body's +0x7A | disassembly |
0x80048EAC..0x80048FC0 | SCUS_942.54 | Non-zero +0x7A routes the body to the shatter renderer | disassembly, capture |
0x80029888, 0x80029DD8 | SCUS_942.54 | Shatter renderer: per-triangle spin about the centre and outward push | disassembly |
0x800481D4..0x800482DC | SCUS_942.54 | Grey stamp for the lone monster of a scripted fight | disassembly, capture |
0x801F86E0..0x801F874C | PROT 0931 | Jedo's shatter loop, skipped in scripted fights | disassembly |
In the port
The port draws the shatter with the same per-triangle formula on both hosts, and runs Zenoir's loop with the boss test and the Berserker list, so a Zenoir kill on the scripted lone Golem leaves the same lit shards. The field vanish uses the same renderer.
Verifying it
Kill the Rikuroa Golem with Zenoir: the shards should still be on screen, lit, after the fire has cleared. Break on 0x801F8254 to watch the store. In a Berserker fight the same breakpoint never fires.
Open questions
- The lone Puera is predicted from the same code and record; it was not run.
- The Golem capture is a forced formation in Rim Elm with an injected cast, not the real Rikuroa fight.
Background: the renderer page (the shatter renderer), the cast-module page.
Evil Fly and other boss-byte monsters can be killed outright
- Reported as
- Evil Fly has the non-insta-killable flag set, but can still be killed outright because it is not a scripted fight (old forum note)
- Cause
- The texture byte is only read in scripted fights; eleven monsters that have it, Lapis among them, are never met in one.
- Where
- Nighto
0x801F6BF0, Aluru0x801F6D44: the scripted-fight test comes before the record is read - Affects
- Evil Fly, Death Wings, Demon Fly, the three Gizams, the three Giliums, the 999-HP Tetsu, Lapis
- Status
- Explained - read from the code; not yet cast on an Evil Fly in a running game
The note is right. Nighto and Aluru only look at record byte +0x20 after they have checked that the fight is scripted. In any other fight they roll the kill chance without looking at the monster at all. The game has no "boss" field, and immunity needs both halves:
| Record +0x20 = 1 | Record +0x20 = 0 | |
|---|---|---|
| Scripted fight | resists (Zeto, Xain, Cort, …) | rolls: Berserker, Golem, Puera, Viguro, Kemaro, the tutorial Piuras |
| Any other fight | rolls - the byte is never read (Evil Fly, Gizam, Gilium, Lapis, …) | rolls (every ordinary monster) |
The two cells of the bottom row behave the same; the split only shows that the byte makes no difference there.
What the byte is. Record +0x20 is a graphics setting: when it is 1 the model upload doubles the width of the monster's texture in video memory. Large detailed models need the wide page, which is why most big bosses carry it, but so do a few ordinary monsters with wide textures. It is set on 37 of 186 monster records. Apart from the upload, exactly three places on the disc read it, all borrowing it as a "big boss" test: Nighto's and Aluru's resist checks and Zenoir's shatter check. No stat, AI or reward code reads it.
The monsters that have the byte and are never in a scripted fight
| Id | Monster | HP | Where it is met |
|---|---|---|---|
| 1, 2, 3 | Evil Fly, Death Wings, Demon Fly | 82, 209, 324 | random encounters |
| 55, 56, 57 | Gizam (three versions) | 266, 567, 2192 | random encounters; the strongest also on unflagged event rows |
| 79 | Tetsu | 999 | unflagged event rows only |
| 159, 160, 161 | Gilium (three versions) | 2880, 3280, 3542 | random encounters and unflagged event rows |
| 167 | Lapis | 64800 | one event row with a zero header byte (Lapis and Cort) |
Counted by joining each monster record's byte with a census of every formation row in every scene, split by the row's header byte.
Verifying it
Cast a level-9 Aluru on an Evil Fly in a random encounter: it lands every time and the fly dies. A breakpoint on 0x801F6D44 shows the scripted-fight flag at 0, and execution never reaches the record read at 0x801F6D70.
Open questions
- Not yet cast on an Evil Fly in a running game; the outcome is read from the code.