SAIL2D LITE · PROMPT + SPEC (ONE PAGE)
Sail2D LiteTop-down 2D browser sailing toy · auto-trimmed sail · table-driven speed · single self-contained page. This document is the complete contract: prompt and requirements together.
Deliberately out of scope — no 3D, no WebGL, no libraries. No race, start line, timing, laps, scores or penalties: no time-capture concept at all. No manual sail trimming — the sail trims itself. No physics engine and no force calculation — boat speed comes from a static lookup table given below; the agent MUST NOT invent its own aerodynamic model. No waves, no leeway, no heel, no collision. Buoys are scenery only.
§0Kickoff — paste into Claude Code
Fetch https://says.hermione.online/sail2d-sim-lite/prompt.md and implement it
fully and autonomously in one session. Do not stop to ask questions; where
something is unspecified, choose the simplest option that keeps the
acceptance checklist passing. Deliverable is a single index.html.
§1Mission
Build a top-down 2D sailing toy on an HTML5 <canvas>: one small yacht seen from above on an endless checkerboard sea, a wind that drifts gently over time, and a scattering of buoys to sail around. The player only steers. Sail trim is automatic; boat speed is looked up from a fixed table of speed-vs-wind-angle and approached with lag so the boat feels like it has mass. HUD shows the wind and the boat's speed. Ships as one index.html that runs from any static host with no build step.
§2Conventions
- World units: metres, seconds. Angles in degrees in UI, radians internally. Speed shown in knots (1 m/s = 1.94384 kn).
- Top-down: screen up = north = bearing 0°; bearings increase clockwise. Bearing θ ⇒ world direction
(sin θ, −cos θ) in (x, y) with y pointing screen-down.
- Wind direction is always FROM (TWD). True wind angle
TWA = wrap180(TWD − heading), signed; positive = wind from starboard. Use |TWA| for the speed table.
- Fixed scale: 1 m = 4 px at zoom 1. Camera is centred on the boat; the world scrolls under it. Optional zoom is a MAY.
§3Speed table — static input, do not derive
Boat target speed is a fixed fraction of true wind speed depending on |TWA|. Interpolate linearly between rows; below 30° the boat is in the no-go zone (target 0). Values are the design intent — copy them into the code verbatim as a constant array, do not fit curves or compute lift.
| |TWA| ° | speed / TWS | speed at TWS 6 m/s (m/s) | ≈ knots | point of sail |
| 0 | 0.00 | 0.0 | 0.0 | head to wind (no-go) |
| 25 | 0.00 | 0.0 | 0.0 | edge of no-go |
| 30 | 0.30 | 1.8 | 3.5 | pinching |
| 45 | 0.48 | 2.9 | 5.6 | close-hauled |
| 60 | 0.58 | 3.5 | 6.8 | close reach |
| 75 | 0.63 | 3.8 | 7.3 | close reach |
| 90 | 0.66 | 4.0 | 7.7 | beam reach (fastest) |
| 105 | 0.64 | 3.8 | 7.5 | broad reach |
| 120 | 0.59 | 3.5 | 6.9 | broad reach |
| 135 | 0.53 | 3.2 | 6.2 | broad reach |
| 150 | 0.46 | 2.8 | 5.4 | run |
| 165 | 0.41 | 2.5 | 4.8 | run |
| 180 | 0.38 | 2.3 | 4.4 | dead run |
targetSpeed = ratio(|TWA|) × TWS where ratio() is piecewise-linear interpolation over the first two columns. Between 25° and 30° the interpolation naturally ramps from 0 to 0.30.
§4Motion model — all rules
Integrate with dt clamped to ≤ 50 ms. State: position p, heading ψ, speed v ≥ 0. The boat only moves along its heading.
| ID | Rule |
| 2M-01 | Speed lag (the "hysteresis"): v approaches targetSpeed exponentially with two different time constants — τ_acc = 4 s when target > v, τ_dec = 8 s when target < v. Formula: v += (target − v) · (1 − exp(−dt/τ)). Consequence: tacking through the wind costs speed and the boat coasts rather than stopping dead; a boat pointed into the wind glides for a while. |
| 2M-02 | Turning: rudder input r ∈ [−1, 1]; yaw rate ψ̇ = r · (15°/s + 5°/s · v), capped at 50°/s. Non-zero base rate so the boat can always turn out of irons. |
| 2M-03 | Position: p += v · dt · (sin ψ, −cos ψ). No leeway, no drift, no current. |
| 2M-04 | Auto trim (visual only): boom angle from centreline b = clamp(|TWA| · 0.5, 10°, 85°), drawn on the leeward side (opposite the sign of TWA). Swaps side when TWA changes sign; smooth over ~0.5 s. In the no-go zone (|TWA| < 25°) the sail is drawn centred and MAY flutter (±5° wobble). |
| 2M-05 | Wind drift: TWD and TWS are smooth random walks. Every 3–6 s pick a new target: TWD target = base_TWD + uniform(−20°, +20°), TWS target = base_TWS + uniform(−1.5, +1.5) m/s (clamped ≥ 2 m/s). Ease the live TWD/TWS toward the targets with τ = 5 s. Wind never gusts abruptly and never leaves the ±20° / ±1.5 m/s band around the base. Defaults: base TWD 0°, base TWS 6 m/s; MUST be overridable via ?wind=deg&tws=mps. |
§5World, drawing & buoys
| ID | Requirement |
| 2W-01 | Checkerboard sea: two blue shades (e.g. #1a5c9e / #17538f, subtle), squares of 10 m, aligned to world coordinates (square edges at multiples of 10 m). Draw only the squares that intersect the viewport each frame using fillRect; the pattern MUST stay fixed to the world so scrolling is visible as the boat moves. |
| 2W-02 | Buoys: exactly six, at fixed world positions: (0,−200), (170,−100), (170,100), (0,200), (−170,100), (−170,−100) in (x, y) metres (±20% fine). Each buoy is an orange filled circle r 1.5 m with a thin dark outline. Pure scenery — MUST NOT have collision, proximity, rounding or sequencing logic. They never move. |
| 2W-03 | Yacht sprite (drawn in boat-local space, bow toward local −y, then rotated by heading): hull = a pointed polygon ~6 m long × 2 m wide, white fill, dark outline; small grey deck rectangle; mast = dark dot at ~1/3 from bow; boom = dark line 3 m long from the mast aft, rotated by 2M-04; sail = a light-grey filled triangle (or curved wedge) between mast and boom end. Nothing else. |
| 2W-04 | Camera fixed on the boat (boat at screen centre). Whole world scrolls; north always up (no rotating camera). |
| 2W-05 | Optional short fading wake (a few dots behind the stern) is a MAY — helps show motion; skip if in doubt. |
§6Controls & HUD
| ID | Requirement |
| 2C-01 | ←/→ (and A/D): rudder, spring-returns to centre on release. R: reset boat to origin, heading 90°. H: toggle help overlay. Nothing else — there is no sheet control. |
| 2H-01 | Wind indicator: a compass rose (circle with N/E/S/W ticks) in the top-right corner, with an arrow pointing in the direction the wind is blowing toward (i.e. from TWD across the rose), labelled with TWD in ° and TWS in kn. Live-updates as the wind drifts. |
| 2H-02 | Speed readout: boat speed in kn, large monospace text, top-left. Also heading ° and |TWA| ° in smaller text underneath. Text lives in DOM overlays or is drawn on the canvas — either is fine. |
| 2H-03 | Help overlay listing the controls, visible on first load, toggled with H. |
§7Technical constraints
| ID | Requirement |
| 2T-01 | Exactly one file index.html, inline CSS + JS, 2D canvas only. No libraries, no CDN, no build step, no frameworks, no WebGL. |
| 2T-02 | Plain JavaScript. Keep the sim step a pure function of (state, input, dt), separate from drawing code, and put the speed table, time constants and wind band as named constants at the top of the script. |
| 2T-03 | Canvas fills the window and resizes with it (handle devicePixelRatio). Smooth ≈60 fps on an ordinary laptop. |
| 2T-04 | No console errors; sailing unattended for 5+ minutes produces no NaNs, wind stays inside its band, checkerboard stays aligned. |
§8Acceptance checklist — walk it, then done
- ☐ Loads from a static server with zero console errors; no external requests.
- ☐ Checkerboard sea scrolls under the boat and stays aligned to the world; boat is always at screen centre; north is up (2W-01, 2W-04).
- ☐ Six orange buoys in a hexagon around the origin; sailing straight through one does nothing (2W-02).
- ☐ Beam reach at TWS 6 m/s settles at ≈ 4.0 m/s (≈ 7.7 kn); close-hauled at 45° ≈ 2.9 m/s; dead run ≈ 2.3 m/s — i.e. the table is being used as given (§3).
- ☐ Turn head to wind: speed decays over several seconds rather than dropping instantly; turn back onto a reach: speed builds up over a few seconds (2M-01, τ_dec > τ_acc).
- ☐ Boat can turn while nearly stopped (2M-02).
- ☐ Boom/sail is always on the leeward side, swaps on tack/gybe, sits centred and fluttery head to wind (2M-04).
- ☐ Wind indicator arrow and numbers visibly drift over a minute but stay within ±20° / ±1.5 m/s of the base (2M-05, 2H-01).
- ☐ Speed readout in kn is live and consistent with the boat's visible motion (2H-02).
- ☐
?wind=90&tws=9 changes the base wind accordingly.
- ☐ H and R work; 5-minute unattended sail is stable (2C-01, 2T-04).
Definition of done — every box above ticked by manual play. There is no automated test suite; the checklist is the gate.