Claim set · data/claims/pokepinball.yaml

Pokémon Pinball (pret/pokepinball)

Game: Pokémon Pinball. Concept: Pinball physics.

In the US and Australian Pokémon Pinball, the main loop runs the game's screen logic once per frame. During play that logic adds gravity, caps the velocity, handles the tilts and, on the lower half of a table, the flippers. It then tests for collisions, bounces the ball if one touches, and moves the ball last. pokepinball/frame-order

Codepokepinball: disassembly of Pokémon Pinball · home.asm#L2114-L2120.master_loop call TickRumbleDuration call DoScreenLogic … rst AdvanceFrame jr .master_looppokepinball: disassembly of Pokémon Pinball · engine/pinball_game.asm#L69-L150GameScreenFunction_HandleBallPhysics: ; 0xd909 ; main loop for stage logic … call ApplyGravityToBall call LimitBallVelocity … call HandleTilts … callba nz, HandleFlippers ; only perform flipper logic on the lower half of stages … call CheckStageCollision … …Checked by claude-agent, 2026-10-22 — read Main's loop and GameScreenFunction_HandleBallPhysics at the pinned commit

Cited by: Pokémon Pinball

Gravity adds 11/256 of a pixel per frame to the ball's downward speed every frame while gravity is on. The ball's position and velocity are both 8.8 fixed point: whole pixels in the high byte, 256ths in the low. pokepinball/gravity

About: Pokémon Pinball Gravity

Value: 11 /256 px per frame²

Codepokepinball: disassembly of Pokémon Pinball · home.asm#L2377-L2392ApplyGravityToBall: ; 0x2168 ; Adds a constant to the pinball's y velocity. ld a, [wEnableBallGravityAndTilt] and a ret z ld de, $000b ; gravity added to y velocity every framepokepinball: disassembly of Pokémon Pinball · wram.asm#L271-L288wBallXPos:: ; 0xd4b3 ; 8.8 fixed-point values in little-endian format … wBallXVelocity:: ; 0xd4bb ; 8.8 fixed-point values in little-endian formatChecked by claude-agent, 2026-10-22 — read ApplyGravityToBall and the ball variables in wram.asm at the pinned commit

The ball starts, is caught, and is lost with wEnableBallGravityAndTilt cleared, which turns gravity and the tilt pushes off together.

Cited by: Pokémon Pinball

Each axis of the ball's velocity is capped on its own: a high byte above 7 becomes 7 and one below −7 becomes −7, the fraction kept, so the caps are 7 255/256 px per frame down or right and −7 up or left (computed), and a diagonal ball goes faster than a straight one. The move is limited again: a velocity of 5 px per frame or more moves the ball 4 255/256 px that frame, and one below −4 moves it 4 255/256 px the other way. pokepinball/velocity-cap

Value
{
 "cap": 7,
 "step": 4.996
}
Codepokepinball: disassembly of Pokémon Pinball · home.asm#L2394-L2415LimitBallVelocity: ; 0x2180 ; Ensures that the ball's x and y velocity are kept under a threshold. ; The ball can travel at a greater max speed when moving diagonally, since it ; limits the x and y components independently. … cp 8 ret c ld a, 7 ; max positive velocity … cp -7 ret nc ld a, -7 ; max negative velocitypokepinball: disassembly of Pokémon Pinball · home.asm#L2436-L2458AddVelocityToPosition: ; 0x21c3 ld a, [de] bit 7, a jr nz, .negativeVelocity cp 5 jr c, .readLoByte ld bc, $04ff jr .updateBallPos .negativeVelocity cp -4 jr nc, .readLoByte ld bc, -$04ffChecked by claude-agent, 2026-10-22 — read LimitBallVelocity and AddVelocityToPosition at the pinned commit; the cap only rewrites the high byte

The step limit compares the high byte, and a negative 8.8 value's high byte is its floor, so a high byte of −5 or less is any velocity below −4. Velocities from 5 to the cap all move the ball the same distance a frame; the larger value only matters after a bounce.

Cited by: Pokémon Pinball

To find a wall, the game tests 16 points around the ball against 1-bit collision masks for the tiles under it. The first and last points of the longest run of touching points index a 256-entry table, CollisionForceAngles, for the surface's normal angle, in 256ths of a turn. Two more tables with the same index push the ball back out of the wall. pokepinball/collision-normal

Codepokepinball: disassembly of Pokémon Pinball · home.asm#L2735ld b, 16 ; length of wCollisionPointTestspokepinball: disassembly of Pokémon Pinball · home.asm#L2878-L2922.determinedCollisionStatus ; e = maximum number of consecutive successful collision points ; d = the start and last index of that consecutive string of collision points (wCollisionPointTests) ; the hi nybble is the start index, lo nybble is the end index … ; Based on the start and end collision point indices, look ; up the corresponding normal angle for them. … ld hl, CollisionForceAngles add hl, de ld a, [hl] ld [wCollisionNormalAngle], a …pokepinball: disassembly of Pokémon Pinball · home.asm#L2345-L2357Cosine: ; 0x2147 … add $40 … and $7f ; ensure angle is between 0 and 180 degreespokepinball: disassembly of Pokémon Pinball · data/collision/collision_deltas.asm#L1-L3CollisionForceAngles: ; 0x8817 ; This data has something to do with collisions. db $C0Checked by claude-agent, 2026-10-22 — read CheckStageCollision (L2661–L2922) and Sine/Cosine at the pinned commit; the index is (first point × 16) + last point, so the table holds 16 × 16 entries, and Cosine adds $40 to the angle for a quarter turn

The collision masks themselves are binary files (data/collision/masks/) the atlas doesn't fetch. A ball wholly inside a wall is handled by a second, near-identical scan.

Cited by: Pinball physics, Pokémon Pinball

On a hit, the game rotates the ball's velocity so the surface's normal points up. A ball moving away is left alone. A ball moving in bounces back at a quarter of its speed into the surface, plus an eighth for each step of the hit's amplification. Its speed along the surface gains half the stored spin, in 256ths of a pixel per frame; the new spin is that speed × 4 / 256. The velocity is rotated back. A speed of 3 px per frame or more into the surface also sets the rumble's duration to 1 and, unless a flipper was hit, plays a sound. pokepinball/bounce

Value
{
 "restitution": 0.25,
 "perAmplification": 0.125,
 "rumbleAt": 3
}
Codepokepinball: disassembly of Pokémon Pinball · engine/pinball_game.asm#L101-L148ld a, [wIsBallColliding] ; check for collision flag … ; First, rotate the velocity vector into a standardized coordinate ; system where the normal angle points upwards. ld a, [wCollisionNormalAngle] call RotateVector ; Apply the collision forces to the ball in the rotated coordinate system. call ApplyCollisionForces … ; Rotate the velocity vector back to the regular coordinate system, and ; set the ball's velocity to the resulting vector. ld a, [wCollisionNormalAngle] …pokepinball: disassembly of Pokémon Pinball · home.asm#L2528-L2625ApplyCollisionForces: ; 0x222b … ; Early exit if the ball is traveling away from the collision normal. bit 7, d jr nz, .exit … cp 3 jr c, .applyforces ; The ball collided hard because its y velocity is a ; large value. Shake the rumble pack, and play a little ; sound effect to enhance the collision. ld a, $ff …Checked by claude-agent, 2026-10-22 — read the collision branch of GameScreenFunction_HandleBallPhysics and ApplyCollisionForces at the pinned commit

Spin is a signed byte, added at the next bounce as spin / 2 in the velocity's low byte, so its effect is small. TickRumbleDuration counts the rumble's duration down once per pass of the main loop. The flipper hit sets the amplification to 1 (pokepinball/flipper-hit); everything else here leaves it at 0.

Cited by: Pinball physics, Pokémon Pinball

Each flipper's position runs from 0 to 15 in 8.8 fixed point. Holding its button adds 0x0333 (3.2 positions) each frame and releasing it subtracts 0x0333, so a flipper swings fully up in 5 frames and fully down in 5 (computed). While the flippers are disabled, a held button counts as released. A flipper resting at either end records no change. pokepinball/flipper-swing

About: Pokémon Pinball Flipper

Value: 5 frames

Codepokepinball: disassembly of Pokémon Pinball · engine/pinball_game/flippers.asm#L14-L76UpdateFlipperStates: ; 0xe118 … ld hl, wKeyConfigLeftFlipper call IsKeyPressed2 ld hl, -$0333 jr z, .checkLeftStateBounds ld a, [wFlippersDisabled] and a jr nz, .checkLeftStateBounds ld hl, $0333 .checkLeftStateBounds … …Checked by claude-agent, 2026-10-22 — read UpdateFlipperStates for the left flipper at the pinned commit; the right flipper's half (L77–L112) is the same

From rest, the high byte goes 3, 6, 9, 12, then 15 (0x0FFF) on the fifth frame, where a held button stops it; release steps it back down in five frames. The change is recorded before the clamp, so a frame that reaches an end still counts as a full 0x0333.

Cited by: Flipper, Pokémon Pinball

Flipper collisions come from precomputed tables. For each flipper position from last frame's to this frame's, a 48 × 32-pixel grid gives the contact's distance from the pivot and the surface normal there; the right flipper reads the left's grid mirrored. A hit bounces the ball with amplification 1, then pushes it out along the normal by FlipperRadiusMagnitudes at that distance times 4 × the flipper's change this frame. A still flipper adds no push, and one moving down adds none either. The push at the tip of a rising flipper is 12.6 px per frame (computed); next frame's cap holds each axis to under 8. pokepinball/flipper-hit

About: Pokémon Pinball Flipper

Value
{
 "tipPush": 12.59,
 "radii": [
  0,
  12,
  28,
  48,
  56,
  72,
  92,
  108,
  112,
  128,
  148,
  164,
  180,
  196,
  212,
  228,
  248,
  252
 ]
}
Codepokepinball: disassembly of Pokémon Pinball · engine/pinball_game/flippers.asm#L202-L297ReadFlipperCollisionAttributes: ; 0xe25a ldh a, [hBallXPos + 1] sub 43 ; check if ball is in horizontal range of flippers ret c cp 48 ret nc … sub 123 ; check if ball is in vertical range of flippers ret c cp 32 ret nc … …pokepinball: disassembly of Pokémon Pinball · engine/pinball_game/flippers.asm#L167-L171CheckRightFlipperCollision: ; 0xe226 ; ball is in right half of screen … ld a, [hli] ; Invert the ball's x position, so that the flipper collision bytes are mirrored.pokepinball: disassembly of Pokémon Pinball · engine/pinball_game/flippers.asm#L622-L673HandleFlipperCollision: ; 0xe442 … ld hl, FlipperRadiusMagnitudes … rl d ; multiplied flipper state change by 4 call CalculateFlipperYForce … cpl ; mirror the normal angle across the y axis … ld a, 1 ld [wCollisionForceAmplification], a … …pokepinball: disassembly of Pokémon Pinball · engine/pinball_game/flippers.asm#L423-L430CalculateFlipperYForce: ; 0xe379 ; Calculates the y force that's added to the ball's velocity when colliding with a flipper. ; Flippers rotate around a point, and the further away from that rotation point that ball is, the ; higher the magnitude value is (bc). This magnitude is multiplied by the flipper state change to ; calculate the final y force.pokepinball: disassembly of Pokémon Pinball · engine/pinball_game/flippers.asm#L800-L833; Each entry corresponds to a distance from the flipper's rotation point. FlipperRadiusMagnitudes: ; 0xe538 dw $0000 dw $000C … dw $00F8 dw $00FCpokepinball: disassembly of Pokémon Pinball · engine/pinball_game.asm#L115-L125; de -= *wFlipperYForce ld hl, wFlipperYForce ld a, [hli] ld h, [hl] ld l, a ld a, e sub l ld e, a ld a, d sbc h ld d, aChecked by claude, 2026-10-25 — read ReadFlipperCollisionAttributes, HandleFlipperCollision, CalculateFlipperYForce, the radius table, and the flipper branch of the physics loop at the pinned commit; the tip push is computed as 0x00FC × 0x0CCC, keeping bits 8–23 (0x0C98, 12 152/256); pinball_game.asm L115-L125 subtracts wFlipperYForce from de

value.radii lists the table's first 18 entries; entries 17 to 31 are all 252. The radii and normal-angle grids are binary files the atlas doesn't fetch. The code also clears and adds a wFlipperXForce, but nothing sets it, so the flipper's push is along the normal only; an unused routine (Func_e3de) looks like the X-force version. A flipper resting at its top still bounces the ball, at 3/8 of its speed into the flipper (pokepinball/bounce).

Cited by: Flipper, Pokémon Pinball

Pokémon Pinball's tilt is a nudge with no tilt-out: the tilt routines keep no warning count and never end a ball. Holding a tilt button (left, right, or up; D-pad Down, B, and Select by default) runs its counter and screen shake for 3 frames (4 for up), and on each of those frames moves the ball 1 pixel if the ball is visible and gravity and tilt are on. That tilt then waits until the button is released and its counter has run back to 0. While a tilt is pushing, a ball touching a surface gets a velocity from a table for that tilt, read by the normal angle. Left and right together cancel. pokepinball/tilt

About: Pokémon Pinball Tilt

Value
{
 "frames": 3,
 "upFrames": 4,
 "pixelsPerFrame": 1
}
Codepokepinball: disassembly of Pokémon Pinball · home/tilt.asm#L1-L240HandleTilts: ; 0x3582 … HandleLeftTilt: ; 0x358c ld a, [wLeftTiltReset] and a jr nz, .tiltCoolDown … ld a, [wLeftTiltCounter] cp 3 jr z, .startCoolDown … ld a, [wPinballIsVisible] …pokepinball: disassembly of Pokémon Pinball · engine/pinball_game.asm#L101-L104ld a, [wIsBallColliding] ; check for collision flag and a jr z, .moveBallPosition call ApplyTiltForcespokepinball: disassembly of Pokémon Pinball · engine/options_screen.asm#L1080-L1087DefaultKeyConfigs: ; 0xca55 … db D_DOWN, $00 ; wKeyConfigLeftTilt db B_BUTTON, $00 ; wKeyConfigRightTilt db SELECT, $00 ; wKeyConfigUpperTiltChecked by claude-agent, 2026-10-22 — read all of home/tilt.asm and the call to ApplyTiltForces at the pinned commit; the tilt counters and flags are written only there, apart from one bonus stage that sets `wUpperTiltPushing` itself

The counter counts up one a frame while the button is held, and down one a frame after, shaking the screen by the same offset. The push tables (data/tilt/) are binary files the atlas doesn't fetch, so their values are a gap. The up tilt moves the ball down a pixel a frame.

Cited by: Pokémon Pinball, Tilt

In Catch 'Em mode on the Red field, each Voltorb hit flips 4 of the billboard's 24 tiles and scores 100,000, so 6 hits reveal the Pokémon from a blank board (computed). The Pokémon appears once all 24 are flipped and the ball is on the table's lower half. pokepinball/catch-reveal

About: Pokémon Pinball Table mode

Value
{
 "tiles": 24,
 "perHit": 4
}
Codepokepinball: disassembly of Pokémon Pinball · engine/pinball_game/catchem_mode/catchem_mode_red_field.asm#L231-L264HandleVoltorbCollision_CatchemMode: ; 0x20230 resolve hitting a voltorb in catch mode? ld a, [wNumberOfCatchModeTilesFlipped] cp $18 … ld a, [wNumberOfCatchModeTilesFlipped] add $4 ;then add 4 to FlippedCount, clamp to 24 … ld bc, OneHundredThousandPointspokepinball: disassembly of Pokémon Pinball · engine/pinball_game/catchem_mode/catchem_mode_red_field.asm#L28-L34Func_20041: ; 0x20041 ld a, [wNumberOfCatchModeTilesFlipped] cp $18 jr nz, .NotDone ld a, [wCurrentStage] bit 0, a jr z, .NotDoneChecked by claude-agent, 2026-10-22 — read HandleVoltorbCollision_CatchemMode and the mode's first state at the pinned commit

Odd stage numbers are the lower halves of the tables (the flipper check in the physics loop tests the same bit). Flipped tiles carry into the next Catch 'Em mode only until it ends: ConcludeCatchEmMode zeroes the count. The Blue field's equivalent hasn't been read.

Cited by: Pokémon Pinball, Table mode

Once the Pokémon appears, each registered hit scores 300,000 and steps a counter from 0 to 3, lighting part of the CATCH bar; a hit with the counter at 3 catches it, so the fourth registered hit catches. Mew counts a hit only when a separate byte of its own rolls over at 256, so it would need 1,024 hits from a zero byte (computed). When the timer runs out on Mew, the game marks Mew caught in the Pokédex anyway. pokepinball/catch-hits

About: Pokémon Pinball Table mode

Value: 4 hits

Codepokepinball: disassembly of Pokémon Pinball · engine/pinball_game/catchem_mode/catchem_mode_red_field.asm#L96-L148ld a, [wBallHitWildMon] and a jp z, .BallDidntHitMon ;if no ball hit(?), jump … ld a, [wCurrentCatchEmMon] cp MEW - 1 jr nz, .notMew ld a, [wNumMewHits] inc a ld [wNumMewHits], a jr nz, .Not256MewHits .notMew …pokepinball: disassembly of Pokémon Pinball · engine/pinball_game/catchem_mode/catchem_mode_blue_field.asm#L99-L120ld a, [wBallHitWildMon] … ld a, [wNumMonHits] cp $3pokepinball: disassembly of Pokémon Pinball · engine/pinball_game/catchem_mode.asm#L866-L888Func_10611: ; 0x10611 … Data_1062a: dw Data_10630 dw Data_10638 dw Data_10640pokepinball: disassembly of Pokémon Pinball · engine/pinball_game/catchem_mode/catchem_mode_red_field.asm#L212-L227CheckIfCatchemModeTimerExpired_RedField: ; 0x201f2 … ; Automatically set Mew as caught, since you can't possibly catch it ld a, [wCurrentCatchEmMon] cp MEW - 1 jr nz, .asm_2021b callba SetPokemonOwnedFlagChecked by claude-agent, 2026-10-22 — read the hit branch of UpdateMonState_CatchemMode_RedField, its Blue field twin, the CATCH-bar graphics routine, and the timer check at the pinned commit; `wNumMonHits` is zeroed when the Pokémon appears (ShowAnimatedWildMon) and `wNumMewHits` is never reset

A "registered hit" is one the mon's update routine sees: it checks the hit flag only when its animation counter runs out or every fourth frame, so whether one contact can count twice is untested (a probe would show it). The next state after the fourth hit runs the capture (CatchPokemon_RedField, BallCaptureInit).

Cited by: Pokémon Pinball, Table mode

Every Pokémon gets the same 2 minutes to be caught: all 151 entries of CatchEmTimerData are 0 seconds and 2 minutes. Starting the mode also starts a 60-second ball saver that saves without limit, and the saver from before is restored when the mode ends. pokepinball/catch-timer

About: Pokémon Pinball Table mode

Value: 120 s

Codepokepinball: disassembly of Pokémon Pinball · data/catchem_timer_values.asm#L1-L157CatchEmTimerData: ; 0x12a22 ; Each 2-byte entry represents the total time you have to catch a Pokemon. ; They're all exactly 2 minutes, 0 seconds, but they can be changed to make ; different pokemon have different times, in order to add difficulty. ; First byte = seconds value ; Second byte = minutes value db $00, $02 ; BULBASAUR … db $00, $02 ; MEWpokepinball: disassembly of Pokémon Pinball · engine/pinball_game/catchem_mode.asm#L118-L131ld hl, CatchEmTimerData ;contains how long each mon stays on screen, all are 2 minutes by default … callba StartTimer callba InitBallSaverForCatchEmModepokepinball: disassembly of Pokémon Pinball · engine/pinball_game/ball_saver/ball_saver_catchem_mode.asm#L1-L32InitBallSaverForCatchEmMode: ; 0xdbd4 … ld a, 60 ld [wBallSaverTimerSeconds], a ld a, $ff ld [wNumTimesBallSavedTextWillDisplay], a ret RestoreBallSaverAfterCatchEmMode: ; 0xdc00Checked by claude-agent, 2026-10-22 — read every line of the timer table (151 entries, each `db $00, $02`), StartCatchEmMode's timer call, and the Catch 'Em ball-saver routines at the pinned commit

A save count of 0xFF has bit 7 set, which the ball-loss routine reads as no limit and no "Ball Saved" text (pokepinball/ball-saver).

Cited by: Ball saver, Pokémon Pinball, Table mode

Each table starts with a 20-second ball saver, and every ball lost for good starts a new one. While it runs, a drained ball is saved: the first save leaves the timer alone, and the second cuts it to 1 second and 1 frame. The timer counts down 60 frames a second, and not while the ball is at x 154 or more. Set to 20 seconds, it lasts 1,259 frames, about 21 seconds, if the ball stays left of x 154 throughout (computed). The slot cave can award 30, 60, or 90 seconds instead. pokepinball/ball-saver

About: Pokémon Pinball Ball saver

Value
{
 "seconds": 20,
 "frames": 1259,
 "saves": 2
}
Codepokepinball: disassembly of Pokémon Pinball · engine/pinball_game/ball_saver/ball_saver_20.asm#L1-L12Start20SecondSaverTimer: ; 0xdbba … ld a, 59 ld [wBallSaverTimerFrames], a ld a, 20 ld [wBallSaverTimerSeconds], a ld a, $2 ld [wNumTimesBallSavedTextWillDisplay], apokepinball: disassembly of Pokémon Pinball · engine/pinball_game/ball_loss/ball_loss_red_field.asm#L1-L31HandleBallLossRedField: ; 0xdd76 ld a, [wBallSaverTimerFrames] ld hl, wBallSaverTimerSeconds or [hl] jr z, .youLose ld a, [wNumTimesBallSavedTextWillDisplay] bit 7, a jr nz, .skip_save_text dec a ld [wNumTimesBallSavedTextWillDisplay], a … jr nz, .skip_save_text …pokepinball: disassembly of Pokémon Pinball · engine/pinball_game/object_collision/red_stage_resolve_collision.asm#L53-L75UpdateBallSaverState: ; 0x146a9 ld a, [wBallSaverTimerFrames] ld hl, wBallSaverTimerSeconds or [hl] ;skip if timer ran out ret z ld a, [wBallXPos + 1] cp 154 ;if high? Byte of ball X pos is >= 154, don't update timers jr nc, .SkipSecondsOrFramesUpdate ld a, [wBallSaverTimerFrames] dec a ld [wBallSaverTimerFrames], a bit 7, a …pokepinball: disassembly of Pokémon Pinball · engine/pinball_game/stage_init/init_red_field.asm#L43callba Start20SecondSaverTimerpokepinball: disassembly of Pokémon Pinball · engine/pinball_game/slot.asm#L161-L164SlotRewards_CallTable: ; 0xeeca dw Start30SecondSaverTimer dw Start60SecondSaverTimer dw Start90SecondSaverTimerChecked by claude-agent, 2026-10-22 — read the 20-second saver, the Red field's ball-loss and saver-update routines, the Red field's start, and the slot reward table at the pinned commit; the Blue field's ball-loss routine and start (init_blue_field.asm L49) call the same timer. The frame count is computed: from 59 frames and 20 seconds, each second takes 60 updates (59 down to −1), then 59 more reach 0 frames and 0 seconds, 20 × 60 + 59 = 1,259

The saver is updated in each half-table's object routine, so once a frame of play. A lost ball that isn't saved uses an extra ball first, if the player has one, before a life. Neither the x-154 rule's purpose nor the Pikachu outlane saver is covered here.

Cited by: Ball saver, Pokémon Pinball