CueLTC — the match screen
Ancestral Gaming Ecosystem

Four minutes here, against four in the queue.

The aim projection is real geometry, not a decoration: move the pointer over the table and it ray-casts against every ball and every cushion, places a ghost ball at the true contact point, and draws where the object ball will actually leave. Click to take the shot.

You Open table · left
30
ndiaga_s left · Open table
POWER52%
POT0.100 LTC
SEAL · SEALED 8f2a41c7…be0d
Foul. Cue ball potted — ball in hand to ndiaga_s.
ndiaga_s is shooting
Your stake is locked until the 8 falls. Nothing settles before then.

no shot yet
move the pointer over the table

The aim line is arithmetic, not art

Every frame it does the same three things a player does, in the same order.

1 · Cast the rayFrom the cue ball centre toward the pointer. Solve the quadratic against every other ball at a separation of two radii, and against the four cushion lines inset by one radius. Take the smallest positive root
2 · Place the ghostAt the cue ball's centre at the moment of contact — not at the object ball. The gap between ghost and object is the cut, and showing it is the whole reason a beginner ever learns to cut
3 · Split the pathsThe object ball leaves along ghost-centre to ball-centre. The cue ball leaves along the tangent, perpendicular to that. Both drawn, both dimmer than the aim line, because they are predictions rather than instructions
If a cushion comes firstReflect and draw one bounce. One, not three — a three-rail projection is a solver, and giving a player a solver stops it being a skill game
If the line ends in a pocketThe pocket ring lights. This is the only positive feedback on the table and it should stay that way
Spin widgetSets the contact point on the cue ball. It does not alter the projection — english changes what happens after contact, and drawing it into the aim line would be a lie

The one line that matters more than the geometry. Under the waiting overlay: your stake is locked until the 8 falls, nothing settles before then. A player who has just watched an opponent pot four in a row needs to know the money has not already gone. That sentence belongs on the screen where the doubt actually occurs, not in a help page.

Skill, on purpose. There is no assist toggle, no auto-aim, no three-rail solver. Beyond the legal argument — a game of pure skill sits outside the Criminal Code's lottery definition, and that argument is doing real work for you in Canada — an aim assist strong enough to matter would also make every match a test of who bought the better assist. One bounce, no english in the projection, and the cut left for the player to judge.

The shot itself

Clicking now runs a real simulation. It is deterministic on purpose, and that purpose is not only technical.

IntegrationFixed step at 1/240 s, sub-stepped so no ball moves more than 2 units at a time. Nothing tunnels through a cushion or through another ball, however hard it is hit
ClothRolling deceleration of 340 units/s², a rest threshold at 14. A ball does not asymptotically creep — it stops
Restitution0.86 off a cushion, 0.94 ball on ball. Equal masses, so a collision exchanges the normal component and leaves the tangential alone
PocketsCushion noses have real gaps. A ball crossing a wall inside a gap latches into the throat and cushions stop applying to it entirely. Without that latch a ball in the throat gets mirrored back onto the table — which is exactly the bug the invariant suite caught
EnglishSpent on the cue ball's first contact and nowhere else, as one impulse, applied along the incoming line rather than what is left of the cue ball's velocity. On a full hit the cue ball nearly stops — which is precisely when draw should bite hardest, and it only does if you measure from the incoming line
Collision orderResolved in stable index order. Simultaneous three-ball contacts are rare but real, and an unstable order is the classic way a physics engine stops being reproducible
OutputFrames at 60 Hz for playback, plus an event log of every cushion, hit and pot with timestamps. The renderer never touches the simulation

Determinism is your fairness proof, and it is a better one than commit-reveal. RunéLTC needs a sealed draw because it has randomness to hide. CueLTC has none: a shot is an angle, a power and a contact point, and the same three numbers always produce the same table. So the verifiable artifact is not a hash — it is the shot log. Anyone can re-run a whole match from it and land on the identical final position, which means a disputed frame is settled by replay rather than by trusting you. Press Verify determinism above: it runs the same descriptor twice and compares the full serialised output.

It is also the legal argument, made in code. The pure-skill position in Canada turns on there being no element of chance anywhere in the outcome. A physics engine with a random number generator in it — even one seeded for jitter or a break scatter — is a chance element a regulator can point at. There is no RNG in this file. That is worth keeping true, and worth being able to demonstrate in one click when counsel asks.

The rules engine is the settlement authority

Every verdict below is read off the event log, mechanically. No branch is a judgement call, because a judgement call is a dispute and a dispute is a payout someone contests.

Open tableUntil a ball is legally pocketed, neither player has a group. The strips read "Open table" rather than guessing
AssignmentThe group of the first ball legally pocketed after the break. The other player gets the complement
Legal first contactYour group. On an open table, anything but the 8. Once your group is cleared, the 8
Rail requirementAfter contact, something must be pocketed or some ball must reach a cushion. Read from cushion events timestamped at or after the first contact — a rail struck before contact does not count
FoulsNo ball struck, cue ball potted, wrong group struck first, the 8 struck early, no rail after contact. All of them hand ball in hand to the opponent
The 8 settles the frameCleared and no foul, you win. Anything else — potted early, or potted on a foul — and you lose. The escrow settles on that single return value
Not modelledCalled pockets, break requirements, the 8 on the break, three-foul rules, and choosing a group when a solid and a stripe drop on the same open-table shot (this takes the first pot in time). Each is a rule to add, none needs randomness

Player against player

The loop is symmetric. There is no "you" branch and no "them" branch — one function, one state object, a shooter field.

advance(state, shot)The whole game in one pure function. No DOM, no globals, no clock. The browser runs it to show the shot, the Worker runs it to settle, a test runs it headless. If those three ever disagree, the platform is broken
Turn handoffFoul or dry shot passes the turn. A legal pot of your own group keeps you at the table. Both read straight off the verdict
Ball in hand is signedThe placement travels inside the shot descriptor. If it lived only on the client, two replays of the same log would put the cue ball in two different places and the chain would fork on an honest player
ScratchA potted cue ball leaves the state with no cue at all. The next shot cannot be accepted without a placement — the protocol makes the illegal state unrepresentable rather than guarding against it
validate(rack, log)What the Worker runs at settlement. Replays the whole frame, returns the winner and the hash chain, and takes the client's word for nothing. Also reports trailing shots — a log with moves after the 8 fell is a tampered log
Hot seatTurn it on in the controls and both sides are playable in one window. Not a shipping feature — it is how you confirm the loop is symmetric without two devices

Replaying a match

A match is an opening rack plus an ordered list of shot descriptors. Nothing else needs storing, and nothing else needs trusting.

A shot is five numbersdx, dy, power, spinX, spinY — quantised to six decimals so the submitted values are exact rather than approximately re-parsed
Direction, not angleThe client does the trig once and submits a vector. The simulation calls no trigonometric function at all
Only exact arithmetic inside+ - * / and Math.sqrt, all IEEE-754. Math.hypot, cos, sin and pow are implementation-approximated in the spec and may legally differ between engines — Math.hypot(3.3,4.7) and Math.sqrt(3.3²+4.7²) already disagree in the last bits inside one engine
Canonical stateBalls sorted by number, coordinates at six fixed decimals. Serialisation order is not allowed to be a source of divergence
Hash chainEach state hashed, each shot advancing the chain. A validator re-runs the log and must land on the same final hash
Hash used hereFNV-1a in two lanes, which is fine for a client-side demo and wrong for settlement. Use SHA-256 on the Worker

Why this matters more here than in an ordinary game. The client cannot be trusted with the outcome when the outcome moves money. But it does not have to be: the client submits five numbers per shot, the Worker re-runs the identical function, and any disagreement is the client lying. The bandwidth is trivial — a whole frame of 8-ball is a few hundred bytes — and the validator needs no state beyond the opening rack. The cross-engine hazard above is the one that would actually bite you: a player on an iPhone and a validator on V8 diverging on Math.sin would produce honest players being flagged as cheats, which is worse than missing a cheat.

Build notes

Table spaceSame 1360 × 760 units as Tetlow No. 1, 10 units per inch. Playfield 1000 × 500 inset at 180, 130
Ball radius11.3 units. Contact separation 22.6, cushion inset 11.3
Pointer mappingSVG getScreenCTM().inverse(), so it stays correct at any size and on any device pixel ratio
RecomputeAim layer only, on pointermove. The table, rails and pockets are built once and never touched again
Shot clockOne setInterval at 1 Hz driving stroke-dashoffset. No animation frame loop anywhere
PlaybackSimulate once, record, then play frames back at 60 Hz. The server can run the identical function to validate a submitted shot
Not yet modelledCalled pockets, the 8 on the break, jump and masse shots, throw and cut-induced spin. All of them are rules or refinements on top; none of them needs randomness
Statesdata-turn you / them, data-hand for ball in hand, data-note for the foul toast