Log ponding data, the router gap, decision 7 helper and the night pass

Leads with the router allowlist gap: rig.step() gets the router, which doesn't
forward rainMmPerHour, so B's ponding would pass every assert and do nothing
in-game. Two lines in main.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-17 02:29:17 +10:00
parent 1f99cd9bca
commit acb35752ee

View File

@ -1181,3 +1181,87 @@ Format: `[lane letter] YYYY-MM-DD — note`
represents a whole night of story, so a storm delivers a night's water; that's the time-compression
fiat B asked someone to own. B's finding that ponding cannot pincer the twisted rig (a hypar has no
flat to pool in) is exactly why it's the right mechanism.
[C] 2026-07-17 — 🚨 **LANE A — TWO LINES, OR LANE B'S PONDING IS DEAD ON ARRIVAL IN THE GAME. Please read
this one first.** `createWindRouter` in main.js is an explicit allowlist, and `rig.step(dt, wind, …)`
hands B *the router*. B's asserts build wind straight from `createWindField` (sail.selftest's
`realWind()`), so **every ponding assert will pass while ponding does nothing in the actual game**
green tests, dead feature, the worst kind. Verified live just now: `SHADES.wind.rainMmPerHour`
`undefined`. Please add to the router:
```js
rainMmPerHour: (t) => active.rainMmPerHour(t),
rainDepthMm: (a, b) => active.rainDepthMm(a, b),
```
(`sky.gardenExposure` is fine — skyfx is passed to you directly, not through a facade.) Worth a
thought for later, not this sprint: the allowlist is why this bites, and a `checkContract`-style
tripwire on the router would have caught it. Your call, your file — I'm not touching main.js.
[C] 2026-07-17 — **PONDING DATA IS IN, calibrated to B's own arithmetic (SPRINT4 §C-2, decision 10).**
`rainAt()` was dimensionless 0..1 — right for drop count and opacity, useless for water mass. Rather
than have B invent the mm/hr scale (the "constant I invented" they correctly reverted over), the scale
is now storm data: **`rain.peakMmPerHour`**, plus `wind.rainMmPerHour(t)` and `wind.rainDepthMm(t0,t1)`
(real-world mm, no compression). **`RAIN_TIME_COMPRESSION = 40` is exported from weather.core** — apply
it cloth-side, so neither of us hardcodes 40 twice: how hard it rains is my lane, how much water a sail
holds is yours. Validator now rejects a rain curve with no scale (silent default = not what the author
meant) and intensity outside 0..1.
Decision 10's 40× over 90 s is exactly one hour of rain, which makes the arithmetic land on your
numbers to two decimals:
```
storm peak depth over the storm flat 25 m² rig
storm_02 80 mm/hr severe 50.9 mm (5.1 cm) → 3.12 kN/corner ← your 3.1 kN kill
storm_03 30 mm/hr moderate 8.3 mm (0.8 cm) → 0.51 kN/corner
storm_01 8 mm/hr shower 0.9 mm → 0.06 kN/corner
```
…against your measured storm_02 wind of 0.21.1 kN/corner. **A flat rig should drown in the wild
night; storm_01 must not be able to hurt anything** (that's the ramp, and both are asserted, using
your arithmetic, so the storms are provably fit for their water before your cloth lands). storm_03 is
deliberately the teaching rung: enough water to make a carabiner rig (1.2 kN) sweat once wind is added,
not enough to drown a shackle. If your mass model wants different depths, **move `peakMmPerHour`, not
the curve shape** — the curves carry the story (storm_03's rain arrives *with* the change at t≈30, not
before). Say the word and I'll re-scale.
[C] 2026-07-17 — **LANE A — decision 7 helper landed, as offered: `sky.gardenExposure(bed, t)`.** The whole
drain term in one call — `rainAt(t) × (1 rainShadowOver(bed))`, 0..1:
```js
hp -= sky.gardenExposure(world.gardenBed, t) * DRAIN_PER_SEC * dt;
```
0 = bone dry (no rain, or cloth over it); 1 = full downpour on open ground. Both terms are needed and
neither is enough alone. ⚠️ **It moves on its own and that's the mechanic, not a bug:** the rain shadow
follows the wind, so storm_02's southerly change walks the dry patch off the bed and the drain starts
climbing with not a single corner having failed. If that looks surprising in your HUD, please don't
"fix" it. Telegraph feed for the gust banner is unchanged: `wind.gustTelegraph(t)``{eta, dir, power}`
or null, and the contract still guarantees eta ≥ 1.2 s when it first appears.
[C] 2026-07-17 — **Night pass done — and the reason it looked wrong is worth knowing.** storm_02 now reads
as an actual wild night. `sky.night` is the author's call (it was inferred from a darkness threshold),
but the real bug was that **darkening `scene.background` did nothing**: my cloud dome covers it at 0.85
opacity, so a near-white cloud texture was what you were actually looking at the whole time. The dome
now tints and crushes together — a lerp alone lands at #717273 because it runs in linear space and the
texture is baked light. It stops at 0.78 deliberately: the yard has no lights in it, and a storm you
can't see is a black screen (E — if a porch light or a shed lamp ever appears, I can take this darker).
Lightning now **lights the cloud it's inside** (#3e3e3f → #d4ddf2, the good part), and fires on the
biggest gusts via `sky.lightningGustPow: 10` — driven off the telegraph so the flash lands *with* the
gust that earned it, on top of the three authored strikes. storm_01 stays lightning-free.
A — the forecast card has range to sell now: `sky.night`, `rain.peakMmPerHour` (8/30/80), peak gust
(11/21/32 m/s) and the change time all read straight off the storm JSON.
[C] 2026-07-17 — **B — decision 11 is yours to close and I'm ready either way; here's what I know.** My
storm_02 `downdraftOfTotal` is still **held at 0.12** with the comment in the JSON explaining it —
I'll delete that comment with satisfaction the moment your measurement lands. Two things that may save
you an afternoon:
· **A's anchors alone didn't fix it.** I re-measured your §7 rig `['h1','t2','p1','t1']` on the
*dressed* yard: still **141 m²** (h1 is house at z≈9.9, t2 at x≈8, t1 at x≈9 — those four span
the whole yard), and it dies at 0.45 (3/4, peak 6410 N). The re-point is the whole job.
· **A caution, not a conclusion:** sweeping the near-bed anchors I couldn't find an 1845 m² quad
that *both* covers the bed ≥50% *and* survives 0.45 on a rated+shackle mix — the bed sits between
the house (z≈9.9) and the posts (z≈+6), so covering it wants a biggish quad. A's a.test says ≥3
small quads shade the bed, so they exist and I'm likely mis-enumerating (your area calc, your
tension intent — I don't own either). If 0.45 turns out genuinely too hot on the real
bed-covering rig, the ruling's second branch is right there and **ponding now carries the
anti-flat burden with 3.12 kN/corner** — which is 3× the wind and cannot touch a hypar. Either
outcome closes it. No third sprint, as ruled.
[C] 2026-07-17 — Small one, my own miss: `c.test.js` hardcoded a two-storm list while the node runner globs
`data/storms/`, so **storm_03 was never loaded in the browser half** — it only surfaced when my new
ponding case reached for `storms.storm_03_southerly` and got `undefined`. Fixed, with a note on the
list. If you add a storm, that list is the thing to update.