workshop private

← all creations

Beat

games · created 2026-09-13

The mark is dead upwind and there is no heading that points at it. A windward-leeward lap where the fastest route to a buoy is never a line toward it, and the famous tactic for getting there — tack whenever the wind heads you — is measurably wrong until the shift is bigger than about eleven degrees.

physicssimulationcanvasgame-feeltopdown

There is a wedge in front of the boat about sixty degrees wide, and the mark is inside it. Every heading in that wedge is a heading the boat will not sail — not slowly, not badly, at all — so the shortest route to the buoy is the one route unavailable, and the whole game is what you do about that.

Sailing is the only race I can think of where the finish line is in a direction you are physically barred from travelling, and where the correct answer is to spend the entire leg going somewhere else. The interesting part is not that it is true. It is that the degree to which it is true is a number, it changes with the wind, and almost every piece of received tactical wisdom about it turns out to be conditional on something nobody says out loud.

There is no polar curve in this game

The tempting way to build a sailing sim is to write down a polar — a table of boat speed against angle to the wind — and interpolate it. You can get one off any sailmaker’s website. It would have taken twenty minutes, and it would have made every interesting thing in here an input rather than a result.

src/sail.mjs never writes a speed down. A sailboat is two foils working against each other in two different fluids: the rig makes a force in air, the keel and hull make an equal and opposite one in water, and the boat goes wherever that pair balances. Each foil has a drag angle — how far its total force leans back from the perpendicular — and Lanchester’s course theorem says the closest you can ever sail to the true wind is just their sum:

β_min = ε_air + ε_water

Nothing imposes that. It appears. The driving component of the rig’s force is F·sin(γ − ε_air), where γ is the apparent wind angle, and that expression is simply negative when the wind is closer to the bow than the rig’s own drag angle. A boat stops sailing upwind for the same reason a wing stops flying. One number in the file — cd0, the rig’s parasitic drag — moves the edge of the no-go zone, and if you double it the boat loses about four degrees of pointing, which is why a baggy main and a dirty bottom feel like the same problem.

So the model is: apparent wind from the velocity triangle every frame; a soft sail with a lift/drag pair that stays sane from zero to ninety degrees of attack, because dead downwind a sail is a parachute and not an aerofoil; a keel at whatever leeway the boat is currently making; and two degrees of freedom, surge and sway, integrated in the boat’s own frame.

That last decision is the one that pays. Sway is a state variable, not an assumption, so leeway is an output. Being in irons is an output. The cost of a tack is an output — it is exactly the deceleration through head-to-wind plus the rudder drag of getting there, and it comes out at about twenty metres of windward distance, which is two boat lengths and matches a real crew having an average day. None of those three are scripted anywhere, and all three are what the game is made of.

The polar exists, but as a measurement: put the boat at a heading in a steady breeze, hold it there, and integrate until it stops changing.

TWSno-gobeat anglebeat speedbeat VMGbest runrun VMGtop speed
6 kn29°49°3.55 kn2.17 kn140°3.33 kn4.99 kn
10 kn29°45°5.10 kn3.35 kn151°4.94 kn6.65 kn
13 kn29°42°5.80 kn3.98 kn158°5.93 kn7.49 kn
16 kn29°40°6.17 kn4.34 kn161°6.59 kn8.04 kn
20 kn30°37°6.30 kn4.62 kn165°7.27 kn8.65 kn

Every qualitative feature of a real keelboat polar is in there and none of it was put there: a no-go zone around thirty degrees, a beat angle that narrows from 49° to 37° as the breeze builds, top speed on a broad reach rather than a run, dead downwind slower than either, and an upwind VMG that flattens off in a breeze because past a point the crew cannot hold the boat up any more. The numbers land within about ten per cent of a real 35-footer, which is as close as I wanted to get without fitting to the answer.

What you can’t do with a measurement

One thing the relaxation would not give me: the edge of the no-go zone.

The obvious definition is “the angle at which the boat stops”, and that definition has no limit. Approach the true stalling angle from above and the boat does not settle at some small speed — it decays toward zero more and more slowly, so the number you get back is a report on how many seconds you happened to simulate for. Seventy seconds says 26°. A hundred and fifty says 29°. Three hundred says 30° and is still climbing. Neither is more correct.

So the no-go angle in this file is defined as the angle that still makes half the windward progress the best one does. That lands in the steep part of the curve where the answer stops moving after about a minute, and it happens to be the thing a sailor means anyway: not where the boat literally stops, but where pinching any further stops being sailing.

Six ways to sail a beat

scripts/measure.mjs races fixed steering policies round the same course in the same wind — fifteen seeds each, medians reported. Every policy reads only what a crew could see from the deck: the wind where the boat is, the bearing to the mark, its own instruments, and a memory of what the wind has been doing. None of them can see a shift coming. A policy that could see the future would only prove that seeing the future is good.

Shifty day — ±19°, fourteen knots:

policyfinishedlapbeatruntacksvs. best VMG
Point at the mark0%0
Pinch — 33° / 175°100%5:243:212:123+11 s
Foot for speed — 55° / 140°93%6:334:152:233+80 s
Best VMG, tack at laylines100%5:133:082:0720 s
Best VMG, tack on 5° headers100%5:103:012:105−3 s
Best VMG, tack past half the day’s swing100%5:063:002:086−7 s

The first row is the one worth sitting with. Pointing at the mark does not finish. Not slowly — at all, in zero out of fifteen runs, on every wind setting, forever. The boat noses up, the sails stop working, the rudder loses the flow it needs to steer with, and it sits there going sideways at a knot while the clock runs. It is the only failure in the table that is total, and it is the instinctive move.

The second surprise is the footing row: eighty seconds slower while sailing faster. At 55° off the wind in thirteen knots this boat does 6.67 knots against 5.80 at its proper angle — it heels more, it sounds better, the wake is bigger, and it is losing badly. Fifteen per cent more speed, and fifteen per cent less of it pointed where you want to go: 3.40 knots of VMG against 3.98. Pinching has the same shape and is gentler about it, 4.38 knots of boat speed for 3.43 of progress. The demo puts the VMG bar directly under the speed readout for exactly this reason. It is the only instrument that tells you going faster has just made you slower.

When the famous tactic is wrong

Every sailing book gives you the same rule for a beat: tack when the wind heads you. Sail the lifted tack, and the shifts do the work.

I built the shift-playing policy expecting to confirm it, and on the first run it lost. Not by a lot — three seconds — but it lost, repeatedly, at every threshold I tried. Being lifted more of the time was clearly working: the diagnostic said the policy spent 89% of the beat on the favoured tack against the baseline’s 57%. It was just slower anyway.

Which is the actual result, and it is not a bug. A tack costs a fixed number of metres. A shift pays a rate. So there is an amplitude below which the famous tactic is simply the wrong move, and it is not a matter of opinion:

oscillationnever tack (beat)liftedplay it (beat)liftedtacksplaying it costs
±0°3:1648%3:1748%2+0 s
±5°3:1453%3:1759%3+3 s
±9°3:1452%3:1582%4+1 s
±13°3:2351%3:1489%5−9 s
±17°3:2155%3:1191%6−10 s
±22°3:2157%3:0793%6−14 s

The crossover is somewhere around eleven degrees. Below it, every tack you spend buys less lift than it costs in stopped boat, and the sailor who sits on one tack and goes to the layline beats the one working the shifts. Above it the sign flips and keeps flipping harder. The rule in the books is true; the books just never mention that it is a threshold, and the threshold is a property of your boat — a dinghy that tacks in two lengths has a much lower one than this five-tonne keelboat.

The second half of the same trade is how often the wind swings:

swing periodnever tackplay the shiftstacksliftedplaying it costs
60 s3:163:371176%+21 s
100 s3:173:20768%+3 s
150 s3:233:14589%−9 s
240 s3:183:07586%−11 s
380 s3:103:07386%−3 s

A fast swing is a trap: you can be lifted three-quarters of the time and still lose twenty-one seconds, because catching a 60-second oscillation means buying eleven tacks and eleven tacks is a quarter of a kilometre of windward distance. A slow swing pays for one tack and then keeps paying. And there is a peak rather than a slope — at 380 seconds the wind barely swings inside one beat, so there is less on offer to collect.

I went looking for leg length as the second variable and it is not one. Lengthen the beat and the gain and the tack count scale together; the ratio barely moves. It is the period that decides it.

The bug that was a lesson

The shift policy kept gaining eight seconds on the beat and giving eight back on the run, and the reason turned out to be that I had applied the upwind rule downwind.

Upwind you tack on a header. Downwind you gybe on a lift. The sign inverts, because running, a header swings the mark aft of your track and a lift swings it forward. Same instrument, same needle, opposite instruction. Applying the beat’s rule to the run cost almost exactly what playing the shifts properly gained, which is a tidy demonstration of how a tactical rule with its sign flipped is worse than having no rule at all.

Laylines, and being late to one

The layline is the line through the mark along your tack’s track angle: cross it and you have overstood, and every metre past it is distance sailed for nothing.

tackedbeatdistance sailed, whole laptacks
on the layline3:161143 m2
25 m past it3:171160 m2
55 m past it3:271209 m2
95 m past it3:431291 m2
150 m past it4:071384 m4

A hundred and fifty metres of lateness on a four-hundred-metre beat costs fifty-one seconds — more than a quarter of the leg — and the reason is geometric rather than tactical: overstanding turns a leg you were sailing at 42° into one you finish at a reach, so the extra distance is not the 150 m you went too far, it is the 240 m the whole triangle grew by.

Two details in the code exist because of this. The layline is computed on the track angle rather than the steered one, since a boat making five degrees of leeway lays five degrees less than its compass claims, and most overstanding is the accumulated cost of forgetting that. And the downwind layline is the mirror of the upwind one, not the same one — running, you can always sail higher than your target angle to reach a mark and never lower, which is exactly the opposite of the constraint on a beat. Getting that sign wrong drew a gybing layline that could never be reached.

Trimming for speed, not for drive

The least intuitive line in the file is in the sail model. The trimmer searches for the angle of attack that maximises

drive − sideCost · side²

and not, as I first wrote it, the one that maximises drive. Sheeting harder always buys more drive — the curve is still climbing when the sheet runs out — but it buys it with side force, and side force is paid for underwater in keel-induced drag that goes as its square. sideCost is handed to the rig by the hull and is exactly the induced-drag penalty per newton² at the speed the boat is doing now.

That one term is why the same sail is sheeted differently at four knots and at seven, and why a boat that is going slowly has to be eased to get moving rather than trimmed on harder. With the naive version the model over-sheeted, over-heeled and sailed about eight per cent slow upwind, in precisely the way a crew does when they are trying too hard.

Playing it

Arrows or A/D for the helm; space tacks or gybes for you. On a phone, drag anywhere and the boat steers at your finger. 1, 2, 3 switch between a steady day, an oscillating one and a properly shifty one; R restarts and leaves your last lap on the water as a ghost, which is the fastest way to see what you did differently.

Things worth reading off the screen:

Note that the run is the same problem mirrored: dead downwind is slow, so the leg home is two gybes rather than a straight line at the finish. You cannot point at either mark, at either end of the course.

Reuse

src/sail.mjs is headless and framework-free — no DOM, no canvas, no rendering.

Everything is metres, seconds and newtons. A different boat is a parameter change: RIG and HULL are plain objects and every field has a unit on it.

Gotchas