On this page
  1. Related
  2. Mechanics
  3. References
  4. Sources

Pokémon Platinum is a Generation IV Pokémon game for Nintendo DS. The code read here is pret’s pokeplatinum decompilation, a work in progress whose battle engine is C and which builds the US release.

Where Pokémon Emerald’s damage formula lets a move’s type pick its stats, Platinum’s reads the move itself: each move carries its own physical or special class, whatever its type (the physical/special split)1. Critical hits keep Emerald’s stage table2, and capture keeps Emerald’s formula and shake checks3.

Mechanics

MechanicKindHow
Damage formulaAttack over defenseeach move's own class picks Attack or Special Attack, not its type
Physical/special splitBy moveeach move's class, physical, special, or status, whatever its type
Critical hitFrom stagesEmerald's stages, plus Super Luck; Lucky Chant blocks it, and Sniper makes it ×3
CaptureFour shake checksEmerald's formula and four shake checks, with the Dusk and Quick Balls added
AbilityTechnician, Super Luck, Sniper, and Mold Breaker
Held itemthe critical-rate items, as in Emerald
Every claim about this game, by concept (6)
AbilityIn 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.Code
CapturePlatinum's BattleScript_CalcCatchShakes keeps Emerald's capture math: the same a=⌊⌊Cm/10⌋(3M−2H)/3M⌋, the same status bonuses (×2 for sleep or freeze; ×15/10 for poison, bad poison, burn, or paralysis), a catch at once when a is 255 or more, and otherwise the threshold b=⌊1048560/⌊16711680/a⌋⌋ with up to four 16-bit rolls, each below b 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.Code
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.Code
Critical hitBattleSystem_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.Code
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.Code
Damage formulaIn 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.Code

References

  1. ^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.Codepret/pokeplatinum · pret/pokeemerald
    How we know
    12typedef struct MoveTable {
    13    u16 effect;
    14    u8 class;
    15    u8 power;
    
    include/move_table.h lines 12–15 at 49f78f3 · upstream ↗
    1CLASS_PHYSICAL
    2CLASS_SPECIAL
    3CLASS_STATUS
    
    generated/move_classes.txt lines 1–3 at 49f78f3 · upstream ↗
    6635    attackStat = BattleMon_Get(battleCtx, attacker, BATTLEMON_ATTACK, NULL);
    6636    defenseStat = BattleMon_Get(battleCtx, defender, BATTLEMON_DEFENSE, NULL);
    6637    spAttackStat = BattleMon_Get(battleCtx, attacker, BATTLEMON_SP_ATTACK, NULL);
    6638    spDefenseStat = BattleMon_Get(battleCtx, defender, BATTLEMON_SP_DEFENSE, NULL);
    
    src/battle/battle_lib.c lines 6635–6638 at 49f78f3 · upstream ↗
    6704    moveClass = MOVE_DATA(move).class;
    
    src/battle/battle_lib.c line 6704 at 49f78f3 · upstream ↗
    6947    if (moveClass == CLASS_PHYSICAL) {
    ⋮
    6950                damage = attackStat * sStatStageBoosts[attackStage].numerator;
    ⋮
    6956            damage = attackStat * sStatStageBoosts[attackStage].numerator;
    ⋮
    6965                stageDivisor = defenseStat * sStatStageBoosts[defenseStage].numerator;
    ⋮
    6971            stageDivisor = defenseStat * sStatStageBoosts[defenseStage].numerator;
    ⋮
    6992    } else if (moveClass == CLASS_SPECIAL) {
    
    src/battle/battle_lib.c lines 6947–6992 at 49f78f3 · upstream ↗
    10    "class": "CLASS_PHYSICAL",
    11    "type": "TYPE_ELECTRIC",
    
    res/moves/thunder_punch/data.json lines 10–11 at 49f78f3 · upstream ↗
    10    "class": "CLASS_SPECIAL",
    11    "type": "TYPE_GHOST",
    
    res/moves/shadow_ball/data.json lines 10–11 at 49f78f3 · upstream ↗
    466#define IS_TYPE_PHYSICAL(moveType) (moveType < TYPE_MYSTERY)
    467#define IS_TYPE_SPECIAL(moveType) (moveType > TYPE_MYSTERY)
    
    include/battle.h lines 466–467 at 731ad5b · upstream ↗
    13#define TYPE_GHOST            7
    ⋮
    15#define TYPE_MYSTERY          9
    ⋮
    19#define TYPE_ELECTRIC         13
    
    include/constants/pokemon.h lines 13–19 at 731ad5b · upstream ↗
    120    [MOVE_THUNDER_PUNCH] =
    ⋮
    124        .type = TYPE_ELECTRIC,
    
    src/data/battle_moves.h lines 120–124 at 731ad5b · upstream ↗
    3214    [MOVE_SHADOW_BALL] =
    ⋮
    3218        .type = TYPE_GHOST,
    
    src/data/battle_moves.h lines 3214–3218 at 731ad5b · upstream ↗
    3232    if (IS_TYPE_PHYSICAL(type))
    
    src/pokemon.c line 3232 at 731ad5b · upstream ↗
    3287    if (IS_TYPE_SPECIAL(type))
    
    src/pokemon.c line 3287 at 731ad5b · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  2. ^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.Codepret/pokeplatinum · pret/pokeemeraldrevised
    How we know
    7096// each value here is implicitly 1 / N
    7097static const u8 sCriticalStageRates[] = {
    7098    16, // neutral
    7099    8, // +1
    7100    4, // +2
    7101    3, // +3
    7102    2, // +4
    
    src/battle/battle_lib.c lines 7096–7103 at 49f78f3 · upstream ↗
    7123    effectiveCritStage = (((attackerVolStatus & VOLATILE_CONDITION_FOCUS_ENERGY) != FALSE) * 2)
    7124        + (itemEffect == HOLD_EFFECT_CRITRATE_UP)
    7125        + criticalStage
    7126        + (attackerAbility == ABILITY_SUPER_LUCK)
    7127        + (2 * (itemEffect == HOLD_EFFECT_CHANSEY_CRITRATE_UP && attackerSpecies == SPECIES_CHANSEY))
    7128        + (2 * (itemEffect == HOLD_EFFECT_FARFETCHD_CRITRATE_UP && attackerSpecies == SPECIES_FARFETCHD));
    ⋮
    7131        effectiveCritStage = 4;
    ⋮
    7134    if (BattleSystem_RandNext(battleSys) % sCriticalStageRates[effectiveCritStage] == 0
    7135        && Battler_IgnorableAbility(battleCtx, attacker, defender, ABILITY_BATTLE_ARMOR) == FALSE
    7136        && Battler_IgnorableAbility(battleCtx, attacker, defender, ABILITY_SHELL_ARMOR) == FALSE
    7137        && (sideConditions & SIDE_CONDITION_LUCKY_CHANT) == FALSE
    7138        && (defenderMoveEffects & MOVE_EFFECT_NO_CRITICAL) == FALSE) {
    ⋮
    
    src/battle/battle_lib.c lines 7123–7139 at 49f78f3 · upstream ↗
    3112    if (Battler_Ability(battleCtx, attacker) != ABILITY_MOLD_BREAKER) {
    3113        if (Battler_Ability(battleCtx, defender) == ability) {
    3114            result = TRUE;
    
    src/battle/battle_lib.c lines 3112–3115 at 49f78f3 · upstream ↗
    2158        battleCtx->criticalMul = BattleSystem_CalcCriticalMulti(battleSys,
    ⋮
    2162            battleCtx->criticalBoosts,
    
    src/battle/battle_script.c lines 2158–2162 at 49f78f3 · upstream ↗
    9531    case BTLVAR_CRITICAL_BOOSTS:
    9532        return &battleCtx->criticalBoosts;
    
    src/battle/battle_script.c lines 9531–9532 at 49f78f3 · upstream ↗
    23#define MOVE_EFFECT_NO_CRITICAL            (1 << 15) // checked for, but never set
    
    include/constants/battle/moves.h line 23 at 49f78f3 · upstream ↗
    1308u16 BattleSystem_RandNext(BattleSystem *battleSys)
    ⋮
    1311    return (u16)(battleSys->seedRandNext / LCRNG_DIVISOR);
    
    src/battle/battle_system.c lines 1308–1312 at 49f78f3 · upstream ↗
    605// The chance is 1/N for each stage.
    606static const u16 sCriticalHitChance[] = {16, 8, 4, 3, 2};
    
    src/battle_script_commands.c lines 605–606 at 731ad5b · upstream ↗
    1267    critChance  = 2 * ((gBattleMons[gBattlerAttacker].status2 & STATUS2_FOCUS_ENERGY) != 0)
    1268                + (gBattleMoves[gCurrentMove].effect == EFFECT_HIGH_CRITICAL)
    1269                + (gBattleMoves[gCurrentMove].effect == EFFECT_SKY_ATTACK)
    1270                + (gBattleMoves[gCurrentMove].effect == EFFECT_BLAZE_KICK)
    1271                + (gBattleMoves[gCurrentMove].effect == EFFECT_POISON_TAIL)
    1272                + (holdEffect == HOLD_EFFECT_SCOPE_LENS)
    ⋮
    1279    if ((gBattleMons[gBattlerTarget].ability != ABILITY_BATTLE_ARMOR && gBattleMons[gBattlerTarget].ability != ABILITY_SHELL_ARMOR)
    ⋮
    1282     && !(Random() % sCriticalHitChance[critChance]))
    1283        gCritMultiplier = 2;
    
    src/battle_script_commands.c lines 1267–1283 at 731ad5b · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

  3. ^Platinum's BattleScript_CalcCatchShakes keeps Emerald's capture math: the same a=⌊⌊Cm/10⌋(3M−2H)/3M⌋, the same status bonuses (×2 for sleep or freeze; ×15/10 for poison, bad poison, burn, or paralysis), a catch at once when a is 255 or more, and otherwise the threshold b=⌊1048560/⌊16711680/a⌋⌋ with up to four 16-bit rolls, each below b 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.Codepret/pokeplatinum · pret/pokeemeraldrevised
    How we know
    10904static int BattleScript_CalcCatchShakes(BattleSystem *battleSys, BattleContext *battleCtx)
    ⋮
    10906    if (BattleSystem_GetBattleType(battleSys) & BATTLE_TYPE_ALWAYS_CATCH) {
    10907        return BALL_3_SHAKES_SUCCESS;
    
    src/battle/battle_script.c lines 10904–10907 at 49f78f3 · upstream ↗
    56#define BATTLE_TYPE_ALWAYS_CATCH             (BATTLE_TYPE_PAL_PARK | BATTLE_TYPE_CATCH_TUTORIAL)
    
    include/constants/battle.h line 56 at 49f78f3 · upstream ↗
    10978    u32 catchRate = (speciesMod * ballMod / 10)
    10979        * (battleCtx->battleMons[battleCtx->defender].maxHP * 3 - battleCtx->battleMons[battleCtx->defender].curHP * 2)
    10980        / (battleCtx->battleMons[battleCtx->defender].maxHP * 3);
    ⋮
    10982    if (battleCtx->battleMons[battleCtx->defender].status & (MON_CONDITION_SLEEP | MON_CONDITION_FREEZE)) {
    10983        catchRate *= 2;
    ⋮
    10986    if (battleCtx->battleMons[battleCtx->defender].status & (MON_CONDITION_POISON | MON_CONDITION_BURN | MON_CONDITION_PARALYSIS | MON_CONDITION_TOXIC)) {
    10987        catchRate = catchRate * 15 / 10;
    
    src/battle/battle_script.c lines 10978–10988 at 49f78f3 · upstream ↗
    10990    int shakes;
    10991    if (catchRate >= 255) {
    10992        shakes = BALL_3_SHAKES_SUCCESS;
    10993    } else {
    10994        u32 sqrtRate = (0xFF << 16) / catchRate;
    10995        CP_SetSqrt32(sqrtRate);
    10996        CP_WaitSqrt();
    ⋮
    10998        sqrtRate = CP_GetSqrtResult32();
    10999        CP_SetSqrt32(sqrtRate);
    11000        CP_WaitSqrt();
    ⋮
    11002        catchRate = CP_GetSqrtResult32();
    11003        catchRate = (0xFFFF << 4) / catchRate;
    ⋮
    
    src/battle/battle_script.c lines 10990–11013 at 49f78f3 · upstream ↗
    8#define BALL_3_SHAKES_SUCCESS 4
    
    include/constants/battle/battle_script.h line 8 at 49f78f3 · upstream ↗
    1308u16 BattleSystem_RandNext(BattleSystem *battleSys)
    ⋮
    1310    battleSys->seedRandNext = battleSys->seedRandNext * LCRNG_MULTIPLIER + LCRNG_INCREMENT;
    1311    return (u16)(battleSys->seedRandNext / LCRNG_DIVISOR);
    
    src/battle/battle_system.c lines 1308–1312 at 49f78f3 · upstream ↗
    8#define LCRNG_MULTIPLIER 1103515245L
    9#define LCRNG_INCREMENT  24691
    10#define LCRNG_DIVISOR    65536L
    
    include/math_util.h lines 8–10 at 49f78f3 · upstream ↗
    9987        odds = (catchRate * ballMultiplier / 10)
    9988            * (gBattleMons[gBattlerTarget].maxHP * 3 - gBattleMons[gBattlerTarget].hp * 2)
    9989            / (3 * gBattleMons[gBattlerTarget].maxHP);
    ⋮
    9991        if (gBattleMons[gBattlerTarget].status1 & (STATUS1_SLEEP | STATUS1_FREEZE))
    9992            odds *= 2;
    9993        if (gBattleMons[gBattlerTarget].status1 & (STATUS1_POISON | STATUS1_BURN | STATUS1_PARALYSIS | STATUS1_TOXIC_POISON))
    9994            odds = (odds * 15) / 10;
    ⋮
    9998            if (gLastUsedItem == ITEM_MASTER_BALL)
    ⋮
    10009        if (odds > 254) // mon caught
    ⋮
    10025            odds = Sqrt(Sqrt(16711680 / odds));
    10026            odds = 1048560 / odds;
    ⋮
    10028            for (shakes = 0; shakes < BALL_3_SHAKES_SUCCESS && Random() < odds; shakes++);
    ⋮
    
    src/battle_script_commands.c lines 9987–10031 at 731ad5b · upstream ↗
    20u16 Sqrt(u32 num);
    
    include/gba/syscall.h line 20 at 731ad5b · upstream ↗
    72	.ifdef L_Sqrt
    73	thumb_func_start Sqrt
    74Sqrt:
    75	svc #8
    
    libagbsyscall/libagbsyscall.s lines 72–78 at 731ad5b · upstream ↗
    16#define ISO_RANDOMIZE1(val) (1103515245 * (val) + 24691)
    
    include/random.h line 16 at 731ad5b · upstream ↗
    11u16 Random(void)
    ⋮
    13    gRngValue = ISO_RANDOMIZE1(gRngValue);
    ⋮
    15    return gRngValue >> 16;
    
    src/random.c lines 11–16 at 731ad5b · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Revised after a check by a second AI model, 2026-10-22.

    In the claim ledger

Sources

Code