Commit Graph

204 Commits

Author SHA1 Message Date
type-two
dd69a2cf6c Lane A S17 gates 0+1+2: THE JOB BOARD — two offers a morning, the chosen job IS the night
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).
2026-07-21 02:11:16 +10:00
type-two
75d5f9a4bf Lane D S17 gate 0.1: SEVEN NIGHTS — the wildnight restored at night 5 (John's ruling)
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>
2026-07-21 00:36:03 +10:00
type-two
d8e7087c2c Lane D S16 gate 4.1+4.2: swing lawn re-scored (zero deltas), THE SIX-NIGHT WEEK wired
- 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>
2026-07-20 21:49:58 +10:00
type-two
1073ac97e8 Sprint 16 builders integration: merge a/e/c/b (469/0/0), hud keep-both, E's palette hunks applied 2026-07-20 21:06:27 +10:00
type-two
6508d53c89 Merge remote-tracking branch 'origin/lane/b'
# Conflicts:
#	THREADS.md
#	web/world/js/hud.js
2026-07-20 20:59:28 +10:00
type-two
a7cb661b62 Merge remote-tracking branch 'origin/lane/c'
# Conflicts:
#	THREADS.md
2026-07-20 20:57:35 +10:00
type-two
186e75839d Merge remote-tracking branch 'origin/lane/e'
# Conflicts:
#	THREADS.md
2026-07-20 20:57:35 +10:00
type-two
f84e00f46b Lane B S16: THREADS — gate-2 entries (heal, record, refusals, membrane table, crossing closed); fabricnote as verdict-sibling for A's ledger merge
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:55:03 +10:00
type-two
f7a22eabb8 Lane B S16: ponding fabric-blindness RULED a known simplification — the comments stop lying
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>
2026-07-20 20:48:34 +10:00
type-two
d03712ae47 Lane C S16: gate 3 lands — the fabric bet pinned, forecast argues it, storm in the lists; selftest 438/0/0
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.
2026-07-20 20:33:10 +10:00
type-two
458e9a5412 Lane B S16: gate 2.3 — separation pins ATTEMPTED and honestly REFUSED, with receipts
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>
2026-07-20 20:32:37 +10:00
type-two
6cbae92028 Lane A S16: gate-1 THREADS entry (landing + rulings + E's flags answered); defuse the 'trampoline' bogus-type fixture (E's flag)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:29:42 +10:00
type-two
e5df63128a Lane B S16 pool: the frame-determinism bound is pinned — and it is 66.7 ms, not 83
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>
2026-07-20 20:25:42 +10:00
type-two
5bfd843b77 Lane B S16: gate 2.2 THE RECORD — the fabric bet is on the paperwork
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>
2026-07-20 20:25:22 +10:00
type-two
b74383722b Lane E S16: sixteen pins for gate 5 — 10 auto scale/node checks on the five new GLBs + 6 named: pergola resolves through the MANIFEST (never a literal) and prices between gutter and swing; the wreck fails partially with byte-close decks; the glasshouse offers NOTHING from three directions and prices as the apex; panes pinned translucent, wreck pinned emptied-not-felled; the pool's 14 denials pinned per-post BY NAME (RULE 1's regex cannot see them — proven, control M3). 434/0/0 in the browser
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:21:57 +10:00
type-two
453f6cb9d2 Lane E S16: 'pergola' enters ANCHOR_TYPE — A's file, my edit, the swing_frame precedent, flagged in THREADS. A deck is not concrete; the type string is the player's pre-rig read
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:21:57 +10:00
type-two
1571c2f1f9 Lane E S16 gate 5: the next temptations — glasshouse (collateral apex, $320, wreck empties not falls), pool kit (14 fence posts, 14 explicit tie_off denials, unpriced by the bike ruling), pergola (the 0.65 middle rung D endorsed, $120, partial-failure wreck). Shared-helper wrecks, deterministic (2 runs, 44 files shasum-identical, zero churn on the 31 existing GLBs); manifest gains exactly the pergola entry
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:21:38 +10:00
type-two
d6af6ba5a2 Lane C S16: makeStorm(seed) — seeded storms inside authored envelopes (gate 3.2)
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.
2026-07-20 20:11:34 +10:00
type-two
71278da837 Lane A S16 gate 1: the ledger — work history on the settlement, warranty at quote time, rep on the letterhead
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>
2026-07-20 20:10:06 +10:00
type-two
c6ded2db7d Lane B S16: gate 2.1 THE HEAL — divergence break now sanitizes the cloth
_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>
2026-07-20 20:02:36 +10:00
type-two
ed62c7d4be Sprint 15 integration: merge all lanes (418/0/0), apply E's world.js manifest patch, [I] entry 2026-07-20 19:38:11 +10:00
type-two
a79f714c86 Merge remote-tracking branch 'origin/lane/b'
# Conflicts:
#	THREADS.md
2026-07-20 19:30:45 +10:00
type-two
82691678eb Merge remote-tracking branch 'origin/lane/c'
# Conflicts:
#	THREADS.md
2026-07-20 19:30:45 +10:00
type-two
ef6b3d0028 gate 2.2 closed: adopt C's converged storm store byte-identical; score panel becomes a view of it; [B] THREADS closing entry (pins reproduced, timings, seam receipts)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 18:32:07 +10:00
type-two
0bb0e9eb6d Lane C S15: converge gate 2.2 onto B's THREADS proposal — B's filename/global/source/default taken, storm+time API kept; THREADS: seam receipt + gate-1.3 storm-side zero-delta confirmation
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>
2026-07-20 18:23:31 +10:00
type-two
d6f04d5e43 editor: one storm picker + a progress line that paints (S15 gates 2.1/2.2, B's halves)
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>
2026-07-20 18:19:44 +10:00
type-two
3a2e4ff5e1 Lane C S15 gate 2.2: ONE storm picker — editor_stormsel.js is the shared storm+time selection; wind panel becomes a view of it + JUMP TO WORST SECOND
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>
2026-07-20 18:05:33 +10:00
type-two
2a540523ff Lane E S15 gate 1.2: RULE 4 pins — manifest===GLBs both directions (4a), runtime===manifest across all three shipped sites (4b); compared against the manifest, never a literal; both mutation-checked red-then-green (post 1.0->0.9)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 17:49:38 +10:00
type-two
8aa49528e3 Lane E S15 gate 1.2: ratings.js — factoryExtras(type), the runtime end of the manifest; refuses to guess for ambiguous types, warns once
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 17:49:38 +10:00
type-two
e46ff18648 Lane E S15 gate 1.2: re-bake sail_post top_anchor 0.90 -> 1.00 per the gate 1.1 ruling; factory emits the per-type anchor ratings manifest (anchor_ratings.gen.js) from the re-imported GLBs, ambiguity-aware, --only-guarded; asset_report carries per-asset anchor extras
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 17:49:38 +10:00
m3ultra
c182fc14cb editor: the honest edges — Enter commits, bounds are seen, keyboard zoom, separation ruled (S15 gate 3 + gate 1.1 ruling)
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>
2026-07-19 11:11:05 +10:00
m3ultra
3dee2a7c83 Merge origin/main: S14 lane integration + review fixes; TEETH becomes SPRINT16
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>
2026-07-19 10:34:21 +10:00
m3ultra
f179d6c25e docs: panel comment tells the truth; README wears the shipped name; THREADS review entry
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>
2026-07-18 19:03:36 +10:00
m3ultra
b74dfd80db rigging: the fabric bet is reachable — F cycles cloth/membrane in prep
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>
2026-07-18 19:03:36 +10:00
m3ultra
b7a069069d debris: delete the dead sail.nodes seam — sail._applyDebris is the one contact model
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>
2026-07-18 19:03:36 +10:00
m3ultra
9d6db356d7 sail: break diverged corners on the production path; enforce pond cap post-flow
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>
2026-07-18 19:03:36 +10:00
type-two
008b78a0d4 Sprint 14 integration: merge all lanes (406/0/0), fix palette _v1 suffix bug, [I] entry 2026-07-18 17:58:44 +10:00
type-two
e49ad04f99 Merge remote-tracking branch 'origin/lane/b'
# Conflicts:
#	THREADS.md
2026-07-18 16:47:17 +10:00
type-two
a2ed98b4db Merge remote-tracking branch 'origin/lane/c'
# Conflicts:
#	THREADS.md
2026-07-18 16:47:17 +10:00
type-two
04f47586eb Merge remote-tracking branch 'origin/lane/e'
# Conflicts:
#	THREADS.md
2026-07-18 16:47:17 +10:00
type-two
19c39acc8d Lane B S14: collateral exposure on the card + the night-5 invoice line (pool item)
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.
2026-07-18 16:29:41 +10:00
type-two
15ea431b23 Gate 2.3: pin editor wind == game wind exact, and fix the stale-wind seam
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.
2026-07-18 16:27:03 +10:00
type-two
ff7c1f7985 Lane B S14 gate 2.3: pin the editor's scored wind to the game's played wind
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).
2026-07-18 16:16:29 +10:00
type-two
f3bf038515 Sprint 14 gate 1 close: round-trip pinned, pool items landed, free-carport fixed
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>
2026-07-18 16:14:34 +10:00
type-two
4c1086c805 Wind authoring for the yard editor: venturi gizmo (axis as a LINE, mod pi), tree-shelter volumes, wind-field overlay
Gate 2.2. Mounts into Lane A's editor through the published seams only —
panel at reserved order 50, every gizmo parented to EDITOR.overlay, venturi
drag through registerTool, site.wind mutated then markDirty(). No second wind
validator (validateSiteWind is already inside A's validateSite) and no private
harness: windForSite() is the only wind this file builds.

Gizmos are drawn from wind.core.venturi/shelters — what the wind actually
flies — not from the site JSON, so a mis-wired setVenturi shows up as a funnel
that vanishes rather than a number nobody reads.
2026-07-18 16:06:16 +10:00
type-two
b9b64604b3 Palette audit + two temptation props: honest extras, in the factory
SPRINT14 gate 3.1. The editor is about to offer these GLBs to an author by
name, which turns every silent field in them into a trap for whoever places
one. Audited the placeable set, fixed the gaps in the factory (nothing
hand-edited), and added the two props D needs to author a yard worth playing.

THE AUDIT, and what it found:

- Four `*_anchor` nodes carried no rating_hint at all — door_anchor,
  pickup_anchor, grip_anchor, window_light_anchor. adoptAnchor does
  `rating_hint ?? 1`, so silence there does not mean "unrated", it means
  RATED PERFECT: a shed door skin outranking a concreted post, conjured out
  of a missing field. They now deny it explicitly (`tie_off: false`).
- The carport's price resolved only by coincidence: collateralFor() matches a
  STRUCTURE whose site id equals the anchors' collateral string, and site_02
  happens to name its structure "carport". The editor will generate
  "carport_2" for the second one, and the trap would have gone free — the
  gutter bug reborn in the sprint meant to bury it. The GLB now carries
  `collateral_key`, like the house does for "gutter". Lane A: the runtime
  half is yours (collateralFor could fall back to it).
- The trampoline is unpriced, and now says so rather than omitting it.

Three new rules make the audit permanent, and each is proven able to fail:
no silent anchors, no collateral string nobody prices, no baked anchor_type
outside the checked ANCHOR_TYPE enum. Rule 2 found the carport gap on its
first run — an unfaked negative control.

THE PROPS:

- swing_set_01 (+ wrecked) — the palette's honest middle. It had a ceiling
  (gum fork 1.0) and a trap (carport beam 0.22) and nothing in between. Two
  apex anchors at 0.45, typed `swing_frame` (NOT `post`: the type string is
  what the player reads before committing, and "post" promises concrete),
  and a crossbar that is the most anchor-looking object in the game and is
  not one. Priced at $140 — proposal, reasoning baked beside it, A rules the
  number: gnome 25 < gutter 90 < swing 140 < carport 180.
- tree_jacaranda_01 — a second species whose LADDER is the feature.
  Gum: 1.00/0.88/0.76, climb freely, pay 24%.
  Jacaranda: 0.95/0.52/0.40, climb at all, pay 58%.
  That makes tree choice a real decision instead of "is there a tree".
  Unpriced by ruling, same as the bike: no limb-failure event exists to see.

Both wreck/orientation claims are measured in browser coords, not reasoned —
`wreck_falls_toward: "+Z"` is baked AND asserted against the mesh.

Selftest 376/0/0. Two consecutive full factory runs: 44 files byte-identical.
2026-07-18 16:01:27 +10:00
type-two
6825794d98 Lane B S14 gate 2.1: SCORE IT — shared scorecard engine + editor panel
scorecard.js: the audit run-logic that audit.html had grown moves into one
shared engine both front-ends import (build the scoring world, sweep, fly the
winners, pick the best line, judge the separation block). Takes a site OBJECT,
not a path — loadSite always returned an object, so the fetch was never the
audit's business. Builds its OWN dressed world on a re-pointable wind proxy so
the editor's calm stub can never reach a score; every m/s still comes from
windForSite() via sweep.js/gardenfly.js.

editor_score.js: front-end only, zero audit logic. Mounts at A's reserved
order 40, renders the card with A's contract classes, says the funnel state
first and loudly, and marks itself stale when the yard changes underneath it.
2026-07-18 15:56:50 +10:00
type-two
16dee119d6 Sprint 14 gate 1: the yard editor core + two shipped bugs it found
web/world/editor.html + js/editor.js. Loads any existing site or an empty
template and renders the REAL dressed world through the same
loadSite/createWorld/dress path the game boots -- not a diagram, and no
second renderer to keep in sync. Place/drag/delete on the ground plane by
raycast, writing the SAME named-key site-JSON shapes world.js already reads
(the gnome pattern; there is deliberately no generic props[] and the editor
does not invent one). Live validateSite into a panel, canonical
stable-key-order export with a loud _INVALID key, visible _design/_why
authoring fields, and an export panel that says export != shipped.

Published the DOM seam contract in editor.html's header so B's SCORE IT and
C's wind authoring can start against it immediately -- mountPanel + a class
kit, the way E's .letterhead kit worked in Sprint 12.

Two rendering bugs in world.js that the editor surfaced in its first ten
minutes, both verified by looking at the yard rather than by reasoning:

- the graybox house was built unconditionally and dress() only retires it
  when a house GLB loads, so site_02_corner_block -- which declares no house
  -- has been shipping a 16x3x6 m grey slab across night 3's whole north
  horizon, in solids, on the public URL.
- SHED alone was bound raw while SHED_TABLE/GNOME/BIKE all get their degrees
  converted, so SHED.rotY was undefined, the shed's quaternion was all NaN,
  and three.js silently declines to draw a NaN world matrix. The Hendersons'
  shed has never rendered; ladder.js has been leaning the ladder on thin air.

Also: createWorld threw on a site with no shedTable (unreachable for both
shipped sites, reached by the editor template on its first frame). Now null,
which every consumer already guards and which CONTRACT.world permits.

Selftest 362/0/0. Asserts for the two fixes land with the round-trip test in
the next commit -- pushing now because B and C are blocked on the seam.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:46:01 +10:00
type-two
edb3377818 Merge remote-tracking branch 'origin/lane/d'
# Conflicts:
#	THREADS.md
2026-07-18 14:26:16 +10:00
type-two
646efa7244 Merge remote-tracking branch 'origin/lane/b'
# Conflicts:
#	THREADS.md
2026-07-18 14:26:16 +10:00