🎱 TAILUGE BILLIARDS

An open-source billiards simulator that runs in the browser

This is a free billiards and pool simulation written in TypeScript. It runs in any modern desktop or mobile browser with nothing to install and no account to create — you open a page and play. Behind it is a small physics engine built from published work on ball motion, collisions and cushion impacts, and the table is drawn in 3D with WebGL and three.js.

The project began in 2018 and has been developed in public ever since, mostly on a Raspberry Pi 4. It is maintained by an independent developer, not a studio, and contributions are welcome.

A 3D WebGL pool table viewed from behind the cue ball, ready for a shot
The 8-ball table in Hi-Res mode, running in a browser.

What you can play

Five rule sets share the same tables and physics, so you can move between them without changing anything on your device.

  • Nine-ball — the fast rotation game. Practice mode lets you build a layout and share a replay link of a break with whoever you like.
  • 8-ball — solids and stripes, with a Hi-Res rendering mode.
  • Snooker — full-size 12-foot tables with 15 reds, plus shorter games on a 6-foot table.
  • Three-cushion carom — the discipline the cushion model has been tuned hardest against. A beginner mode is available, and drills can be set up in practice.
  • Sagu — the Korean four-ball carom game, also playable on a small 5-foot table.

Two practice bots, ClawBreak and TheFarJaw, will play you when nobody else is around. For deliberate practice there is a table where you can place balls freely, a set of graded exam shots that score your level of play, and a speedrun challenge against the clock. The reveal game uses straight pool potting to uncover hidden pictures.

How the physics works

The engine is deliberately lightweight and deterministic rather than exhaustive; the project's own README calls the physics “unsophisticated”. The intention is to model the effects a player actually notices — spin, throw, and how the ball leaves a cushion — from published, checkable models rather than from tuning by feel.

Ball motion follows Han (2005) with corrections noted by Kiefl; ball-to-ball collisions follow Alciatore, including the small throw effect caused by friction between the balls; cushion impacts use Mathavan (2010) by default, with the analytical compliant-cushion model from Stronge's Impact Mechanics available as an alternative.

Ball motion on the cloth

The engine tracks the ball's velocity and angular velocity, and the relative velocity at the point where the ball touches the cloth tells it whether the ball is sliding or rolling. That surface velocity is built from linear velocity and spin:

\[ \vec{v}_a = \vec{v} + (\vec{up} \times R\vec{\omega}) \]

While contact is slipping, friction opposes the slip direction and slows both the ball and its spin:

\[ \dot{v} = -\mu g \frac{\vec{v}_a}{|\vec{v}_a|} \]

Once contact rolls, a smaller deceleration sets in, and the ball curves as spin carries it off line:

\[ \dot{v} = -\frac{5}{7}\frac{M_{xy}}{mR} \frac{\vec{up} \times \vec{\omega}}{|\vec{\omega}|} \]

Ball-to-ball collisions

On contact, a normal impulse separates the balls and a tangential impulse applies the throw effect. For the striking ball:

\[ \vec{v}_a \leftarrow \vec{v}_a + \frac{J_{\text{normal}}}{m}\hat{n} + \frac{J_{\text{tangential}}}{m}\hat{t} \]

Ball-to-cushion collisions

Cushion behaviour is the hardest part to get right. Play uses the numerical model from Mathavan (2010) by default: the compression and restitution phases are solved step by step, tracking slip at both the cushion contact point \(I\) and the cloth contact point \(C\). At the cushion that slip velocity is the surface velocity there:

\[ \dot{x}_I = \dot{v}_x + \dot{\omega}_y R \sin\theta - \dot{\omega}_z R \cos\theta \] \[ \dot{y}'_I = -\dot{v}_y \sin\theta + \dot{\omega}_x R \]

The slip direction at each contact \(\phi\) (cushion) and \(\phi'\) (cloth), together with its magnitude \(s\), sets the friction acting over each increment of normal impulse \(\Delta P_I\), which is what advances the centroid velocity through the collision:

\[ (\dot{v}_x)_{n+1} - (\dot{v}_x)_n = -\frac{1}{M}\left[\mu_w \cos\phi + \mu_s \cos\phi'\cdot(\sin\theta + \mu_w \sin\phi \cos\theta)\right]\Delta P_I \]

Compression iterates until the ball stops approaching the cushion (\(\dot{v}_y \le 0\)); restitution continues until the work done on the cushion reaches \(e_e^2\) times the work done in compression. Equations the paper leaves out had to be inferred to close that numerical solution; that is documented rather than hidden.

The Stronge compliant cushion model

The engine can instead use the analytical compliant-cushion model from Stronge's Impact Mechanics, selectable on the three-cushion physics page or with cushionModel=stronge in the URL. There the contact velocity is simply the velocity at the cushion contact point:

\[ \vec{V}_c = \vec{v} - (\vec{\omega} \times R \hat{n}) \]

The Stronge solution then classifies the impact into one of three slip regimes before reconstructing the outgoing velocity:

Slip regimes in the Stronge compliant cushion model
Regime Condition
Gross slip High tangential-to-normal velocity ratio
Initial stick Low tangential-to-normal velocity ratio
Slip–stick–slip Between the two — the contact sticks, then slips again

Both models can be inspected and re-tuned yourself: the three-cushion physics page exposes the constants, and the Mathavan and Stronge diagram pages recreate figures from the papers to check the code against them.

Features

Playing against other people

The lobby lists who is online so you can challenge someone directly; the session runs over a public nchan server. There is also a two-tab mode for playing across two windows on one machine, and hourly arenas in the style of lichess, where you try to win as many games as possible in half an hour.

Long-running progress is tracked outside the game: the highest breaks go to a leaderboard on Vercel, and rated games contribute to an ELO rating using Glicko2. If you would rather not play in a browser at all, thin client wrappers are published for Windows, macOS and Linux and for Android.

Open source and licence

The project is open source under the GNU General Public License — the full text is in the LICENSE file. The code lives at github.com/tailuge/billiards and contributions are welcome, whether that is a bug, a physics tweak, a translation or a new rule set.

Local development uses Node and Yarn: yarn install, yarn build, then yarn serve to play at localhost:8080. yarn test runs the Jest suite and yarn coverage reports on it. The README in the repository has the up-to-date commands and the exact tool versions.

Development and technology

The simulation itself is plain TypeScript with no engine dependency; three.js handles the 3D rendering. Because the engine is deterministic and light, a single run can be rolled forward thousands of times — about 500 rollouts per second on four CPU cores — which makes it usable for shot analysis and optimisation as well as play.

Those rollouts run either in the browser through Web Workers or headlessly under Node.js. That is what supports multi-shot parameter fitting, physics optimiser runs, and calibration against recorded real-world trajectories for friction, spin decay, restitution and cushion deflection. The worker and simulation research page documents the protocol and the runtime parameter overrides.

Research and references

The models implemented here are not presented as new research; they are implementations of other people's published work, with the validation figures kept alongside the code so the results can be compared with the papers.

Two forks have taken the simulation in their own directions: a three-cushion trainer with sensitivity analysis and an Italian five-pin game. The writing covers how the physics feels and how the models were implemented, in more depth for the cushion work.

More

Everything else the project ships, in one place.

Play and practise

Physics and analysis

Writing

Source, scores and downloads