From acb35752ee4877f773e25bd4351b6a017f905231 Mon Sep 17 00:00:00 2001 From: m3ultra Date: Fri, 17 Jul 2026 02:29:17 +1000 Subject: [PATCH] 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 --- THREADS.md | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/THREADS.md b/THREADS.md index 1e8f450..ec693aa 100644 --- a/THREADS.md +++ b/THREADS.md @@ -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.2–1.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 18–45 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.