Invasoren and Schafschiesser look like engineering drawings, but underneath runs a complete pinball simulation: about 750 lines of vanilla JavaScript per table, with no physics engine, no framework and no build step. This article shows how it works – and why it runs at 960 steps per second.
Why 960 Hz?
The ball has a radius of 12.5 units and may reach 2,800 units per second. On a 60 Hz display it travels about 47 units per frame – almost two ball diameters. A thin post or the tip of a flipper can simply be skipped over: the classic tunneling bug.
So physics is decoupled from rendering. Every frame fills a time accumulator, which is then drained in fixed steps of 1/960 s. Per step the ball moves at most about 3 units, well below its radius. The result is the same whether the monitor runs at 60, 120 or 144 Hz.
var acc = 0, STEP = 1 / 960;
function frame(now) {
var dt = Math.min(0.05, (now - last) / 1000); last = now;
acc += dt;
while (acc >= STEP) { step(STEP); acc -= STEP; }
draw();
requestAnimationFrame(frame);
}
The ball: slope force instead of gravity
A pinball playfield is tilted by roughly 6.5 degrees, so the ball does not feel full gravity, only the component along the playfield – 1,150 units/s² in the model. Add a little rolling damping and a speed cap. If the ball almost comes to rest, it gets a tiny random nudge so it never balances on an edge forever.
Walls, posts, bumpers
Every line of the drawing is a segment, every post a circle. On each step the closest point of every element to the ball is computed. If the distance is smaller than the radius, the ball is pushed out along the normal and the normal component of its velocity is reflected with a restitution factor – 0.42 for walls. Slingshots and pop bumpers add an active kick of 560 to 620 units/s: that is the coil firing in the real machine.
The flipper as a tapered capsule
The flipper is a capsule: an axis from the pivot to the tip whose radius shrinks from r₀ to r₁. Collision works like a wall, with one crucial difference: the flipper moves. At the contact point its surface has the velocity ω × r – the further out the ball is hit, the faster. The response is therefore computed from the relative velocity between ball and surface.
function collideFlipper(b, f) {
var tx = f.x + f.len * Math.cos(f.a), ty = f.y + f.len * Math.sin(f.a);
var q = closest(f.x, f.y, tx, ty, b.x, b.y); // closest point on the axis
var rr = f.r0 + (f.r1 - f.r0) * q[2]; // radius at that point
var dx = b.x - q[0], dy = b.y - q[1], dist = Math.hypot(dx, dy), lim = R + rr;
if (dist >= lim || dist === 0) return;
var nx = dx / dist, ny = dy / dist;
b.x += nx * (lim - dist); b.y += ny * (lim - dist); // push the ball out
var cx = q[0] + nx * rr - f.x, cy = q[1] + ny * rr - f.y;
var sx = -f.w * cy, sy = f.w * cx; // surface velocity ω × r
var rvx = b.vx - sx, rvy = b.vy - sy, vn = rvx * nx + rvy * ny;
if (vn < 0) { // only when approaching
var e = Math.abs(f.w) > 1 ? 0.55 : 0.25; // lively when swinging, damped at rest
b.vx -= (1 + e) * vn * nx; b.vy -= (1 + e) * vn * ny;
}
}
Two details define the feel. First, restitution: a swinging flipper bounces with 0.55, a resting one damps with 0.25. Only this makes it possible to catch and cradle the ball – the key technique for aimed shots. Second, friction is deliberately minimal. Too much and the ball sticks at the pivot; that was a real bug in an early version, and players noticed immediately.
The captive ball
The clone chamber in Invasoren holds a second ball that can only roll along its channel. When the playing ball hits it, the impulse is split along the contact normal – but only the component along the channel moves the captive ball. It is a one-dimensional constrained collision, and it produces the typical behaviour: a straight hit sends the clone ball all the way up, a glancing one barely moves it.
Nudging and TILT
A nudge gives the ball a small kick upwards and sideways and fills a tilt meter that slowly decays. Nudge too often and you get TILT: flippers dead, bonus lost – just like the real thing.
What is not simulated
The ball does not spin, and there are no ramps in the third dimension. Flippers, slingshots and bumpers fire instantly without coil delay. For tables from 1979 and 1980 that is enough – their mechanics are essentially two-dimensional.