Landed ON TOP of f029274 with not one line of it changed. Two Lane C sessions
ran gate 1 in parallel; I found out by having my push rejected. forecastHonest
answers "can the forecast describe this STORM" and is better than the storm-side
half I had written, so mine is deleted. These are the two things it structurally
cannot reach — it takes a def, not a pairing, and it never touches the card.
weather.js is +180/-0. The only line of theirs I touch anywhere is widening one
c.test import from { NIGHTS } to { NIGHTS, nightAt }.
1. THE OFFER BAND, and the finding that motivates it. A's card prints f.wind
and f.stones, and under that pair NIGHT 7 LIES:
soaker (scripted) gusts to ~55 km/h · fine pea stones
early buster (alt) gusts to ~76 km/h · pea stones
On both printed lines the soaker is the softer job. It is not: 14.9s of hail
against 2.4s, 44 mm/hr against 16.5. The soaker's whole design is that its
wind reads gentle and its hail is the trap, so the board recommended the trap
in the storm's own voice. offerBand(def, lead) returns an ORDERED LIST, so
the forecast decides which facts a night carries and a card cannot drop the
next one added — same construction as A's stormsToPreload(nights, pool).
Verified in the real card on :8826.
Second finding: storm_03 and storm_03b are identical in every number the band
prints except the change time (30s vs 18s), and the board offers exactly that
pair on nights 5 and 7. Without the change line it offers two indistinguish-
able jobs and calls it a choice. Pinned.
2. PAIRING_LAW. A band can be perfectly honest about a storm and still be an
invitation to a night nobody can work. A's three POOL exclusions were prose;
promoted to checked data with the measurement in each message, credited by
owner (the soaker's is mine with the S16 ~31% receipt; wildnight and icenight
are recorded as A's reasons, not re-measured by me). Absent key = unrestricted
deliberately — inventing a refusal for a pairing nobody flew is the same
offence inverted, and that direction is pinned too. pairingRefusals is NOT a
filter: silently dropping an entry is a runtime surprise wearing a fix's
clothes. Walks NIGHTS u POOL carrying the SITE: 12 pairings, 0 refusals, with
a vacuity guard in the same case.
Mutation-checked on THIS tree after the code changed address: hail line dropped
(RED, names the storm and the seconds), soaker restriction deleted (RED x2,
including the vacuity guard), change line dropped (RED, names the two busters).
All restored, 494/0/0.
Filed in THREADS, not landed: the hud/main hunk for A (leadFor precedent), and
a floor for stoneWord — hail.size 0 words itself "fine pea stones" because
stoneWord has no floor. Their STONE_WORD_CEILING fixes the top of that range;
nothing fixes the bottom. It belongs inside forecastHonest, which is theirs.
Selftest 494/0/0 (their 490 + 4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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).
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>
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>
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>
The store: one owner for 'which storm, which second' on the editor page.
Garbage keys refused loudly, no-op writes silent (the anti-ring guard that
lets a subscriber clamp from inside its own handler), listener errors
contained, default = the gate-2.3 pin (wildnight @ 60 s). editor.wind.js
drops its private stormKey/time and re-exports the store's STORM_KEYS —
one list, same array. B's editor_score.js patch proposed in THREADS.
Polish (my call): JUMP TO WORST SECOND — argmax of speedAt over grid ×
storm, venturi throats passed as exact probes, dt = the scrubber's own
0.5 s step. Writes the SHARED second. site_02 × wildnight: 51.11 m/s
@ 60.5 s at (-7.5, 0), the funnel plateau, found by scan not patience.
Selftest 413/0/0 (410 + 3). Five mutations red-then-green: no-op guard
off, validation off, singleton doubled, comparator flipped, probes
dropped — each reds its own assert with its own message.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gate 1.1 RULED in THREADS: the honest post is 1.00 — the shipped value
becomes canon, E re-bakes to match, and the adoption mechanism must make
a baked hint reach JSON-declared anchors (same integration, no 0.90
window). B's re-measure becomes a zero-delta verification.
Gate 3: numInput/textInput commit on Enter (idempotent — a no-op commit
pays no rebuild); boundsFaults() rides the validation cache so header,
panel and export read one measurement, export leads with _OUT_OF_BOUNDS
(not _INVALID — that key's sentence would lie); the yard rectangle draws
as an amber line where fence-off sides hid it; +/- zoom on the wheel's
own curve; separation ruled a measured pin with the SITE panel saying so.
New a.test mutation-checked both ways in the browser harness. 410/0/0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two same-day integrations reconciled: their SPRINT15 (post ruling, editor
iterative, honest edges) stands and runs first because it re-measures the
numbers TEETH pins; the TEETH plan moves to SPRINT16 with a fires-after-15
banner, its D gate rewired to the landed swing lawn. ROADMAP arc 0 records
the fork. THREADS keeps both sides chronologically.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
main.js said panel:false in the comment and passed panel:true for two
sprints — the call was right (B's panel is the only prep UI), the
comment now says so. README retitled HARD YARDS with the SHADES codename
noted. THREADS carries the fresh-eyes review receipts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FABRIC and setFabric() shipped in SPRINT6 and the sim honoured porosity
all along, but no key, click or HUD line ever reached them — every rig
ever played was DEFAULT_FABRIC. F toggles the two entries, the panel
shows the fabric, the splash teaches it, the blurb announces the switch.
Session-level assert pins the cycle, the $0 price, and the refusal of
unknown ids.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SPRINT2-era duck-typed path (if sail.nodes → applyToSail) has been
dead since decision 5 chose option b, and was a double-apply waiting for
the day anyone exposed .nodes. The sail key on step()'s world arg is
accepted and ignored, documented.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A NaN corner load compares false to every rating, so a diverged corner
held forever while NaN spread node-to-node — watchDivergence only ever
guarded the tests. Non-finite load now breaks the corner instantly with
reason:'divergence', load zeroed for the HUD.
The pond per-node cap only ran on the rain-add: once rain stopped,
consolidation could stack a basin node past POND_MAX_KG_M2 unbounded.
Clamp moved post-flow, every step. Also fixes the dumpPond assert that
compared dumped to itself through a helper that returned its argument.
All three mutation-checked red before landing green.
Co-Authored-By: Claude Fable 5 <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>
COLLATERAL ON THE CARD. 'Winnable at $80' is an incomplete sentence on a yard
where letting a corner go also bills $180 of carport, so the card now prices
it — through world.collateralFor(), the one resolver (A's structKey fix), never
a second copy of that lookup in a panel. Deduped per STRUCTURE the way main.js
bills ('two beams letting go is one carport gone'): site_02's four carport
anchors read $180 total, not $720. The bill also rides on the margin-flag line
itself, because 'knife-edge' and 'knife-edge AND $180' are different authoring
decisions. An unpriced label renders UNPRICED — not scored, in red, never
'free' — which is the gutter bug's shape and the lie the card exists to prevent.
Verified A's structKey fix rather than assuming it: an editor-shaped carport
(id s1 + collateralKey) prices to $180, a second one (s2) also $180, and the
negative control (id s1, no collateralKey) reads null = not scored. My first
test reported a false alarm because I renamed a SHIPPED spec's id without the
palette's collateralKey — A's editor writes that field explicitly, so the
editor path was never broken.
THE NIGHT-5 INVOICE LINE (pool item, offered S13, accepted). Night 5's garden
is beyond saving by design, and the job sheet was the last surface still
dangling 'up to $45 / paid on what's left of the bed at dawn' — a maximum
nobody can earn, phrased as a rigging problem the player could solve.
NO ECONOMY CHANGE, deliberately. week.js's own comment forbids quote and settle
diverging ('the job sheet promises a number the invoice doesn't pay'), and
zeroing the quote would break that in the opposite direction — settle really
does pay the bonus proportionally. So the NUMBER stays and its CONDITION tells
the truth, which is the job sheet's own idiom:
nights 1-4 'paid on what's left of the bed at dawn' (unchanged)
night 5 'scales with the bed at dawn — and tonight the ice takes it
whatever you rig'
and the ledger names it too, because an unexplained small number next to
'night lost' reads as the player's failure:
night 4 'garden bonus — 18% of the bed +$8'
night 5 'garden bonus — 18% of the bed, beyond saving from the start +$8'
Verified in the running game on all five nights: flag fires on 5 only, every
quoted and paid figure identical (garden $45, total $136, bonus $8).
Selftest 374 passed / 0 failed / 0 skipped.
The pin (co-owned with B, probe/second moved and argued in THREADS):
site_02_corner_block / storm_02_wildnight / the authored throat centre
(-6,0) / t=60.0 / exact ===. B proposed backyard_01, the garden bed and
t=30; all three are places the funnel contributes ~nothing (backyard_01's
venturi is [], the bed measures 0.0000 m/s of funnel even on site_02, and
t=30 is worth 0.4%), so that pin could not have failed. A vacuity guard
now asserts the funnel is worth >25% AT the pin, so it cannot rot back
into decoration.
Also fixed, found by looking: editor.wind rebuilt its wind in commit()
rather than on 'change', so any EXTERNAL mutation + markDirty() re-rendered
the panel against the previous site's wind. The readout kept insisting a
restored gap was 24% aligned. Refresh moved to the listener, so the overlay
is a function of the site as it is, whoever changed it.
368/0/0 (362 baseline + 6). All six mutation-checked in one run: dropping
setVenturi from windForSite reds the pin (31.60 vs 47.38 m/s), the guard
and the sweep; normalizeAxis as identity reds the fold; drifting the
shelterAtten mirror 0.5 -> 0.62 reds the volume assert.
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).
Gate 1.5 -- the round trip. a.test drives the editor's OWN placement function
(placeEntry, exported for exactly this) through
template -> place -> export -> parse -> validateSite -> createWorld -> dress,
then asserts the GLB-backed anchors ADOPTED and the carport's 0.22/0.30 came
out the far side. Mutation-checked with five deliberate breakages; four went
red and ONE DID NOT -- the key-order test shuffled only root keys, every one
of which is in KEY_ORDER, so the alphabetical fallback it claimed to protect
was never reached. Rewritten around unknown keys, re-mutated, red in both
halves. A determinism test that passed while testing nothing.
Picking bug, found by dispatching real PointerEvents rather than reading the
code: clicking an object's FOOT selected nothing, because the pick handle is a
cylinder centred 1.7 m up and from a raised camera the ray has already run
~2 m horizontally by the time it climbs there. pickRef now falls back to
nearest-placed-thing within 1.3 m of the ground hit -- camera-angle
independent, so it can't rot when the pitch changes.
Phantom sail (pool, filed unlanded in S13) -- LANDED and negative-controlled.
Teardown factored into disposeSailView() and called from loadSiteInto before
refreshCameraSolids (which reads sailView and would re-register a disposed
mesh as a camera collider). Rig state goes with the view. Verified through the
real game both ways: with the fix removed the cloth still hangs over the
corner block, screenshotted.
Dev-line (pool) -- two findings, both landed. The line counted `pieces` only,
so it read "debris 0" while seven leaves flew; now reports both populations
off C's leafCount. And index.html's boot-failure handler wrote into #dev,
which is display:none off localhost -- so a failed boot on partly.party showed
a stranger a blank page. It has its own visible #fatal element now.
E's gap 2, runtime half -- and it was worse than "one click away": nextId
namespaces structures s1, s2..., so the FIRST carport the editor places was
already free. collateralFor/wreckStructure/isWrecked now resolve through
structKey (site collateralKey, then GLB collateral_key, then id last so every
existing site is untouched). Negative-controlled: id-equality prices an
editor carport to null. E's tie_off:false honoured at both ends. E's swing set
and jacaranda are in the palette behind a `requires` gate, so they switch
themselves on when the ANCHOR_TYPE widening merges.
Also: `return t.skip(msg)` is a fake pass -- Suite.skip pushes a new result and
lets the current test record as pass. a.test had six; all converted to the
documented 'SKIPPED --' idiom.
Selftest 373/0/0, no skips.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>