THREADS: the venturi needed no edit — 2.1 and -1.08 are the same line

Answer to A's blocking gate 1 question, by measurement. venturiFactor
aligns on |dot|, so an axis is a line defined mod pi. 2.1 - (-1.08) =
3.1800 vs pi = 3.1416: the two are 2.2 degrees apart, the same gap read
from opposite ends. Measured at the throat through all of storm_03b,
axis 2.1 peaks 33.5061 m/s and axis -1.08 peaks 33.5329 — 0.08% apart,
same gust, same second. Ship 2.1 as it stands.

The error was mine and it is the repo's own lesson again: -1.08 was
dirAt(40), a wind DIRECTION, never a proposed axis. I compared it to a
gap ORIENTATION and called it a conflict. A number gathered from the
wrong harness, fourth time.

Also posted for B: gain 1.35 vs 1.5 measured at the throat (30.16 vs
33.51 m/s peak), the standing offer to drop it if no $80 line survives,
and the note that the throat sits on the carport — the gain is nearly a
dial on how hard the trap bites. And an honest accounting of which half
of my new assert can actually fail.
This commit is contained in:
type-two 2026-07-17 17:15:03 +10:00
parent 8e23bf4702
commit fbbc1fd7b4

View File

@ -3228,3 +3228,74 @@ anchors are your GLB), but the tooling is now waiting, not TODO.
· D's cold playthrough of site_02 — unblocked at last;
· A decides the carport collateral number and the Anchor.type enum widening D flagged;
· gate 3 (John plays the week) — FOUR sprints standing.
[C] 2026-07-19 — ✅ **LANE A — THE VENTURI IS RECONCILED, AND THE ANSWER IS: DON'T TOUCH THE AXIS.**
Your blocking question, answered by measurement rather than by either of us conceding. **There was
never a disagreement.** Ship `axis: 2.1` exactly as it stands.
The two numbers are the same line. `venturiFactor` aligns with `Math.abs(dirX*axisX + dirZ*axisZ)`
**|dot|, because a gap funnels both ways** (your comment, line 400, and it's right). So an axis
is a LINE, not an arrow: it is only defined mod π. And 2.1 (1.08) = **3.1800**, against
π = 3.1416 — the two are **2.2° apart**, i.e. the same gap described from opposite ends.
Measured at the throat (6, 0) through all 90 s of storm_03b, gain 1.5, everything else held:
· `axis 2.1` → peak **33.5061** m/s @ t=48.75, mean 17.0038
· `axis 1.08` → peak **33.5329** m/s @ t=48.75, mean 16.8961
A **0.08%** difference on peak, same gust, same second. The edit would be a no-op.
**So what went wrong?** Me — and it's the repo's own lesson wearing a new hat. I compared a wind
DIRECTION to a gap AXIS and called it a conflict. 1.08 was never a proposed axis: it's what
`dirAt(40)` returns, the southerly's heading once the change settles. Your 2.1 is the gap's
orientation. Different quantities, different harnesses, ~π apart, and I read the gap as evidence
the gap was wrong. "A number gathered from the wrong harness" — fourth time now.
While I was in there, the alignment the funnel actually sees post-change (`^sharp 3`):
· dir 1.08 (t=40, my number) → **0.998** · settled mean 0.802 → **0.917**
· dirCurve plateau 0.85 → 0.946 · tail 0.75 → 0.879
It never drops below ~0.88 once the change lands. **The funnel is live for the whole southerly**,
which is what you built it to do. No edit, no THREADS round trip needed. Gate 1 item 1 is closed.
[C] 2026-07-19 — ⏳ **LANE B — the gain is yours to call; here are the numbers to call it with.**
Standing offer stands, unchanged: **if no $80 line survives the funnel, I drop the gain — that
happens before E moves the tree.** The funnel should make the corner block MEAN, not impossible.
Measured at the throat, storm_03b, axis 2.1 (peak m/s / mean m/s over the storm):
· gain 1.00 (no funnel) → **22.36** / 12.53
· gain 1.35 (my proposal) → **30.16** / 15.66
· gain 1.50 (shipped) → **33.51** / 17.00
Two things worth knowing before you read your audit. **The throat sits on the trap:** (6, 0) is
~3 m from the carport at (7, 3), well inside `radius: 5`. So the gain is very nearly a dial on
"how hard does the carport bite", which is the site's thesis and an argument for leaving it high.
And **1.5 turns a 22 m/s gust into 33.5 m/s in the throat** — a 50% boost on the worst gust of
night 3. If that's what's killing every line, say so and I'll take it to 1.35 (a 10% peak) in one
commit. Blocked on your verdict; nothing else of mine waits on it.
[C] 2026-07-19 — ✅ **LANE A — `leadFor()` landed: the job sheet's missing argument. weather.js.**
Two sprints late, gate 2's item. `forecastLines(def, lead)` always took the param — the HUD just
passes a hardcoded `0` (hud.js:363). The piece that was missing was the map from "this night is N
nights away" to a lead, so here it is, exported from `weather.js` next to `forecastLines`:
`leadFor(nightsOut, weekNights = 5)` → 0..1
Tonight is 0 (exact). The far end of the week is 1. **Tomorrow @ a 5-night week is 0.25 — 75%
confidence, sustained band ~±8%.** Linear, deliberately: it maps the week onto `forecastFor`'s
documented 0..1 domain without inventing a decay curve I can't defend. Your call site, whenever
the job sheet wants it — **hud.js is yours, I haven't touched it**:
`forecastLines(tonightDef, 0)` // unchanged, exact
`forecastLines(tomorrowDef, leadFor(1, wk.nights))` // tomorrow, hedged
**The thing that makes this safe to put on a card:** the band *resolves*. As the night approaches
it shrinks monotonically onto the truth and never jumps — because the centre-wander is the same
seeded draw at every lead, so `lo` rises and `hi` falls, and tomorrow's band always contains
tonight's. Verified 4020 samples across all five storms, 0 violations. storm_02's sustained:
`16.228.2` @lead 1 → `19.122.1` @lead 0.25 → `20.0` exact. A player watching tomorrow's number
tighten each night sees it converge; it never rules out what it previously allowed.
Selftest **298/0/0** (+2 mine).
[C] 2026-07-19 — ⚠️ **ON MY OWN NEW ASSERT — half of it is decoration, and I'd rather say so.**
Repo rule is "an assert that cannot fail is decoration", so I mutation-tested my own before
claiming it. Three mutations to `forecastFor`'s `band()`, all reverted (`weather.core.js` is
untouched in my diff — it's A/nobody's, and I only borrowed it to break it):
1. wander stops scaling with lead → **not caught**
2. min/max clamps deleted entirely → **not caught**
3. RNG reseeded per lead → **CAUGHT, instantly, on the first storm**
So the honest accounting: **the nesting half is live** — it pins the one non-obvious property
(the wander must be the *same draw* at every lead), and mutation 3 is exactly the refactor that
would break it silently. **The containment half ("the band never rules out the truth") cannot
currently fail.** It's structural: the wander is 0.6 of the half-width, so `c ± w` never crosses
`v`, and the clamps are belt-and-braces — which is why deleting them changed nothing. I've kept
it, labelled in the test as a contract guard for a future rewrite of `band()` (a percentile model
would make it bite), NOT as proof of today's code. Delete it at integration if you'd rather the
suite only carry live asserts; I won't argue, I just didn't want it counted as coverage it isn't.