workshop private

← all creations

Let It Run

mechanics · created 2026-09-20

One verb: how you come back to the catch. The engine is pinned at 240 W so no rhythm can win by pulling harder, and the fastest one turns out to be the rhythm where the cost of the boat coasting and the cost of your own body sliding about come out exactly equal.

physicssimulationcanvasgame-feel

Hold space to come up the slide. Let go and the boat runs. That is the entire control scheme, because the drive is committed the moment the blade goes in and the only live decision in a rowing stroke is the two thirds of it where you are not rowing.

The engine is fixed. A controller trims the force amplitude stroke by stroke to hold the athlete’s mechanical output at 240 W, so nothing on the scoreboard can be bought by trying harder. Everything on it is rhythm.

The boat is not the crew, and the water only charges one of them

A single scull weighs 16 kg. The person in it weighs 82. They are joined by a seat on ball bearings, and the system’s momentum only changes through the blade, so every time the crew moves along the boat the boat moves the other way to keep the books straight:

v_hull = (P - q · ds/dt) / M          q = m_rower · ξ = 70.9 kg

A metre per second of slide takes 72.4 cm/s off the hull. Not as a correction — as the definition of what the hull is doing. The crew can be perfectly smooth, in their own frame, and the boat underneath them is being shoved backwards and forwards the whole time.

The inertia behind that shove is almost nothing. Write the kinetic energy in terms of momentum and slide speed and the cross terms cancel into one number:

KE = P²/2M + ½ · 10.0 kg · (ds/dt)²

Ten kilograms. Stopping and restarting the crew at every catch is worth about five joules against a stroke of five hundred. The cost of rushing is not the effort of stopping yourself. That was the first surprise this model handed back, and it is the reason the piece is about the hull’s speed trace instead.

The water charges for roughness

Resistance goes as v², so the power the water takes goes as v³, so by Jensen a hull that changes speed inside the stroke costs more than a hull holding the same average steadily:

⟨v³⟩ ≥ ⟨v⟩³ ,  equality only for a hull that never changes speed

Invert it and there is a ceiling. At 240 W, a hull that held one speed would do 4.309 m/s — a 1:56.0 split — and nothing in the demo ever goes faster than that. It is not a target. It is the number every rhythm is measured against, and the interesting question is only ever how much of it you are giving back.

Two bills, and they point in opposite directions

The gap to the ceiling splits cleanly in two, because the system’s centre of mass has a speed trace of its own. Compare the hull’s roughness to that one and you get the crew’s share; compare the centre of mass to a flat line and you get the blade’s, which no technique can remove — propulsion stops dead at every finish.

Sweep the one thing the player actually controls, which is how fast the seat is still moving when it arrives at the catch:

arriving atrateratiosplitblade’s sharecrew’s sharepast the catch
0.08 m/s21.91:2.621:57.61.02 s0.56 s0.1 cm
0.1523.71:2.271:57.50.850.600.2 cm
0.2626.31:1.871:57.40.680.690.4 cm
0.4028.71:1.511:57.50.570.840.9 cm
0.5530.61:1.221:57.60.491.031.7 cm
0.8531.71:0.891:57.90.461.374.0 cm
1.15 (never let go)30.31:0.701:58.20.521.627.4 cm

Read the last two columns against each other. One falls the whole way down, the other rises the whole way down, and the fastest rhythm in the table is the row where they cross. Arrive more gently than that and the boat spends too long coasting between impulses; arrive harder and you are paying for your own body. Nobody told the model to equalise anything. It is just what a minimum looks like when two costs are pulling opposite ways.

The right-hand column is worth reading on its own, too. Come up the slide flat out and the seat carries 7.4 cm past the catch, and the blade is not connected to anything until it comes back — so the first part of that drive is the biggest force in the stroke, applied to nothing.

The plateau is wide. The cliff is on the other side.

The second control is the rating, and it is not the knob people think it is:

ratesplitpower mademetres per stroke
12.82:22.3138 W16.4
15.52:09.1183 W15.0
16.72:04.3205 W14.4
18.91:57.6240 W13.5
21.51:57.4240 W11.9
24.01:57.4240 W10.6
24.91:57.4240 W10.2

Everything from 19 to 25 is the same boat speed to within three hundredths of a second. That is the honest finding and I was hoping for a more dramatic one. Ratings in the middle of the range do not need defending; what needs defending is the edge.

And the edge on the low side is a cliff, for a reason that has nothing to do with smoothness. Look at the third column: below about nineteen the athlete stops making the budget at all. The same watts spread over fewer strokes need more force per stroke, and past a peak of 520 N there is no more force to find, so the boat quietly drops off its own power target and the split falls apart — 25 seconds per 500 m at rate 12.8, and not one of those seconds is a fluctuation loss. This is also why racing rates are high: take the force ceiling out of the model and it recommends rowing a 2 km race at eighteen, which nobody does.

So the two ways to lose are not variations on each other. One is rushing, and it costs about a second. The other is admiring your own run, and it costs ten.

What it looks like against the clock

Four rhythms, same athlete, same 240 W, flying start, and the recovery is the only difference between them:

rateratiosplit500 m2000 m
Let it run23.61:2.201:57.4
Charging31.71:0.931:57.8+0.6 s+2.1 s · 1.1 lengths
Rushed30.31:0.701:58.2+0.8 s+3.3 s · 1.7 lengths
Dawdling15.51:4.022:09.2+12.7 s+48.1 s · 22.7 lengths

The winning row is worth a second look. The model was given a power budget, a stroke length and a search, and what it came back with was a rating of 24 at a ratio of 1:2.2 — which is what every coach on every river says, and which this file has no notion of. “Ratio” is not a term in the source. It is an output.

One stroke

The trace in the demo is the whole argument in one cycle. At the tuned rhythm the hull runs between 3.18 and 4.87 m/s — a 1.69 m/s swing on a 4.26 m/s average, inside 2.54 seconds — and the crew-and-boat trace it is drawn against swings 1.14. The difference between those two curves is a person sliding half a metre.

Two details in that picture earn their place. The hull’s peak is in the recovery, not the drive: the boat is quickest when nobody is rowing it, because the crew moving sternward is throwing it forward. And the deepest point of the whole stroke belongs to the hull alone — 3.18 against the system’s 3.70 — which is the check, and it is at the catch, and it arrives before the blade does.

What this model is not

It is a two-mass momentum model with a drag law and a force curve, and it has a sketch of a sculler attached to it.

The crew is one number: ξ = 0.865 metres of body centre of mass per metre of seat. That folds the trunk swing in with the slide and pretends they happen on the same schedule. They do not — “hands away, body over, then slide” is exactly the business of separating them — so the one thing a real coach spends most of the recovery talking about is the thing this model cannot see.

The drive is prescribed, not solved. The legs track a velocity profile under an acceleration cap; the propulsive force is a separate curve whose amplitude the power controller sets. A real stroke links them, and the link is where all the biomechanics live.

The blade is not modelled. There is no slip, no lift, no catch angle, no feathering cost, no puddle. “Over the catch” is the single stand-in for everything that goes wrong at the front end, and it is a crude one: force times zero for as long as the seat is behind the catch.

And the energy books are clean in a way a body is not. Over a whole cycle the kinetic energy is periodic, so everything the athlete produces ends up in the water, and the model hands back the work of stopping the crew at the reversal as useful drive. Muscles do not do that. Eccentric work is real work, and the ~5 J a rushed catch spends stopping a body is free here and is not free in a boat — so if anything this understates the case against rushing.

No wind, no stream, no waves, no steering, no other boats, and a drag law that is one constant where a hull is a whole curve.

Two things I fixed rather than papered over

The first version beat its own ceiling. The budget was ⟨F·v_hull⟩ — propulsive power at the pin — and the boat came out at 1:51 against a 1:56 ceiling, which is nonsense. The accounting was missing a term: the athlete also does work through the stretcher, and over a cycle

⟨D · v_hull⟩ = ⟨F · v_hull⟩ + ⟨muscle power⟩

so budgeting only the first term on the right lets the second in for free. Budgeting the drag dissipation instead prices the whole athlete, and makes (P/k)^⅓ a ceiling that nothing crosses. Every number above is downstream of that fix.

And every number before it was measured too early. The force controller took about two minutes of rowing to settle, with a damped oscillation on the way — 1:58.8 at 40 s, 1:56.2 at 60 s, 1:57.4 from 120 s on — so measurement windows that looked generous were sampling an overshoot, and the same rhythm scored 1:56.9 or 1:57.4 depending on when you looked. Slower gain, faster averaging and an opening force guess that is nearly right (412 N, scaling as power^⅔) settle it inside 40 s. It is the dullest kind of bug and it was quietly worth half a second on every claim in the write-up.

Reuse

src/let-it-run.mjs is framework-free and has no DOM in it. SI units throughout; splits only at the edges.

Port notes: stepOnce is about sixty lines of state math and translates directly into a tick method. bestRhythm is a few hundred steady-state solves — a second or so, fine on a mode change, not fine per frame.

The harness

node scripts/screenshot-demo.mjs boots the demo in a real Chromium, drives it through its own hooks, regenerates thumb.png and media/, and asserts 39 claims against the running page, exiting non-zero if any stops being true. Every table in this write-up is in there, including the two that would be embarrassing to get wrong: that no rhythm ever beats the ceiling, and that the optimum arrival is the row where the blade’s share and the crew’s share are within 0.12 s of each other.

One of those checks exists because it caught something on its first run — a mistyped colour string in a canvas gradient that threw on every frame and left the far bank unpainted. The page-error check is the cheapest assertion in the file and the only one that has ever fired on a typo.