Two chains built independently — GAME (loadSite -> createWorld -> dress ->
windForSite) and EDITOR (siteClone-shaped object -> buildScoringWorld ->
windForSite) — asserted EXACTLY equal at t=30, speed and vector, at every
probe on both shipped yards. Exactly, not nearly: a tolerance is where the
funnel-off bug hid, and it moved the S13 headline 91.5 -> 39.8.
The mutation checks caught my own probe TWICE before this went green, which is
the only reason it is worth anything:
1. I proposed backyard_01 in THREADS. It declares venturi: [] — no funnel to
lose, so a funnel-off regression could never turn it red.
2. So I moved to site_02 — and its BED is 6.08 m from a radius-5 throat.
Starving that yard of its venturi moved the bed probe by 0.000 m/s.
Both versions passed their equality assert and both were worthless.
Measured sensitivity on site_02/earlybuster at t=30 (funnel on/off, trees
present/hidden):
bed ( 0, 1) 12.452 dV +0.000 dT +0.000 <- blind, not a pin site
throat (-6, 0) 15.910 dV +4.948 dT +0.000 <- funnel tripwire
near tr1 ( 7,-1) 8.593 dV +0.000 dT -4.891 <- shelter tripwire
Mutation-checked: starving the editor clone of its venturi turns the @throat
equality red (plus both funnel asserts), while bed/tree stay green exactly as
the table predicts.
Also recorded as a pin, because it is load-bearing for anyone reading the
score card: the corner block's funnel does NOT reach its garden bed. It is
aimed at the rigging zone (the site JSON says so: q1 is 3.5 m inside the
radius), so on that yard the funnel is a hardware-load story, not a
garden-exposure one.
Selftest 374 passed / 0 failed / 0 skipped (362 baseline + 12).
The scoring quantity is the FLOWN garden state now, not static cover%.
New gardenfly.js: windForSite (C's shared builder — venturi + shelters, one
copy in the repo) → real attach (throws on D's skipped-attach trap) → skyfx
exposure → garden.js, per candidate line; cover% demoted to a labelled
geometry diagnostic. audit.html flies every affordable line + bare bed and
judges the site's pinned separation block on the block's own storm; audit.mjs
(node) keeps fast winnability, gains p5 in its verified dump, and points at
the browser for garden truth.
On C's correction, three structural adoptions:
- windForSite everywhere (three harnesses independently mis-built site wind;
no fourth copy);
- LIVE anchors via the re-pointable wind proxy — the audit's own frozen-sway
remap was C's landmine 2 wearing my file's name (audit.html:69);
- the MARGIN rule (AUDIT.MARGIN = 0.15, one copy): every row prices twice —
$hw holds vs $cleanHw with >=15% headroom — and only clean lines are
winners; verdict code 'marginal-only' when the budget can only buy the
knife edge (D's 39.8-TATTERED wild night, C's dead 91.9 headline).
Also game-true flight: the phantom 12s calm settle is GONE from the sweep
(commit->attach->storm is one keypress; the settle skewed every storm sample
12 s off the authored curve — SailRig samples wind at its INTERNAL clock).
That skew is not academic: it is a.test's separation-flight harness too, and
gardenfly.selftest now pins BOTH chains (game-true 63.8 tattered vs skewed
68.4 'full' on the pinned p5 line — the disagreement is flagged to A in
THREADS, not overruled here). The pinned recipe also sweeps regardless of the
18-45 band (it is 45.9 m² — the band predates p5; flagged to A).
Selftest 356/0/0 on the scratch merge (b+a+c@45bdc2d). Mutation-checked:
MARGIN=0 reddens the margin test (node); dropping siteDef from gardenfly's
windForSite call reddens the venturi test (browser, exact test named).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
site_audit's headless false-negative is fixed by the browser front-end
(A's loadSite/createWorld landed in main — that was all it waited on), and
fixing it surfaced a worse bug underneath: the sweep built its wind from the
STORM def alone and never called setVenturi, which main.js:424 does at every
site load. The venturi is SITE data, so every site_02 audit ever run — including
the numbers quoted at C in SPRINT10 — was of a corner block with no gap. The
SPRINT6 p1=7.4kN failure inverted: there the tool called a good site unriggable,
here it called a mean site cheap.
- sweep.js: setVenturi on both winds (the gap doesn't switch off for the settle);
both front-ends pass the site's funnel and print it, so a funnelled run is
legible as one.
- sweep.selftest.js (new, wired into b.test.js): the auditor gets an auditor.
Funnel must strictly raise every corner; a funnel-less site must be
byte-identical. Verified it fails when the setVenturi calls are deleted.
- rigging.js: setWorld(world) + session.setBudget(n)/setAnchors() — A's two asks.
Markers are rebuilt (the anchor set changes across sites) and disposed; stale
picks die with the old yard. Deletes A's "one private touch" into this module.
- dev_rigging.html: dead since SPRINT10 (called createWorld without a site, which
now throws) and nothing noticed, because a dev page has no selftest to go red.
Fixed, dressed, and given the site switch it should have had (N = next yard).
Verdict for C/A, in THREADS: site_02 PASSES with the funnel on — 64 of 66 lines
affordable, cheapest $20. The funnel kills nothing, so the gain does NOT need
dropping and E's tree stays put. It's a reach problem, not a gain problem: the
throat's radius is 5 m and the bed centre is 6.08 m away, so the gap screams over
the carport at +50% and reaches the garden at +0.0%.
selftest 300/0/0 (296 + 4 new).
Rebased onto M0 and reconciled against the real spine. checkContract
('sailRig') now conforms and js/tests/b.test.js runs 28 asserts green.
Contract fixes:
- anchor.sway(t) is the ABSOLUTE position, not an offset (thanks A —
I had it adding sway to pos, which would have flung every
tree-anchored corner to double its coordinates).
- events is an Emitter emitting {type, corner}, not a drained array.
- coverageOver() rects are centre+size, matching world.gardenBed. It
consumes world.sunDir directly: a hit along sunDir means shaded.
- START_BUDGET/SPARE_COST/HARDWARE/FIXED_DT now come from contracts.js
rather than being redeclared here.
Bug: a corner that blew was marked broken but never had its mass
returned, so invMass stayed 0 and the "blown" corner sat welded in
mid-air — no flogging, and the sail silently went dead. PLAN3D §5-B
wants flogging emergent from the freed node, so _checkFailure now frees
it. The cascade test missed this because it called _repin() by hand;
the new test drives a real overload failure instead and asserts the
corner tears 2 m off its anchor and keeps moving.
Tension dial remapped from the prototype's rest/tension to a real
pre-strain. rest/tension asks for 17% strain at dial 1.2 and 29% at 1.4
— stretching an 18 m sail by three metres — and put 68 kN on a corner of
the yard's biggest quad with no wind blowing. At 0.10 strain-per-dial it
swings a 5x5 rig's peak load 2.1x loose-to-tight and redlines a 192 m2
quad at 8.3 kN drum-tight, which is punishing and correct.
HARDWARE ratings retuned in contracts.js to real newtons per the
standing note there that Lane B owns these numbers. Costs and tier shape
untouched; $80 still buys rated hardware on at most 2 of 4 corners.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
selftest.html only imports; each lane owns js/tests/<letter>.test.js. Five lanes
sharing one selftest.html would be the single guaranteed merge conflict in the
repo, so the stubs are pre-created with each lane's PLAN3D asserts written into
the header.
No rAF anywhere: it's throttled in a hidden tab, so a rAF-driven selftest would
pass only while you watch it. fixedLoop() drives time instead.
Lane A's suite covers the §5-A acceptance criteria, including "camera never
clips through house" — stated as the underlying invariant (no solid between the
player's head and the camera) so it survives Lane E swapping the house GLB.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>