Implements the contracts.js wind surface (PLAN3D §4): sample(pos,t) and
gustTelegraph(t), plus storm defs as data.
The prototype scheduled gusts by integrating (wind.gustT += dt). We can't:
sample(pos,t) is called by sail/player/debris/rain at arbitrary t, so gusts
are precomputed into a timeline from a seeded PRNG at load and read from t.
Envelope shape is a faithful port — telegraph 1.5s / ramp 0.8s / hold 1.7s /
fade 1.0s.
Maths lives in weather.core.js with zero imports (no THREE, no DOM, no
Date.now), so the determinism rule is structural and the suite runs in node
without waiting on Lane A's M0.
storm_01_gentle peaks at 11 m/s; storm_02_wildnight sustains 20 m/s and gusts
to 32 (BOM 'destructive'), with the southerly change landing just before the
worst of it — the corners that were slack all storm are the ones that cop it.
15 asserts green: telegraph lead >=1.2s, wind continuity in time and space,
storm JSON validator (incl. 14 deliberately-broken defs it must reject),
determinism, sample-order independence, tree wind shadow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
selftest.html only imports; each lane owns js/tests/<letter>.test.js. Five lanes
sharing one selftest.html would be the single guaranteed merge conflict in the
repo, so the stubs are pre-created with each lane's PLAN3D asserts written into
the header.
No rAF anywhere: it's throttled in a hidden tab, so a rAF-driven selftest would
pass only while you watch it. fixedLoop() drives time instead.
Lane A's suite covers the §5-A acceptance criteria, including "camera never
clips through house" — stated as the underlying invariant (no solid between the
player's head and the camera) so it survives Lane E swapping the house GLB.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>