Damage Formula & Integer Rounding - How Polished Crystal Calculates Damage
A technical reference for Polished Crystal's exact damage formula, its integer rounding order, and how it differs from Gen 9 / Pokémon Showdown's damage calculation.
This guide has not been peer reviewed yet!
The information presented may be incomplete, inaccurate, or represent a single perspective. Please verify critical details independently or join the Discord for community support if needed.
Polished Crystal runs on the Game Boy Color and does all of its damage math with integer arithmetic, flooring at specific points in a specific order. If you implement the formula with floating-point math (or with Gen 9's rounding rules), your results will drift from the real game by a point or two in many matchups - enough to get a KO range wrong.
This page documents the exact pipeline as implemented in the game source
(engine/battle/effect_commands.asm, engine/battle/abilities.asm,
home/math.asm), and then summarizes how it differs from Gen 9 / Showdown.
It is written for anyone re-implementing the formula (e.g. Polished Showdown);
the damage calculator tool on this site follows this pipeline.
The one primitive: MultiplyAndDivide
Almost every modifier in Polished Crystal goes through a single routine,
MultiplyAndDivide (home/math.asm). It takes a fraction packed into one byte
($xy = multiply by x, divide by y) and applies it to the current running
damage value:
damage = floor((damage * x) / y)
Two things matter here:
- Each modifier floors once, immediately after its own division. Modifiers are not combined into one big multiplier first.
- Because the nibbles only go up to 15, every modifier is a small rational:
1.5× is
3/2, 1.3× is13/10, 2.25× is9/4, 2/3 is2/3, and so on.
So a chain of modifiers is floor(floor(floor(d·a/b)·c/d)·e/f)…, applied in the
fixed order below.
The full pipeline, in order
Step 1 - Stats (16-bit) and the 8-bit truncation
BattleCommand_damagestats loads the raw attack and defense stats (16-bit),
then applies stat-side modifiers:
- Weather defense boosts (defender): Hail gives Ice-types 1.5× Defense,
Sandstorm gives Rock-types 1.5× Sp.Def - computed as
def + floor(def/2). - Screens: Reflect/Light Screen double the raw defense stat (
def * 2). Skipped if the attack is a crit, the attacker has Infiltrator, or the move is Brick Break. - Items: Thick Club / Light Ball double attack (
atk * 2); Metal Powder (Ditto) and Eviolite (unevolved) give 1.5× defense (def + floor(def/2)).
Then comes the most exotic step, TruncateHL_BC: the Game Boy passes attack
and defense to the core formula in 8-bit registers, so while either stat
is above 255, both stats are halved together (floor), with a minimum of 1.
A 400 Atk vs 300 Def matchup actually computes as 200 vs 150. This preserves
the ratio approximately, but introduces its own rounding - and it means big
stats lose precision that Gen 9 never loses.
Stat stages are not applied here. See Step 3.
Step 2 - Base damage passes 1–2
d = floor(2 * Level / 5) + 2
d = d * Power
d = d * Attack (the truncated 8-bit attack)
No division has happened yet, so d is at its largest here. The game
deliberately applies most modifiers at this point, before dividing by
Defense and 50, so that flooring costs almost nothing.
Step 3 - Stat stages (applied to damage, not stats!)
Attack and Defense stages are applied to the running damage value via MultiplyAndDivide, using the standard stage fractions:
- Attack stage
+n→(2+n)/2;-n→2/(2+n) - Defense stage is applied inverted: a defender at +2 Def multiplies your
damage by
2/4.
Rules layered on top:
- Crits skip the attacker's negative stages and the defender's positive stages (positive attack stages and negative defense stages still count).
- Unaware (defender) skips the attacker's attack stages; Unaware (attacker) skips the defender's defense stages.
Step 4 - First ability pass, burn, Flash Fire, crit, items
Still before any division, each of these applies via MultiplyAndDivide in this
order (BattleCommand_damagecalc / ApplyDamageAbilities):
- Offensive abilities: Technician 3/2 (BP ≤ 60), Huge Power 2/1 (physical), Hustle & Gorilla Tactics 3/2 (physical), Overgrow/Blaze/Torrent/Swarm 3/2 (≤⅓ HP, matching type), Rivalry 5/4 or 3/4, Sheer Force 13/10, Analytic 13/10, Solar Power 3/2, Iron Fist 6/5, Tough Claws 13/10, Mega Launcher 3/2, Sand Force 13/10, Reckless 6/5, Guts 3/2, -ate abilities 6/5, Steely Spirit 3/2, Sharpness 3/2.
- Defensive abilities: Multiscale 1/2, Marvel Scale 2/3, Thick Fat 1/2,
Dry Skin 5/4 (Fire), Fur Coat 1/2, Fluffy (2/1 for Fire, 1/2 for contact
- both apply to a Fire contact move). Note: "physical defense" mods (Marvel Scale, Fur Coat) also apply against Psystrike.
- Burn: 1/2 for physical moves (skipped with Guts or Facade).
- Flash Fire: 3/2 when boosted and using a Fire move.
- Critical hit: 3/2, or 9/4 with Sniper.
- Attacker's item: type-boost items 6/5, Muscle Band / Wise Glasses 11/10,
Choice Band/Specs 3/2, Metronome
(5+n)/5, Punching Glove 11/10, Life Orb 13/10. - Defender's item: Assault Vest 2/3 vs special moves.
Step 5 - Base damage passes 3–4
d = floor(d / Defense) (truncated 8-bit defense, screens/items included, NO stages)
d = floor(d / 50)
d = d + 2 (capped at 65535)
Step 6 - Type effectiveness, STAB, and weather: the ×16 byte
This is the part that surprises people most. Effectiveness, STAB, and the
sun/rain move boost are all folded into a single one-byte fixed-point
multiplier, wTypeMatchup, where $10 (16) = 1.0×:
- Type effectiveness sets it: 4 (¼×), 8 (½×), 16 (1×), 32 (2×), 64 (4×). 0 means immune → damage is zeroed and the move fails.
- STAB:
matchup += floor(matchup / 2); Adaptability doubles instead. - Weather (sun/rain move type boost/penalty):
+ floor(matchup/2)orfloor(matchup / 2).
Damage is multiplied by this byte, and then - while still at the ×16 scale - these apply via MultiplyAndDivide:
- Parental Bond second hit: 1/4
- Tinted Lens: 2/1 (when not very effective); Filter / Solid Rock: 3/4 (when super effective)
- Expert Belt: 6/5 (when super effective)
Only after all of those does the game do a single floor(d / 16). Applying
these at the raised scale means they round less than they would post-division.
If the result is 0 (and the target isn't immune), it becomes 1.
Step 7 - Damage roll
If damage is ≥ 2: d = floor(d * R / 100) where R is uniform in 85–100
inclusive (16 possible rolls). Damage of 0 or 1 skips the roll entirely.
How this differs from Gen 9 or Showdown
| Aspect | Polished Crystal | Gen 9 (Showdown) |
|---|---|---|
Modifier arithmetic | Small rationals ( x/y, nibbles), floor after each modifier | 4096-based fixed point, chained mods use pokeRound (round half down), applied once |
Stat stages | Applied to the running damage as (2+n)/2 fractions, mid-formula | Applied to the stat itself before the base formula |
Ability/item boosts | Applied before /Def, /50, +2 (on the huge intermediate product) | Mostly applied to stats, base power, or the final damage after the base formula |
Stat size limit | Attack & Defense truncated together to 8 bits (halved until ≤ 255) | No truncation; full precision |
Screens | Double the defense stat (skipped on crit/Infiltrator/Brick Break) | Final damage ×0.5 (2732/4096) |
Type/STAB/weather | One shared ×16 fixed-point byte, single /16 at the end | Separate multipliers; STAB is a 6144/4096 final mod, effectiveness directly multiplies with flooring between steps |
Crit timing | Mid-formula, before /Def and /50 | 1.5× applied to base damage after the core formula, before random factor |
Random factor | 85–100 (16 rolls), applied last, skipped if damage < 2 | 85–100 (16 rolls), applied before STAB/effectiveness/burn/final mods |
Burn | ×1/2 mid-formula (before /50) | ×0.5 near the end of the modifier chain |
Minimum damage | Forced to 1 after the /16 type step | Forced to 1 at the end |
The practical consequences:
- Order matters more than the fractions. Gen 9 applies the random roll early and effectiveness/STAB late; Polished applies every modifier before its divisions where possible and rolls last. Two implementations using the "same" multipliers will disagree by 1–3 points constantly if the order and floor placement differ.
- High-stat matchups diverge extra because of the 8-bit truncation - a mechanic with no Gen 9 equivalent at all.
pokeRoundvs floor: Showdown's chained modifiers round half down at 4096 scale; Polished floors every intermediate. Even a single 1.5× modifier can differ by 1 between the two schemes.
Worked example
Level 50, 80 BP physical move, 120 Atk vs 100 Def, STAB, neutral effectiveness, no other modifiers:
floor(2*50/5) + 2 = 22
22 * 80 * 120 = 211200
floor(211200 / 100) = 2112
floor(2112 / 50) = 42
42 + 2 = 44
type byte: 16 + floor(16/2) = 24 (STAB)
floor(44 * 24 / 16) = 66
rolls: floor(66 * 85/100) … floor(66 * 100/100) = 56 … 66
A float implementation of "44 × 1.5 = 66" happens to agree here, but shift any
input and the floors start to bite - e.g. 43 + 2 = 45 → floor(45·24/16) = 67,
where 45 × 1.5 = 67.5 rounds to 68 under half-up rounding.
BattleCommand_damagestats,
TruncateHL_BC,
BattleCommand_damagecalc,
DamagePass1–4,
BattleCommand_stab,
BattleCommand_damagevariation in engine/battle/effect_commands.asm;
ApplyDamageAbilities in engine/battle/abilities.asm;
MultiplyAndDivide in home/math.asm;
DoWeatherModifiers in
engine/battle/misc.asm.Was this useful?
Loading votes...
