GATE 0.3 — THE CHARTER, SAID OUT LOUD (D's soaker finding, A consulted on
wording and their every-card rule adopted). The audit always flew shade cloth
and never said so; on the soaker that silence read 'WINNABLE over an all-DEAD
garden — the night is broken'. Now: AUDIT.POROSITY is the one charter knob
(sweep.js + gardenfly.js both read it), scorecard.fabricCharter() names the
fabric and whether tonight's stones pass its weave (same hailBlockFor the sim
charges), and the SCORE IT header prints 'fabric · shade cloth (sweep
charter)' on EVERY card — plus, on leaky-storm nights, A's wording: the
fabric is the bet, and the audit does not place it.
GATE 2 — CLIENT CONSTRAINTS: A's shapes (seam contract, built against as
posted), this lane's teeth.
· rigging.js: validateNightConstraint (checked enum, both ends of the seam),
setConstraints (survives reset like _startBudget; [] is the only clear).
The house ban refuses the pick with the CLIENT'S WORDS as the ticker
reason; the cap refuses at SPEND time on every path (rig / cycle / set /
spares / fabric), exact at the boundary, refunds always free — and commit
is belt-and-braces: a caller who walked around the doors gets a THROW.
The cap covers the whole invoice, spares included — the client queries the
invoice, not the hardware line.
· sweep.js: applyNightConstraints, ONE function both drivers share — bans
shrink the candidate list (a card that recommends a forbidden line is the
card lying), the cap re-prices affordable/clean without touching a load.
· scoreSite carries constraints/budget/constrainedOut; the card prints the
client's terms next to funnel and fabric, and tells 'no geometry' from
'client forbids all of it'.
· spent getter now measures against _startBudget (the re-banked session was
measuring spend against the default bank).
Selftest 485/0/0 in the browser (474 + exactly the 11 added: 1 sail, 4
session, 2 charter, 4 audit-constraints). Wiring line for A filed in THREADS:
session.setConstraints(week.job?.constraints ?? []) at the night boundary.
D's S16 poison poke, filed for this lane: _checkFailure's corner loop is the
heal's only trigger and it skips broken corners, so once all four were
divergence-broken a SECOND poison had no unbroken corner left to read NaN
through — the corpse stayed NaN forever and rendered as nothing. A lost sail
is still cloth in the yard; it must LOOK lost, not vanish.
The sentinel runs ONLY on a fully-broken rig (the corner wire covers every
other case): sum pos+prev — any NaN/±Inf makes the sum non-finite (Inf−Inf is
NaN), finite cloth cannot overflow it. Branch-free O(n) adds, no mutation, so
a finite lost sail is untouched byte-for-byte.
Test replays D's gait: cascade 4/4 → 5 s finite with ZERO heals (the negative
control — the sentinel must never mistake 'lost' for 'sick') → second poison
heals in one substep, finite a full second on. Node suite 47/47.
- forecastHonest(def): "a storm the forecast can't describe honestly doesn't
get offered" as a predicate, not a sentence — validator + measurable stats +
the stone vocabulary ceiling (STONE_WORD_CEILING 2.0: past it, "golf ball"
undersells the ice and the word ambushes where the band can't). The band-
containment recomputes are disclosed as structural regression armour, not
counted as coverage.
- c.test walks NIGHTS ∪ POOL from the shipped data (the union, per A's
stormsToPreload finding — a pool-only storm is covered the day it exists).
Negative controls: a lying windchange, a cricket-ball stone, both refused
by name. Lead-0 card pins: the offer card's gust/sustained figures parsed
back out and matched against stormStats; a lead-0 offer must not hedge.
- The exposure cross (the S14 pin pattern, third outing): my failure-route
envelope (built world, anchor collateral keys through world.collateralFor,
the gnome per scoreRun) vs A's exposureOf — item-level equality on ALL
THREE yards: backyard 115, corner block 205, swing lawn 255. Zero unpriced
keys. Teeth proven in-suite: strip the carport key and the cross reds.
Selftest 490/0/0 (A's 485 + 5), browser-verified; board verified live on
:8826 — both cards print the honest lead-0 lines and the crossed dollars.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gate 0 rulings (written, with reasoning, in THREADS):
- 0.4 hail-light balance: CANON. B's finding reproduces exactly ($4/$2 garden
spread), but the mild-night rig market buys the STEEL and the collateral, not
the bed — a cheap rig that fails on site_02 swings $184 and two stars against
the same rig holding, and the cheapest HOLDING line is $40, not the $20 floor.
Ruled for B's option (b): the separation block asks the wrong question on
those nights; the new shape is B's and is NOT this sprint. Rain drain-weight
lever declined at its proper price (full gauntlet re-measure, own gate).
- 0.4b gradeFor scraped/solvent >=3 STANDS on the seven-night shape, ruled on
the arithmetic (savable 6 → solvent is 4-6 dead, which is what the copy says).
- 0.3 score-header wording consulted for B (no code from me).
Gate 1 — the board: board.js (pure data, node-testable), week.offers()/take(),
seeded from week+night via contracts' mulberry32 — no Date.now, no Math.random.
An offer carries a whole NIGHT ENTRY and take() installs it, so sheet, quote,
invoice, ledger, warranty, rep and end card all read a chosen job through the
path they already had — seven surfaces, zero changed lines. Scripted spine runs
byte-identically when nothing is taken.
Gate 2 (data half) — constraints as night data, checked enum, premium ruled
(+25% anchor ban / +20% budget cap, both UNMEASURED and flagged cut-first),
stated on the offer, the job sheet and the invoice it was paid under. ONE
calloutFee() now serves quote() and settle(). Enforcement is B's.
Two bugs found off the suite: take() stored raw pool entries (crashed quote()
on a missing pay block) → normaliseNight() split so both doors share it; and
NIGHT 1 HAD NO BOARD — the splash bypassed it — found by looking, not testing.
Pool pairings audited in the browser before shipping; both constraints checked
for soft-lock against real holding lines.
Mutation-checked ×6. One did NOT go red: "every pool storm is preloaded" passed
with the POOL source deleted, because every pool storm is also in NIGHTS — the
same coincidence that hid the calm-day bug. stormsToPreload(nights, pool) split
so it can fail. Selftest 485/0/0 (474 + 11).
Recovers an interrupted session and finishes its verification honestly.
RESTORED (the predecessor was killed mid-mutation-check):
- build_sail_post_corroded still had negative control M1 APPLIED — clean
galvanised steel, DROOP=0 — so both shipped GLBs were the untelled post.
Restored: steel_weathered/rust/rust_deep, DROOP_DEG = 38.
- asset_report.json held 1 of 37 assets and contact_sheet.png was a 196 KB
truncated write. Both were interrupted artifacts, not decisions; a clean
rebuild restores 37 assets and a 5.07 MB 1680x4200 sheet.
DETERMINISM: 37/37 GLBs byte-identical across two consecutive full runs;
report and contact sheet byte-identical too. Zero churn on the 35 pre-existing
GLBs.
ASSERT THAT COULD NOT FAIL, KILLED: the sag pin (honestY - corrodedY > 0.05)
reads 0.060 at ZERO droop, because the eye hangs off a weld line 160 mm down
the post — measured on the M-B build, anchor at (0, 3.920, 0). It measured the
weld offset and called it a sag. Replaced with the droop ANGLE derived from
the exported anchor and pinned to baked extras (padeye_droop_deg / arm_m /
weld_y_threejs / dir_threejs), all stated in three.js coords — the axis trap
closed the swing_set way, so note and mesh can only lie together.
ASSERT WHOSE REASON WAS BACKWARDS, FIXED: the 20 m pin claimed the corroded
shaft read darker. Measured off look.html's canvas at 20 m: honest lum 50,
corroded lum 94 — it renders twice as BRIGHT. Base colour is darker but
metalness drives the pixels (gal 0.90/0.35 goes near-black with no env map;
weathered 0.55/0.60 is diffuse). Test now pins both halves and names the
mechanism the player actually sees.
Three tells split into three tests so one failure cannot mask the others —
pre-split, the M-A mutation went red on rust area and never reached the
lightness or droop asserts at all.
Mutation-checked in the browser, all three witnessed red: M-A clean steel
(1/3 + 2/3 red, 3/3 green, factory GREEN throughout — the trap it cannot see,
confirmed); M-B flat eye (3/3 red, others green); M-C baked note lies while
mesh is honest (3/3 red, proving the pin reads the extra, not a literal).
Selftest 483/0/0 (474 baseline + 9 exactly). Tell verified by looking at
20 m at the game's own camera height, screenshots taken.
editor.js untouched — palette hunk filed verbatim in THREADS for A.
contracts.js ANCHOR_TYPE widening flagged for A.
Lane D flagged: the tier is placeable, gate 3.2 unblocked.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Preserved verbatim from the interrupted session. NOTE: build_sail_post_corroded
still carries the M1 mutation (clean galvanised steel, DROOP=0), so the two GLBs
in this commit are the UNTELLED post, not the shipped asset. Restored in the
next commit after the red-check is witnessed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ladder: gentle, southerly, earlybuster×site_02, southerly×swing_lawn,
WILDNIGHT×backyard, icenight×backyard (beyondSaving rides the night),
soaker×site_02. Icenight brief reverts to 'less wind than last night'
(true again); new Hendersons brief sells the pyrrhic stake. Law pins
retitled + one new ruling pin (wildnight in, known yard, before the
icenight); a.test re-pinned to the seven-shape; gradeFor clean bar
derived (held >= savable) per the S16 same-commit demand. Selftest
474/0/0; verified live on :8827.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- site_03 re-scored on the post-ruling tree, S15 loop, FUNNEL ON: every number
reproduces the S14 card to the digit (81 quads, 11 clean/16 marginal, cheapest
honest $65 h3,t2,t2b,p1 85.3 FULL, bare 83.7, best clean 90.0, sep +6.3,
best-reading 91.8 MARGINAL). Nothing moved; nothing to fix; B's PIN REFUSED
(measured) card renders the finding on this yard.
- NIGHTS is six: gentle, southerly, earlybuster(s02), SOUTHERLY over the SWING
LAWN (new site, survived storm), icenight (flag intact, no longer final),
SOAKER over the CORNER BLOCK (C's measured pairing). Two new briefs: Ruby's
swing set sells the crossbar trap; the Vasilaros return sells the cloth
default the forecast argues against.
- THE STRUCTURAL CONSEQUENCE, filed loudly: six slots cannot hold five old
storms + a survived-storm debut + the soaker. The WILD NIGHT leaves the
rotation (every other candidate is load-bearing); storm file, tests and the
pinned backyard separation all stand. If the eviction is wrong the fix is a
seven-night week — one entry, a ruling.
- week asserts updated (a.test): six nights, final-night=6 both directions on
the broke rule, three clients with night 6 a RETURN booking.
- four six-night-law pins in d.test: one new site / one new storm / never same
night; survived-storm debut; soaker pairing (C's receipt in the message);
beyond-saving night neither new nor final.
- hud surfaces read NIGHTS.length (splash, morning header, end card) — flagged
for A; hardcoded 'five' would lie on every paper.
- selftest 473/0/0 (469 + 4); mutations M1 soaker→backyard, M2 debut→wildnight,
M3 flag→final each RED with the law's own words, restored, green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
C's gate-3 finding, answered in the file it names: _applyPonding never reads
porosity, so cloth and membrane pond identically while setFabric's comment and
the cloth blurb promised shedding. Ruled, not silently fixed: every pinned
cloth number (backyard separation, the section-7 gates, C's storm_06 pins, the
S15 zero-delta lattice) was measured under fabric-blind ponding, and a pond-
physics change mid-TEETH moves them all against the one-variable law. The fix
(porosity-scaled intake + through-weave leak) wants its own gate with a full
gauntlet re-measure; C's soaker is its test case. setFabric's comment carries
the ruling, _applyPonding carries the warning, the cloth blurb drops 'sheds
water' (a player-facing claim the sim doesn't cash).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both lanes added the identical param (same name, same 0.30 default, one-line
rig change) the same day. C's copy taken verbatim so integration merges clean;
my default-unmoved + membrane-heavier asserts in gardenfly.selftest.js stand
and cover both copies.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gate 3.1 pins (c.test): the soaker over site_02 through gardenfly, porosity
the only variable — bare <30 (17.5), best cloth line <50 (42.3, still >bare:
the leak is partial), same $75 ring on membrane >90 FULL (96.5) with lost<2
and cost<=80, $60 membrane ring wins too (68.8), separation >40 (54.2). The
inverse guard: membrane on carabiners reads WORSE than cloth (19.1/3 lost vs
24.5/2) — the F key is a bet, not an upgrade. Gate 3.3: forecastLines grows
stones + rainRate (worded, banded); stormStats.hailSize + forecastFor
hail.size band, spread 0.30, nesting/truth-holding pinned in the resolving
loop; existing card fields byte-identical (new band draws last). storm_06 in
STORM_KEYS + suite STORMS (sync assert is the tripwire). Envelope tool flown:
throat 21.55 @ 59.2, q1 17.78 (rated slot), q2 wind-coldest yet cloth-side
second-hottest — the pond's signature isolated between harnesses. Mutations,
separate runs, red-then-green: gardenfly seam hardcoded -> both 3.1 pins red;
STORM_KEYS drop -> list-sync red with diff; stormgen rng -> byte-equal red.
Browser selftest 438/0/0 (418 + 4 storm honesty + 12 stormgen + 4 gate-3).
THREADS: gate entries + D pairing receipt + B seam/finding notes.
Measured post-ruling (garden_probe, funnel ON, 2026-07-20):
site_02 x earlybuster: bare 82.6 FULL (hail 10.7 + rain 6.8) · best flown 90.2 · gain +7.6
site_03 x southerly: bare 83.7 FULL (hail 10.7 + rain 5.6) · best flown 89.8 · gain +6.1
Bare WINS both nights by >30 points; 'bareMustLoseBelow' cannot be written
honestly at any threshold, so per the gate's own rule this is a FINDING, not
a fudged pin. Each yard carries _separation_finding (measured refusal with
numbers + the delete-on-pin instruction); scorecard exposes it and SCORE IT's
NONE PINNED branch becomes PIN REFUSED (measured). New assert: every shipped
yard is separation-JUDGED — pinned XOR refused-with-finding; silence and
stale-finding both red. Balance fight filed in THREADS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ROADMAP carried '>83 ms determinism bound (documented, un-tested)'. Measured:
the spiral guard fires whenever a step() consumes MAX_SUBSTEPS, dropping even
a legal sub-SIM_DT residual — so the unconditionally-lossless bound is
(MAX_SUBSTEPS-1)*SIM_DT = 66.7 ms; 67-83 ms may drop up to one SIM_DT by
accumulator phase; >=83 ms always drops. Pinned AS MEASURED (no retune):
frames <=66 ms converge on the fixed trace at equal sim clocks with zero wall
time lost; one 120 ms hitch drops exactly its overflow (36.7 ms). Mutations
red-then-green: MAX_SUBSTEPS shrunk (6 s went missing); guard deleted
(deficit 3 ms vs expected 37). Test header is the documented waiver.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Default stays the pinned 0.30 shade cloth (asserted: omitting porosity ===
explicit 0.30 to the digit, hp and every corner peak), so no pinned number
moves. porosity 0 flies genuinely heavier (per-corner strictly greater,
measured 1.23x average on the synthetic funnel yard). C's storm_06 gate-3.1
membrane-vs-cloth pin and B's membrane-balance tables ride this seam.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fabricNoteFor() (rigging.js, pure, headless-tested): one verdict sentence
when the bet provably mattered — cloth on a night whose stones pass the
2mm-weave ruling with real hail damage names the leak; membrane past the
verdict's own 80 kg pond threshold names the trade; silent everywhere a
sentence would be a guess (big stones, dry nights, unknown fabric).
scoreRun carries {fabric, fabricNote}; the invoice grows an unconditional
'fabric flown' row and the verdict prints the note under itself (existing
verdict prose byte-untouched — a.test's verdictMode pins unaffected).
Prep-summary half already shipped in the fresh-eyes review (panel line 2).
Mutations red-then-green: leak gate inverted; pond threshold dropped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
stormgen.js: envelopes are authored data (every knob a range, knot times
fixed), a seed picks one storm via a single mulberry32 stream in a pinned
draw order. Same seed → byte-equal def; def carries its seed so the gust
timeline downstream is seeded by the same number. checkEnvelope() recomputes
membership FROM THE DEF (never trusts the generator) and also runs the storm
validator. One family ships: soaker_family, ranged around storm_06's measured
personality. Cases ride the shared node+browser list: byte-equal determinism,
100 seeds all valid + all inside (gustPeak spread 12.8-17.2 across seeds),
four named escape routes red-not-shrugged, valid-but-off-family storm red
(checkEnvelope is not the validator in a hat), change event === physics for
sampled seeds, and two fixed-seed generated storms ride the SAME telegraph/
continuity/validator gates as authored ones. Node suite 72/0. Mutation check:
rng → Math.random went red on the byte-equal case, restored, green.
week.js: REP (stars, half-steps, x1+0.04(rep-3) fee multiplier), warrantyItems()
derived from the previous settlement's rig record so quote==settle holds by
construction; rep moves at dawn of the night that earned it and never reads the
garden. main.js: rigRecordFor (exported value — the tally remembers what
repairCorner's cleared flag forgets), night break/repair tally, rep on the
forecast call. hud.js: rep under the ABN on job sheet AND invoice, warranty
lines on both papers worded per spec, fee-row multiplier the night it applies,
rep line under the verdict, end card speaks rep + shows warranty receipts,
C's gate-3.3 forecast print seam (inert until they land). a.test: 8 new tests
incl. the clean-week negative control. Selftest 426/0/0 in the browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first night the default fabric is the wrong answer, tuned by measurement
through the real chain (gardenfly, dressed anchors, funnel ON, post-ruling
tree): bare 17.5 DEAD, best cloth line 42.3 TATTERED (a loss at any price),
same $75 ring on membrane 96.5 FULL, $60 ring 68.8 FULL — one key, Δ54.2 HP.
Membrane on $20 carabiners inverts (19.1 < 24.5): the doubled load is real.
Measured over backyard_01 too: its geometry caps hail cover at ~31%, so the
night only works on site_02 — the pairing receipt for D. tools/fabric_bet/
probe.html is the measurement harness; flyGarden grows an additive porosity
opt (default 0.30 unchanged — B: contest at integration if wanted).
_healNonFinite(): on a divergence break, every non-finite node position
resets from its nearest finite neighbour (ring flood, corners re-seeded from
anchors if the whole cloth is gone), prev matched so verlet reads zero
velocity out of the heal, non-finite water zeroed (second wire behind the
break's dumpPond, unit-asserted so it can't rot). Deterministic, emits
'heal' {nodes, t}. Existing divergence-break test untouched and green.
Three new asserts (45/45 headless): finite-in-1s + prev matched + byte-equal
across two poisoned runs; NaN pond zeroed under live rain + direct-heal
water contract; negative control (honest overload breaks never heal).
Mutations, each red-then-green: heal off; prev left NaN; water zeroing off.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
editor_stormsel.js -> editor_storm.js (B's name); globalThis.EDITOR_STORM
(B's name); setStorm/setTime carry B's source tag in the payload; default
becomes B's storm_03_southerly at my receipted t=40 (the pin stays pinned
in c.test — it never needed to be the default, and the c.test assert now
pins the CONVERGED pair instead so a private default cannot regrow).
Zero-delta second harness: envelope.html week mode, both shipped yards,
all five nights vs my S13 tables — byte-identical everywhere (site_02 x
earlybuster all 11 rows; backyard h1-h3 Pa/hint 266/946-1037/2093-2505/
1844-1933; branch ladder 0.88/0.76; t1 southerly 15.45). Measured on
lane/c @ gate-2.2 commit atop main c182fc1, pre-E-re-bake; zero expected
under the 1.00 ruling, zero measured, nothing to flag.
Selftest 413/0/0; mutations 6 (default drift) and 7 (source dropped)
red-then-green on top of the five in the previous commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
editor_storm.js owns THE storm selection and THE storm list; SCORE IT's
dropdown is a view of it (default = southerly, C's old default, kept on
purpose). The score panel renders scoreSite's progress ticks — flight x/y,
elapsed — and audit.html gets the same line. C's half: subscribe
editor.wind.js per the THREADS proposal.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
findCandidates/priceCandidate/judgeSweep extracted so the sync auditSweep and
the new auditSweepAsync are ONE copy of the math; scoreSite drives the async
path with onProgress ticks (sweep/fly/separation phases). Yield is a
MessageChannel task, not setTimeout — Chrome's intensive timer throttling
turned an occluded run into one flight per minute, measured. Three new
asserts, mutation-checked red-then-green in one sitting (impure chunk + a
swallowed tick = all three red with the intended diagnostics).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>