
I couldn't miss the chance to practice my drawing!
There exists a bug in the damage formulas of certain weapons in Final Fantasy V.
The agility stat is meant to play a major role in the damage formula of most weapons belonging to certain classes (bells, boomerangs, bows, knives, short swords, throwables, and whips). However, only the damage formulas of bells, throwables, and the Chicken Knife implement that properly.
While some have speculated that this behavior is intentional, possibly to prevent certain weapon classes (e.g., knives) from becoming overly powerful, the consequences and the technical explanation suggest that it is more likely the result of an accidental oversight.
The bug can be fixed in the SNES version of the game by modding the ROM (preferably after making a safety back-up).
The damage formula[]
The general damage algorithm of Final Fantasy V can be written as a damage formula:
where:
Damageis the damage output of a regular attack performed with the equipped weapon.Ais an attack-based parameter that mainly depends on the weapon's Atk.Dis a defense-based parameter that mainly depends on the target's Def or MDef.Mis a multiplier that manly depends on the attacker's level, and other stats.- the
min()andmax()functions enforce the damage ceiling (9999), and the damage floor (0).
With limited exceptions, boomerangs, bows, knives, short swords, and whips share the same sub-formulas for A, D, and M:
where // represents the integer division operation (i.e. taking the quotient ignoring the remainder), and mod represents the modulo operation (i.e. taking the remainder ignoring the quotient).
The formula for M is bifurcated: the first one represents the original intention of the developers, and the second one represents the actual implementation, including the bug. In that regard, the mod 256 reduction is purely a mathematical tool to represent the bug. The technicals details of the likely oversight causing the bug are explained in detail here.
The bug[]
As Level and Agility are capped at 99 for the purpose of the damage formula, the upper limit of their product is 9801, which is in the [256, 65535] range. Every unsigned number in that range:
- Cannot be represented in binary using fewer than 2 bytes.
- Can always be represented in binary using 2 bytes.
Therefore, to consistently be able to represent in binary all possible products, 2 bytes (16 bits) are needed.
The bug consists in only the value encoded by the least significant byte of being used in the damage formula, while the most significant byte is ignored. Mathematically, this is equivalent to reducing .
Since, by definition, any value modulo 256 must be in the [0, 255] range, when dividing it by 128, the only two possible integer quotients are 0 and 1. Therefore, the impact of the agility stat on the multiplier formula (and, thus, on the overall damage formula) is very limited.
Consequences[]
The primary consequence of the bug is that the agility stat can only add 0 or 1 to the damage formula multiplier. Therefore, the contribution of the agility stat to the final damage becomes almost negligible. If the bug was not there, the maximum additive impact on the multiplier would be , which makes the actual impact of the agility stat even more underwhelming.
Additionally, it is possible for a character attacking with an affected weapon to deal less damage after a level up, everything else being equal. That is the case whenever M decreases after a level up (i.e. from Level to Level + 1):
The easiest scenario in which the above inequality is satisfied (due to the agility bug) is when:
In other words, the additive contribution of the Strength stat to the multipler does not increase, and the bugged contribution of the Agility stat decreases from 1 to 0. For instance, considering a character with 55 strength and 50 agility before and after leveling up from 10 to 11:
Exceptions[]
Certain bells and the shuriken-type throwables also feature the agility stat in the multiplier M of their damage formula. However, in those cases, both bytes of are correctly used. Additionally, within the weapon classes that are affected by the bug, there are some exceptions:
- The Twin Lance and the Man-Eater use the spears' damage formula, which does not feature agility.
- The Magic Bow deals no damage by design. As such, it does not use a damage formula.
- The Chicken Knife uses a unique damage formula, which does not feature the bug in the multiplier. As such, it is considerably stronger than all other knives, and deals more damage than any other weapon in the game in most practical scenarios.
In-depth analysis[]
The SNES uses a 65C816-derived Ricoh 5A22 CPU, which, among other things, is little-endian, and features a variable-width accumulator register for basic arithmetic.
In the affected weapons' damage formulas (all of which share the same code for the computation of the multiplier M), after performing the multiplication, the result is stored in a temporary 16-bit memory location (virtual addresses $26 and $27)[note 1] for further use in arithmetic operations. Due to little-endianness, the least significant byte is stored at $26 and the most significant byte is stored at $27.
The instruction LDA loads data from memory into the accumulator. By default, if the accumulator is in 8-bit mode, it only loads a single byte. If the M flag[note 2] in the processor status register is cleared (with REP #$20), the accumulator switches to 16-bit mode and LDA will then load two bytes.
In the relevant routine (ROM addresses [0x28113 ~ 0x28116]), the game code executes these instructions in the wrong order:
| Addresses | Hex bytes | Operation | Meaning |
|---|---|---|---|
| 0x28113 ~ 0x28114 | A5 26 | LDA $26 | Loads the value (8-bit by default) from memory location $26 into the accumulator. |
| 0x28115 ~ 0x28116 | C2 20 | REP #$20 | Clears the M flag, switching the accumulator to 16-bit mode. |
By loading the value first while still in 8-bit mode, only the least significant byte of the product is kept. The high byte is lost, effectively reducing to .
The correct sequence is:
| Addresses | Hex bytes | Operation | Meaning |
|---|---|---|---|
| 0x28113 ~ 0x28114 | C2 20 | REP #$20 | Clears the M flag, switching the accumulator to 16-bit mode. |
| 0x28115 ~ 0x28116 | A5 26 | LDA $26 | Loads the full 16-bit value from memory locations $26/$27 into the accumulator. |
Fix[]
To fix the bug in the SNES version (both the original, and the RPGe translated version), the rom needs to be patched (e.g. with a hex editor). As mentioned in the previous section, the relevant address range is [0x28113 ~ 0x28116].
| Address | Original value | Patched value |
|---|---|---|
| 0x28113 | A5 | C2 |
| 0x28114 | 26 | 20 |
| 0x28115 | C2 | A5 |
| 0x28116 | 20 | 26 |
The player can quickly test the patch by comparing the damage of the Assassin's Dagger to the damage of the Enhancer against enemies with low defense before and after patching the rom.