Billiards Physics Sandbox
A Roblox billiards simulation focused on believable ball motion, collisions, cloth contact, and diagnostics.
I built Billiards Physics Sandbox to work out why billiards physics often feels wrong in games. It models sliding, rolling, spinning, airborne, and resting states, then moves a ball between those states as it interacts with the cloth.
The simulator runs with fixed-step integration and looks for the next contact between balls, cushions, pocket mouths, and pocket jaws. Separate contact modules handle cue impulses, cloth friction, rolling resistance, spin decay, ball collisions, and cushion response. I also added trajectory recording, energy monitoring, solver metrics, and server-side shot validation with rate limiting.
Technology and tooling
- Luau on Roblox for the shared simulation, server services, and client presentation.
- Rojo for mapping the file-based project into a Roblox place.
- Rokit for managing the project toolchain, including Rojo, Wally, Selene, and Lune.
- Wally for package management. The current project uses Cmdr on the server side.
Engineering challenges
Making sliding become rolling
A struck ball does not immediately roll without slipping. I calculate contact slip from its linear and angular velocity, apply a friction impulse while it slides, and then apply rolling resistance and spin decay once the slip drops below a tolerance. The tests check that slip decreases, velocity changes smoothly across fixed steps, and the ball reaches the expected rolling state.
Handling fast contacts reliably
Discrete collision checks can miss a fast-moving ball between frames. The simulator uses a fixed 240 Hz step and time-of-impact queries for ball-ball, cushion, pocket, and pocket-jaw events. It advances to the first event, resolves it, and continues with the remaining time. Iteration limits keep a bad contact sequence from turning into an endless loop.
Modeling pockets and cushions as different contacts
A pocket is not just a hole in the table. The detector separates capture events from pocket-jaw contacts and leaves the rail opening out of cushion checks. That lets a jaw redirect a ball when it reaches the opening at the wrong angle instead of always pocketing it.
Keeping responses stable and debuggable
Collision resolution records normal and tangential impulses, penetration, and event type. The simulator also tracks energy changes, collision counts, pocket events, solver CPU time, and optional trajectories. Those numbers help me investigate an unstable response instead of judging it only by looking at the rendered ball.
Keeping shot input authoritative
Shot commands are checked on the server before they reach the simulator. The validator checks the command shape and applies a per-player cooldown, so the physics state does not depend on trusting arbitrary client input.
What I learned
The main lesson was that believable physics comes from several small models working together: slip, friction, rolling resistance, collision impulses, geometry, and state transitions. Fixed-step integration and focused scenario tests make the behavior much easier to reason about than one large update loop. It is still a personal sandbox, so I am not presenting it as production-tuned or released.