data/claims/pokeemerald-capture.yamlPoké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
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 entryThe 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
}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 tableEmerald computes from catch rate , ball multiplier , max HP , and current HP , 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
}
}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 testsCited by: Capture
An over 254 catches at once. Otherwise Emerald turns it into a threshold , with Sqrt the GBA BIOS call, and draws up to four 16-bit Random() values; each one below 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
]
}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 when . Since 16,711,680 is 255 × 65,536 and 1,048,560 is 16 × 65,535, is close to and four passes close to , higher for small because both roots round down. A Bulbasaur (catch rate 45) with max HP 100 in a Poké Ball has and at full HP, about 6.25%, and , 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
}
}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.1975Ruby 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 maskCited 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 sameCited 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
}
}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 statsCited by: Capture