The deterministic physics engine behind RealPool
RealPool does not use a third-party physics engine. The way the balls roll, collide, slow down, and drop into pockets is all code I wrote by hand. That was a deliberate choice, and the reason is one word: determinism. This post explains what that word means here, why it was worth writing the physics myself to get it, and how it keeps two players on two different computers looking at the exact same table.
What does deterministic physics mean?
Deterministic means the same shot, played from the same starting layout, always produces the same result. Same speed, same angle, same spin, same opening positions: you get the same final table every time. The outcome is decided entirely by the inputs, with nothing left to chance. That is the property the client and the server rely on to agree, since both run the exact same simulation code.
To keep that promise the simulation uses no randomness, no wall-clock time, and no unstable ordering of objects. Anything that could differ between two runs, like a random number or the current time, is simply not allowed in the physics path. The math is the same math, in the same order, with the same numbers, no matter where it runs.
Why hand-roll the physics instead of using an engine?
A general-purpose physics engine is built to be flexible, and that flexibility works against me here. I do not need ragdolls or stacked crates. I need balls on a flat cloth to behave one specific way, and I need the exact same numbers out of two separate machines. Off-the-shelf engines do not promise that kind of cross-machine agreement, and chasing it through code I did not write would have been harder than writing the part I actually need.
So the simulation is small and focused. It does the things a pool table does (rolling friction, ball-to-ball impacts, cushion rebounds, pocketing) and nothing else. Because I control every line of it, I can guarantee the behavior I want and prove it stays put. There is a "golden" snapshot test that records the exact output of a reference shot. If any change to the physics shifts that output by even a hair, the test fails and tells me at once. The physics cannot drift without me knowing.
The goal is a table that feels right and behaves consistently, built on physically grounded rules for friction, restitution, and spin. It is tuned to play well, not to be a laboratory model of every real-world effect. When I change a constant to improve the feel, the golden snapshot shifts on purpose, and I note why in the commit.
How does determinism keep online play in sync?
Online play in RealPool is server-authoritative. Your client does not decide where the balls end up. It proposes a shot, the server runs the same simulation, and the server broadcasts the resulting table back to both players. One computed result stands for everyone at the table.
This only works if both sides can agree on the math. If the client and the server computed even slightly different numbers for the same shot, they would slowly fall out of step, and the two players would end up looking at two different tables. That is a desync, and it is the bug determinism exists to prevent. Because the result is fully decided by the inputs, one shared outcome is trustworthy for both players. The same simulation code runs on the client and the server because it lives in one shared module, so there is only ever one version of the physics to keep honest. The move to WebGL described in why we cut the 2D fallback changed how the table is drawn, not how it is simulated.
What is a time-of-impact solver?
Collisions are the part where a naive approach quietly breaks. The obvious method is to step every ball forward a little, then check whether anything now overlaps. The problem: a fast ball can move more than its own width in one step, so it can hop clean over a cushion or another ball between two checks. On screen it looks like the ball passed straight through a solid wall. That bug has a name, tunneling.
RealPool avoids it with a time-of-impact solver, or TOI. Instead of nudging positions and hoping nothing was missed, the engine computes exactly when the next contact will happen: ball to ball, ball to cushion, ball to pocket. It then advances time to precisely that moment, resolves that one contact, and repeats. Because it always jumps to the next real collision rather than past it, a fast ball can never skip through a cushion. It catches every impact in the order it truly occurs, which is part of why a replay of the same shot resolves the same way.
The same care shows up elsewhere on the table. The pockets are described by precise geometry, covered in how we built the pool table, and the spin model is what makes a massé shot curve the way it does. If a term in this post was new to you, the pool terms glossary has short definitions.
Common questions
What does deterministic physics mean?
It means the same shot, from the same starting layout, always lands on the same result, on any machine. No randomness, no wall-clock time, no luck: the inputs decide the outcome completely.
Why is RealPool's physics hand-written instead of using a physics engine?
Writing it by hand gives exact control over determinism and behavior. It lets me guarantee the client and the server compute identical results, something a general-purpose engine does not promise across different machines, and that guarantee is what online play rests on.
What is a time-of-impact solver?
It is a collision method that computes exactly when the next contact will happen and advances time to that moment, rather than stepping forward and checking for overlaps. That is why a fast-moving ball never tunnels through a cushion.
Play a rack