data/claims/pokered-safari.yamlPoké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
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 selectionCited 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"
}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
}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 0Cited 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 , so with chance . 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, is divided by 4, rounded down; while it is angry, is doubled, up to 255. The count is taken off before the roll, so bait that adds eating turns lowers the chance on 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
}
}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 < 255Cited 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
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
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 offA 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
}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