The stone leaves your hand at 2.30 m/s, which is walking pace for a toddler, and then does nothing for nineteen seconds except slow down and turn. You are not allowed to touch it. The only thing you get is a broom, and the broom does exactly two things — it makes the ice slippier, and it makes the stone go straighter.
Everyone learns the first one. The mechanic is in the second.
Friction is per metre, turning is per second
That sentence is the whole piece, and it is not a metaphor — it is two lines of the integrator.
Friction takes energy out of the stone per unit of distance. A sweep that
cuts friction by 15% over a stretch of length L hands back 0.15 × L metres of
extra slide — and nothing in that expression says where L was. Sweep four
metres at the start of the run or four metres at the very death and the stone
finishes in the same place, to the centimetre.
Turning happens per unit of time. The stone’s heading rotates at a constant rate the whole way down — about half a degree a second, nine degrees in total — and it spends most of its seconds nearly stopped. Which is why curl, seen from above, looks like something the stone decides to do at the last moment:
| the last… | is this much of the slide | and carries this much curl |
|---|---|---|
| 16 m | 85% of the time | 1.13 m — 94% |
| 8 m | 60% of the time | 0.78 m — 65% |
| 4 m | 42% of the time | 0.46 m — 39% |
| 2 m | 29% of the time | 0.26 m — 21% |
| 1 m | 20% of the time | 0.13 m — 11% |
Forty per cent of the time buys eleven per cent of the distance and thirty-nine per cent of the curl. Nothing dramatic happens at the end. The stone is just going slowly enough that a constant turn rate finally shows up.
Four metres of broom, spent in five different places
Same release. Same handle. Four metres of ice polished, moved down the sheet a window at a time:
| broom window | stops at | distance gained | curl left | curl lost |
|---|---|---|---|---|
| none | 21.90 m | — | 1.20 m | — |
| 0–4 m | 22.51 m | +0.61 | 1.08 m | 0.12 m |
| 4–8 m | 22.51 m | +0.61 | 1.10 m | 0.10 m |
| 8–12 m | 22.51 m | +0.61 | 1.12 m | 0.08 m |
| 12–16 m | 22.50 m | +0.61 | 1.15 m | 0.04 m |
| 16–20 m | 22.50 m | +0.60 | 1.19 m | 0.01 m |
| the last 4 m | 22.50 m | +0.60 | 1.21 m | −0.01 m |
The distance column is a constant. Across the whole sheet it moves by 0.8%, and that residue is the swept window sliding off the end of a shortening slide, not physics. The curl column moves by an order of magnitude.
So the broom is not one control with a cost. It is a range control that is free, plus a line control you did not ask for and cannot switch off, and the second one is priced entirely by when you use it.
The bottom row is my favourite and I did not predict it: sweeping the final four metres adds a hair of curl. Cutting the friction at the death buys extra seconds of crawling, the heading keeps rotating through all of them, and the turn you gain from the extra time slightly beats the turn you gave up. It is 0.01 m — noise, not a strategy — but it is the right sign, and it is the clearest possible statement that curl is a clock and not a distance.
What that costs you in the game
Here is the demo’s second scenario, measured. A guard on the centre line 1.55 m in front of the house, a yellow stone lying 0.95 m from the button, one red stone, and eight metres of broom. Every row is the same release speed, the same broom line, the same handle. The only variable is when the broom goes down:
| past the guard | stops at | from the button | curl | |
|---|---|---|---|---|
| no broom at all | +0.03 m | (21.19, 0.16) | 0.78 m | 1.14 m |
| all 8 m from release | +0.33 m | (22.39, 0.46) | 0.64 m | 0.91 m |
| held until 16 m | +0.05 m | (22.11, 0.20) | 0.25 m | 1.16 m |
All three are shot stone; only one is a shot. The early sweep spent all 8 m of broom and gained 1.20 m of slide; the held sweep ran out of stone at 6.1 m and gained 0.92 m — both exactly 0.15 m per metre of broom, which is the invariant from the table above showing up in a real shot rather than a test rig. What the two do not share is where they finish across the sheet. The early one arrives 46 cm off the centre line instead of 20, sitting out in the open on the wing where anything can hit it, because a quarter of a metre of curl was spent flattening a part of the path that was never the problem.
That is what panicking looks like. You throw, it feels light, the broom goes down at the hog line, and the stone gets there. It just gets there straight.
The broom is a correction, not a boost
Which leaves the real reason to hold it, and it is not physics. At the moment of release you do not know whether the weight was right. Ten metres later you do. Sweeping early spends a free, timeless resource on a guess; sweeping late spends the identical resource on a measurement. The demo makes that concrete by drawing a dashed ring at where the stone stops if the broom comes off right now — watch it creep down the sheet while you hold, and the game stops being about power and starts being about patience.
The two dashed lines before you throw are the same idea stated up front: the path with no broom, and the path with every metre of it spent from release. Everything you are able to do with that stone is between those two lines. If the shot you want is not in there, the broom cannot save you and you should throw a different weight.
Controls
- Move — the broom out at the tee line. That is your aiming line.
- Hold (mouse or space) — the weight meter fills; release to throw. Ticks for hog, house, tee and back line.
- Hold again while it slides — sweep. 8 m of broom per stone, drained by the distance the stone covers under it, so it is genuinely a budget and not a timer.
←→handle ·ccoach lines ·1an end (8 stones, scored) ·2the draw ·rreset.
Reuse
src/curling.mjs is a framework-free ES module and draws nothing:
createStone({ x, y, speed, aim, spin, team })—aimis the lateral offset of the broom at the tee line, not an angle, because that is how a curler thinks and it saves every caller the sameatan2.step(stones, dt, sweeping)advances everything, resolves collisions, and applies the sidelines, the back line and the hog-line rule.sweepingis a per-stone predicate, so a caller can sweep one stone, all of them, or none.integrate(stone, dt, swept)is the single-stone version — use it to fast-forward a copy of a stone and find out where it stops. The demo’s projection ring is nine lines of exactly that.simulateThrow({ speed, aim, spin, sweep, samples })is the headless one: give it a(stone, t) => booleansweep plan and it hands back the stop point, the total curl and optionally the whole track. Every table above is a call to it;scripts/measure.mjsreproduces them.envelope({ speed, aim, spin, budget })returns the two bounding paths.scoreEnd,shotOrder,inHouse,distToButton,curlOffor the game layer.SHEETcarries real dimensions in metres,WEIGHTSthe named ones.
A whole 19-second slide at dt = 0.001 is about 1.4 ms in Node, so projecting
forward every frame is free.
Gotchas
- The curl model is fitted, not derived. Why a curling stone curls the way
it does is genuinely unsettled — the pivot-slide and scratch-guiding camps are
still arguing about it. So this ships a phenomenological rule: constant
heading rotation while the stone is in motion,
dθ/dt = 0.0086 rad/s, tuned against the two things nobody disputes — a draw curls about 1.2 m, and almost all of it appears at the end. Constant turn rate in time reproduces both for free, which is a nice argument for it and not a proof. The early-versus-late asymmetry is a consequence of that choice, and a model that put the turning somewhere other than the clock would weaken it. What I will defend is the friction half, which is just∫a dxand needs no theory at all. sweptFrictionis the parameter with a real-world anchor;sweptTurnis not. 0.85 over a full slide is +3.91 m on a 22 m draw, which is what elite sweepers claim for themselves. The 0.45 turn multiplier is a plausible middle of a wide range of claims. If you are tuning this for feel, tune that one.- The result is not a timestep artefact, which was my first suspicion given how flat that distance column is. Over a 16× refinement, 0.004 s down to 0.00025 s, the gain from the first four metres moves from 0.6080 to 0.6071 m and the gain from metre 14 onward from 0.6056 to 0.6055, while the curl lost holds at 0.124 m against 0.027 m. The invariance sharpens under refinement instead of dissolving, which is what you want from a claim like this.
- Spend the budget in metres, not seconds. A stone covers its last three metres in seven of its nineteen seconds, so a stopwatch budget quietly makes late sweeping four times more expensive and buries the whole result under a UI decision. Metres of ice polished is also what actually tires a sweeper out.
- A struck stone leaves with no rotation. It was sitting still; there is
nothing spinning to make it curl.
collide()zeroes the spin of whichever stone was at rest, and the shooter keeps its own — which is why a takeout runs dead straight and the shooter does not. - The camera never zooms. Every decision in this game is a lateral one, and a view that fits all 26 m of sheet across a normal window renders 30 cm of curl as eleven pixels — the exact quantity the piece is about, made invisible. So the scale is pinned so the sheet’s width always fills the frame’s height, the camera only pans, and the whole-sheet view lives in a minimap strip where being 26:5 is not a problem. This was the single biggest improvement to the demo and it was a framing decision, not a physics one.
demo/bundles its own copy of the module (self-contained by contract). If you touchsrc/, re-copy it intodemo/.


