data/claims/pokeplatinum.yamlPokémon Platinum (pret/pokeplatinum)
Game: Pokémon Platinum.
In Platinum the damage formula reads each move's own class (MOVE_DATA(move).class: CLASS_PHYSICAL, CLASS_SPECIAL, or CLASS_STATUS) and uses Attack against Defense for a physical move, Special Attack against Special Defense for a special one, whatever the move's type. Emerald picks the pair by type, below or above TYPE_MYSTERY in the type list, so Thunder Punch (Electric) is special in Emerald and physical in Platinum, and Shadow Ball (Ghost) is physical in Emerald and special in Platinum. pokeplatinum/physical-special-by-move
About: Pokémon Platinum Damage formula
Value
{
"kind": "split",
"platinum": "move-class",
"emerald": "type",
"classes": [
"physical",
"special",
"status"
],
"examples": {
"thunder-punch": {
"emerald": "special",
"platinum": "physical"
},
"shadow-ball": {
"emerald": "physical",
"platinum": "special"
}
}
}include/move_table.h#L12-L15typedef struct MoveTable {
u16 effect;
u8 class;
u8 power;pokeplatinum: decompilation of Pokémon Platinum · generated/move_classes.txt#L1-L3CLASS_PHYSICAL
CLASS_SPECIAL
CLASS_STATUSpokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_lib.c#L6635-L6638attackStat = BattleMon_Get(battleCtx, attacker, BATTLEMON_ATTACK, NULL);
defenseStat = BattleMon_Get(battleCtx, defender, BATTLEMON_DEFENSE, NULL);
spAttackStat = BattleMon_Get(battleCtx, attacker, BATTLEMON_SP_ATTACK, NULL);
spDefenseStat = BattleMon_Get(battleCtx, defender, BATTLEMON_SP_DEFENSE, NULL);pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_lib.c#L6704moveClass = MOVE_DATA(move).class;pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_lib.c#L6947-L6992if (moveClass == CLASS_PHYSICAL) {
…
damage = attackStat * sStatStageBoosts[attackStage].numerator;
…
stageDivisor = defenseStat * sStatStageBoosts[defenseStage].numerator;
…
} else if (moveClass == CLASS_SPECIAL) {pokeplatinum: decompilation of Pokémon Platinum · res/moves/thunder_punch/data.json#L10-L11"class": "CLASS_PHYSICAL",
"type": "TYPE_ELECTRIC",pokeplatinum: decompilation of Pokémon Platinum · res/moves/shadow_ball/data.json#L10-L11"class": "CLASS_SPECIAL",
"type": "TYPE_GHOST",pokeemerald: decompilation of Pokémon Emerald · include/battle.h#L466-L467#define IS_TYPE_PHYSICAL(moveType) (moveType < TYPE_MYSTERY)
#define IS_TYPE_SPECIAL(moveType) (moveType > TYPE_MYSTERY)pokeemerald: decompilation of Pokémon Emerald · include/constants/pokemon.h#L13-L19#define TYPE_GHOST 7
…
#define TYPE_MYSTERY 9
…
#define TYPE_ELECTRIC 13pokeemerald: decompilation of Pokémon Emerald · src/data/battle_moves.h#L120-L124[MOVE_THUNDER_PUNCH] =
…
.type = TYPE_ELECTRIC,pokeemerald: decompilation of Pokémon Emerald · src/data/battle_moves.h#L3214-L3218[MOVE_SHADOW_BALL] =
…
.type = TYPE_GHOST,pokeemerald: decompilation of Pokémon Emerald · src/pokemon.c#L3232if (IS_TYPE_PHYSICAL(type))pokeemerald: decompilation of Pokémon Emerald · src/pokemon.c#L3287if (IS_TYPE_SPECIAL(type))Checked by claude, 2026-10-22 — read the MoveTable struct, the class list, the four stat reads, the class assignment, and both branches of BattleSystem_CalcMoveDamage (a status move takes neither); the two move records; Emerald's macros, its type numbers, the two moves' entries, and the two branches of CalculateBaseDamage that use the macrosCited by: Damage formula, Physical/special split, Pokémon Platinum
BattleSystem_CalcCriticalMulti keeps Emerald's stage table, 1 in 16, 8, 4, 3, or 2, capped at stage 4, and its sum: +2 for Focus Energy, +1 for a held item with HOLD_EFFECT_CRITRATE_UP, +2 for Chansey or Farfetch'd holding an item whose hold effect names that species, plus the move's boosts, which in Platinum come from a battle-script variable (criticalBoosts) rather than a list of move effects. It adds +1 for the ability Super Luck, which Emerald's sum doesn't read. Besides Battle Armor and Shell Armor, which an attacker with Mold Breaker ignores, Lucky Chant on the target's side blocks a critical, and so does a MOVE_EFFECT_NO_CRITICAL flag that a comment in the header says is checked but never set. Platinum draws its 16-bit roll before those tests, where Emerald draws it only after its other tests pass. pokeplatinum/critical-chance
About: Pokémon Platinum Critical hit
Value
{
"byStage": [
16,
8,
4,
3,
2
],
"form": "RandNext() % N == 0",
"cap": 4
}src/battle/battle_lib.c#L7096-L7103// each value here is implicitly 1 / N
static const u8 sCriticalStageRates[] = {
16, // neutral
8, // +1
4, // +2
3, // +3
2, // +4pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_lib.c#L7123-L7139effectiveCritStage = (((attackerVolStatus & VOLATILE_CONDITION_FOCUS_ENERGY) != FALSE) * 2)
+ (itemEffect == HOLD_EFFECT_CRITRATE_UP)
+ criticalStage
+ (attackerAbility == ABILITY_SUPER_LUCK)
+ (2 * (itemEffect == HOLD_EFFECT_CHANSEY_CRITRATE_UP && attackerSpecies == SPECIES_CHANSEY))
+ (2 * (itemEffect == HOLD_EFFECT_FARFETCHD_CRITRATE_UP && attackerSpecies == SPECIES_FARFETCHD));
…
effectiveCritStage = 4;
…
if (BattleSystem_RandNext(battleSys) % sCriticalStageRates[effectiveCritStage] == 0
&& Battler_IgnorableAbility(battleCtx, attacker, defender, ABILITY_BATTLE_ARMOR) == FALSE
&& Battler_IgnorableAbility(battleCtx, attacker, defender, ABILITY_SHELL_ARMOR) == FALSE
…pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_lib.c#L3112-L3115if (Battler_Ability(battleCtx, attacker) != ABILITY_MOLD_BREAKER) {
if (Battler_Ability(battleCtx, defender) == ability) {
result = TRUE;pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_script.c#L2158-L2162battleCtx->criticalMul = BattleSystem_CalcCriticalMulti(battleSys,
…
battleCtx->criticalBoosts,pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_script.c#L9531-L9532case BTLVAR_CRITICAL_BOOSTS:
return &battleCtx->criticalBoosts;pokeplatinum: decompilation of Pokémon Platinum · include/constants/battle/moves.h#L23#define MOVE_EFFECT_NO_CRITICAL (1 << 15) // checked for, but never setpokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_system.c#L1308-L1312u16 BattleSystem_RandNext(BattleSystem *battleSys)
…
return (u16)(battleSys->seedRandNext / LCRNG_DIVISOR);pokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L605-L606// The chance is 1/N for each stage.
static const u16 sCriticalHitChance[] = {16, 8, 4, 3, 2};pokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L1267-L1283critChance = 2 * ((gBattleMons[gBattlerAttacker].status2 & STATUS2_FOCUS_ENERGY) != 0)
+ (gBattleMoves[gCurrentMove].effect == EFFECT_HIGH_CRITICAL)
+ (gBattleMoves[gCurrentMove].effect == EFFECT_SKY_ATTACK)
+ (gBattleMoves[gCurrentMove].effect == EFFECT_BLAZE_KICK)
+ (gBattleMoves[gCurrentMove].effect == EFFECT_POISON_TAIL)
+ (holdEffect == HOLD_EFFECT_SCOPE_LENS)
…
if ((gBattleMons[gBattlerTarget].ability != ABILITY_BATTLE_ARMOR && gBattleMons[gBattlerTarget].ability != ABILITY_SHELL_ARMOR)
…
&& !(Random() % sCriticalHitChance[critChance]))
gCritMultiplier = 2;Checked by claude, 2026-10-22 — read the table and all of BattleSystem_CalcCriticalMulti; Battler_IgnorableAbility, which returns FALSE for a Mold Breaker attacker; BtlCmd_CalcCrit, which passes criticalBoosts (it also forces no critical in the catching tutorial and the first battle, as Emerald's test of BATTLE_TYPE_FIRST_BATTLE does); the script-variable switch; RandNext, a u16 like Emerald's Random(), so stage 3 passes 21,846 of 65,536 values in both. Emerald's && chain evaluates the armor tests before Random(); Platinum's starts with the rollCited by: Critical hit, Pokémon Platinum
A critical multiplies Platinum's damage by 2 after the base formula (its +2 included), at the same step as Emerald, and by 3 when the attacker has Sniper; Emerald's multiplier has no such case. As in Emerald, the critical then ignores the attacker's lowered attacking stat, the defender's raised defending stat, and Reflect or Light Screen. pokeplatinum/critical-effect
About: Pokémon Platinum Critical hit
Value
{
"multiplier": 2,
"sniper": 3
}src/battle/battle_lib.c#L7142-L7146if (criticalMul == 2 && Battler_Ability(battleCtx, attacker) == ABILITY_SNIPER) {
criticalMul = 3;
}
return criticalMul;pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_script.c#L1334-L1344battleCtx->damage = BattleSystem_CalcMoveDamage(battleSys,
…
battleCtx->damage *= battleCtx->criticalMul;pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_lib.c#L6947-L7024if (moveClass == CLASS_PHYSICAL) {
if (criticalMul > 1) {
if (attackStage > DEFAULT_STAT_STAGE) {
…
if (criticalMul > 1) {
if (defenseStage < DEFAULT_STAT_STAGE) {
…
if ((sideConditions & SIDE_CONDITION_REFLECT) != FALSE
&& criticalMul == 1
…
} else if (moveClass == CLASS_SPECIAL) {
if (criticalMul > 1) {
…pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_lib.c#L7078return damage + 2;pokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L1296gBattleMoveDamage = gBattleMoveDamage * gCritMultiplier * gBattleScripting.dmgMultiplier;pokeemerald: decompilation of Pokémon Emerald · src/pokemon.c#L3234-L3268if (gCritMultiplier == 2)
{
// Critical hit, if attacker has lost attack stat stages then ignore stat drop
…
// Critical hit, if defender has gained defense stat stages then ignore stat increase
…
if ((sideStatus & SIDE_STATUS_REFLECT) && gCritMultiplier == 1)pokeemerald: decompilation of Pokémon Emerald · src/pokemon.c#L3287-L3318if (IS_TYPE_SPECIAL(type))
{
if (gCritMultiplier == 2)
{
// Critical hit, if attacker has lost sp. attack stat stages then ignore stat drop
…
if ((sideStatus & SIDE_STATUS_LIGHTSCREEN) && gCritMultiplier == 1)Checked by claude, 2026-10-22 — read the Sniper branch, BattleScript_CalcMoveDamage's multiply after BattleSystem_CalcMoveDamage returns damage + 2, and both class branches; Beat Up multiplies the same way (battle_script.c L6212). Emerald's CalculateBaseDamage also returns damage + 2 (src/pokemon.c L3371) before Cmd_damagecalc multipliesCited by: Critical hit
Platinum's BattleScript_CalcCatchShakes keeps Emerald's capture math: the same , the same status bonuses (×2 for sleep or freeze; ×15/10 for poison, bad poison, burn, or paralysis), a catch at once when is 255 or more, and otherwise the threshold with up to four 16-bit rolls, each below a shake, and the Master Ball's shakes set to four after the draw. The two square roots run on the DS hardware square-root unit (CP_SetSqrt32) instead of Emerald's GBA BIOS call, and both games roll with the same generator, multiplier 1,103,515,245 and increment 24,691, keeping the top 16 bits. A Pal Park battle or the catching tutorial always catches. pokeplatinum/capture-same-math
About: Pokémon Platinum Capture
Value
{
"kind": "shake-checks",
"catchAtOrAbove": 255,
"numerator": 1048560,
"inner": 16711680,
"rolls": 4,
"rollRange": [
0,
65535
],
"status": {
"sleepOrFreeze": 2,
"poisonBurnParalysis": 1.5
}
}src/battle/battle_script.c#L10904-L10907static int BattleScript_CalcCatchShakes(BattleSystem *battleSys, BattleContext *battleCtx)
{
if (BattleSystem_GetBattleType(battleSys) & BATTLE_TYPE_ALWAYS_CATCH) {
return BALL_3_SHAKES_SUCCESS;pokeplatinum: decompilation of Pokémon Platinum · include/constants/battle.h#L56#define BATTLE_TYPE_ALWAYS_CATCH (BATTLE_TYPE_PAL_PARK | BATTLE_TYPE_CATCH_TUTORIAL)pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_script.c#L10978-L10988u32 catchRate = (speciesMod * ballMod / 10)
* (battleCtx->battleMons[battleCtx->defender].maxHP * 3 - battleCtx->battleMons[battleCtx->defender].curHP * 2)
/ (battleCtx->battleMons[battleCtx->defender].maxHP * 3);
…
if (battleCtx->battleMons[battleCtx->defender].status & (MON_CONDITION_SLEEP | MON_CONDITION_FREEZE)) {
catchRate *= 2;
…
if (battleCtx->battleMons[battleCtx->defender].status & (MON_CONDITION_POISON | MON_CONDITION_BURN | MON_CONDITION_PARALYSIS | MON_CONDITION_TOXIC)) {
catchRate = catchRate * 15 / 10;pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_script.c#L10990-L11013int shakes;
if (catchRate >= 255) {
shakes = BALL_3_SHAKES_SUCCESS;
} else {
u32 sqrtRate = (0xFF << 16) / catchRate;
CP_SetSqrt32(sqrtRate);
CP_WaitSqrt();
…
sqrtRate = CP_GetSqrtResult32();
CP_SetSqrt32(sqrtRate);
CP_WaitSqrt();
…
…pokeplatinum: decompilation of Pokémon Platinum · include/constants/battle/battle_script.h#L8#define BALL_3_SHAKES_SUCCESS 4pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_system.c#L1308-L1312u16 BattleSystem_RandNext(BattleSystem *battleSys)
{
battleSys->seedRandNext = battleSys->seedRandNext * LCRNG_MULTIPLIER + LCRNG_INCREMENT;
return (u16)(battleSys->seedRandNext / LCRNG_DIVISOR);pokeplatinum: decompilation of Pokémon Platinum · include/math_util.h#L8-L10#define LCRNG_MULTIPLIER 1103515245L
#define LCRNG_INCREMENT 24691
#define LCRNG_DIVISOR 65536Lpokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L9987-L10031odds = (catchRate * ballMultiplier / 10)
* (gBattleMons[gBattlerTarget].maxHP * 3 - gBattleMons[gBattlerTarget].hp * 2)
/ (3 * gBattleMons[gBattlerTarget].maxHP);
if (gBattleMons[gBattlerTarget].status1 & (STATUS1_SLEEP | STATUS1_FREEZE))
odds *= 2;
if (gBattleMons[gBattlerTarget].status1 & (STATUS1_POISON | STATUS1_BURN | STATUS1_PARALYSIS | STATUS1_TOXIC_POISON))
odds = (odds * 15) / 10;
…
if (odds > 254) // mon caught
…
odds = Sqrt(Sqrt(16711680 / odds));
odds = 1048560 / odds;
…pokeemerald: decompilation of Pokémon Emerald · include/gba/syscall.h#L20u16 Sqrt(u32 num);pokeemerald: decompilation of Pokémon Emerald · libagbsyscall/libagbsyscall.s#L72-L78.ifdef L_Sqrt
thumb_func_start Sqrt
Sqrt:
svc #8pokeemerald: decompilation of Pokémon Emerald · include/random.h#L16#define ISO_RANDOMIZE1(val) (1103515245 * (val) + 24691)pokeemerald: decompilation of Pokémon Emerald · src/random.c#L11-L16u16 Random(void)
{
gRngValue = ISO_RANDOMIZE1(gRngValue);
…
return gRngValue >> 16;Checked by claude, 2026-10-22 — read BattleScript_CalcCatchShakes whole beside Emerald's Cmd_handleballthrow: the same odds expression (catchRate is u32, as Emerald's odds), the same status masks, 0xFF << 16 = 16,711,680 and 0xFFFF << 4 = 1,048,560, the loop that stops at the first roll not below b, and the Master Ball override; seedRandNext is a u32 (include/struct_defs/battle_system.h L134), so dividing by 65,536 keeps the top 16 bits, as Emerald's >> 16 doesCited by: Capture, Pokémon Platinum
Platinum keeps Emerald's ball multipliers in tenths (Ultra 20, Great and Safari 15, Poké 10; Net 30 against Water or Bug, Nest 40 less the level below 40 with a floor of 10, Repeat 30, Timer 10 plus the turn count up to 40) and Emerald's 10 for the Luxury and Premier Balls. Two balls have new multipliers: a Dusk Ball gives 35 at night, late at night, or on cave terrain, and a Quick Ball 40 while the battle's turn count (totalTurns) is 0. The Dive Ball's 35 now asks for water terrain (TERRAIN_WATER) where Emerald asks for an underwater map. A Safari Ball multiplies the catch rate by one of 13 fractions from 10/40 to 40/10, picked by a stage that starts at 6 (×1), where Emerald uses a Safari catch factor. The Heal and Cherish Balls, also new, give 10. pokeplatinum/capture-balls
About: Pokémon Platinum Capture
Value
{
"kind": "ball-modifier",
"unit": "tenths",
"ultra": 20,
"great": 15,
"poke": 10,
"safari": 15,
"net": [
30,
10
],
"dive": [
35,
10
],
"nest": "max(40 - level, 10) below level 40, else 10",
"repeat": [
30,
10
],
"timer": "min(10 + turns, 40)",
"dusk": [
35,
10
],
"quick": [
40,
10
],
"other": 10,
"safariStages": {
"start": 6,
"fractions": [
[
10,
40
],
[
10,
35
],
[
10,
30
],
[
10,
25
],
[
10,
20
],
[
10,
15
],
[
10,
10
],
[
15,
10
],
[
20,
10
],
[
25,
10
],
[
30,
10
],
[
35,
10
],
[
40,
10
]
]
}
}src/battle/battle_script.c#L10868-L10889static const u8 sBasicBallMod[] = {
20, // ITEM_ULTRA_BALL
15, // ITEM_GREAT_BALL
10, // ITEM_POKE_BALL
15, // ITEM_SAFARI_BALL
};
…
static const struct Fraction sSafariCatchRate[] = {
{ 10, 40 },
…
{ 10, 10 },
…
…pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_script.c#L10910-L10975if (battleCtx->msgItemTemp == ITEM_SAFARI_BALL) {
…
speciesMod = speciesMod * sSafariCatchRate[battleCtx->safariCatchStage].numerator / sSafariCatchRate[battleCtx->safariCatchStage].denominator;
…
u32 ballMod = 10;
…
if (battleCtx->msgItemTemp > ITEM_SAFARI_BALL) {
switch (battleCtx->msgItemTemp) {
case ITEM_NET_BALL:
…
case ITEM_DIVE_BALL:
if (BattleSystem_GetTerrain(battleSys) == TERRAIN_WATER) {
…pokeplatinum: decompilation of Pokémon Platinum · src/battle/battle_lib.c#L2036battleCtx->safariCatchStage = 6;pokeplatinum: decompilation of Pokémon Platinum · generated/items.txt#L1-L17ITEM_NONE
ITEM_MASTER_BALL
ITEM_ULTRA_BALL
ITEM_GREAT_BALL
ITEM_POKE_BALL
ITEM_SAFARI_BALL
ITEM_NET_BALL
ITEM_DIVE_BALL
ITEM_NEST_BALL
ITEM_REPEAT_BALL
ITEM_TIMER_BALL
ITEM_LUXURY_BALL
…pokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L9932-L9985if (gLastUsedItem == ITEM_SAFARI_BALL)
catchRate = gBattleStruct->safariCatchFactor * 1275 / 100;
…
case ITEM_NET_BALL:
if (IS_BATTLER_OF_TYPE(gBattlerTarget, TYPE_WATER) || IS_BATTLER_OF_TYPE(gBattlerTarget, TYPE_BUG))
ballMultiplier = 30;
…
case ITEM_DIVE_BALL:
if (GetCurrentMapType() == MAP_TYPE_UNDERWATER)
ballMultiplier = 35;
…
case ITEM_NEST_BALL:
…pokeemerald: decompilation of Pokémon Emerald · include/constants/items.h#L8-L21// Note: If moving ball IDs around, updating FIRST_BALL/LAST_BALL is not sufficient
…
ITEM_MASTER_BALL,
…
ITEM_TIMER_BALL,
ITEM_LUXURY_BALL,
ITEM_PREMIER_BALL,pokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L841-L846static const u8 sBallCatchBonuses[] =
{
[ITEM_ULTRA_BALL - ITEM_ULTRA_BALL] = 20,
[ITEM_GREAT_BALL - ITEM_ULTRA_BALL] = 15,
[ITEM_POKE_BALL - ITEM_ULTRA_BALL] = 10,
[ITEM_SAFARI_BALL - ITEM_ULTRA_BALL] = 15Checked by claude, 2026-10-22 — read the two tables, every case of the switch (Net and Repeat as in Emerald; Luxury, Premier, Heal, and Cherish have no case and keep ballMod = 10), the Safari stage's start in battle_lib.c, and the item order, where the balls above the Safari Ball take the switch; compared every case with Emerald's switch; Heal and Cherish have no case in either game's code, and Emerald's balls, contiguous by its header comment, end at the Premier Ball. The bait and rock commands move the stage (battle_controller_player.c L2046-L2075); the claim doesn't say which way each one moves itCited by: Capture
In Platinum the ability Technician multiplies a move's power by 15/10 (rounded down) when that power is 60 or less, Struggle excepted. The test reads the power after the battle's power multiplier, Charge, and Helping Hand have changed it. pokeplatinum/technician
About: Pokémon Platinum Ability
Value
{
"factor": [
15,
10
],
"maxPower": 60
}src/battle/battle_lib.c#L6687-L6702GF_ASSERT(battleCtx->powerMul >= 10);
movePower = movePower * battleCtx->powerMul / 10;
…
if ((battleCtx->battleMons[attacker].moveEffectsMask & MOVE_EFFECT_CHARGE) && moveType == TYPE_ELECTRIC) {
movePower *= 2;
…
if (battleCtx->turnFlags[attacker].helpingHand) {
movePower = movePower * 15 / 10;
…
if (attackerParams.ability == ABILITY_TECHNICIAN
&& move != MOVE_STRUGGLE
&& movePower <= 60) {
…Checked by claude, 2026-10-22 — read BattleSystem_CalcMoveDamage from the power assignment through the Technician test; movePower is a u16