Claim set · data/claims/pokered-safari.yaml

Pokémon Red and Blue's Safari Zone (pret/pokered)

Game: Pokémon Red and Blue. Concept: Capture.

In the Safari Zone the wild Pokémon never attacks. Every battle on the Safari Zone's maps is a Safari battle, whose menu offers BALL×, BAIT, THROW ROCK, and RUN in place of the usual commands. After each of the player's actions that doesn't end the battle, the Pokémon either flees or the menu comes back; no move is ever chosen for either side. pokered-safari/turn

Codepokered: disassembly of Pokémon Red and Blue · engine/battle/init_battle_variables.asm#L30-L36ld a, [wCurMap] cp SAFARI_ZONE_EAST jr c, .notSafariBattle cp SAFARI_ZONE_CENTER_REST_HOUSE jr nc, .notSafariBattle ld a, BATTLE_TYPE_SAFARI ld [wBattleType], apokered: disassembly of Pokémon Red and Blue · data/text_boxes.asm#L72-L74SafariZoneBattleMenuText: db "BALL× BAIT" next "THROW ROCK RUN@"pokered: disassembly of Pokémon Red and Blue · engine/battle/core.asm#L158-L173.checkAnyPartyAlive call AnyPartyAlive ld a, d and a jp z, HandlePlayerBlackOut ; jump if no mon is alive call LoadScreenTilesFromBuffer1 ld a, [wBattleType] and a ; is it a normal battle? jp z, .playerSendOutFirstMon ; if so, send out player mon ; safari zone battle .displaySafariZoneBattleMenu call DisplayBattleMenu …pokered: disassembly of Pokémon Red and Blue · engine/battle/core.asm#L203-L207.compareWithRandomValue call Random cp b jr nc, .checkAnyPartyAlive jr EnemyRan ; if b was greater than the random value, the enemy runsChecked by claude, 2026-10-27 — read the battle setup that marks a battle on the maps from the Safari Zone's east area up to the center rest house as a Safari battle, the menu text, and the battle loop: a battle that isn't a normal one shows the Safari menu, and after the flee roll misses it jumps back to the party check, which returns to the Safari menu, never reaching the move selection

Cited by: Capture

Bait makes a Pokémon harder to catch and a rock easier. Bait halves its catch rate, rounded down, and a rock doubles it, up to 255; each throw works on the rate the last one left, so a Chansey's 30 becomes 15 after bait and 60 after a rock. Halving has no floor: six baits take a catch rate of 45 to 0 (computed), and with no status a rate of 0 still passes the catch-rate test, in which the ball's first random number, less the status amount, must not be above the catch rate, when that first number is 0. When the angry count runs out, the rate returns to the species' own value, undoing every bait and rock before it. When the eating count runs out, the rate stays where the bait left it, and bait thrown at an angry Pokémon clears the angry count without resetting the rate, as a rock thrown at an eating one clears the eating count. pokered-safari/catch-rate

Value
{
 "kind": "safari-rate",
 "bait": 0.5,
 "rock": 2,
 "cap": 255,
 "resetWhen": "angry-ends"
}
Codepokered: disassembly of Pokémon Red and Blue · engine/items/item_effects.asm#L1433-L1465ItemUseBait: ld hl, ThrewBaitText call PrintText ld hl, wEnemyMonActualCatchRate srl [hl] ; halve catch rate ld a, BAIT_ANIM ld hl, wSafariBaitFactor ld de, wSafariEscapeFactor jr BaitRockCommon ; for CASCADEBADGE when used from the ; ITEM window, which corresponds to ; SAFARI_ROCK during Safari Game encounters …pokered: disassembly of Pokémon Red and Blue · engine/battle/safari_zone.asm#L1-L28PrintSafariZoneBattleText: ld hl, wSafariBaitFactor ld a, [hl] and a jr z, .no_bait dec [hl] ld hl, SafariZoneEatingText jr .done .no_bait dec hl ld a, [hl] and a …pokered: disassembly of Pokémon Red and Blue · engine/items/item_effects.asm#L298-L303pop bc ; b = Rand1 - Status ; If Rand1 - Status > CatchRate, the ball fails to capture the Pokémon. ld a, [wEnemyMonActualCatchRate] cp b jr c, .failedToCapturepokered: disassembly of Pokémon Red and Blue · data/pokemon/base_stats/chansey.asm#L7db 30 ; catch ratepokered: disassembly of Pokémon Red and Blue · data/text/text_6.asm#L84-L91_ThrewBaitText:: text "<PLAYER> threw" line "some BAIT." done _ThrewRockText:: text "<PLAYER> threw a" line "ROCK."pokered: disassembly of Pokémon Red and Blue · ram/wram.asm#L479-L480wSafariEscapeFactor:: db wSafariBaitFactor:: dbChecked by claude, 2026-10-27 — read the bait routine's shift right of the catch-rate copy and the rock routine's doubling with the carry capped at $ff; the turn message routine, which, when the angry count reaches 0, reloads the species data and copies its catch rate back, while the eating branch only counts down; and the catch-rate test, which fails only when the first roll less the status value is above the rate (0 is not above 0); 45, 22, 11, 5, 2, 1, 0 is six halvings The counter the turn routine decrements after the eating one is the angry one, the byte before it in RAM; the bait and rock tail zeroes the other counter and doesn't touch the rate.

Cited by: Capture

Bait makes the Pokémon eat and a rock makes it angry, each for 1 to 5 more turns, and each ends the other: bait clears the angry count and a rock the eating count. The turns added are a random byte's low three bits, redrawn until under 5, plus 1, and a count stops at 255. After each of the player's actions that doesn't end the battle, the game prints "Wild CHANSEY is eating!" (with the Pokémon's name) and takes a turn off the eating count; when the Pokémon isn't eating, it prints "Wild CHANSEY is angry!" and takes one off the angry count, and with neither count it prints nothing. pokered-safari/eating-angry

Value
{
 "kind": "safari-counter",
 "add": [
  1,
  5
 ],
 "cap": 255
}
Codepokered: disassembly of Pokémon Red and Blue · engine/items/item_effects.asm#L1433-L1481ItemUseBait: ld hl, ThrewBaitText call PrintText ld hl, wEnemyMonActualCatchRate srl [hl] ; halve catch rate ld a, BAIT_ANIM ld hl, wSafariBaitFactor ld de, wSafariEscapeFactor jr BaitRockCommon ; for CASCADEBADGE when used from the ; ITEM window, which corresponds to ; SAFARI_ROCK during Safari Game encounters …pokered: disassembly of Pokémon Red and Blue · engine/battle/safari_zone.asm#L1-L36PrintSafariZoneBattleText: ld hl, wSafariBaitFactor ld a, [hl] and a jr z, .no_bait dec [hl] ld hl, SafariZoneEatingText jr .done .no_bait dec hl ld a, [hl] and a …pokered: disassembly of Pokémon Red and Blue · engine/battle/core.asm#L174-L181ld a, [wNumSafariBalls] and a jr nz, .notOutOfSafariBalls call LoadScreenTilesFromBuffer1 ld hl, .outOfSafariBallsText jp PrintText .notOutOfSafariBalls callfar PrintSafariZoneBattleTextpokered: disassembly of Pokémon Red and Blue · ram/wram.asm#L479-L480wSafariEscapeFactor:: db wSafariBaitFactor:: dbpokered: disassembly of Pokémon Red and Blue · data/text/text_2.asm#L1329-L1341_SafariZoneEatingText:: text "Wild @" text_ram wEnemyMonNick text_start line "is eating!" prompt _SafariZoneAngryText:: text "Wild @" text_ram wEnemyMonNick text_start line "is angry!" promptChecked by claude, 2026-10-27 — read the shared bait and rock tail: it zeroes the other counter, draws Random and 7 until below 5, adds 1, and adds the result to its own counter with the carry capped at $ff; then the turn message routine, called after the Safari Ball check and before the flee roll, which decrements the eating counter if it is nonzero and otherwise the angry counter (the byte before it in RAM, ram/wram.asm L479-L480), returning without a message when both are 0

Cited by: Capture

A Safari Pokémon flees more often the faster it is, much less often while eating, and more often while angry: at a speed of 50 it flees about 39% of the time (100 in 256), 9.8% while eating, and 78% while angry (computed). After each of the player's actions that doesn't end the battle, it flees ("Wild CHANSEY ran!") when a random byte is below a threshold b, so with chance b/256. b starts at twice its speed (the speed's low byte, all of any speed under 256), and a low byte of 128 or more makes it flee at once. While it is eating, b is divided by 4, rounded down; while it is angry, b is doubled, up to 255. The count is taken off before the roll, so bait that adds n eating turns lowers the chance on n−1 of them (computed). pokered-safari/flee

Value
{
 "kind": "safari-flee",
 "speedMultiplier": 2,
 "alwaysAt": 128,
 "eatingDivisor": 4,
 "angryMultiplier": 2,
 "cap": 255,
 "example": {
  "speed": 50,
  "base": 0.3906,
  "eating": 0.0977,
  "angry": 0.7813
 }
}
Codepokered: disassembly of Pokémon Red and Blue · engine/battle/core.asm#L181-L207callfar PrintSafariZoneBattleText ld a, [wEnemyMonSpeed + 1] add a ld b, a ; init b (which is later compared with random value) to (enemy speed % 256) * 2 jp c, EnemyRan ; if (enemy speed % 256) > 127, the enemy runs ld a, [wSafariBaitFactor] and a ; is bait factor 0? jr z, .checkEscapeFactor ; bait factor is not 0 ; divide b by 4 (making the mon less likely to run) srl b srl b …pokered: disassembly of Pokémon Red and Blue · engine/battle/core.asm#L254-L270EnemyRan: call LoadScreenTilesFromBuffer1 ld a, [wLinkState] cp LINK_STATE_BATTLING ld hl, WildRanText jr nz, .printText ; link battle xor a ld [wBattleResult], a ld hl, EnemyRanText .printText call PrintText …pokered: disassembly of Pokémon Red and Blue · data/text/text_2.asm#L832-L837_WildRanText:: text "Wild @" text_ram wEnemyMonNick text_start line "ran!" promptpokered: disassembly of Pokémon Red and Blue · engine/battle/safari_zone.asm#L1-L14PrintSafariZoneBattleText: ld hl, wSafariBaitFactor ld a, [hl] and a jr z, .no_bait dec [hl] ld hl, SafariZoneEatingText jr .done .no_bait dec hl ld a, [hl] and a …Checked by claude, 2026-10-27 — read the flee check: the speed's low byte doubled (a carry, so a byte of 128 or more, jumps straight to the fleeing routine), two right shifts when the bait counter is nonzero, a left shift with the carry capped at $ff when the escape counter is nonzero, then Random compared with the threshold, fleeing when the byte is below it; the turn message routine that decrements the counter runs just before it; 2 × 50 = 100, 100 / 4 = 25, 200 < 255

Cited by: Capture

A Safari Ball catches exactly as an Ultra Ball does: its first roll is redrawn until it is at most 150, its HP check uses a ball factor of 12, and a missed throw counts its shakes against 150. Each throw takes one of the Safari Balls the gate hands out instead of a ball from the bag. pokered-safari/safari-ball

Codepokered: disassembly of Pokémon Red and Blue · engine/items/item_effects.asm#L130-L137ld a, [wBattleType] cp BATTLE_TYPE_SAFARI jr nz, .skipSafariZoneCode .safariZone ld hl, wNumSafariBalls dec [hl] ; remove a Safari Ballpokered: disassembly of Pokémon Red and Blue · engine/items/item_effects.asm#L185-L214.loop call Random ld b, a ; Get the item ID. ld hl, wCurItem ld a, [hl] ; The Master Ball always succeeds. cp MASTER_BALL jp z, .captured ; Anything will do for the basic Poké Ball. cp POKE_BALL jr z, .checkForAilments …pokered: disassembly of Pokémon Red and Blue · engine/items/item_effects.asm#L252-L258; Determine BallFactor. It's 8 for Great Balls and 12 for the others. ld a, [wCurItem] cp GREAT_BALL ld a, 12 jr nz, .skip1 ld a, 8pokered: disassembly of Pokémon Red and Blue · engine/items/item_effects.asm#L335-L349; Determine BallFactor2. ; Poké Ball: BallFactor2 = 255 ; Great Ball: BallFactor2 = 200 ; Ultra/Safari Ball: BallFactor2 = 150 ld a, [wCurItem] ld b, 255 cp POKE_BALL jr z, .skip4 ld b, 200 cp GREAT_BALL jr z, .skip4 ld b, 150 …pokered: disassembly of Pokémon Red and Blue · engine/items/item_effects.asm#L576-L579.done ld a, [wBattleType] and a ; is this the old man battle? ret nz ; if so, don't remove a ball from the bagpokered: disassembly of Pokémon Red and Blue · constants/battle_constants.asm#L48-L50const BATTLE_TYPE_NORMAL ; 0 const BATTLE_TYPE_OLD_MAN ; 1 const BATTLE_TYPE_SAFARI ; 2pokered: disassembly of Pokémon Red and Blue · engine/battle/core.asm#L2163-L2165.throwSafariBallWasSelected ld a, SAFARI_BALL ld [wCurItem], apokered: disassembly of Pokémon Red and Blue · scripts/SafariZoneGate.asm#L186-L187ld a, 30 ld [wNumSafariBalls], aChecked by claude, 2026-10-27 — read the ball routine's three item tests: the Master Ball and Poké Ball tests and the Great Ball test fall through for a Safari Ball, so it retries above 200 and above 150 like an Ultra Ball; the ball factor is 12 for any ball but the Great Ball; the shake divisor is 150 after the Poké and Great Ball tests (the Ultra Ball test jumps to the same place); the Safari branch decrements the Safari Ball count, and the bag removal is skipped for any battle type but 0 (the Safari type is 2); the Safari menu's BALL× uses the Safari Ball item The gate stores 30 in the Safari Ball count when the player pays.

Cited by: Capture

A Safari Zone encounter ends in one of four ways: a catch; the Pokémon fleeing; the player choosing RUN, which in the Safari Zone always gets away ("Got away safely!"); or a missed throw with the last Safari Ball, after which the PA announces "Ding-dong! You are out of SAFARI BALLs!" and the battle ends before the flee check. pokered-safari/encounter-end

Codepokered: disassembly of Pokémon Red and Blue · engine/battle/core.asm#L168-L181.displaySafariZoneBattleMenu call DisplayBattleMenu ret c ; return if the player ran from battle ld a, [wActionResultOrTookBattleTurn] and a ; was the item used successfully? jr z, .displaySafariZoneBattleMenu ; if not, display the menu again; XXX does this ever jump? ld a, [wNumSafariBalls] and a jr nz, .notOutOfSafariBalls call LoadScreenTilesFromBuffer1 ld hl, .outOfSafariBallsText jp PrintText …pokered: disassembly of Pokémon Red and Blue · engine/battle/core.asm#L2270-L2295.checkIfMonCaptured ld a, [wCapturedMonSpecies] and a ; was the enemy mon captured with a ball? jr nz, .returnAfterCapturingMon ld a, [wBattleType] cp BATTLE_TYPE_SAFARI jr z, .returnAfterUsingItem_NoCapture ; not a safari battle call LoadScreenTilesFromBuffer1 call DrawHUDsAndHPBars call Delay3 .returnAfterUsingItem_NoCapture …pokered: disassembly of Pokémon Red and Blue · engine/battle/core.asm#L1496-L1501TryRunningFromBattle: call IsGhostBattle jp z, .canEscape ; jump if it's a ghost battle ld a, [wBattleType] cp BATTLE_TYPE_SAFARI jp z, .canEscape ; jump if it's a safari battlepokered: disassembly of Pokémon Red and Blue · engine/battle/core.asm#L1602-L1611.playSound ld [wBattleResult], a ld a, SFX_RUN call PlaySoundWaitForCurrent ld hl, GotAwayText call PrintText call WaitForSoundToFinish call SaveScreenTilesToBuffer1 scf ; set carry retpokered: disassembly of Pokémon Red and Blue · data/text/text_2.asm#L947-L948_GotAwayText:: text "Got away safely!"pokered: disassembly of Pokémon Red and Blue · data/text/text_2.asm#L825-L830_OutOfSafariBallsText:: text "PA: Ding-dong!" para "You are out of" line "SAFARI BALLs!" promptpokered: disassembly of Pokémon Red and Blue · engine/battle/core.asm#L203-L207.compareWithRandomValue call Random cp b jr nc, .checkAnyPartyAlive jr EnemyRan ; if b was greater than the random value, the enemy runsChecked by claude, 2026-10-27 — read the Safari battle loop: the menu returns with carry after a catch (the item code sets carry when a species was captured) or a successful run, and the loop returns at once; otherwise, with no Safari Balls left it prints the PA text and returns before the turn message and the flee roll; the run routine jumps straight to its escape branch for a Safari battle; a flee jumps to the fleeing routine, which slides the Pokémon off

A Safari Game costs ¥500 and gives 30 Safari Balls and a step counter that starts at 502, shown out of 500. Each step takes one off, and a step that finds the counter at 0 ends the game ("PA: Ding-dong! Time's up!"); running out of Safari Balls ends it as soon as the player is back on the map. Either way the PA says "Your SAFARI GAME is over!" and the player is sent back to the gate. pokered-safari/game

Value
{
 "kind": "safari-game",
 "price": 500,
 "balls": 30,
 "steps": 502,
 "shown": 500
}
Codepokered: disassembly of Pokémon Red and Blue · scripts/SafariZoneGate.asm#L158-L191xor a ldh [hMoney], a ld a, $05 ldh [hMoney + 1], a ld a, $00 ldh [hMoney + 2], a call HasEnoughMoney jr nc, .success ld hl, .NotEnoughMoneyText call PrintText jr .CantPayWalkDown .success …pokered: disassembly of Pokémon Red and Blue · text/SafariZoneGate.asm#L6-L7_SafariZoneGateSafariZoneWorker1WouldYouLikeToJoinText:: text "For just ¥500,"pokered: disassembly of Pokémon Red and Blue · engine/overworld/player_state.asm#L251-L252SafariSteps: db "/500@"pokered: disassembly of Pokémon Red and Blue · home/overworld.asm#L53-L56farcall SafariZoneCheck ld a, [wSafariZoneGameOver] and a jp nz, WarpFound2pokered: disassembly of Pokémon Red and Blue · home/overworld.asm#L307-L312CheckEvent EVENT_IN_SAFARI_ZONE jr z, .notSafariZone farcall SafariZoneCheckSteps ld a, [wSafariZoneGameOver] and a jp nz, WarpFound2pokered: disassembly of Pokémon Red and Blue · engine/events/hidden_events/safari_game.asm#L1-L80SafariZoneCheck:: CheckEventHL EVENT_IN_SAFARI_ZONE ; if we are not in the Safari Zone, … ld a, [wNumSafariBalls] and a jr z, SafariZoneGameOver … SafariZoneCheckSteps:: … ld a, [wSafariSteps] ld b, a ld a, [wSafariSteps + 1] …pokered: disassembly of Pokémon Red and Blue · data/text/text_2.asm#L310-L318_TimesUpText:: text "PA: Ding-dong!" para "Time's up!" prompt _GameOverText:: text "PA: Your SAFARI" line "GAME is over!"pokered: disassembly of Pokémon Red and Blue · engine/overworld/player_state.asm#L219-L235PrintSafariZoneSteps:: ld a, [wCurMap] cp SAFARI_ZONE_EAST ret c cp CERULEAN_CAVE_2F ret nc hlcoord 0, 0 ld b, 3 ld c, 7 call TextBoxBorder hlcoord 1, 1 ld de, wSafariSteps …Checked by claude, 2026-10-27 — read the gate's payment (BCD $00 $05 $00, 500, checked and subtracted), the 30 balls and HIGH/LOW(502) it stores, the "/500" printed after the step count, the overworld's ball check on every loop and step check after each step in the Safari Zone, the step routine (0 ends the game, otherwise it decrements), and the game-over routine, which prints "Time's up!" only when balls remain, then the game-over line, and warps to the gate The step box prints the counter, then "/500" after it, on the Safari Zone's maps.

Cited by: Capture