On this page
  1. Time of day
  2. Wild encounters
  3. Capture
  4. Critical hits
  5. Related
  6. Mechanics
  7. References
  8. Sources

Pokémon HeartGold and SoulSilver are the Nintendo DS remakes of Pokémon Gold and Silver. The code read here is pret’s pokeheartgold decompilation, a work in progress that builds the US HeartGold and SoulSilver ROMs from the same code. Each system below is compared with Platinum’s code and with Gold and Silver’s or Crystal’s.

#Time of day

The clock’s hour falls into one of Platinum’s periods, read from the same hour table (5 periods, Platinum's table1)1. Gold and Silver have fewer periods, and their night starts earlier1. Wild Pokémon fold the periods back into morning, day, and night, and the clock-bound evolution methods ask only whether it is night, night from 20:00 to 3:592,2.

#Wild encounters

A step on grass or water rolls twice. The first roll must pass a gate that depends on how the player moves, on very tall grass, on the radio, and on how often the player has turned to face the other way; the second must pass the map’s own rate3. Platinum’s gate is simpler, and for its first attempts after a battle or a map change Platinum adds a grace roll; HeartGold and SoulSilver skip those checks outright3. Gold and Silver have a single roll against a rate that changes with the time of day4,5. Abilities, the flutes, and the Cleanse Tag then adjust the map’s rate6.

The slot odds are Platinum’s, 20 · 20 · 10 · 10 · 10 · 10 · 5 · 5 · 4 · 4 · 1 · 1 %7 over a map’s grass slots7. The time of day picks the species in those slots from a full list for each period. Platinum swaps only a pair of its slots, and Gold and Silver hold a smaller set for each period5.

#Capture

Capture is Platinum’s formula and shake checks, unchanged8. The Apricorn balls, which Crystal also has, change the catch rate itself before the formula runs9. Crystal’s Level Ball skips the HP and status steps, and its disassembly marks the Moon, Love, and Fast Balls as bugged. In the remake every Apricorn ball goes through the HP and status steps, the Moon Ball reads a species list, the Love Ball wants the opposite gender, and the Fast Ball reads base Speed9. The Heavy Ball gains a bug of its own, −20 below 204.8 kg (Crystal −20 below 102.4 kg)10, because its last test reads the catch rate in place of the weight10.

#Critical hits

Critical hits keep Platinum’s stages, abilities, and Lucky Chant11.

Mechanics

MechanicKindHow
Time of dayExternal clockPlatinum's five periods from the DS clock's hour, folded to three for wild Pokémon; night starts at 20:00
Random encounterBy areaa gate set by movement, very tall grass, the radio, and turning in place, then the map's one rate; 12 slots whose species change with the time of day
CaptureFour shake checksPlatinum's formula and four shake checks; the Apricorn balls change the catch rate itself
Critical hitFrom stagesPlatinum's stage table, with Super Luck, Lucky Chant, and Sniper
Abilitylead-Pokémon abilities that double or halve the encounter rate, and the critical-hit abilities
Held itema Cleanse Tag or Pure Incense on the lead Pokémon cuts the encounter rate to two thirds
Every claim about this game, by concept (10)
CaptureHeartGold and SoulSilver's BattleSystem_CalculateBallShakes keeps Platinum's capture math: a=⌊⌊Cm/10⌋(3M−2H)/3M⌋ for catch rate C, ball multiplier m in tenths, max HP M and current HP H, ×2 for sleep or freeze and ×15/10 for burn, paralysis, poison, or bad poison, a catch at once when a is 255 or more, and otherwise four 16-bit rolls each below ⌊1048560/⌊16711680/a⌋⌋, with the Master Ball's shakes set to four after the draw. The Ultra, Great, Poké, and Safari Balls keep Platinum's 20, 15, 10, and 15 (an assembly data table in the decompilation), and a Pal Park or tutorial battle always catches.Code
In HeartGold and SoulSilver the Apricorn balls change the catch rate itself and leave the multiplier at 10, after which the rate is capped at 255 (a negative one becomes 1) and goes through the usual HP and status steps: the Fast Ball ×4 when the wild species' base Speed is 100 or more, the Level Ball ×2 when the attacking battler's (battlerIdAttacker) level is above the wild Pokémon's, ×4 when half its level (rounded down) is above it, and ×8 when a quarter of its level (rounded down) is, the Lure Ball ×3 when fishing, the Love Ball ×8 for the same species of the opposite gender, and the Moon Ball ×4 for the 14 species of the Nidoran, Clefairy, Jigglypuff, and Skitty families listed in an assembly data table. Crystal's Level Ball skips the HP and status steps, and its disassembly's comments mark the Moon Ball as never boosting, the Love Ball as boosting the same gender, and the Fast Ball as boosting only three species.Code
HeartGold and SoulSilver's Heavy Ball adds 40 to the catch rate at 409.6 kg or more, 30 at 307.2 kg, and 20 at 204.8 kg, reading weight in tenths of a kilogram from the Pokédex data. Below 204.8 kg its last branch tests the catch rate against 1024 instead of the weight, and a catch rate is at most 255, so every lighter Pokémon loses 20 (a result below 0 becomes 1); a comment in the decompilation calls this a mistake. Crystal subtracts 20 only below 102.4 kg and adds nothing from there to 204.8 kg.Code
Critical hitHeartGold and SoulSilver's TryCriticalHit keeps Platinum's critical-hit rule: the stage sum (+2 Focus Energy, +1 for a HOLD_EFFECT_CRITRATE_UP item, +1 Super Luck, +2 for Chansey or Farfetch'd with its own item, plus the move's boosts) capped at 4, a chance of 1 in 16, 8, 4, 3, or 2, blocked by Battle Armor or Shell Armor unless ignored, by Lucky Chant on the target's side, or by bit 15 of the target's move-effect flags (named MOVE_EFFECT_FLAG_LUCKY_CHANT here), and Sniper turning ×2 into ×3.Code
Random encounterWhen a step ends on grass or water, HeartGold and SoulSilver roll twice, each LCRandom() % 100. The first roll must fall below a gate that starts at 40, loses 20 unless the player is cycling or surfing or an unnamed avatar check (sub_0205DE98, still assembly) passes, gains 40 in very tall grass or else 30 when cycling, gains 30, 40, or 60 once a counter of reversed facings (reverseTurnFrameSteps) reaches 2, 3, or 4, and gains or loses 25 for the Pokémon March or the Pokémon Lullaby on the radio, capped at 100. The second must fall below the map's own rate for that terrain, capped at 100. A check comes only once a step counter (encounterInhibitSteps), which a wild battle or a warp clears, is past 3. Platinum's gate is 40, plus 30 in very tall grass or when cycling, adjusted on special dates, with no walking penalty, facing counter, or radio. Before its gate, Platinum adds a grace roll: while its count of attempts is below 8−min⁡(⌊r/10⌋,8) for the rate r it is passed (computed), each attempt adds 1 to the count and goes on only if LCRNG_RandMod(100) is below 5; a comment says this covers the first attempts after each map transition or wild battle. HeartGold and SoulSilver's check has no such roll; they skip the check outright while the step counter is 3 or less.Code
Before the two rolls, HeartGold and SoulSilver adjust the map's rate in this order: the lead Pokémon's ability (unless it is an Egg) doubles it for Arena Trap, No Guard, or Illuminate, halves it for White Smoke, Quick Feet, or Stench, or for Snow Cloak in snow, capped at 100; the Black Flute halves it and the White Flute raises it by half; and a Cleanse Tag or Pure Incense held by the lead Pokémon cuts it to 2/3. Gold and Silver instead double the rate for the Pokémon March or the Ruins of Alph radio and halve it for the Pokémon Lullaby or a Cleanse Tag.Code
HeartGold and SoulSilver have 12 grass slots and 5 surfing slots, picked by one LCRandom() % 100 against bounds in EncounterSlot_WildMonSlotRoll_Land and EncounterSlot_WildMonSlotRoll_Surfing, so they come up 20, 20, 10, 10, 10, 10, 5, 5, 4, 4, 1, and 1 percent and 60, 30, 5, 4, and 1 percent (computed from the bounds). Platinum's GetGroundEncounterSlot and GetWaterEncounterSlot have the same bounds. Gold and Silver have 7 grass slots and 3 water slots, with cumulative bounds in GrassMonProbTable (30, 60, 80, 90, 95, 99, 100) and WaterMonProbTable (60, 90, 100), so 30, 30, 20, 10, 5, 4, and 1 percent and 60, 30, and 10 percent (computed from the differences).Code
A HeartGold or SoulSilver map's encounter record holds one walking rate and three full species lists for its 12 grass slots, morning, day, and night, sharing one list of levels. Gold and Silver keep a separate grass rate for each time of day and three sets of 7 slots. Platinum keeps one 12-slot list and swaps only slots 2 and 3 for their day or night species outside the morning.Code
Time of dayHeartGold and SoulSilver (the US releases, which share this code) split the clock's hour into five periods with a 24-entry table: morning from 4:00 to 9:59, day from 10:00 to 16:59, evening from 17:00 to 19:59, night from 20:00 to 23:59, and late night from 0:00 to 3:59. Platinum's table gives every hour the same period (its names are morning, day, twilight, night, and late night). Gold and Silver have three periods: morning from 4:00, day from 10:00, and night from 18:00.Code
HeartGold and SoulSilver fold the five periods into three for wild Pokémon (GF_RTC_GetTimeOfDayWildParamByHour: morning; day or evening; night or late night), and grass encounters pick their species list the same way. IsNighttime is true at night or late night, so from 20:00 to 3:59 (computed from the hour table), and the evolution methods tied to the clock read it: friendship of at least 220 by day (EVO_FRIENDSHIP_DAY) or by night (EVO_FRIENDSHIP_NIGHT), and a held item by day or by night (EVO_ITEM_DAY, EVO_ITEM_NIGHT).Code

References

  1. ^ abcHeartGold and SoulSilver (the US releases, which share this code) split the clock's hour into five periods with a 24-entry table: morning from 4:00 to 9:59, day from 10:00 to 16:59, evening from 17:00 to 19:59, night from 20:00 to 23:59, and late night from 0:00 to 3:59. Platinum's table gives every hour the same period (its names are morning, day, twilight, night, and late night). Gold and Silver have three periods: morning from 4:00, day from 10:00, and night from 18:00.Codepret/pokeheartgold · pret/pokeplatinum · pret/pokegold
    How we know
    8typedef enum RTC_TimeOfDay {
    9    RTC_TIMEOFDAY_MORN = 0,
    10    RTC_TIMEOFDAY_DAY,
    11    RTC_TIMEOFDAY_EVE,
    12    RTC_TIMEOFDAY_NITE,
    13    RTC_TIMEOFDAY_LATE,
    
    include/gf_rtc.h lines 8–15 at 6988158 · upstream ↗
    164TIMEOFDAY GF_RTC_GetTimeOfDayByHour(s32 hour) {
    165    static const u8 sTimeOfDayByHour[24] = {
    166        RTC_TIMEOFDAY_LATE,
    167        RTC_TIMEOFDAY_LATE,
    168        RTC_TIMEOFDAY_LATE,
    169        RTC_TIMEOFDAY_LATE,
    170        RTC_TIMEOFDAY_MORN,
    171        RTC_TIMEOFDAY_MORN,
    172        RTC_TIMEOFDAY_MORN,
    173        RTC_TIMEOFDAY_MORN,
    174        RTC_TIMEOFDAY_MORN,
    175        RTC_TIMEOFDAY_MORN,
    ⋮
    
    src/gf_rtc.c lines 164–193 at 6988158 · upstream ↗
    153enum TimeOfDay TimeOfDayForHour(int hour)
    ⋮
    155    static const u8 lookup[24] = {
    156        TIMEOFDAY_LATE_NIGHT,
    157        TIMEOFDAY_LATE_NIGHT,
    158        TIMEOFDAY_LATE_NIGHT,
    159        TIMEOFDAY_LATE_NIGHT,
    160        TIMEOFDAY_MORNING,
    161        TIMEOFDAY_MORNING,
    162        TIMEOFDAY_MORNING,
    163        TIMEOFDAY_MORNING,
    164        TIMEOFDAY_MORNING,
    165        TIMEOFDAY_MORNING,
    ⋮
    
    src/rtc.c lines 153–183 at 49f78f3 · upstream ↗
    37DEF MORN_HOUR EQU 4  ; 4 AM
    38DEF DAY_HOUR  EQU 10 ; 10 AM
    39DEF NITE_HOUR EQU 18 ; 6 PM
    
    constants/misc_constants.asm lines 37–39 at fc702b6 · 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. ^ abHeartGold and SoulSilver fold the five periods into three for wild Pokémon (GF_RTC_GetTimeOfDayWildParamByHour: morning; day or evening; night or late night), and grass encounters pick their species list the same way. IsNighttime is true at night or late night, so from 20:00 to 3:59 (computed from the hour table), and the evolution methods tied to the clock read it: friendship of at least 220 by day (EVO_FRIENDSHIP_DAY) or by night (EVO_FRIENDSHIP_NIGHT), and a held item by day or by night (EVO_ITEM_DAY, EVO_ITEM_NIGHT).Codepret/pokeheartgold
    How we know
    142BOOL IsNighttime(void) {
    143    switch (GF_RTC_GetTimeOfDay()) {
    144    case RTC_TIMEOFDAY_NITE:
    145    case RTC_TIMEOFDAY_LATE:
    146        return TRUE;
    147    default:
    148        return FALSE;
    
    src/gf_rtc.c lines 142–150 at 6988158 · upstream ↗
    196TimeOfDayWildParam GF_RTC_GetTimeOfDayWildParamByHour(s32 hour) {
    197    switch (GF_RTC_GetTimeOfDayByHour(hour)) {
    198    case RTC_TIMEOFDAY_MORN:
    199        return TIMEOFDAY_WILD_MORN;
    200    case RTC_TIMEOFDAY_DAY:
    201    case RTC_TIMEOFDAY_EVE:
    202        return TIMEOFDAY_WILD_DAY;
    203    default:
    204        return TIMEOFDAY_WILD_NITE;
    
    src/gf_rtc.c lines 196–205 at 6988158 · upstream ↗
    119static void EncSlotArray_Init_Land(const EncounterData *encData, EncounterSlot *encSlots) {
    120    TIMEOFDAY timeOfDay = GF_RTC_GetTimeOfDay();
    ⋮
    123    if (timeOfDay == RTC_TIMEOFDAY_MORN) {
    124        slotSpecies = encData->landSlots.species_morn;
    125    } else if (timeOfDay == RTC_TIMEOFDAY_DAY || timeOfDay == RTC_TIMEOFDAY_EVE) {
    126        slotSpecies = encData->landSlots.species_day;
    127    } else if (timeOfDay == RTC_TIMEOFDAY_NITE || timeOfDay == RTC_TIMEOFDAY_LATE) {
    128        slotSpecies = encData->landSlots.species_nite;
    
    src/field/encounter_check.c lines 119–130 at 6988158 · upstream ↗
    2829            case EVO_FRIENDSHIP_DAY:
    2830                if (IsNighttime() == 0 && friendship >= 220) {
    ⋮
    2835            case EVO_FRIENDSHIP_NIGHT:
    2836                if (IsNighttime() == 1 && friendship >= 220) {
    
    src/pokemon.c lines 2829–2839 at 6988158 · upstream ↗
    2902            case EVO_ITEM_DAY:
    2903                if (IsNighttime() == 0 && evoTable[i].param == heldItem) {
    ⋮
    2908            case EVO_ITEM_NIGHT:
    2909                if (IsNighttime() == 1 && evoTable[i].param == heldItem) {
    
    src/pokemon.c lines 2902–2909 at 6988158 · 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

  3. ^ abWhen a step ends on grass or water, HeartGold and SoulSilver roll twice, each LCRandom() % 100. The first roll must fall below a gate that starts at 40, loses 20 unless the player is cycling or surfing or an unnamed avatar check (sub_0205DE98, still assembly) passes, gains 40 in very tall grass or else 30 when cycling, gains 30, 40, or 60 once a counter of reversed facings (reverseTurnFrameSteps) reaches 2, 3, or 4, and gains or loses 25 for the Pokémon March or the Pokémon Lullaby on the radio, capped at 100. The second must fall below the map's own rate for that terrain, capped at 100. A check comes only once a step counter (encounterInhibitSteps), which a wild battle or a warp clears, is past 3. Platinum's gate is 40, plus 30 in very tall grass or when cycling, adjusted on special dates, with no walking penalty, facing counter, or radio. Before its gate, Platinum adds a grace roll: while its count of attempts is below 8−min⁡(⌊r/10⌋,8) for the rate r it is passed (computed), each attempt adds 1 to the count and goes on only if LCRNG_RandMod(100) is below 5; a comment says this covers the first attempts after each map transition or wild battle. HeartGold and SoulSilver's check has no such roll; they skip the check outright while the step counter is 3 or less.Codepret/pokeheartgold · pret/pokeplatinumrevised
    How we know
    565static BOOL FieldSystem_EncounterRateRoll(FieldSystem *fieldSystem, u8 encounterRate, u8 metatileBehavior) {
    566    if (encounterRate > 100) {
    567        encounterRate = 100;
    ⋮
    569    int avatarState = PlayerAvatar_GetState(fieldSystem->playerAvatar);
    570    u8 modifiedRate = 40;
    571    if (avatarState != PLAYER_STATE_CYCLING && avatarState != PLAYER_STATE_SURFING && !sub_0205DE98(fieldSystem->playerAvatar)) {
    572        modifiedRate -= 20;
    ⋮
    574    if (MetatileBehavior_IsVeryTallGrass(metatileBehavior)) {
    575        modifiedRate += 40;
    576    } else if (avatarState == PLAYER_STATE_CYCLING) {
    577        modifiedRate += 30;
    ⋮
    579    modifiedRate += getStepCountEncounterRateBoost(fieldSystem->reverseTurnFrameSteps);
    ⋮
    
    src/field/encounter_check.c lines 565–590 at 6988158 · upstream ↗
    595static u8 getStepCountEncounterRateBoost(u16 stepCount) {
    ⋮
    598    if (stepCount >= 4) {
    599        result = 60;
    600    } else if (stepCount >= 3) {
    601        result = 40;
    602    } else if (stepCount >= 2) {
    603        result = 30;
    
    src/field/encounter_check.c lines 595–607 at 6988158 · upstream ↗
    623static BOOL FieldSystem_SecondEncounterRoll(FieldSystem *fieldSystem, u16 encounterRate) {
    ⋮
    626    if (LCRandRange(100) < encounterRate) {
    
    src/field/encounter_check.c lines 623–627 at 6988158 · upstream ↗
    30static inline u16 LCRandRange(const u16 maximum) {
    ⋮
    35        u16 result = LCRandom() % maximum;
    
    include/math_util.h lines 30–35 at 6988158 · upstream ↗
    1397static void FieldSystem_UpdateTurnFrameCounter(FieldSystem *fieldSystem) {
    1398    u8 facingDirection = PlayerAvatar_GetFacingDirection(fieldSystem->playerAvatar);
    ⋮
    1401    case DIR_NORTH:
    1402        if (fieldSystem->lastFacingDirection == DIR_SOUTH) {
    1403            isTurnFrame = TRUE;
    ⋮
    1408            isTurnFrame = TRUE;
    ⋮
    1413            isTurnFrame = TRUE;
    ⋮
    1418            isTurnFrame = TRUE;
    ⋮
    1425    if (isTurnFrame && fieldSystem->reverseTurnFrameSteps < 0xFFFF) {
    1426        ++fieldSystem->reverseTurnFrameSteps;
    ⋮
    1428    fieldSystem->lastFacingDirection = facingDirection;
    
    src/field/encounter_check.c lines 1397–1428 at 6988158 · upstream ↗
    215BOOL FieldSystem_PerformLandOrSurfEncounterCheck(FieldSystem *fieldSystem) {
    216    if (fieldSystem->encounterInhibitSteps <= 3) {
    217        return FALSE;
    ⋮
    230    encounterRate = FieldSystem_GetTileEncounterTypeAndRate(fieldSystem, metatileBehavior, &encounterType);
    231    if (encounterRate == 0) {
    232        return FALSE;
    ⋮
    234    FieldSystem_UpdateTurnFrameCounter(fieldSystem);
    
    src/field/encounter_check.c lines 215–234 at 6988158 · upstream ↗
    315    if (found) {
    316        sub_02050B08(fieldSystem, battleSetup);
    317        fieldSystem->encounterInhibitSteps = 0;
    318        fieldSystem->reverseTurnFrameSteps = 0;
    
    src/field/encounter_check.c lines 315–318 at 6988158 · upstream ↗
    268    fieldSystem->encounterInhibitSteps = 0;
    269    fieldSystem->reverseTurnFrameSteps = 0;
    
    src/field_warp_tasks.c lines 268–269 at 6988158 · upstream ↗
    471static BOOL FieldSystem_CheckWildEncounter(FieldSystem *fieldSystem) {
    472    if (fieldSystem->encounterInhibitSteps < 0xFFFF) {
    473        fieldSystem->encounterInhibitSteps++;
    
    src/field/field_control.c lines 471–474 at 6988158 · upstream ↗
    1031static int FieldSystem_GetLandEncounterRate(FieldSystem *fieldSystem) {
    1032    const EncounterData *encData = MapEvents_GetLoadedEncTable(fieldSystem);
    1033    return encData->encounterRate_walking;
    ⋮
    1037    const EncounterData *encData = MapEvents_GetLoadedEncTable(fieldSystem);
    1038    return encData->encounterRate_surfing;
    
    src/field/encounter_check.c lines 1031–1039 at 6988158 · upstream ↗
    763    // flat 60% chance for the encounter attempt to fail, which is decreased by long grass, biking, and on certain dates.
    764    u8 flatEncounterRate = 40;
    ⋮
    766    if (TileBehavior_IsVeryTallGrass(tileBehavior)) {
    767        flatEncounterRate += 30;
    768    } else if (PlayerAvatar_GetPlayerState(fieldSystem->playerAvatar) == PLAYER_AVATAR_CYCLING) {
    769        flatEncounterRate += 30;
    ⋮
    772    flatEncounterRate = SpecialDates_ModifyEncounterRate(flatEncounterRate, FieldSystem_HasPenalty(fieldSystem));
    ⋮
    778    return LCRNG_RandMod(100) < flatEncounterRate && CheckEncounterRateSuccess(fieldSystem, encounterRate);
    
    src/overlay006/wild_encounters.c lines 763–778 at 49f78f3 · upstream ↗
    750static BOOL ShouldGetRandomEncounter(FieldSystem *fieldSystem, const u32 encounterRate, const u8 tileBehavior)
    ⋮
    752    u32 encRate = encounterRate << 8;
    ⋮
    754    // lowers effective encounter rate by 95% for the first few steps after each encounter.
    755    if (!GracePeriodStepsUsed(fieldSystem, encRate)) {
    756        fieldSystem->wildBattleMetadata.encounterAttempts++;
    ⋮
    758        if (LCRNG_RandMod(100) >= 5) {
    759            return FALSE;
    
    src/overlay006/wild_encounters.c lines 750–761 at 49f78f3 · upstream ↗
    797// Suppresses the first few encounter attempts after each map transition or wild battle.
    798static BOOL GracePeriodStepsUsed(FieldSystem *fieldSystem, u32 encounterRate)
    ⋮
    800    encounterRate = (encounterRate / 10) >> 8;
    ⋮
    802    if (encounterRate > 8) {
    803        encounterRate = 8;
    ⋮
    806    encounterRate = 8 - (encounterRate); // higher encounter rates also lower the grace period.
    ⋮
    808    return fieldSystem->wildBattleMetadata.encounterAttempts >= encounterRate;
    
    src/overlay006/wild_encounters.c lines 797–808 at 49f78f3 · 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

  4. ^On each step, Gold and Silver's TryWildEncounter reads the map's encounter rate for the current time of day (morning, day, or night), or its water rate, doubles it for the Pokémon March or Ruins of Alph radio, halves it for the Pokémon Lullaby or a Cleanse Tag, and starts a battle when Random is below it; Crystal keeps this unchanged.Codepret/pokegold · pret/pokecrystal
    How we know
    212.EncounterRate:
    213	call GetMapEncounterRate
    214	call ApplyMusicEffectOnEncounterRate
    215	call ApplyCleanseTagEffectOnEncounterRate
    216	call Random
    217	cp b
    218	ret
    ⋮
    220GetMapEncounterRate:
    221	ld hl, wMornEncounterRate
    222	call CheckOnWater
    223	ld a, wWaterEncounterRate - wMornEncounterRate
    224	jr z, .ok
    ⋮
    
    engine/overworld/wildmons.asm lines 212–251 at fc702b6 · upstream ↗
    engine/overworld/wildmons.asm#L176-L251 · upstream ↗

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

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

    In the claim ledger

  5. ^ abA HeartGold or SoulSilver map's encounter record holds one walking rate and three full species lists for its 12 grass slots, morning, day, and night, sharing one list of levels. Gold and Silver keep a separate grass rate for each time of day and three sets of 7 slots. Platinum keeps one 12-slot list and swaps only slots 2 and 3 for their day or night species outside the morning.Codepret/pokeheartgold · pret/pokegold · pret/pokeplatinum
    How we know
    23typedef struct LandEncounterData {
    24    u8 levels[NUM_ENCOUNTERS_LAND]; // shared between times of day
    25    u16 species_morn[NUM_ENCOUNTERS_LAND];
    26    u16 species_day[NUM_ENCOUNTERS_LAND];
    27    u16 species_nite[NUM_ENCOUNTERS_LAND];
    28} LandEncounterData;
    
    include/wild_encounter.h lines 23–28 at 6988158 · upstream ↗
    36typedef struct EncounterData {
    37    u8 encounterRate_walking;                                   // 0x00
    38    u8 encounterRate_surfing;                                   // 0x01
    ⋮
    44    LandEncounterData landSlots;                                // 0x08
    
    include/wild_encounter.h lines 36–44 at 6988158 · upstream ↗
    133    for (int i = 0; i < NUM_ENCOUNTERS_LAND; ++i) {
    134        encSlots[i].species = slotSpecies[i];
    135        encSlots[i].maxLevel = encData->landSlots.levels[i];
    136        encSlots[i].minLevel = encData->landSlots.levels[i];
    
    src/field/encounter_check.c lines 133–136 at 6988158 · upstream ↗
    220GetMapEncounterRate:
    221	ld hl, wMornEncounterRate
    222	call CheckOnWater
    223	ld a, wWaterEncounterRate - wMornEncounterRate
    224	jr z, .ok
    225	ld a, [wTimeOfDay]
    226.ok
    
    engine/overworld/wildmons.asm lines 212–251 at fc702b6 · upstream ↗
    283	ld a, [wTimeOfDay]
    284	ld bc, NUM_GRASSMON * 2
    285	call AddNTimes
    
    engine/overworld/wildmons.asm lines 283–286 at fc702b6 · upstream ↗
    181// Default encounters are morning. They get replaced by this if it is not morning.
    182void WildEncounters_ReplaceTimedEncounters(const WildEncounters *encounterData, int *timedSlot1, int *timedSlot2)
    ⋮
    184    int timeOfDay = GetTimeOfDay();
    ⋮
    186    if (timeOfDay == TIMEOFDAY_DAY || timeOfDay == TIMEOFDAY_TWILIGHT) {
    187        *timedSlot1 = encounterData->dayEncounters[0];
    ⋮
    189    } else if (timeOfDay == TIMEOFDAY_NIGHT || timeOfDay == TIMEOFDAY_LATE_NIGHT) {
    190        *timedSlot1 = encounterData->nightEncounters[0];
    
    src/overlay006/wild_encounters.c lines 181–193 at 49f78f3 · upstream ↗
    334        WildEncounters_ReplaceTimedEncounters(encounterData, &encounterTable[2].species, &encounterTable[3].species);
    
    src/overlay006/wild_encounters.c line 334 at 49f78f3 · 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

  6. ^Before the two rolls, HeartGold and SoulSilver adjust the map's rate in this order: the lead Pokémon's ability (unless it is an Egg) doubles it for Arena Trap, No Guard, or Illuminate, halves it for White Smoke, Quick Feet, or Stench, or for Snow Cloak in snow, capped at 100; the Black Flute halves it and the White Flute raises it by half; and a Cleanse Tag or Pure Incense held by the lead Pokémon cuts it to 2/3. Gold and Silver instead double the rate for the Pokémon March or the Ruins of Alph radio and halve it for the Pokémon Lullaby or a Cleanse Tag.Codepret/pokeheartgold · pret/pokegold
    How we know
    244    encounterRate = ApplyAbilityEffectToEncounterRate(FALSE, encounterRate, &encounterGen, LocalFieldData_GetWeatherType(Save_LocalFieldData_Get(fieldSystem->saveData)), leadMon);
    245    ApplyFluteEffectToEncounterRate(fieldSystem, &encounterRate);
    246    ApplyLeadMonHeldItemEffectToEncounterRate(leadMon, &encounterRate);
    247    BOOL ret = FieldSystem_EncounterRateRoll(fieldSystem, encounterRate, metatileBehavior) ? TRUE : FALSE;
    
    src/field/encounter_check.c lines 244–247 at 6988158 · upstream ↗
    1123    if (!encounterGen->isEgg) {
    ⋮
    1126                ret *= 2;
    ⋮
    1128        } else if (encounterGen->ability == ABILITY_ARENA_TRAP || encounterGen->ability == ABILITY_NO_GUARD || encounterGen->ability == ABILITY_ILLUMINATE) {
    1129            ret *= 2;
    1130        } else if (encounterGen->ability == ABILITY_SNOW_CLOAK) {
    1131            if (weatherType == WEATHER_SNOW) {
    1132                ret /= 2;
    ⋮
    1134        } else if (encounterGen->ability == ABILITY_WHITE_SMOKE || encounterGen->ability == ABILITY_QUICK_FEET || encounterGen->ability == ABILITY_STENCH) {
    1135            ret /= 2;
    ⋮
    1137        if (ret > 100) {
    1138            ret = 100;
    
    src/field/encounter_check.c lines 1120–1140 at 6988158 · upstream ↗
    720    if (heldItem == ITEM_CLEANSE_TAG || heldItem == ITEM_PURE_INCENSE) {
    721        *pEncounterRate = *pEncounterRate * 2 / 3;
    ⋮
    727    if (flute == FLUTE_BLACK) {
    728        *pEncounterRate /= 2;
    729    } else if (flute == FLUTE_WHITE) {
    730        *pEncounterRate += *pEncounterRate / 2;
    
    src/field/encounter_check.c lines 718–731 at 6988158 · upstream ↗
    214	call ApplyMusicEffectOnEncounterRate
    215	call ApplyCleanseTagEffectOnEncounterRate
    ⋮
    234; Pokemon March and Ruins of Alph signal double encounter rate.
    235; Pokemon Lullaby halves encounter rate.
    ⋮
    251; Cleanse Tag halves encounter rate.
    
    engine/overworld/wildmons.asm lines 212–251 at fc702b6 · 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

  7. ^ abHeartGold and SoulSilver have 12 grass slots and 5 surfing slots, picked by one LCRandom() % 100 against bounds in EncounterSlot_WildMonSlotRoll_Land and EncounterSlot_WildMonSlotRoll_Surfing, so they come up 20, 20, 10, 10, 10, 10, 5, 5, 4, 4, 1, and 1 percent and 60, 30, 5, 4, and 1 percent (computed from the bounds). Platinum's GetGroundEncounterSlot and GetWaterEncounterSlot have the same bounds. Gold and Silver have 7 grass slots and 3 water slots, with cumulative bounds in GrassMonProbTable (30, 60, 80, 90, 95, 99, 100) and WaterMonProbTable (60, 90, 100), so 30, 30, 20, 10, 5, 4, and 1 percent and 60, 30, and 10 percent (computed from the differences).Codepret/pokeheartgold · pret/pokeplatinum · pret/pokegoldrevised
    How we know
    632static u8 EncounterSlot_WildMonSlotRoll_Land(void) {
    633    u8 rnd = LCRandRange(100);
    ⋮
    635    if (rnd < 20) {
    636        return 0;
    637    } else if (rnd >= 20 && rnd < 40) {
    638        return 1;
    ⋮
    640        return 2;
    ⋮
    642        return 3;
    ⋮
    644        return 4;
    ⋮
    651    } else if (rnd >= 90 && rnd < 94) {
    652        return 8;
    653    } else if (rnd >= 94 && rnd < 98) {
    ⋮
    
    src/field/encounter_check.c lines 632–676 at 6988158 · upstream ↗
    820static u8 GetGroundEncounterSlot(void)
    ⋮
    822    u8 roll = LCRNG_RandMod(100);
    ⋮
    824    if (roll < 20) {
    825        return 0;
    ⋮
    827        return 1;
    ⋮
    829        return 2;
    ⋮
    831        return 3;
    ⋮
    833        return 4;
    ⋮
    842    } else if (roll >= 94 && roll < 98) {
    843        return 9;
    844    } else if (roll == 98) {
    845        return 10;
    ⋮
    
    src/overlay006/wild_encounters.c lines 820–866 at 49f78f3 · upstream ↗
    6GrassMonProbTable:
    7	table_width 2
    8	mon_prob 30,  0 ; 30% chance
    9	mon_prob 60,  1 ; 30% chance
    10	mon_prob 80,  2 ; 20% chance
    11	mon_prob 90,  3 ; 10% chance
    12	mon_prob 95,  4 ;  5% chance
    13	mon_prob 99,  5 ;  4% chance
    14	mon_prob 100, 6 ;  1% chance
    ⋮
    17WaterMonProbTable:
    18	table_width 2
    19	mon_prob 60,  0 ; 60% chance
    ⋮
    
    data/wild/probabilities.asm lines 6–21 at fc702b6 · 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

  8. ^HeartGold and SoulSilver's BattleSystem_CalculateBallShakes keeps Platinum's capture math: a=⌊⌊Cm/10⌋(3M−2H)/3M⌋ for catch rate C, ball multiplier m in tenths, max HP M and current HP H, ×2 for sleep or freeze and ×15/10 for burn, paralysis, poison, or bad poison, a catch at once when a is 255 or more, and otherwise four 16-bit rolls each below ⌊1048560/⌊16711680/a⌋⌋, with the Master Ball's shakes set to four after the draw. The Ultra, Great, Poké, and Safari Balls keep Platinum's 20, 15, 10, and 15 (an assembly data table in the decompilation), and a Pal Park or tutorial battle always catches.Codepret/pokeheartgold · pret/pokeplatinum
    How we know
    7145static u32 BattleSystem_CalculateBallShakes(BattleSystem *bsys, BattleContext *ctx) {
    ⋮
    7150    if (BattleSystem_GetBattleType(bsys) & (BATTLE_TYPE_PAL_PARK | BATTLE_TYPE_TUTORIAL)) {
    7151        return BALL_SHAKE_MAX;
    
    src/battle/battle_command.c lines 7145–7152 at 6988158 · upstream ↗
    7290    } else {
    7291        // Item IDs from 2-5: Ultra Ball, Great Ball, Pokeball, Safari Ball.
    7292        ballMultiplier = sStandardBallCatchRates[ctx->itemTemp - 2];
    ⋮
    7295    s32 maxHpTimes3 = ctx->battleMons[ctx->battlerIdTarget].maxHp * 3;
    7296    u32 lostHp = maxHpTimes3 - (ctx->battleMons[ctx->battlerIdTarget].hp * 2);
    ⋮
    7300    u32 baseCatchMultiplier = catchRate * ballMultiplier;
    7301    u32 modifiedCatchRate = ((baseCatchMultiplier / 10) * lostHp) / maxHpTimes3;
    ⋮
    7303    s32 status = ctx->battleMons[ctx->battlerIdTarget].status;
    7304    if ((STATUS_SLEEP | STATUS_FREEZE) & status) {
    7305        modifiedCatchRate *= 2;
    ⋮
    7307    if ((STATUS_BURN | STATUS_PARALYSIS | STATUS_BAD_POISON | STATUS_POISON) & status) {
    7308        modifiedCatchRate = (modifiedCatchRate * 15) / 10;
    ⋮
    
    src/battle/battle_command.c lines 7290–7327 at 6988158 · upstream ↗
    721#define BALL_SHAKE_MAX 4
    
    include/constants/battle.h line 721 at 6988158 · upstream ↗
    17sStandardBallCatchRates: ; 0x0226C2EC
    18	.byte 20, 15, 10, 15
    
    asm/overlay_12_battle_command.s lines 17–18 at 6988158 · upstream ↗
    1112u16 BattleSystem_Random(BattleSystem *battleSystem) {
    1113    battleSystem->rand = battleSystem->rand * 1103515245L + 24691;
    1114    return battleSystem->rand / 65536L;
    
    src/battle/battle_system.c lines 1112–1114 at 6988158 · 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 ↗
    10991    if (catchRate >= 255) {
    10992        shakes = BALL_3_SHAKES_SUCCESS;
    10993    } else {
    10994        u32 sqrtRate = (0xFF << 16) / catchRate;
    ⋮
    11003        catchRate = (0xFFFF << 4) / catchRate;
    ⋮
    11011        if (battleCtx->msgItemTemp == ITEM_MASTER_BALL) {
    11012            shakes = BALL_3_SHAKES_SUCCESS;
    
    src/battle/battle_script.c lines 10990–11013 at 49f78f3 · upstream ↗
    10868static const u8 sBasicBallMod[] = {
    10869    20, // ITEM_ULTRA_BALL
    10870    15, // ITEM_GREAT_BALL
    10871    10, // ITEM_POKE_BALL
    10872    15, // ITEM_SAFARI_BALL
    
    src/battle/battle_script.c lines 10868–10873 at 49f78f3 · 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

  9. ^ abIn HeartGold and SoulSilver the Apricorn balls change the catch rate itself and leave the multiplier at 10, after which the rate is capped at 255 (a negative one becomes 1) and goes through the usual HP and status steps: the Fast Ball ×4 when the wild species' base Speed is 100 or more, the Level Ball ×2 when the attacking battler's (battlerIdAttacker) level is above the wild Pokémon's, ×4 when half its level (rounded down) is above it, and ×8 when a quarter of its level (rounded down) is, the Lure Ball ×3 when fishing, the Love Ball ×8 for the same species of the opposite gender, and the Moon Ball ×4 for the 14 species of the Nidoran, Clefairy, Jigglypuff, and Skitty families listed in an assembly data table. Crystal's Level Ball skips the HP and status steps, and its disassembly's comments mark the Moon Ball as never boosting, the Love Ball as boosting the same gender, and the Fast Ball as boosting only three species.Codepret/pokeheartgold · pret/pokecrystalrevised
    How we know
    7218        // All the apricorn balls directly alter catchRate instead of setting ballMultiplier.
    ⋮
    7221        case ITEM_FAST_BALL: {
    7222            u32 speed = GetMonBaseStat(ctx->battleMons[ctx->battlerIdTarget].species, BASE_SPEED);
    7223            if (speed >= 100) {
    7224                catchRate *= 4;
    ⋮
    7228        case ITEM_LEVEL_BALL: {
    7229            u8 attackerLevel = ctx->battleMons[ctx->battlerIdAttacker].level;
    7230            u8 defenderLevel = ctx->battleMons[ctx->battlerIdTarget].level;
    7231            if (attackerLevel <= defenderLevel) {
    7232            } else if (attackerLevel / 2 <= defenderLevel) {
    7233                catchRate *= 2;
    7234            } else if (attackerLevel / 4 <= defenderLevel) {
    ⋮
    
    src/battle/battle_command.c lines 7218–7245 at 6988158 · upstream ↗
    7260        case ITEM_LOVE_BALL: {
    7261            if (ctx->battleMons[ctx->battlerIdAttacker].species == ctx->battleMons[ctx->battlerIdTarget].species && ctx->battleMons[ctx->battlerIdAttacker].gender != ctx->battleMons[ctx->battlerIdTarget].gender) {
    7262                catchRate *= 8;
    ⋮
    7266        case ITEM_MOON_BALL: {
    ⋮
    7268            for (i = 0; i < NELEMS(sMoonBallPokemon); i++) {
    7269                if (sMoonBallPokemon[i] == ctx->battleMons[ctx->battlerIdTarget].species) {
    7270                    catchRate *= 4;
    ⋮
    7285        if (catchRate > 0xFF) {
    7286            catchRate = 0xFF;
    7287        } else if (catchRate < 0) {
    7288            catchRate = 1;
    
    src/battle/battle_command.c lines 7259–7288 at 6988158 · upstream ↗
    7161    ballMultiplier = 10; // All ball multipliers are /10, so this is x1.
    ⋮
    7165    if (ctx->itemTemp > ITEM_SAFARI_BALL) { // Skip all balls that have no condition.
    
    src/battle/battle_command.c lines 7161–7166 at 6988158 · upstream ↗
    132sMoonBallPokemon: ; 0x0226C3E8
    133	.short SPECIES_NIDORAN_F
    134	.short SPECIES_NIDORINA
    135	.short SPECIES_NIDOQUEEN
    136	.short SPECIES_NIDORAN_M
    137	.short SPECIES_NIDORINO
    138	.short SPECIES_NIDOKING
    139	.short SPECIES_CLEFFA
    140	.short SPECIES_CLEFAIRY
    141	.short SPECIES_CLEFABLE
    142	.short SPECIES_IGGLYBUFF
    143	.short SPECIES_JIGGLYPUFF
    ⋮
    
    asm/overlay_12_battle_command.s lines 132–146 at 6988158 · upstream ↗
    272.skip_or_return_from_ball_fn
    273	ld a, [wCurItem]
    274	cp LEVEL_BALL
    275	ld a, b
    276	jp z, .skip_hp_calc
    
    engine/items/item_effects.asm lines 272–276 at 97187cd · upstream ↗
    917; BUG: Moon Ball does not boost catch rate (see docs/bugs_and_glitches.md)
    ⋮
    925	cp MOON_STONE_RED ; BURN_HEAL
    
    engine/items/item_effects.asm lines 917–925 at 97187cd · upstream ↗
    977; BUG: Love Ball boosts catch rate for the wrong gender (see docs/bugs_and_glitches.md)
    978	ld a, d
    979	pop de
    980	cp d
    981	pop bc
    982	ret nz
    
    engine/items/item_effects.asm lines 977–982 at 97187cd · upstream ↗
    1001FastBallMultiplier:
    1002	ld a, [wTempEnemyMonSpecies]
    1003	ld c, a
    1004	ld hl, FleeMons
    1005	ld d, 3
    ⋮
    1008; BUG: Fast Ball only boosts catch rate for three Pokémon (see docs/bugs_and_glitches.md)
    
    engine/items/item_effects.asm lines 1001–1008 at 97187cd · 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

  10. ^ abHeartGold and SoulSilver's Heavy Ball adds 40 to the catch rate at 409.6 kg or more, 30 at 307.2 kg, and 20 at 204.8 kg, reading weight in tenths of a kilogram from the Pokédex data. Below 204.8 kg its last branch tests the catch rate against 1024 instead of the weight, and a catch rate is at most 255, so every lighter Pokémon loses 20 (a result below 0 becomes 1); a comment in the decompilation calls this a mistake. Crystal subtracts 20 only below 102.4 kg and adds nothing from there to 204.8 kg.Codepret/pokeheartgold · pret/pokecrystal
    How we know
    7246        case ITEM_HEAVY_BALL: {
    7247            s32 weight = GetMonWeight(ctx->battleMons[ctx->battlerIdTarget].species);
    7248            // Weight is in kilograms, moved to the left by 1 decimal point.
    7249            if (weight >= 4096) { // 409.6 kg / 903.0 lbs. or more.
    7250                catchRate += 40;
    7251            } else if (weight >= 3072) { // 307.2 kg / 677.3 lbs. or more.
    7252                catchRate += 30;
    7253            } else if (weight >= 2048) { // 204.8 kg / 451.5 lbs. or more.
    7254                catchRate += 20;
    7255            } else if (catchRate < 1024) { // Catch rate is mistakenly checked here instead of weight, causing all Pokemon that do not benefit from the Heavy Ball to be penalized by it.
    7256                catchRate -= 20;
    
    src/battle/battle_command.c lines 7246–7260 at 6988158 · upstream ↗
    7286            catchRate = 0xFF;
    7287        } else if (catchRate < 0) {
    7288            catchRate = 1;
    
    src/battle/battle_command.c lines 7286–7288 at 6988158 · upstream ↗
    779HeavyBallMultiplier:
    780; subtract 20 from catch rate if weight < 102.4 kg
    781; else add 0 to catch rate if weight < 204.8 kg
    782; else add 20 to catch rate if weight < 307.2 kg
    783; else add 30 to catch rate if weight < 409.6 kg
    784; else add 40 to catch rate
    
    engine/items/item_effects.asm lines 779–784 at 97187cd · upstream ↗
    843	cp HIGH(1024) ; 102.4 kg
    844	jr c, .lightmon
    ⋮
    857	ld a, b
    ⋮
    859	ld b, a
    860	ret nc
    ⋮
    862	ret
    ⋮
    864.lightmon
    865	ld a, b
    866	sub 20
    867	ld b, a
    868	ret nc
    869	ld b, $1
    ⋮
    
    engine/items/item_effects.asm lines 843–870 at 97187cd · 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

  11. ^HeartGold and SoulSilver's TryCriticalHit keeps Platinum's critical-hit rule: the stage sum (+2 Focus Energy, +1 for a HOLD_EFFECT_CRITRATE_UP item, +1 Super Luck, +2 for Chansey or Farfetch'd with its own item, plus the move's boosts) capped at 4, a chance of 1 in 16, 8, 4, 3, or 2, blocked by Battle Armor or Shell Armor unless ignored, by Lucky Chant on the target's side, or by bit 15 of the target's move-effect flags (named MOVE_EFFECT_FLAG_LUCKY_CHANT here), and Sniper turning ×2 into ×3.Codepret/pokeheartgold · pret/pokeplatinum
    How we know
    5960static const u8 sCritChance[] = {
    5961    16, 8, 4, 3, 2
    ⋮
    5979    critUp = (((status2 & STATUS2_FOCUS_ENERGY) != 0) * 2) + (item == HOLD_EFFECT_CRITRATE_UP) + critCnt + (ability == ABILITY_SUPER_LUCK) + 2 * ((item == HOLD_EFFECT_CHANSEY_CRITRATE_UP) && (species == SPECIES_CHANSEY)) + 2 * ((item == HOLD_EFFECT_FARFETCHD_CRITRATE_UP) && (species == SPECIES_FARFETCHD));
    ⋮
    5981    if (critUp > 4) {
    5982        critUp = 4;
    ⋮
    5985    if ((BattleSystem_Random(battleSystem) % sCritChance[critUp]) == 0) {
    5986        if (!CheckBattlerAbilityIfNotIgnored(ctx, battlerIdAttacker, battlerIdTarget, ABILITY_BATTLE_ARMOR) && !CheckBattlerAbilityIfNotIgnored(ctx, battlerIdAttacker, battlerIdTarget, ABILITY_SHELL_ARMOR) && !(sideCondition & SIDE_CONDITION_LUCKY_CHANT) && !(moveEffect & MOVE_EFFECT_FLAG_LUCKY_CHANT)) {
    5987            ret = 2;
    ⋮
    5991    if ((ret == 2) && GetBattlerAbility(ctx, battlerIdAttacker) == ABILITY_SNIPER) {
    5992        ret = 3;
    
    src/battle/overlay_12_0224E4FC.c lines 5960–5995 at 6988158 · upstream ↗
    177#define MOVE_EFFECT_FLAG_LUCKY_CHANT        (1 << 15)
    
    include/constants/battle.h line 177 at 6988158 · upstream ↗
    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)
    ⋮
    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) {
    7139        criticalMul = 2;
    
    src/battle/battle_lib.c lines 7123–7139 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 ↗
    7142    if (criticalMul == 2 && Battler_Ability(battleCtx, attacker) == ABILITY_SNIPER) {
    7143        criticalMul = 3;
    
    src/battle/battle_lib.c lines 7142–7143 at 49f78f3 · 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

Sources

Code