SPRINT2 decisions 3 and 5, plus Lane A's fog nit.
Decision 3 — gusts now descend. Cloth pressure goes with dot(wind, normal); a
flat panel's normal points at the sky, so in a perfectly horizontal wind the
dot is ~0 and "lie it flat and ignore the storm" was the cheapest winning rig.
A gust front is descending air, not just faster air. Per-gust downdraft
fraction in storm JSON (storm_02 0.3, storm_01 0.18, default 0.25, validated
0..1), each gust varying 0.6-1.4x. Peak downdraft in storm_02 is 4.4 m/s, 17%
of the horizontal. Lane B: the cloth-side assert is yours.
The vertical draws from its OWN rng stream, and there's an assert pinning
that: pulling it from the main stream would shift every subsequent (t0, pow)
and silently re-time storms Lane A has already hand-verified. Their carabiner
still blows at t=45.4 and cascades at t=56.
speedAt() stays horizontal — an anemometer doesn't read falling air, and a
wind meter that spikes because a gust is descending reads as a bug.
Decision 5 — debris.pieces frozen and documented in contracts.js as the seam
Lane B reads in sail.step(), with the sphere/SI/mutated-in-place semantics
spelled out and an assert tying the live shape to the contract table.
Fog: dispose() captured scene.fog by reference and step() mutates that object
in place, so restoring it restored nothing (Lane A caught it). Now captured by
value, and fog we created ourselves is removed rather than left behind.
Selftest 130/0/0 (was 121); Lane C 28 asserts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>