You aim, you pick a weight, you let go — and then you have twenty seconds and a finite pair of sweepers, on a stone you can no longer steer. Everything interesting about curling is in that window, and this is a mechanic built around it rather than around the delivery.
The window you are throwing into
Set the friction law first, because everything else is downstream of it. Deceleration rises as the stone slows:
a(v) = A / √v A = 0.113, so μ runs 0.008 off the hand → ~0.02 as it dies
That is the measured behaviour of a stone on pebbled ice, and it is also the
reason a curling stone stops instead of trickling to a halt. Integrating it
gives a closed form for run-out, D = (2/5)·v^(5/2)/A, and inverting that is
where the game gets its cruelty:
| off the hand | where it stops |
|---|---|
| 2.087 m/s | just clears the far hog line |
| 2.239 m/s | front edge of the house |
| 2.300 m/s | the button |
| 2.358 m/s | through the back line, gone |
Every legal draw in the sport lives inside 0.27 m/s. The house alone is 0.12 m/s wide. A 1% error at the hand is 2.5% of the run-out, because the exponent is 5/2 — which is why the weight control here is a bar you have to stop on a target rather than a number you type, and why the bar is drawn as a picture of the sheet with the house marked on it. You are not choosing a speed. You are choosing a place, and the speed is a consequence you are not very good at.
Curl, and why it is a late phenomenon
Lateral acceleration goes as the inverse square of speed, applied perpendicular to travel:
a_lat = K / v² · sign(handle)
Three things fall out that are worth having:
- Almost half the sideways metre is spent in the last three metres. A draw curls 1.15 m in total and 0.56 m of it after the stone has slowed to a walk. That is the hook you can watch happen.
- Weight and line are not independent. The same handle that moves a draw 1.15 m moves a takeout 9 cm, because a fast stone is never in the regime where curl lives. Throw harder and the ice stops helping you; you are on the line you aimed, for better or worse.
- How hard you turn the handle does not matter. Only its sign is in the model, which is not a simplification — curl distance in the real sport is famously near-independent of rotation rate, as long as the stone turns a few times on the way down. The demo spins it about two and a half revolutions.
The one bound that is not physics: total steering angle is capped at 0.26 rad. Without it the 1/v² term keeps turning a stone that has nothing left, and every draw ends up crabbing sideways at 50°, which looks exactly as wrong as it sounds.
What sweeping actually buys
Sweeping is one knob — it removes friction — and it is the only input the model gets after release. Less friction means the stone runs further and curls less, because the curl is friction too. Nothing in the code says when it should matter. What comes out:
- Nine seconds of sweeping spent in the first half of a shot adds 2.1 m of run. The same nine seconds spent at the end adds 1.0 m.
- Hold the finishing distance constant, and early sweeping still wins on line: 19 cm against 6 cm.
Both because friction you remove early is friction the stone does not pay for the rest of the trip. Which is the entire problem the mechanic is about: the sweeping is worth most at the moment you know least about whether you need it. The budget is nine seconds of a twenty-second shot, it does not refill mid-shot, and if you burn it early on a stone that turns out to be heavy, you have bought yourself a rock through the back of the house and no way to stop it.
Sweeping everything, from release, is not a strategy: a dead-weight draw swept the whole way goes out the back.
What the demo shows
A four-stone end against a CPU that draws, guards and hits, with the hammer alternating on the score.
- The weight bar is a map of the sheet, not a power meter — hog line, guard zone, house, through the back, takeout. It ping-pongs; you stop it where you want the stone to stop.
- The broom is the line you throw along, and with coach lines on (
C) the demo draws the curved path that line actually produces, plus a ghost of where the stone comes to rest. Turn it off once you can see the hook without help. - While the stone is live, the readout answers the only question that matters — brooms up now → stops 0.4 m short of the tee, 0.2 m above the centre. It is the same forward integration the sweepers are doing in their heads, re-solved four times a second.
- A struck stone loses its handle: rotation does not survive a collision, so anything you hit runs dead straight. Takeouts are geometry; draws are physics.
- The camera frames the stone and the house together and closes in as the gap does, because a sweeping decision made without the target in shot is a guess.
Ice runs at 2.8× real time, so a full draw takes about seven seconds on screen instead of twenty.
Reuse
src/curling.mjs is a framework-free ES module that draws nothing:
createSheet({ params })→{ stones, shooter, add, deliver, step, settled, clone, removeStone }.deliver({ team, speed, broom, turn })throws one —broomis where the line is aimed on the far tee line, in metres off the centre, so aiming is a position rather than an angle.step(dt, { sweep })returns collision and out-of-play events.predictShot(sheet, shot, { sweep, dt, subStep })runs a shot to rest on a copy of the ice and hands back the path, the resting point, first contact, and whether it hogged.sweeptakes a predicate(stone, t) => bool, so a whole sweeping plan can be tried before it is committed — that one function is the coach line, the live forecast and the CPU’s sweepers.weightFor(distance)/runOut(speed)are the closed-form inverse pair, no search.scoreEnd(stones)counts an end.ICEcarries the real WCF geometry.- Tuning knobs that change the feel:
frictionsets the whole speed scale,curlandcurlExpset how much of the hook is late,sweepFrictionandsweepCurlset what the brooms are worth.
Gotchas
- The steering cap is load-bearing. Take
curlMaxTurnout and the endgame looks broken. It is also what keeps a stone from finishing with more lateral velocity than forward velocity, which the perpendicular-acceleration form will happily produce. - Sub-step the update.
step()fixes its own 8 ms sub-steps. At 1/v² a long frame near the end of a shot integrates a visibly wrong amount of curl. - Solve the line for the weight you are actually throwing, not for a weight
you had in mind.
weightForis not linear in distance and a broom solved at 3.3 m/s misses badly at 2.7. Cost me a screenshot. - A struck stone needs its delivery heading reset at the moment of contact, or the steering cap it inherited from before the hit applies to a direction it is no longer travelling in.
demo/bundles its own copy of the module (self-contained by contract). If you touchsrc/, re-copy it intodemo/.

