Pinball physics
How a video pinball game moves its ball, finds the surfaces it touches, and bounces it off them.
On this page
A video pinball game adds gravity to the ball each frame, tests what it touches, and turns the touch into a bounce. Pokémon Pinball tests 16 points around the ball against 1-bit masks and looks the surface’s angle up from the run of points that touch1. It bounces the ball back at a quarter of its speed into the wall2.
- Masks and a normal tablePokémon Pinball (1999). 16 points around the ball tested against 1-bit masks; the touching run picks a normal from a 256-entry table, and the bounce keeps a quarter of the speed into the wall.3,4,1,2
Games
| Game | Year | How | Note |
|---|---|---|---|
| Pokémon Pinball | 1999 | Masks and a normal table | 16 points around the ball tested against 1-bit masks; the touching run picks a normal from a 256-entry table, and the bounce keeps a quarter of the speed into the wall |
Every claim about this mechanic, by game (4)
References
- ^ abTo 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.Codepret/pokepinballHow we know
2735 ld b, 16 ; length of wCollisionPointTestshome.asm line 2735 at 0a6f6dc · upstream ↗ 2878.determinedCollisionStatus 2879 ; e = maximum number of consecutive successful collision points 2880 ; d = the start and last index of that consecutive string of collision points (wCollisionPointTests) 2881 ; the hi nybble is the start index, lo nybble is the end index ⋮ 2892 ; Based on the start and end collision point indices, look 2893 ; up the corresponding normal angle for them. ⋮ 2896 ld hl, CollisionForceAngles 2897 add hl, de 2898 ld a, [hl] 2899 ld [wCollisionNormalAngle], a 2900 ; Again, based on the start and end collision point indices, apply 2901 ; position offsets to position the ball safely outside of the wall ⋮
home.asm lines 2878–2922 at 0a6f6dc · upstream ↗ 2345Cosine: ; 0x2147 ⋮ 2349 add $40 ⋮ 2357 and $7f ; ensure angle is between 0 and 180 degrees
home.asm lines 2345–2357 at 0a6f6dc · upstream ↗ 1CollisionForceAngles: ; 0x8817 2; This data has something to do with collisions. 3 db $C0
data/collision/collision_deltas.asm lines 1–3 at 0a6f6dc · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^ abOn 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.Codepret/pokepinball
How we know
101 ld a, [wIsBallColliding] ; check for collision flag ⋮ 106 ; First, rotate the velocity vector into a standardized coordinate 107 ; system where the normal angle points upwards. 108 ld a, [wCollisionNormalAngle] 109 call RotateVector 110 ; Apply the collision forces to the ball in the rotated coordinate system. 111 call ApplyCollisionForces ⋮ 144 ; Rotate the velocity vector back to the regular coordinate system, and 145 ; set the ball's velocity to the resulting vector. 146 ld a, [wCollisionNormalAngle] 147 call NegateAngleAndRotateVector 148 call SetBallVelocity
engine/pinball_game.asm lines 101–148 at 0a6f6dc · upstream ↗ 2528ApplyCollisionForces: ; 0x222b ⋮ 2542 ; Early exit if the ball is traveling away from the collision normal. 2543 bit 7, d 2544 jr nz, .exit ⋮ 2547 cp 3 2548 jr c, .applyforces 2549 ; The ball collided hard because its y velocity is a 2550 ; large value. Shake the rumble pack, and play a little 2551 ; sound effect to enhance the collision. 2552 ld a, $ff 2553 ld [wRumblePattern], a 2554 ld a, 1 ⋮
home.asm lines 2528–2625 at 0a6f6dc · 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 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.Codepret/pokepinball
How we know
2114.master_loop 2115 call TickRumbleDuration 2116 call DoScreenLogic ⋮ 2119 rst AdvanceFrame 2120 jr .master_loop
home.asm lines 2114–2120 at 0a6f6dc · upstream ↗ 69GameScreenFunction_HandleBallPhysics: ; 0xd909 70; main loop for stage logic ⋮ 74 call ApplyGravityToBall 75 call LimitBallVelocity ⋮ 78 call HandleTilts ⋮ 81 callba nz, HandleFlippers ; only perform flipper logic on the lower half of stages ⋮ 86 call CheckStageCollision ⋮ 91 call CheckGameObjectCollisions ⋮ 111 call ApplyCollisionForces ⋮ 150 call MoveBallPosition
engine/pinball_game.asm lines 69–150 at 0a6f6dc · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^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.Codepret/pokepinball
How we know
2394LimitBallVelocity: ; 0x2180 2395; Ensures that the ball's x and y velocity are kept under a threshold. 2396; The ball can travel at a greater max speed when moving diagonally, since it 2397; limits the x and y components independently. ⋮ 2406 cp 8 2407 ret c 2408 ld a, 7 ; max positive velocity ⋮ 2410 ret ⋮ 2413 cp -7 2414 ret nc 2415 ld a, -7 ; max negative velocity
home.asm lines 2394–2415 at 0a6f6dc · upstream ↗ 2436AddVelocityToPosition: ; 0x21c3 2437 ld a, [de] 2438 bit 7, a 2439 jr nz, .negativeVelocity 2440 cp 5 2441 jr c, .readLoByte 2442 ld bc, $04ff 2443 jr .updateBallPos ⋮ 2445.negativeVelocity 2446 cp -4 2447 jr nc, .readLoByte 2448 ld bc, -$04ff ⋮
home.asm lines 2436–2458 at 0a6f6dc · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
Sources
Code
- pret contributors. 2026. “pokepinball: disassembly of Pokémon Pinball.” pret. Commit 0a6f6dc. pret/pokepinball @ 0a6f6dc · licence, all uses