Claim set · data/claims/pokeemerald-capture.yaml

Pokémon Emerald's capture formula (pret/pokeemerald)

Game: Pokémon Emerald. Concept: Capture.

Cmd_handleballthrow reads the target species' catch rate (45 for Bulbasaur), except with a Safari Ball, which uses the Safari Zone's catch factor times 1275 / 100; the factor starts at the species' catch rate times 100 / 1275. pokeemerald-capture/catch-rate

Codepokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L9932-L9935if (gLastUsedItem == ITEM_SAFARI_BALL) catchRate = gBattleStruct->safariCatchFactor * 1275 / 100; else catchRate = gSpeciesInfo[gBattleMons[gBattlerTarget].species].catchRate;pokeemerald: decompilation of Pokémon Emerald · src/battle_main.c#L3115*(&gBattleStruct->safariCatchFactor) = gSpeciesInfo[GetMonData(&gEnemyParty[0], MON_DATA_SPECIES)].catchRate * 100 / 1275;pokeemerald: decompilation of Pokémon Emerald · src/data/pokemon/species_info.h#L39-L48[SPECIES_BULBASAUR] = … .catchRate = 45,Checked by claude, 2026-10-22 — read the catch-rate branch, the Safari factor's start in battle_main.c, and Bulbasaur's species entry

The ball multiplier is in tenths: 20 for an Ultra Ball, 15 for a Great or Safari Ball, 10 for a Poké Ball; a Net Ball 30 against a Water or Bug type, a Dive Ball 35 underwater, a Nest Ball 40 less the target's level below level 40 (at least 10), a Repeat Ball 30 for a species already caught, a Timer Ball 10 plus the turn count up to 40, and 10 for the Luxury and Premier Balls and for a Net, Dive, Nest, or Repeat Ball outside its condition. pokeemerald-capture/ball-modifier

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)",
 "luxury": 10,
 "premier": 10
}
Codepokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L840-L847// - ITEM_ULTRA_BALL skips Master Ball and ITEM_NONE static 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] = 15pokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L9937-L9985case 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: if (gBattleMons[gBattlerTarget].level < 40) { ballMultiplier = 40 - gBattleMons[gBattlerTarget].level; …Checked by claude, 2026-10-22 — read the table and every case of the switch, with the else branches that give 10; balls above the Safari Ball in include/constants/items.h L10-L21 take the switch, the four below it the table

Emerald computes a=⌊⌊Cm/10⌋(3M−2H)/3M⌋ from catch rate C, ball multiplier m, max HP M, and current HP H, then doubles it for sleep or freeze, or multiplies it by 15/10 (rounded down) for poison, bad poison, burn, or paralysis. pokeemerald-capture/odds

Value
{
 "kind": "odds",
 "maxFactor": 3,
 "hpFactor": 2,
 "ballUnit": 10,
 "status": {
  "sleepOrFreeze": 2,
  "poisonBurnParalysis": 1.5
 }
}
Codepokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L9987-L9994odds = (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;Checked by claude, 2026-10-22 — read the integer expression (odds is u32; C evaluates left to right, so the product is divided once by 3 × max HP) and both status tests

Cited by: Capture

An a over 254 catches at once. Otherwise Emerald turns it into a threshold b=⌊1048560/Sqrt(Sqrt(⌊16711680/a⌋))⌋, with Sqrt the GBA BIOS call, and draws up to four 16-bit Random() values; each one below b is a shake. Four shakes catch; a throw that fails shows the shakes it passed, 0 to 3. A Master Ball's shakes are set to four after the draw. pokeemerald-capture/shake-checks

Value
{
 "kind": "shake-checks",
 "catchAbove": 254,
 "numerator": 1048560,
 "inner": 16711680,
 "rolls": 4,
 "rollRange": [
  0,
  65535
 ]
}
Codepokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L10009-L10050if (odds > 254) // mon caught { BtlController_EmitBallThrowAnim(B_COMM_TO_CONTROLLER, BALL_3_SHAKES_SUCCESS); … else // mon may be caught, calculate shakes { u8 shakes; odds = Sqrt(Sqrt(16711680 / odds)); odds = 1048560 / odds; for (shakes = 0; shakes < BALL_3_SHAKES_SUCCESS && Random() < odds; shakes++); if (gLastUsedItem == ITEM_MASTER_BALL) shakes = BALL_3_SHAKES_SUCCESS; // why calculate the shakes before that check? …pokeemerald: decompilation of Pokémon Emerald · include/battle_controllers.h#L134-L141enum { BALL_NO_SHAKES, BALL_1_SHAKE, BALL_2_SHAKES, BALL_3_SHAKES_FAIL, BALL_3_SHAKES_SUCCESS, BALL_TRAINER_BLOCK,pokeemerald: decompilation of Pokémon Emerald · src/random.c#L11-L16u16 Random(void) … return gRngValue >> 16;pokeemerald: decompilation of Pokémon Emerald · libagbsyscall/libagbsyscall.s#L72-L78.ifdef L_Sqrt thumb_func_start Sqrt Sqrt: svc #8Checked by claude, 2026-10-22 — read the branch, the loop (it stops at the first value not below the threshold, or at BALL_3_SHAKES_SUCCESS = 4), the Master Ball override, the enum, Random's 16-bit return, and the Sqrt wrapper around BIOS call 8 (declared u16 Sqrt(u32) in include/gba/syscall.h L20)

Cited by: Capture

Computed, counting every Random() value as equally likely and taking the BIOS square root as rounding down: a throw catches with chance (b/65536)4 when a≤254. Since 16,711,680 is 255 × 65,536 and 1,048,560 is 16 × 65,535, b is close to 65535(a/255)1/4 and four passes close to a/255, higher for small a because both roots round down. A Bulbasaur (catch rate 45) with max HP 100 in a Poké Ball has a=15 and b=32767 at full HP, about 6.25%, and a=44, b=43690 at 1 HP, about 19.8%. pokeemerald-capture/chance

Value
{
 "kind": "example",
 "catchRate": 45,
 "maxHP": 100,
 "ball": "poke",
 "status": "none",
 "fullHP": 0.0625,
 "oneHP": 0.1975,
 "thresholds": {
  "full": 32767,
  "one": 43690
 }
}
Codepokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L9987-L9989odds = (catchRate * ballMultiplier / 10) * (gBattleMons[gBattlerTarget].maxHP * 3 - gBattleMons[gBattlerTarget].hp * 2) / (3 * gBattleMons[gBattlerTarget].maxHP);pokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L10025-L10028odds = Sqrt(Sqrt(16711680 / odds)); odds = 1048560 / odds; for (shakes = 0; shakes < BALL_3_SHAKES_SUCCESS && Random() < odds; shakes++);pokeemerald: decompilation of Pokémon Emerald · src/data/pokemon/species_info.h#L48.catchRate = 45,Checked by claude, 2026-10-22 — full HP: a = floor(45 × 100 / 300) = 15, floor(16711680 / 15) = 1114112, √ → 1055, √ → 32, b = floor(1048560 / 32) = 32767, (32767/65536)^4 ≈ 0.0625; 1 HP: a = floor(45 × 298 / 300) = 44, 379810 → 616 → 24, b = 43690, ≈ 0.1975

Ruby and Sapphire run the same formula and shake checks as Emerald, except that their status test leaves out bad poison (STATUS1_TOXIC_POISON, a separate status bit from poison), so a badly poisoned Pokémon gets no bonus. Emerald's test includes it. pokeemerald-capture/ruby-sapphire-toxic

About: Pokémon Ruby and Sapphire Capture

Codepokeruby: decompilation of Pokémon Ruby and Sapphire · src/battle_script_commands.c#L9452-L9456odds = (catch_rate * ball_multiplier / 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 */)) //nice one gf odds = (odds * 15) / 10;pokeruby: decompilation of Pokémon Ruby and Sapphire · src/battle_script_commands.c#L9484-L9486odds = Sqrt(Sqrt(16711680 / odds)); odds = 1048560 / odds; for (shakes = 0; shakes < 4 && Random() < odds; shakes++) {}pokeruby: decompilation of Pokémon Ruby and Sapphire · src/battle_script_commands.c#L1033-L1036static const u8 sBallCatchBonuses[] = { 20, 15, 10, 15 // Ultra, Great, Poke, Safaripokeruby: decompilation of Pokémon Ruby and Sapphire · include/constants/battle.h#L85-L89#define STATUS1_POISON (1 << 3) … #define STATUS1_TOXIC_POISON (1 << 7)pokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L9987-L9994odds = (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;pokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L10025-L10028odds = Sqrt(Sqrt(16711680 / odds)); odds = 1048560 / odds; for (shakes = 0; shakes < BALL_3_SHAKES_SUCCESS && Random() < odds; shakes++);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,Checked by claude, 2026-10-22 — compared atkEF_handleballthrow (pokeruby L9378-L9507) with Cmd_handleballthrow line by line: the same ball cases and multipliers (Ruby's Nest Ball tests level <= 39, Emerald's < 40), the same odds and shake code; the one difference in the math is the commented-out toxic bit, which pokeruby's matching build leaves out of the mask

Cited by: Capture

FireRed and LeafGreen run Emerald's formula, status test (bad poison included), and shake checks unchanged; before them, a ball thrown in a battle flagged BATTLE_TYPE_GHOST is dodged. pokeemerald-capture/firered-leafgreen-same

About: Pokémon FireRed and LeafGreen Capture

Codepokefirered: decompilation of Pokémon FireRed and LeafGreen · src/battle_script_commands.c#L9473-L9477if (gBattleTypeFlags & BATTLE_TYPE_GHOST) { BtlController_EmitBallThrowAnim(BUFFER_A, BALL_GHOST_DODGE);pokefirered: decompilation of Pokémon FireRed and LeafGreen · src/battle_script_commands.c#L9549-L9556odds = (catchRate * ballMultiplier / 10) * (gBattleMons[gBattlerTarget].maxHP * 3 - gBattleMons[gBattlerTarget].hp * 2) / (3 * gBattleMons[gBattlerTarget].maxHP); … if (gBattleMons[gBattlerTarget].status1 & (STATUS1_POISON | STATUS1_BURN | STATUS1_PARALYSIS | STATUS1_TOXIC_POISON)) odds = (odds * 15) / 10;pokefirered: decompilation of Pokémon FireRed and LeafGreen · src/battle_script_commands.c#L9587-L9590odds = Sqrt(Sqrt(16711680 / odds)); odds = 1048560 / odds; for (shakes = 0; shakes < BALL_3_SHAKES_SUCCESS && Random() < odds; shakes++);pokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L9987-L10028odds = (catchRate * ballMultiplier / 10) … odds = Sqrt(Sqrt(16711680 / odds)); odds = 1048560 / odds;Checked by claude, 2026-10-22 — diffed Cmd_handleballthrow between the pinned pokeemerald and pokefirered: besides the ghost branch, the tutorial battle types, buffer names, and two braces, the functions match, and the ball table (pokefirered L808-L814) is the same

Cited by: Capture

Computed, for a Bulbasaur (catch rate 45) with max HP 100 and no status in a Poké Ball: at full HP it is caught about 6.0% of the time in Red and Blue, 6.25% in Crystal, and 6.25% in Emerald; at 1 HP, 18.0%, 17.6%, and 19.8%. Here the three formulas land close together, though they roll twice in Red and Blue, once in Crystal, and up to four times in Emerald. pokeemerald-capture/across-generations

Value
{
 "kind": "comparison",
 "catchRate": 45,
 "maxHP": 100,
 "ball": "poke",
 "fullHP": {
  "red-blue": 0.0604,
  "crystal": 0.0625,
  "emerald": 0.0625
 },
 "oneHP": {
  "red-blue": 0.1797,
  "crystal": 0.1758,
  "emerald": 0.1975
 }
}
Codepokered: disassembly of Pokémon Red and Blue · engine/items/item_effects.asm#L185-L199.loop call Random ld b, a … cp POKE_BALL jr z, .checkForAilmentspokered: disassembly of Pokémon Red and Blue · engine/items/item_effects.asm#L241-L316; Calculate MaxHP * 255. … ; Determine BallFactor. It's 8 for Great Balls and 12 for the others. … ; Let W = ((MaxHP * 255) / BallFactor) / max(HP / 4, 1). Calculate W. … ; If Rand1 - Status > CatchRate, the ball fails to capture the Pokémon. ld a, [wEnemyMonActualCatchRate] cp b jr c, .failedToCapture … call Random ; Let this random number be called Rand2. …pokered: disassembly of Pokémon Red and Blue · data/pokemon/base_stats/bulbasaur.asm#L7db 45 ; catch ratepokecrystal: disassembly of Pokémon Crystal · engine/items/item_effects.asm#L289-L338sla c rl b … srl d rr e srl d rr e srl b rr c srl b rr c … …pokecrystal: disassembly of Pokémon Crystal · engine/items/item_effects.asm#L372-L380ld [wFinalCatchRate], a call Random cp b ld a, 0 jr z, .catch_without_fail jr nc, .fail_to_catchpokecrystal: disassembly of Pokémon Crystal · data/pokemon/base_stats/bulbasaur.asm#L7db 45 ; catch ratepokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L9987-L10028odds = (catchRate * ballMultiplier / 10) … if (odds > 254) // mon caught … odds = Sqrt(Sqrt(16711680 / odds)); odds = 1048560 / odds; for (shakes = 0; shakes < BALL_3_SHAKES_SUCCESS && Random() < odds; shakes++);pokeemerald: decompilation of Pokémon Emerald · src/battle_script_commands.c#L845[ITEM_POKE_BALL - ITEM_ULTRA_BALL] = 10,pokeemerald: decompilation of Pokémon Emerald · src/data/pokemon/species_info.h#L48.catchRate = 45,Checked by claude, 2026-10-22 — the three per-game chance claims (pokered-capture/chance, pokecrystal-capture/chance, pokeemerald-capture/chance) worked out by one script that runs each routine's integer steps; Bulbasaur's catch rate is 45 in all three repos' base stats

Cited by: Capture