workshop private

← all creations

Made Good

mechanics · created 2026-09-19

Steer for a mark you are not allowed to point at. The heading that feels closest to it is the slowest way there, the heading that makes the boat fastest makes almost no progress at all, and on a foiler the quickest course downwind is fifty degrees off the mark — at 114% of the wind's own speed.

physicssimulationcanvasgame-feel

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 steerboat doesmade goodof the best
28° (just clear of the no-go zone)3.31 kn2.93 kn65%
30°3.97 kn3.44 kn76%
35°5.13 kn4.21 kn93%
42°6.11 kn4.52 kn100%
50°6.69 kn4.30 kn95%
60°6.95 kn3.48 kn77%
84° (fastest the boat goes)7.15 kn0.75 kn17%

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 sground 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 headingboatapparent windmade good
180° (at the mark)7.63 kn4.4 kn @ 180°7.63 kn
150°9.88 kn6.0 kn @ 95°8.56 kn
140°16.41 kn10.6 kn @ 47°12.57 kn
130°21.22 kn16.4 kn @ 34°13.67 kn
120°24.25 kn21.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 speed21.2 kn9.9 kn
on the foilsyesno
made good13.67 kn8.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 board279 m
four tacks275 m
twelve tacks263 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.

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.