Hold left or right. That is the entire control scheme. There is no throttle, because there is no engine, and the one thing you are steering toward is the one direction the boat cannot go.
Two numbers, and they are never the same number
The boat’s speed is the number you feel. The number that wins the race is the component of that speed pointing at the mark — velocity made good, VMG — and the heading that maximises one is nowhere near the heading that maximises the other.
For the keelboat in 12 knots of true wind:
| you steer | boat does | made good | of the best |
|---|---|---|---|
| 28° (just clear of the no-go zone) | 3.31 kn | 2.93 kn | 65% |
| 30° | 3.97 kn | 3.44 kn | 76% |
| 35° | 5.13 kn | 4.21 kn | 93% |
| 42° | 6.11 kn | 4.52 kn | 100% |
| 50° | 6.69 kn | 4.30 kn | 95% |
| 60° | 6.95 kn | 3.48 kn | 77% |
| 84° (fastest the boat goes) | 7.15 kn | 0.75 kn | 17% |
Read the last row against the bold one. The fastest heading available is a knot quicker through the water and makes one sixth of the progress. And read the first row too, because that is the one that actually costs races: at 28° you are sailing, the sails are full, the boat is moving, the bow is pointed nearer the mark than anyone else’s — and you are throwing away a third of your afternoon.
Both errors cost about the same, which is the part that makes it a mechanic rather than a lesson. There is a ridge, it is narrow at the top, and falling off either side feels completely different: pinching feels careful and footing feels fast.
So the demo flies the claim rather than asserting it. Ninety seconds of actual sailing, same boat, same breeze, three headings:
| held for 90 s | ground made to windward |
|---|---|
| 42° | 209 m |
| 30° | 173 m |
| 60° | 161 m |
The wind you are sailing in is not the wind that is blowing
The sail does not feel the true wind. It feels the true wind minus your own velocity, and since you are always moving toward the wind’s source to some degree, your own motion adds a headwind of your own making. Every knot you go drags the breeze forward and strengthens it.
Close-hauled at 42° true in a 12-knot breeze, the keelboat’s sail is working in a 17-knot apparent wind at 28° off the bow. Fourteen degrees forward, five knots stronger, and neither number is a correction — it is the only wind the rig has ever been trimmed to. This is why the sheet is already at its stop (13°) with the sail at a 15° angle of attack instead of the 20° it wants: you cannot sheet inside the shrouds, so close-hauled the trimmer is simply not allowed to have the angle that would be best.
Because the boat’s speed is an input to the wind that produces the boat’s
speed, none of this can be tabulated. polar() solves it: at each wind angle
it searches for the speed where the sail’s drive equals the hull’s resistance,
recomputing the apparent wind inside the loop.
Which is why a fast boat sails downwind sideways
Everything above is mildly interesting on a keelboat and violent on a foiler, because the feedback only runs away if the boat is fast enough to change its own wind materially.
The keelboat’s answer to “which way downwind” is boring: 179°, near enough straight at the mark, making 5.50 kn — 46% of the wind speed. It cannot outrun the breeze, so pointing at the mark is right.
The foiler’s answer to the same question, same 12 knots:
| downwind heading | boat | apparent wind | made good |
|---|---|---|---|
| 180° (at the mark) | 7.63 kn | 4.4 kn @ 180° | 7.63 kn |
| 150° | 9.88 kn | 6.0 kn @ 95° | 8.56 kn |
| 140° | 16.41 kn | 10.6 kn @ 47° | 12.57 kn |
| 130° | 21.22 kn | 16.4 kn @ 34° | 13.67 kn |
| 120° | 24.25 kn | 21.0 kn @ 30° | 12.12 kn |
Three things in that table.
The best course downwind is fifty degrees off the way you want to go, and sailing it beats pointing at the mark by 79%.
The VMG in the bold row is 13.67 kn in a 12-knot breeze — 114% of the true wind speed, made good dead downwind. Nothing is broken. The boat is not being pushed; it is being pulled by a wind it manufactures, and a sail generating lift does not care that the air it is working in was partly its own idea.
And look at the apparent wind column while you read it. At 130° true the sail is trimmed to 34°. The boat is close-hauled — sheeted in hard, heeled, working the same way it works beating to windward — while it runs away downwind at nearly twice the speed of the breeze. That single disagreement between the two columns is the whole piece.
Then bear away twenty degrees more, toward the mark, and the floor goes:
| 130° | 150° | |
|---|---|---|
| boat speed | 21.2 kn | 9.9 kn |
| on the foils | yes | no |
| made good | 13.67 kn | 8.56 kn (−37%) |
Below about 145° the apparent wind has collapsed far enough that the rig cannot hold the boat up, it drops off the foils, the wetted area goes back up by a factor of three and it is suddenly a slow heavy catamaran. Getting back up costs more than the mistake did. Twenty degrees of helm, in the direction that looks like the direction you want.
Inside the laylines the route is free. The tacks are not.
laylinePlan() prices a leg, and the interesting thing is what the answer does
not contain. For a leg of need metres to windward at the optimal angle β:
time = need / (v · cos β)
No cross-track term. So from the gate, from 180 m to the right of it, from 260 m to the left — every route to the windward mark takes exactly the same 266.6 seconds, as long as you hold the angle and stay inside the laylines. Two tacks or twenty, a long board then a short one or the other way round: the zigzag is not the thing being optimised.
The angle is. And the tacks are pure cost:
| 120 s at 42°, ground to windward | |
|---|---|
| one board | 279 m |
| four tacks | 275 m |
| twelve tacks | 263 m |
That is the actual game. Not “find the clever route” — there isn’t one — but “hold the angle, and stop turning.” The two ways to lose are the two ways people lose: tacking more than you need, and overstanding. Sail past the layline and the same mark costs 81 seconds more, because you have stopped converting distance into progress and started converting it into distance.
The autopilot sails the 620 m beat and the run home in 469.0 s. The polar says that distance is worth 459 s. The 11-second gap is one tack and two mark roundings, and there is no route that gets it back.
Getting it wrong is a state, not a penalty
Turn through the wind too slowly and the boat does not just go slow. The sail loses its angle of attack, starts flogging, and a flogging sail is a large flapping obstruction rather than a wing lying quietly edge-on — it has real drag, pointed the wrong way. The boat stops. With no water moving past it, the rudder does nothing, so you cannot steer out. What gets you out is the rig blowing your own bow off the wind, slowly, while you watch.
This is the one thing in here that a player has to feel rather than read, and it is the reason the tack has weight. Twelve tacks costs you 16 m. One tack you botch costs you the leg.
What this model is not
It is a velocity prediction program of the simplest honest kind, and it has a sketch of a boat attached to it.
The sail is a thin cambered wing with a stall blend — attached-flow coefficients up to 19°, flat-plate coefficients past 34°, smoothstepped between — trimmed to hold a 20° angle of attack wherever the sheet has the travel for it. That is a model of a sail, not a measurement of one. The absolute numbers are the model’s; the shape of the answers is what I would defend.
The boat sails along its heading. There is no leeway in the motion: the
sideways slip is folded into the keel’s induced-drag term and reported as an
angle (leeway, 2.8° close-hauled) rather than applied to the track. A real
boat’s wake is not its heading, and this one’s is.
Both hulls are one number each where they should be several. The keelboat’s speed wall is a single exponential in Froude number fitted to give a sensible resistance curve near hull speed, not a Michell integral. The foiler’s takeoff is a smoothstep on speed, not a lift balance — it does not know what a ride height is, and it cannot crash.
No wind shifts, no gusts, no waves, no tide, no other boats. That is a real omission and not a small one: the entire reason “sail to the layline” is bad racing advice in the real world is shifts, and in a model with none of them it is optimal, which is exactly why the autopilot does it. Take the layline result as a statement about geometry, not about tactics.
One thing I did fix rather than paper over. The keel is a wing, and a wing has a maximum lift coefficient. The first version left that out, so the induced drag went to infinity as the speed went to zero, a stopped boat could never start, and — because the singularity happened to sit near the no-go boundary — it drew a no-go zone that looked right for the wrong reason. Capping the keel’s lift moved the boundary to 18°, which is nonsense, and made the real cause obvious: past the keel’s stall there is no steady state on that heading at all. The boat is not sailing slowly, it is sliding. Reject those solutions and the boundary comes back to 26.8° — the same number, now with a cause. Every equilibrium speed in the polar is unchanged to the last decimal, because at sailing speeds the keel was never near stalling.
The no-go zone is not where the sail stops pulling. The sail is still pulling, hard, mostly sideways. It is where the keel stops being able to turn that into a course.
Reuse
src/made-good.mjs is framework-free and has no DOM in it. SI units
throughout, knots only at the edges.
forces(boat, tws, twa, v)→ every force at one instant: drive, side force, the four resistance terms separately, the depower factor, implied leeway, whether it is flying and whether the keel is slipping. Everything else in the file is a way of asking this one function a question.apparentWind(tws, twa, v)→{aws, awa}. Eight lines, and the source of most of what is surprising here.windTriangle(tws, twa, v)/toWorld(p, heading)→ the same sum as three points to draw. These exist because both views were drawing the triangle by hand and both had the boat’s own contribution pointing aft, which made two pictures that disagreed with the number printed beside them. One construction, asserted equal toapparentWindat every angle.solveSpeed/polar/bestVMG/noGo/topSpeed→ the steady-state solver.solveSpeedkeeps the fastest admissible root deliberately: a foiler has two equilibria at the same wind angle, a slow floating one and a fast flying one, and which one you are on depends on where you came from.laylinePlan(pos, mark, windDir, twaBest, vBest)→ the leg priced in wind axes. The cross-track term dropping out is the interesting part.Helm→ the same forces integrated instead of solved, with steerage that dies with speed. The polar is not fed to it; the polar is where it ends up if you hold a heading and wait. That relationship is the reason a tack costs time in the demo rather than being charged a penalty.
Port notes: Helm.step is about forty lines of state math and translates
directly into a tick method. polar() at 1° is ~26k evaluations of forces —
a few milliseconds, fine to recompute when the wind changes, not fine per
frame.
The harness
node scripts/screenshot-demo.mjs boots the demo in a real Chromium,
regenerates thumb.png and media/, and asserts 32 claims against the
running page, exiting non-zero if any stops being true. Every number in this
write-up is in there: both VMG tables, the no-go angles, the 90-second sailed
comparison, the layline identity across three starting positions, the tack
ladder, the foiler’s 114% and its fall off the foils, the irons state, and
both autopilot times.
Two of those checks exist because they caught something. The triangle check
pins the drawn geometry to apparentWind at twelve boat-and-angle
combinations, after the diagram spent a version contradicting the panel next to
it. And the autopilot check prices the ideal against the distance the course
actually makes you sail rather than the nominal leg length — the rounding
radius is 24 m at each end, which is 11 seconds, which was the entire size of
the gap I was trying to explain.



