`ready` only meant the graph got built — a suspended AudioContext is still
silent, so there was no way to tell whether the storm was actually audible.
The HUD now reports the real context state.
Verified through it: context runs on first gesture; 7.5 -> 17.8 m/s takes the
wind bed 0.16 -> 0.36 gain while the howl layer goes 0.016 -> 0.104 and the
cutoff opens 428 -> 767 Hz, so a gale reads as a gale and not just a louder
breeze. Rain tracks its curve 0.03 -> 0.32.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three bugs the bench found once it was actually running a storm:
- Debris was glued to the floor. The scrape `v *= 0.86` ran every frame while
grounded, which is 0.86^60 per second — it pinned a 9 kg crate at 0.7 m/s in
a 19 m/s wind. Friction now applies on impact, with dt-scaled rolling
friction while resting. A crate crosses the whole yard at ~6 m/s.
- Debris slid instead of tumbling: wind is horizontal, so once down there was
no vertical force at all and it skated at constant height. Added tumbling
lift that flips sign as it rolls — bins hop now.
- The cloud dome had a dead straight seam across the sky. The fbm claimed to
tile and didn't; now each octave wraps at its own integer period.
Also: shelters and the bench now use Lane A's landed yard coords (t1 -9,2 /
t2 8,-2, gardenBed 1,2) instead of my guesses, so shelter tuning means
something.
Verified in-browser against a real storm: crate crosses the yard and the t=74
bin spawns from storm JSON; sail node shoved 0.32 m; a 14 kg bin at 20 m/s
knocks the player down and a tub drifting at 0.3 m/s correctly does not;
lightning peaks 0.88. Lane A's selftest: 37 pass / 3 skip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
debris.js: hand-rolled kinematic tumble, drag ∝ speed² so the gust that
spikes a corner is the one that launches the neighbour's bin. Ground bounce
via world.heightAt (contracts.js documents it as ours), sphere-vs-player
knockdown reported to Lane D, sphere-vs-sail-node impulse duck-typed so it
lights up when Lane B exposes nodes and stays silent until then.
skyfx.js: instanced rain that wraps around the camera rather than respawning,
storm sky + procedural cloud dome, lightning, and synthesized WebAudio layers
(wind bed, howl, rain, gust whoosh on the telegraph, rope creak off the worst
corner, flog when one blows). It modulates Lane A's lights and hands them back
on dispose() rather than owning them.
weather_demo.html: graybox bench to drive all three before M0 — mock sail,
storm scrub, 4x, throw-a-crate.
Aligned to contracts.js now that it has landed: relative three imports (there
is no importmap), contracts' rng() instead of a local copy, and storm paths
resolved off import.meta.url — server.py serves the repo root, so an absolute
/world/... would have 404'd at integration.
storm_02: fix the southerly change. contracts.js puts north at -Z and the wind
vector blows toward (cos d, sin d), so the old swing to +2.6 blew toward due
south — a northerly wearing a southerly's name. Now slews to -1.35: a SSW
buster off the open side of the yard, into the house.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>