site_audit answers "is this site winnable?" before it ships — the check that
was missing when SPRINT6 shipped a yard whose bed-covering quad pulled 7.4 kN
against a shop that holds 6.5. It enumerates in-band quads, flies the real
storm headless, maps each corner to the cheapest tier that holds it, and
verdicts against the $80 budget. On backyard_01 it independently reproduces
THE LINE: p1,p2,p3,p4 at $65 + $15 spare, matching balance.test to within 2%.
It also caught three of my own bugs, which is the point of building it:
· it settled on STORM wind frozen at t=0 — the exact anti-pattern
balance.test documents at 1.94 kN vs 0.40 kN. Now settles on the calm
day on a running clock, the way main.js does.
· peakLoad is peak-since-ATTACH, so the settle transient was being
reported as a storm peak. sail.js gains resetPeaks().
· its p4 was typed from world.js's postSpecs, missing that posts are RAKED
8° away from centre — 0.56 m off. The audit now re-derives all four posts
from live world.js on every run and hard-fails on drift.
That last check is deliberately narrow. Node CANNOT dress: dress() needs
GLTFLoader (bare 'three') and fetch()es .glb over file://. createWorld() still
SUCCEEDS in node — it just returns the GRAYBOX yard, house at x=±5, no branch
anchors. So a headless tool that "reads world.js for the real yard" gets the
fictional x=±5 house that cost two sprints. Reading live code is not reading
truth. Verified in-browser: the dump matches the dressed yard on all 12
anchors. Posts are the only thing dress() never touches, so they're the only
thing worth cross-checking — and that check is what caught p4.
C IS RIGHT ABOUT p1 AND I WAS WRONG, for the third time. My comment claimed
C's 1.08 kN didn't reproduce, that p1 peaked at 1.27 and the line won on TIME
rather than headroom. Re-measured on the dressed yard, same flight:
shade cloth p1 0.98 kN 0/4 lost membrane p1 1.23 kN p1 LETS GO
p1 never touches its 1.2 kN rating on cloth — 18% of real headroom. Where 1.27
came from: a loadout where a corner BROKE and dumped its share onto p1 (the
same quad with hardware the budget can't buy reads 2.48). Any p1 number
gathered without checking `lost` measures a cascade, not a fabric.
So the fabric is the decision, not a skin: 0.98 vs 1.23 against a 1.20 rating
is the difference between the $5 carabiner holding the wild night and failing
it. New assert `fabric decides p1` locks that in, so nobody has to trust the
paragraph.
selftest.html rendered ['A'..'E'] while importing BAL too, so the balance
suite ran, counted toward the summary, and had every row silently dropped —
including, had one gone red, the row saying which assert failed. The merge
gate was invisible in its own report. My bug: I added the import, not the
renderer. Now derived from the report.
Selftest: 276 passed, 0 failed, 0 skipped (LANE BAL now visible).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The gate asks the only question no per-lane suite can: can the night be
won through the real $80 shop. It buys loadouts via RiggingSession, flies
the real storm JSON over the real yard, integrates skyfx's real hail/rain
exposure into main.js's real garden drain, and judges with main.js's own
win rule.
Status: storm_01 warm-up PASS, cheap-rig-punished PASS, decision-13
miss-the-bed control PASS, and **storm_02's winnable line FAILS** at
hp=36, 2/4 lost. That reproduces the integrator's finding exactly and is
the gate doing its job — SPRINT6 says everything waits on gate 1.
Two things I got wrong on the way, both worth the comments they now
carry:
- The first draft hardcoded the anchor table from a THREADS entry and
was badly wrong (the dressed yard has the house at x=±3, not ±5). It
flew a fictional yard. It now reads the yard from world.js and freezes
only the sway — the yard IS the balance, so it cannot be a copy. Same
failure as Sprint 3's 16.7° reference rig.
- The asserts were async. testkit's Suite.test() calls fn() WITHOUT
awaiting, so every one would have passed forever while proving nothing
— the exact vacuous-test pattern I've flagged three times. Storms are
now flown up front in run() (which runAll does await) and the asserts
are synchronous judgements over the results.
Registers as a sixth 'BAL' entry in selftest.html. A: your "nobody
touches this file" rule guards against five lanes conflicting here; one
jointly-owned entry is the case it makes room for. Revert if you disagree.
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>