# Sail2D Lite — one-page prompt + spec (v1.0, 2026-08-15) You are implementing **Sail2D Lite**, a top-down 2D browser sailing toy, in one autonomous session. Do not stop to ask questions; where something is unspecified, choose the simplest option that keeps the acceptance checklist passing. This document is the complete contract. **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 (sail trims itself). No physics engine and no force calculation — boat speed comes from the **static lookup table** below; you MUST NOT invent an aerodynamic model. No waves, no leeway, no heel, no collision. Buoys are scenery only. Deliverable: **a single `index.html`**. ## §1 Mission One small yacht seen from above on an endless checkerboard sea, a wind that drifts gently over time, six buoys to sail around. The player only steers. Sail trim is automatic; speed is looked up from a fixed speed-vs-wind-angle table and approached with lag so the boat feels like it has mass. HUD shows the wind and the boat's speed. Runs from any static host, no build step. ## §2 Conventions - Metres, seconds; degrees in UI, radians internally; speed shown in knots (1 m/s = 1.94384 kn). - Top-down: screen up = north = bearing 0°; bearings clockwise. Bearing θ ⇒ world direction (sin θ, −cos θ) with y pointing screen-down. - Wind direction is always **FROM** (TWD). TWA = wrap180(TWD − heading), signed, + = wind from starboard. Use |TWA| for the table. - Fixed scale 1 m = 4 px at zoom 1. Camera centred on the boat; world scrolls. Zoom = MAY. ## §3 Speed table — STATIC INPUT, do not derive Target speed = ratio(|TWA|) × TWS, ratio() = piecewise-linear interpolation over this table. Copy it into the code verbatim as a constant array. Below 25° the boat is in the no-go zone (0); 25→30° ramps naturally. | \|TWA\| ° | speed / TWS | m/s @ TWS 6 | ≈ kn | 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 | ## §4 Motion model (dt clamped ≤ 50 ms; state: position p, heading ψ, speed v ≥ 0; boat moves only along heading) - **2M-01 Speed lag ("hysteresis")**: v approaches targetSpeed exponentially with **τ_acc = 4 s** when target > v and **τ_dec = 8 s** when target < v: `v += (target − v) · (1 − exp(−dt/τ))`. Tacking costs speed; head-to-wind the boat glides rather than stopping dead. - **2M-02 Turning**: rudder r ∈ [−1,1]; ψ̇ = r·(15°/s + 5°/s·v), cap 50°/s. Base rate lets the boat always turn out of irons. - **2M-03 Position**: p += v·dt·(sin ψ, −cos ψ). No leeway, drift or current. - **2M-04 Auto trim (visual only)**: boom angle b = clamp(|TWA|·0.5, 10°, 85°) drawn on the leeward side (opposite sign of TWA); swaps side when TWA changes sign; smooth ~0.5 s. In no-go (|TWA| < 25°) sail drawn centred, MAY flutter ±5°. - **2M-05 Wind drift**: TWD and TWS are smooth random walks. Every 3–6 s pick new targets: TWD target = base_TWD + uniform(−20°,+20°); TWS target = base_TWS + uniform(−1.5,+1.5) m/s (clamp ≥ 2). Ease live values toward targets with τ = 5 s. Never abrupt, never outside the band. Defaults base TWD 0°, base TWS 6 m/s; overridable via `?wind=deg&tws=mps`. ## §5 World, drawing & buoys - **2W-01 Checkerboard sea**: two subtle blue shades (e.g. #1a5c9e / #17538f), 10 m squares aligned to world coordinates (edges at multiples of 10 m). Draw only squares intersecting the viewport each frame with fillRect. Pattern fixed to the world so scrolling is visible. - **2W-02 Buoys**: exactly six at fixed world positions (0,−200), (170,−100), (170,100), (0,200), (−170,100), (−170,−100) in (x,y) m (±20% fine). Orange filled circle r 1.5 m, thin dark outline. Pure scenery — MUST NOT have collision, proximity, rounding or sequencing logic. Never move. - **2W-03 Yacht sprite** (boat-local space, bow toward local −y, rotated by heading): hull = pointed polygon ~6 m × 2 m, white fill, dark outline; small grey deck rectangle; mast = dark dot ~1/3 from bow; boom = dark line 3 m from mast aft, rotated per 2M-04; sail = light-grey filled triangle (or curved wedge) between mast and boom end. Nothing else. - **2W-04** Camera fixed on the boat (screen centre); north always up; no rotating camera. - **2W-05** Optional short fading wake behind the stern = MAY. ## §6 Controls & HUD - **2C-01** ←/→ (A/D): rudder with spring-to-centre. R: reset to origin heading 90°. H: toggle help. Nothing else — no sheet control. - **2H-01 Wind indicator**: compass rose (circle with N/E/S/W ticks) top-right; arrow pointing in the direction the wind blows *toward* (from TWD across the rose); labelled TWD ° and TWS kn; live-updates as wind drifts. - **2H-02 Speed readout**: boat speed in kn, large monospace, top-left; heading ° and |TWA| ° smaller underneath. DOM overlay or drawn on canvas — either. - **2H-03** Help overlay with controls, visible on first load, toggled by H. ## §7 Technical - **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; sim step a pure function (state, input, dt) separate from drawing; speed table, time constants and wind band as named constants at the top. - **2T-03** Canvas fills the window, resizes with it, handles devicePixelRatio; ~60 fps. - **2T-04** No console errors; 5+ min unattended: no NaNs, wind inside its band, checkerboard aligned. ## §8 Acceptance checklist (manual — this is the gate) 1. Loads from a static server, zero console errors, no external requests. 2. Checkerboard scrolls under the boat, stays world-aligned; boat at screen centre; north up. 3. Six orange buoys in a hexagon around the origin; sailing through one does nothing. 4. At TWS 6 m/s: beam reach settles ≈ 4.0 m/s (≈ 7.7 kn); close-hauled 45° ≈ 2.9 m/s; dead run ≈ 2.3 m/s — table used as given. 5. Head to wind: speed decays over several seconds, not instantly; back on a reach: builds up over a few seconds (τ_dec > τ_acc). 6. Boat can turn while nearly stopped. 7. Boom/sail always leeward, swaps on tack/gybe, centred and fluttery head to wind. 8. Wind indicator arrow and numbers drift visibly over a minute but stay within ±20° / ±1.5 m/s of base. 9. Speed readout in kn live and consistent with visible motion. 10. `?wind=90&tws=9` changes the base wind. 11. H and R work; 5-minute unattended sail is stable. **Definition of done**: every checklist item ticked by manual play.