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>
Headline for A: editor.js palette entries 239/243/257/260 omit the _v1 suffix every
other entry carries, so swing_set_01 and tree_jacaranda_01 404, never draw, and
adopt at ratingHint 1.00 instead of E's designed 0.45 and 0.95/0.52/0.40.
adoptAnchor's `rating_hint ?? 1` turns a missing model into the best steel in the
game, and the palette writes the bad name into the exported site JSON. Negative-
controlled both directions on a local patch; editor.js untouched on lane/d.
Also: site_03 candidate authored, scored (82.5 s, winnable at $80, 11 clean of 81)
and played (trap bites: t1c breaks, 83.9 vs a bare 83.7). Phantom sail verified
dead across a live site transition. Friction filed by owner; three candidate
findings checked and withdrawn.
Selftest 406/0/0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gate 3.2. Data only; inert until a week.js NIGHTS entry names it.
SCORE IT (storm_03_southerly, funnel ON, 11 anchors, 82.5 s):
WINNABLE at $80 — 81 quads in band, 11 clean, 16 marginal. Cheapest honest
line h3,t2,t2b,p1 at $65. Bare bed 83.7 FULL, best clean 90.0, separation
+6.3 HP. The best-READING line is MARGINAL at 91.8 — the prettiest number in
the yard is the one that breaks. Collateral exposure $230 on an $80 job.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Played night 4 on the pinned line through the real UI (D's protocol):
hp 55.7 WIN, 2/4 lost, pyrrhic verdict — the ~8 gap to the sim chain is the
t=38 crate + ponding landing on p2's 4% margin. Ruled deliberate: the wild
night sells no clean line; the pyrrhic win is the intended reading.
- Site block records _playedReference; a.test pin re-labeled as the
deterministic sim-chain tripwire whose margin is what keeps the PLAYED
night over the win line. Band 18-45 ruled an availability floor (B's 1c).
- THREADS: full second ruling, night-5 canon, Sprint-14 notes.
- a.test's separation flight had the phantom 12 s settle B caught (SailRig
samples wind at its INTERNAL clock; the storm flew 12 s off the authored
curve). Settle deleted — commit->attach->storm from t=0, C's corrected bench
pattern; venturi wired from site data so the flight can never assume a yard.
- Game-true the line reads 63.6, not 68.3 — FULL (>66) was met by the harness,
not the game, and the re-sweep says nothing buyable reaches 66 (the twin-post
covering quad tears at 6.9 kN). Target RESTATED to the game's real stakes,
with headroom: held >60 and 0/4 (win line 50; measured 63.6), bare <50
(35.7), separation >=25 (27.9). Field names kept — B's gardenfly reads them.
- DESIGN CANON in the site block: the wild night's garden is WON SCARRED,
never FULL; the week's arc is full/full/full, won-tattered, beyond-saving.
- p5 _why table corrected to game-true numbers (first sweep ran ~4 HP hot).
- backyard_01 gains p5 (5.5,-2) h2.6, honest steel NE of the bed: before it the
yard could not beat tattered on the wild night at any price (covering quads
need a fascia corner capped at 2.27 kN; the holding line ceilings at 61.3).
Swept against the $80 wallet; position/height table in the site JSON.
- Separation target adopted AMENDED (ruled on D's funnel-corrected picture):
wild night, best buyable line FULL (>66) and WIN; bare bed LOSES (<50).
Pinned as site data (backyard_01.separation names the $75 recipe
p5=rated + p1/p2/p3=shackle) and FLOWN by a.test through the real chain:
shop at START_BUDGET -> commit -> attach -> skyfx -> garden.js. Measured
68.3 FULL / 0 lost vs bare 35.7 (sep 32.6).
- Full cover stays unbuyable (covering quads still pull 6.4-6.9 kN), the
>45 m2 floor holds, and the ice night still refuses $80 (needs $105 -
p1/p2 miss their shackles by 0.05 kN) - night 5 stays bank-funded canon.
- New asserts: M reaches the real bus / is only advertised when real;
anchor census now 13 (5 posts). Selftest 341/0/0.
Gate 3.3: collateralFor('gutter') returns {90, 'the gutter'} — canonical in
backyard_01.json beside the reasoning, GLB extras as fallback, per E's
collateralKey chain. House wreck wired on the carport's swap contract
(no-op until lane/e's GLB merges; window_glow state copied). site_02 stays
null: no house, no gutter. hud.js bars + main.js verdict now read
rating × ratingHint, matching sail.js's failure line (B's flag).
selftest 319/0/0.
Gate 3.2: bike_kid_01_v1 on backyard_01's south fence, rotYDeg 180 so the
baked -Z lean lands ON the fence — bars measured to the rail (0.286 m
reach, z 9.69 -> grip at 9.98), tyres 0.31 m clear, between posts. Unpriced
by ruling: the sim can't knock a bike over, and billing an unseen event is
the lie the invoice kills; the windward-fence pose is the honest one.
a.test pins lean direction, standoff band, and the ruling. 318/0/0.
C AND D BOTH LANDED WHILE I WAS IN GATE 2, AND BOTH CORRECT ME.
VENTURI — reverted to axis 2.1 / gain 1.5. C reached the same physics I did,
independently (|dot| means an axis is a line, mod PI), and then went one step
further and caught my reasoning: -1.08 was never a proposed AXIS. It's
dirAt(40), the southerly's HEADING once the change settles. 2.1 is the GAP'S
ORIENTATION — site geometry, which doesn't move when the wind does. They're
different quantities that sit a hair over PI apart only because the gap was
BUILT along the southerly.
So "measured beats authored" was the wrong rule for this question: I'd have
swapped a geometry fact for a weather fact because they nearly coincide, and
the site's axis would silently mean the wrong thing the day someone authors a
storm from another quarter. "A number gathered from the wrong harness" caught
both of us on the same question from opposite sides in one sprint.
Gain reverted too, and that one I simply jumped: it's a balance lever, B's audit
calls it, and C's standing offer is to drop to 1.35 only if no $80 line survives.
B hasn't run. D then measured the site winnable off honest anchors at 98% — so
the funnel isn't killing the line and the gain stays.
D's LANDMINES, both mine, both in gate 2's path:
· CALM_STORM loaded only because night one HAPPENED to be storm_01_gentle.
Gate 2 rewrote every night entry. Drop gentle and windTime() throws every
non-storm frame on "Cannot read properties of undefined (reading 'duration')"
— naming neither storms, nor calm, nor the week. Now explicit in the preload,
and the assert calls stormsToPreload() with a gentle-free ladder, because
asserting it against the shipped NIGHTS passes either way. That's how it hid.
· boot({site}) was a documented lie that failed INVERSELY — markers on the
requested yard, world on the week's. Honoured through the week now (seek to
that site's night), because a site without its night is half a game: no
client, no brief, no storm, no bank. Plus opts.bank, so D can boot the corner
block at the $237 they measured instead of night one's $80 and OFF THE JOB.
RATINGHINT — D is right that nothing reads it, and the naive fix is worse than
the bug: honest posts are built from site JSON, never touch adoptAnchor, so
their ratingHint was undefined — and `load > hw.rating * undefined` is
`load > NaN`, always false. The strongest anchors would have become UNBREAKABLE
while the carport became the only thing that can fail: the trap would look like
it works, for the wrong reason, and read as a successful playtest. Every anchor
carries a real number now (DEFAULT_RATING_HINT), pinned, so the wire is safe
before it's made. The wire itself is sail.js — B's — and the ruling is in THREADS.
selftest 306/0/0 (was 304).
DESIGN.md has opened with this since line 3 — "every callout is a different
site, a different client" — and the game had the site and not the client. The
forecast card is a job sheet now and the aftermath is an invoice.
THE WEEK, once it was asked to name a client, mostly wrote itself: four nights
are the same yard, so they're one client on retainer while they're away (the
Hendersons, in Cairns from Friday — SPRINT11's own line). Night 3 is a
different SITE, so it is necessarily a different CLIENT: a short-notice callout
to a corner block you've never rigged, on the night the change comes early.
That isn't flavour bolted on, it's what `site: site_02_corner_block` already
meant, finally said out loud.
BUDGET Y IS THE FEATURE, not the letterhead. DESIGN.md's brief is "client wants
X, budget Y, forecast Z" and this card had Z alone. The fee has existed since
Sprint 8 and the player has never seen it until the money was already spent —
you chose what to rig without knowing what the job was worth, which is a reveal,
not a decision. The sheet quotes base + garden + clean up front, and a test pins
that a perfect night pays exactly what the sheet advertised: a job sheet that
over-promises is the game lying on paper.
THE CLEAN BONUS is the new money, and it's the one number in week.js that isn't
measured. It gives the invoice three axes that mean three different things —
you turned up, their garden lived, you didn't wreck the place — and it's NOT
gated on winning, because a lost night where you broke nothing of theirs is a
different fact and deserves its own row. It also makes the carport bite twice:
180 + 20 = 200, inside E's own "over ~250 is a silent game over" ceiling.
Measured: it adds ~$100 to a competent week (385 -> 485), which makes
BROKE_BELOW's known runaway worse. I did NOT pay for it by quietly trimming
feeFor or gardenBonusMax — those carry measured evidence, and re-tuning someone
else's evidence to fund my own feature is the move this repo keeps catching. If
gate 3 says the bank runs away, this is the lever to cut first and it's data.
TWO BUGS, BOTH CAUGHT BY LOOKING AT THE CARD, neither by a test:
· "THE HENDERSONS" over "The Backyard — Your own place". The blurb and the
letterhead called each other liars on screen. The blurb gave way: "your own
backyard" is a leftover from when the game had one yard, and DESIGN.md never
wanted it. The line's job survives — night 1 is the tutorial because you know
this yard, now from having worked it for years rather than owning it.
· C's `lead` is a 0..1 haze dial (confidence = 1 − lead), not "nights out". I
read it as nights, passed 1, and the sheet advertised "forecast confidence
0%". Now 0.6, because C's own hail rule calls L > 0.55 too distant to promise
ice, and tomorrow is exactly that. C: the slot is mine, the number is yours.
Lane C's forecastLines(def, lead) finally has a caller above 0 — two sprints
after they built it. Lane E: .letterhead / .brief / .jobsheet / .tomorrow are
structure only and yours to style.
selftest 304/0/0 (was 300).
The corner block's seams, all of them A's to close.
ENUM (D's flag). Anchor.type was a closed JSDoc enum 'house'|'tree'|'post',
so site_02 typed its carport 'post' to fit — quietly enrolling it in the
sail-post family that C's venturi and B's audit both read. Widened to E's own
asset strings (carport, carport_post) and, more to the point, CHECKED: it was
documentation for ten sprints, and documentation cannot fail. validateSite now
rejects an unknown type by name. The ladder is untouched either way — D keyed
it on `work`, the mechanism, which is exactly why that was right.
CARPORT (E's proposal, adopted). $180, with E's reasoning intact: gnome 25, a
night's budget 80, so 180 is 2.25 nights — a good week turned broke and felt
for the rest of the run, without ending a strong one outright. Under ~120 it's
a shrug, over ~250 a silent game over that teaches nothing.
The number lives in site JSON, not the GLB: sites are data, and what a carport
COSTS is this site's economy, not a property of the mesh. E's baked value stays
as the fallback and the proposal.
And it BILLS now. E shipped the trap and said it plainly — the anchors said
collateral "carport" and nothing said what one cost, so nothing scored it. You
could lose the worst steel in the game and pay for a $15 shackle. One broken
corner is enough (the gnome needs two: it needs the sail to land on it; the
carport doesn't need the sail at all), priced per structure so two beams is one
carport gone, not $360. The wreck swaps on the same event — E built it to the
same origin for exactly this.
VENTURI (C's question). There was no disagreement to settle. weather.core
aligns on |dot(wind, axis)| because a gap funnels either way through it, so an
axis is a LINE: -1.08 + PI = 2.0616, and my shipped 2.1 was the same gap read
from the other end. Took C's number because C MEASURED it off storm_03b and I
eyeballed mine at authoring time; the 2.2 deg of my eyeballing was worth
0.44 m/s. gain left at C's 1.35 — that's a balance lever, theirs with B's audit.
Also wired validateSiteWind at site load, C's ask from Sprint 10.
selftest 300/0/0 (was 296).
SPRINT10 gate 1b. A night is now {storm, site}; the week hands over a yard per
night and night three is the corner block. Nights 1/2/4/5 are the backyard; the
plain-string night form still works and defaults to backyard_01.
site_02_corner_block.json: a smaller 24x16 yard with E's carport as the trap it
was built to be — beam anchors rating_hint 0.22, posts 0.30, both below the
house fascia's 0.35, collateral "carport". The site IS winnable off the honest
anchors (one tree east, four ground posts): I enumerated the honest quads at
authoring time and there are four in-band ones covering the bed, so the carport
is temptation not necessity. Full load audit is Lane B's tools/site_audit.
The carport's beams are keyed work:"bracket" — D's field, the MECHANISM not the
type. A bracket up a bare beam is the one anchor you can't fake from the ground,
so it needs the ladder; keyed on type it would fail open (a carport isn't a
house) and the ladder mechanic would silently vanish, which is exactly the audit
D ran. C's venturi list is in the JSON with two gap coordinates they asked for.
The world is rebuilt on a site change, not re-pointed: createPlayer captures the
world into its ground clamp, collider, ladder and broom (Lane D's modules, no
setters), so a clean rebuild is honest — and a site change only happens at a
forecast, never mid-storm. world.dispose() frees the old yard so nothing leaks.
The re-point of rig + session lives inside loadSiteInto, keyed on the rebuild
itself, after a caller-side version proved fragile.
Verified end to end through real phase transitions: play night 1, hit the
aftermath, and the forecast into night 3 rebuilds into the corner block —
carport present, 10 anchors (cb/cp/q/tr, not h/t/p), smaller bed, player rebuilt,
no scene leak. Then back to the backyard, 12 anchors restored. Selftest 289/0/0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SPRINT10 gate 1a, carried since Sprint 8. Everything that was a module constant
— bed rect, house line, tree and post placements, shed, gnome, sun angle, fence
sides — is now data/sites/backyard_01.json, and world.js is a builder.
The extraction is byte-identical, and that's checkable rather than claimable:
E's branch-anchor tripwires pin five positions to 6 dp, a.test's quad-band
asserts pin the whole rigging geometry (>=3 quads in 18-45 m2, full coverage
still >45 m2), D's ladder field and B's balance lines all pass unchanged.
287/0/0.
It's byte-identical by construction, not by luck: the site-derived values keep
the builder's original local names, so ~500 lines of geometry that was already
correct is untouched and only its SOURCE changed.
Two things earned their place:
Anchors carry D's `work: "cloth" | "bracket"` — the MECHANISM, not the type. A
post is tensioned from a cleat at its base and a tree strop is thrown, but a
bracket bolted up a bare wall is the one you cannot fake. Keyed on mechanism
because `type` is a closed enum ('house'|'tree'|'post') that site_02's carport
doesn't fit, and needsLadder keyed on type FAILS OPEN there — the ladder
silently vanishes and you re-rig a 2.6 m bracket standing on the grass.
p4's swept position table moved INTO the JSON's `_why`, beside the coordinate it
justifies. That table is the only thing stopping someone nudging the post toward
the bed and collapsing DESIGN.md's central tension; it belongs with the number,
not in a file the number left.
validateSite() fails loud: sites are content, and a missing bed silently becomes
NaN coverage while a missing anchor list silently becomes an unriggable site.
createWorld now requires a site and says so.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>