Before a game the ice is sprayed with water from a can worn like a backpack, in a walking side-to-side arc, and the droplets freeze where they land. The sheet you play on is covered in small frozen beads, and a forty-four pound stone touches only their tops — through a ring six millimetres wide with about eighty square millimetres actually in contact. That is the whole game. Take the pebble away and the same delivery dies at 4.9 m instead of drawing the house at 28.4, and it goes there in a straight line.
So the interesting thing about curling is not that the stone curls. It is that the amount is a number, the number depends on things nobody tells you about, and the mechanism that produces it is still argued over by people who have measured it.
There is no curl constant in this file
The easy way to build this is y += curlRate * t² with curlRate tuned until
it looks like the telly. It would have taken twenty minutes and every
interesting thing below would have been an input rather than a result.
src/curl.mjs never writes a sideways displacement down. There is a running
band — the 6.5 cm-radius annulus that touches ice — chopped into 48 sectors.
Working in a frame aligned with the stone’s velocity, with e1 forward and
e2 = ẑ × v̂ its own left, sector θ sits at R(cos θ, sin θ) and its contact
point is moving at
u(θ) = (v − ωR sin θ) e1 + (ωR cos θ) e2
Two things act on each sector.
Friction, −μ W(θ) û, where the load W(θ) is very slightly front-heavy. It has to be: friction acts at the ice and the centre of mass is 11.5 cm above it, so decelerating pitches the stone onto its nose by about 1.5% of its load. This is the mechanism every popular account of curling gives you — the front of the stone presses harder, so it grips more, so the stone turns. It is in the model. Section “seven millimetres, backwards” below is what it is worth.
Groove steering. On pebbled ice the leading edge of the band scratches the pebble tops as it passes. Thirteen centimetres later — a tenth of a second at draw weight — the trailing edge crosses that same ground, and it is now sitting in a groove that was cut by a contact point moving in a different direction from its own. Which contact cut it is the neat part: the groove under the sector at θ was cut by the sector at π−θ, and that groove runs along (u1, −u2) — the exact mirror of the trailing contact’s own velocity. So the trailing contact wants to slide at −α while the groove it is riding runs at +α, and the wall of the groove pushes it sideways.
That push is toward e2 whenever ω > 0. A counter-clockwise stone curls to its own left. Which is what stones do, and it is not something I told the file.
α = atan(ωR / v), and that is where the shape of a curling shot comes from: ωR is roughly constant at about 4 cm/s, so at 2 m/s the groove is crossed at one degree and at 0.25 m/s it is crossed at ten. The stone does not curl gradually. It goes almost dead straight for twenty metres and then falls.
Seven millimetres, backwards
bandForces reports the two sideways contributions separately, which turns the
popular explanation into a measurement.
| speed m/s | load asymmetry mN | groove steering mN | ratio |
|---|---|---|---|
| 2.00 | −0.53 | 146.12 | 277× |
| 1.00 | −1.05 | 291.19 | 276× |
| 0.50 | −2.10 | 574.02 | 273× |
| 0.25 | −4.16 | 1081.15 | 260× |
| 0.12 | −8.20 | 1629.17 | 199× |
Note the sign. Front-heavy load asymmetry curls the stone the wrong way — the same way a spinning tumbler slides off across a bar, and the opposite of what a curling stone does. Over a whole draw it is worth 6.8 mm, to the right, on a stone that really goes 1.11 m to the left. It is not a small effect that gets overwhelmed; it is a small effect pointing at the other wall.
This is not a bug I found and kept. It is the standard objection to the standard explanation, and having both terms in the same sum is the only way to see it as a number rather than a footnote.
The coefficient, and what it is allowed to be
One number in this model is fitted rather than derived: ICE.guide, the
strength of the groove-wall push, expressed as a multiple of the trailing
band’s own friction at full cross angle. It is 3.4.
That value is doing something specific, and it is worth being blunt about it.
| model | guide | curl |
|---|---|---|
| groove steering off — load asymmetry alone | 0.00 | −0.007 m |
| steering capped at the band’s friction budget | 1.00 | 0.452 m |
| steering at half the fitted value | 1.70 | 0.692 m |
| as delivered | 3.40 | 1.109 m |
A friction-limited mechanism cannot exceed guide = 1 — if the sideways force were friction being redirected, that row would be the ceiling, and it gives 0.45 m on a sheet where real stones find a metre or more. So the curl cannot be a redirection of friction. It has to be a normal force from the wall of a plowed groove, which has no μW ceiling: it is limited by how hard ice resists being pushed sideways, and in absolute terms it is tiny — 0.15 N against a 186 N stone at 2 m/s, a tenth of a percent of the weight, applied for twenty-six seconds.
What the coefficient does not set is anything else in this document. It is a scalar on one force. The direction, the α = atan(ωR/v) shape, where along the sheet the curl arrives, how it trades against weight, and what happens when you sweep are all geometry, and none of them move when you change it.
Calibration: one number, and the clock agrees
Friction is the other fitted constant: μ = 0.0087, chosen so that a stone released at 2.20 m/s comes to rest on the button. Everything else about distance is then a prediction, and the one to check is the interval time — the seconds a stone takes between the hog lines, which is what every team in the world actually measures with a stopwatch.
| stopping at | m past the tee | release m/s | hog-to-hog s | slide s | curl m |
|---|---|---|---|---|---|
| hogged, 1.2 m short | −7.60 | 1.881 | — | 21.9 | 0.949 |
| top of the house | −1.83 | 2.127 | 14.58 | 24.8 | 1.072 |
| top four foot | −0.61 | 2.176 | 13.85 | 25.4 | 1.097 |
| the button | 0.00 | 2.199 | 13.53 | 25.7 | 1.109 |
| back four foot | 0.61 | 2.223 | 13.23 | 25.9 | 1.120 |
| back line | 1.80 | 2.268 | 12.72 | 26.5 | 1.143 |
A tee-line draw on club ice is about thirteen and a half seconds hog to hog and the stone is in motion for something over twenty seconds. That is what came out, from a Coulomb friction coefficient picked to land one stone in one place.
Two things in that table are worth sitting with. The first is how flat it is: every draw in the house, from the top twelve to the back four, lives inside a hundred millimetres per second of release speed and one and a half seconds of interval time. The second is the last column — the curl barely moves across the whole house either, which is why a skip can set one broom and call four different weights off it.
The light end is where it gets vicious. A stone asked to stop 1.2 m short of the far hog line needs 1.881 m/s, only 14% less than a draw, and it never crosses the line at all, so its interval time is undefined and the stone is removed. There is no soft failure at the light end of curling.
Where the metre happens
| at | m from release | s since release | speed left m/s | curl so far | % of total |
|---|---|---|---|---|---|
| 8 m still to go | 20.4 | 12.1 | 1.17 | 0.411 m | 37% |
| the far hog line | 22.0 | 13.5 | 1.05 | 0.498 m | 45% |
| top of the house | 26.5 | 19.2 | 0.57 | 0.851 m | 77% |
| the eight foot | 27.1 | 20.4 | 0.46 | 0.919 m | 83% |
| the four foot | 27.7 | 22.0 | 0.33 | 0.999 m | 90% |
| a stone short | 28.1 | 23.1 | 0.23 | 1.048 m | 95% |
More than half the curl is bought in the last six metres, and a quarter of it in the last two. The lateral velocity is very nearly constant — the crossing angle grows exactly as fast as the stone slows — so what is really changing is the angle of the track, and the shot looks straight right up until it doesn’t.
Weight eats the curl
Because the lateral rate is roughly constant, total curl is set by how long the stone is on the ice, not how far it goes. Which means weight is a line control.
| shot | hog-to-hog s | on the ice | curl |
|---|---|---|---|
| guard, stopping 4 m out | 16.38 | 23.8 s | 1.028 m |
| draw to the button | 13.53 | 25.7 s | 1.109 m |
| takeout arriving at 1.35 m/s | 10.24 | 14.4 s | 0.497 m |
| peel arriving at 2.80 m/s | 6.71 | 8.9 s | 0.157 m |
A peel is thrown 62% harder than a draw and finds one seventh of the curl by the time it reaches the tee, because it is only on the ice for a third as long. Everyone who has played knows hits go straighter than draws; the reason is a clock, not a grip.
Brooms, and the thing that has the wrong sign
Sweeping in this model does two separate things: it multiplies μ by 0.88, and it multiplies the groove steering by 0.55. Both are the same physical claim — a swept path is a polished path — and they pull in opposite directions.
| same release, 2.199 m/s | stops at | gained | curl |
|---|---|---|---|
| brooms up | 0.01 m | — | 1.109 m |
| swept half the way | 1.83 m | 1.82 m | 1.005 m |
| swept all the way | 3.90 m | 3.88 m | 0.845 m |
Four metres on a twenty-eight metre draw, which is about what a good front end is worth. But that table is the wrong comparison, because nobody sweeps a stone they already threw correctly. The honest one holds the destination fixed:
| both finish on the button | release m/s | hog-to-hog s | on the ice | curl |
|---|---|---|---|---|
| thrown to the button, not swept | 2.199 | 13.53 | 25.7 s | 1.109 m |
| thrown light, swept the length | 2.063 | 14.43 | 27.4 s | 0.793 m |
| same, if brooms only cut friction | 2.063 | 14.42 | 27.3 s | 1.183 m |
The third row is the one I did not expect. If sweeping only reduced friction, it would make the stone curl more — you have to throw it lighter to finish in the same place, a lighter stone is on the ice longer, and longer is more curl. The friction term alone has the wrong sign for the thing sweepers are actually doing when they yell hard. Holding a stone straight requires the brooms to be flattening the grooves, not just making the ice slippery, and the model only reproduces the real behaviour because it says so explicitly.
The one it gets wrong
| release rad/s | turns in the slide | curl | m per turn |
|---|---|---|---|
| 0.52 | 1.02 | 0.443 m | 0.435 |
| 1.04 | 2.04 | 0.887 m | 0.435 |
| 1.30 | 2.55 | 1.109 m | 0.435 |
| 1.56 | 3.06 | 1.331 m | 0.435 |
| 2.60 | 5.11 | 2.224 m | 0.435 |
A straight line through the origin: 0.435 m per revolution, to three decimal places, across a factor of five.
Real ice does not do that. One of the most-repeated observations about the game is that turning the handle harder barely changes how far the stone curls — within the range anybody actually throws, the amount is close to flat. This model says double the turns and you double the curl, and I do not have a fix for it that isn’t just writing the answer down. It is the same disagreement that makes curling physics an argument rather than a chapter, and I would rather ship a mechanism that is wrong in a stateable way than a spline that is right by construction.
It costs the game nothing, as it happens, because the handle amount is not a
player choice here any more than it is in the real sport. You pick a direction;
HANDLE picks 1.30 rad/s, which is two and a half turns.
The hammer
The AI uses the same simulator the player is playing against — it reads the board, picks a guard, a draw or a takeout, solves the delivery exactly, and then spoils it with Gaussian error on line and on weight, which is the only difference between a skip and a good skip. Set both sides to the same skip and the only asymmetry left is who throws last.
| over 400 four-stone ends | per end |
|---|---|
| points with hammer | 0.99 |
| points stolen against | 0.37 |
| net value of the hammer | 0.63 |
| ends blanked | 5% |
| ends stolen | 27% |
Four-stone ends with an equal opponent: last stone is worth about six tenths of a point, you score one 43% of the time, and more than a quarter of ends get stolen — high, because four stones is not enough traffic to protect a lead. The number was not put anywhere. It is what falls out of a shot-selection heuristic, a Gaussian, and a metre of curl.
Playing it
Three ends, four stones a side, and the clock runs at 3.4× because a real draw takes twenty-six seconds.
- Point down the sheet to call the weight, across it to set the broom. Those are the two numbers a skip holds. Click, or space, to throw. Arrow keys do the same thing, with shift for fine.
- H flips the handle. Hold space (or hold the pointer down) to sweep, against a bar that drains — real sweeping is limited by lungs, this one by a budget, and it makes when a decision.
- M draws the model on the running stone: the trailing edge, the groove, the angle it is crossing it at, and the sideways push in millinewtons. The angle in that picture is drawn at eight times life size, because two degrees is real and invisible; the true value is in the table under it.
- P scrapes the pebble off. The end restarts, because half of it was played on different ice, and then nothing you throw reaches the hog line.
- R restarts. F fast-forwards a stone you have already read.
The one thing to actually look at: after every shot the demo leaves the line you sent the stone down and the track it took, with the gap between them shaded and measured. That gap is the whole sport. On a draw it is about 1.11 m, and if you put the broom on the thing you want to hit, you will miss by exactly that.
Reuse
src/curl.mjs is headless and framework-free — no DOM, no canvas, no
rendering. Metres, seconds, newtons throughout.
bandForces(vx, vy, om, ice, sweep)→{fx, fy, torque, lateralFriction, lateralGuide}. The contact model, and the two sideways terms reported apart so they can be argued with.stepStone/stepWorld(stones, dt, ice, sweepOf)→ integration, elastic stone-on-stone collisions, and the three rules that remove a stone (side line, back line, hog line).simulateThrow({speed, aim, om, sweep})→ a whole slide, withhogToHog,driftand an optional traced path.solveWeight(x),solveWeightThrough(x, residual),solveAim(x, y)→ the delivery that does what you asked. Backed byweightCard, which builds one table of 126 slides per ice condition and then answers in constant time.scoreEnd,createEnd,nextThrower,finishEnd→ the rules of an end.chooseShot(game, team, {skill})→ the opponent.
A different sheet is a parameter change: ICE is a plain object and every
field has a unit and a reason on it.
Gotchas
- The groove steering only applies to the trailing half of the band
(
cos θ < 0), and it has to. Drop that condition so it applies all the way round and the leading sectors get steered by grooves that have not been cut yet: 1.109 m of curl becomes −0.022 m. The two halves very nearly cancel and the remainder points the other way, which looks exactly like a sign error and is not one. - The deceleration is not μg all the way down. The sector sum gives |F| = μW only while ωR ≪ v. As the stone slows, more of each sector’s friction goes into fighting rotation instead of translation, and the deceleration falls off a cliff: 99.7% of μg at 2 m/s, 79% at 0.25, 20% at 0.12, 4% at 0.06. Nothing imposes that and it is why a curling stone finishes by creeping rather than by stopping. It also means any code that wants to know whether a stone is still going has to look at speed, not at the force on it.
- The stone spins down faster here than on a real sheet. The torque is honest — it is the same sector sum — but it means a delivery has to leave the hack at 1.30 rad/s to end up having turned two and a half times, where a real curler releases nearer 0.7. The number of turns is right; the release rate is high.
- A struck stone is given no handle (
om = 0on impact) and the shooter keeps half of its own. Real collisions transfer very little spin, and the consequence — struck stones run straight — is a property of the game worth preserving. It is still a modelling choice, not a derivation. weightCardis built at dt = 1/50, because it is 126 full slides and has to happen between a keystroke and the next frame. The integrator barely notices: 1/50 moves a 28 m draw by 8 mm and its curl by a tenth of a millimetre. Anything that wants precision should callsimulateThrowdirectly.- Aim is not searched for. The contact model is written in the velocity
frame and knows nothing about the sheet, so rotating a delivery rotates its
whole trajectory: drift is a property of weight and handle alone.
solveAimthrows one stone straight and aims off by the difference, which is one slide instead of twenty and is exact to a few millimetres. demo/bundles its own copy ofcurl.mjs— self-contained by contract (ADR-0002). Re-copy after editingsrc/.- Every table above is reproducible:
node scripts/measure.mjsprints all of them in about twenty seconds, andnode scripts/smoke.mjsplays whole games through the demo’s real state machine in both orientations looking for errors.
Not in here
No free guard zone, so the first stones can be peeled; no delivery out of the hack, so the model starts at release; no ice that changes as the game goes on, which in a real club is most of what a skip is reading; four stones a side rather than eight. All of those are the game rather than the ice, and the ice was the point.





