From a6ffd522096519ac931630252b76e0719b603d54 Mon Sep 17 00:00:00 2001 From: m3ultra Date: Fri, 17 Jul 2026 13:19:10 +1000 Subject: [PATCH 1/2] =?UTF-8?q?Gate=200':=20the=20harnesses=20AGREE=20?= =?UTF-8?q?=E2=80=94=20and=20my=20settle=20claim=20is=20retracted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ran every remaining suite-vs-game difference one at a time against the real storm. None of them moves the corner count: live calm settle (running clock + calmWind) entry 1.94 -> 0.40 kN, 2 lost live tree sway t2 4.33 -> 4.55 kN, 2 lost walked repair timing (~4 s) 2 lost all three together hp 58, 2 lost Then checked the structural candidates: anchors are bit-identical between harnesses (all four, 0.0000 m), ring order is identical (p4,t2,t2b,p3), and wind.sample() at t2 matches to 2 dp (36.49 m/s @ t=75.9) across suite, suite with shelters, and the live router. So I re-ran the live game itself, fresh page, A's exact line, tension 1.0: live -> hp 58, 2 lost [t2,t2b], t2 peak 4.55 kN suite -> hp 58, 2 lost [t2,t2b], t2 peak 4.54 kN They agree. The suite was right. RETRACTED: my Sprint-7 "12 s settle flips 2 lost -> 1". It does not reproduce — 0 s and 12 s both end 2 lost, because the storm's own peak (4.54 kN at t=75.9) dwarfs the attach transient. The settle is a real fidelity fix and NOT the corner-count cause. B's arithmetic was right from their first post: t2 pulls ~4.5 kN, a shackle is rated 3.2, and the spare cannot save it (measured: t2b blew @9 s, repaired, blew again). Landed anyway, because it is still true and still worth guarding: fly() now settles the way main.js drives prep (calmWind on a running clock, not the storm frozen at t=0), and a new "the yard is SETTLED when the storm starts" assert holds every run under 1.0 kN at entry. 266 pass / 1 red — the red is the pyrrhic-win design call, not a bug, and it now says so. Co-Authored-By: Claude Opus 4.8 --- web/world/js/tests/balance.test.js | 110 ++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 26 deletions(-) diff --git a/web/world/js/tests/balance.test.js b/web/world/js/tests/balance.test.js index 6a8517c..3ebc9e9 100644 --- a/web/world/js/tests/balance.test.js +++ b/web/world/js/tests/balance.test.js @@ -33,6 +33,8 @@ const [CARABINER, SHACKLE, RATED] = HARDWARE; /** main.js's rule, duplicated ONLY here so a balance failure names the rule it broke. */ const WIN = (hp, lost) => hp >= 50 && lost < 2; +/** main.js's CALM_STORM — what wind.use() hands the rig outside a storm, and so what settles it. */ +const CALM_STORM = 'storm_01_gentle'; const GARDEN_DRAIN = 0.9; // main.js const W_HAIL = 5.0; // main.js, decision 13 — integration weights const W_RAIN = 0.25; @@ -112,6 +114,11 @@ function shop(yard, ids, hw, spares = 0, tension = 1.0) { async function fly(yard, session, stormName, { repair = false, broom = false } = {}) { const def = await loadStorm(stormName); const wind = createWind(def); + // The settle below runs on the CALM day, because that is what main.js hands the rig outside a + // storm: `wind.use(to === 'storm' ? winds[stormKey] : calmWind)`. + const calmDef = await loadStorm(CALM_STORM); + const calmWind = createWind(calmDef); + calmWind.setSheltersFromTrees(yard.anchors.filter((a) => a.type === 'tree')); // main.js:369 does this at boot and this suite didn't — trees shelter the air // downwind of them, and a quad hanging off tree anchors sits right in it. // (Measured during gate 0: it doesn't change storm_02's verdict, but a harness @@ -149,15 +156,34 @@ async function fly(yard, session, stormName, { repair = false, broom = false } = // anchors sitting inside those shadows. wind.setSheltersFromTrees(yard.anchors.filter((a) => a.type === 'tree')); - // THE SETTLE (gate 0, cause #2, found by Lane D 2026-07-18). A player plays - // through prep, so ~12 s of world.update pass before ENTER and the cloth + - // tree sway reach steady state. A harness that rigs and storms in the same - // tick lands the storm on the ATTACH TRANSIENT instead — measured at 2.7× the - // settled load on tree corners (t2 4.54 kN unsettled vs 1.71 settled), loud - // enough to swamp tension entirely. Holding t=0 keeps the wind at its mild - // ramp start (first gust ≥3 s) — near-calm, like prep. Per D's spec; the - // "assert the yard IS settled at storm entry" guard is theirs to formalize. - for (let i = 0, n = Math.round(12 / FIXED_DT); i < n; i++) rig.step(FIXED_DT, wind, 0); + // THE SETTLE. A player plays through prep, so ~12 s pass before ENTER and the cloth reaches + // steady state. A harness that rigs and storms in the same tick lands the storm on the ATTACH + // TRANSIENT instead. + // + // Now driven the way main.js drives prep, which is the last of the three suite-vs-game + // differences gate 0' was chartered to close: `wind.use(calmWind)` on the phase change, and + // `windTime()` returning `simT % calmWind.duration` — i.e. CALM WIND ON A RUNNING CLOCK, not the + // storm's wind frozen at t=0. Measured, Lane D 2026-07-18, this quad: + // frozen t=0, storm wind → 1.94 kN sitting on the corners at storm entry + // calm wind, clock running → 0.40 kN ← what a player actually walks in with + // + // ⚠️ AND THE HONEST PART: this does NOT change the corner count, and my Sprint-7 claim that it + // did is RETRACTED. Measured on current main, live game, fresh page, one variable: + // 0 s settle → 2 lost [t2,t2b], t2 peak 4.54 kN + // 12 s settle → 2 lost [t2,t2b], t2 peak 4.55 kN + // The storm's own peak (4.54 kN at t=75.9) dwarfs the transient, so the transient never decided + // anything. The settle is a fidelity fix, not the corner-count cause. B's arithmetic was right: + // t2 pulls ~4.5 kN and a shackle is rated 3.2 — the line cannot hold, at any tension, settled or + // not. See THREADS. + { + const settleWind = calmWind || wind; + const period = Math.max(1, (calmDef && calmDef.duration) || 1); + for (let i = 0, n = Math.round(12 / FIXED_DT); i < n; i++) { + rig.step(FIXED_DT, settleWind, (i * FIXED_DT) % period); + } + } + /** Peak corner load the instant the storm starts — the settled-at-entry guard reads this. */ + const entryPeak = Math.max(...rig.corners.map((c) => c.load || 0)); let hp = 100, pond = 0, used = 0; const steps = Math.round(def.duration / FIXED_DT); @@ -189,6 +215,8 @@ async function fly(yard, session, stormName, { repair = false, broom = false } = lost: rig.corners.filter((c) => c.broken).length, spent: START_BUDGET - session.budget, pond: Math.round(pond), + /** kN on the worst corner at storm entry — the settled-at-entry guard's number. */ + entryPeak: entryPeak / 1000, }; } @@ -269,28 +297,58 @@ export default async function run(t) { // number. With a camera it reads what Lane A measured. Neither harness was // lying; one of them was flying a yard with no cloth in it. if (!WIN(line.hp, line.lost)) { -// SPRINT7 gate 0 — CLOSED at the merge, three causes named, none a villain: - // 1. GARDEN (A): skyfx built with no camera → `if (!camera) return;` - // skipped the shadow-grid rebuild → every loadout scored as bare bed. - // hp 36 WAS the no-sail constant. Fixed above (camera in fly()). - // 2. CORNERS (D): the settle. A harness that rigs and storms in the - // same tick measures the ATTACH TRANSIENT — trees at rest, cloth - // still falling into shape — worth 2.7× on tree corners (t2 4.54 kN - // unsettled vs 1.71 settled). A plays through prep, so A's rig was - // settled; fly() wasn't. Fixed below (12 s settle, per D's spec). - // 3. TENSION (A, self-reported): the win was measured at 1.0 and - // published without declaring it; the shop default was 0.9. - // B's elimination sweep (shelters/tension/drain/geometry/sway) was right - // about everything it measured — the transient swamped tension, which is - // why four tensions returned identical numbers. See THREADS 2026-07-18. + // SPRINT8 gate 0' — CLOSED, and this suite was RIGHT. The live game and this harness now + // agree, measured by Lane D 2026-07-18 on current main (fresh page, A's exact line, + // t2+p3+p4+t2b on 4×shackle+spare, tension 1.0): + // live game → hp 58, 2 lost [t2,t2b], t2 peak 4.55 kN + // this suite → hp 58, 2 lost [t2,t2b], t2 peak 4.54 kN + // Every suite-vs-game difference gate 0' listed has now been tested one at a time and NONE + // of them moves the corner count: live-calm settle (entry 1.94→0.40 kN, verdict unchanged), + // live tree sway (t2 4.33→4.55, verdict unchanged), walked repair timing (unchanged). + // Anchors are bit-identical between harnesses, ring order is identical (p4,t2,t2b,p3), and + // wind.sample() at t2 is identical to 2 dp (36.49 m/s @ t=75.9). + // + // RETRACTED, on the record: Lane D's "the settle flips 2 lost → 1" (SPRINT7). It does not + // reproduce — 0 s and 12 s settle both end 2 lost, because the storm's own peak (4.54 kN at + // t=75.9) dwarfs the attach transient. The settle is a real fidelity fix and NOT the + // corner-count cause. A's hp 58 reproduces exactly; A's "1 lost" reproduces in neither + // harness. + // + // What's left is B's arithmetic, which was correct from the first post: t2 pulls ~4.5 kN and + // a shackle is rated 3.2 kN. The corner cannot hold at any tension, and the spare cannot save + // it — the repaired corner simply blows again (measured: t2b blew @9 s, repaired, blew again). + // So this is no longer an engineering question. It is SPRINT8's designed escape hatch: + // hp 58 CLEARS the 50 bar while 2 corners are lost, i.e. the player saves the garden and + // loses the sail — DESIGN.md's own story — and the win rule (`hp>=50 && lost<2`) calls it a + // loss. Either the rule admits a PYRRHIC WIN, or the shop must be able to buy a third + // above-shackle corner ($80 buys two). That is a design call, not a harness bug. See THREADS. throw new Error( - `no winnable line even settled: ${COVER_QUAD.join(',')} on 4×shackle+spare ` + - `($${line.spent}) ended hp=${line.hp}, lost=${line.lost}/4 (need hp>=50, lost<2). ` + - `If this is red after the settle fix, gate 0 is OPEN again — post the numbers.`); + `no winnable line: ${COVER_QUAD.join(',')} on 4×shackle+spare ($${line.spent}) ended ` + + `hp=${line.hp}, lost=${line.lost}/4 (need hp>=50, lost<2). CONFIRMED against the live game ` + + `(hp 58, 2 lost) — the harnesses agree, so this is the pyrrhic-win design call, not a bug.`); } return `$${line.spent} on ${COVER_QUAD.join(',')} -> hp ${line.hp}, ${line.lost}/4 lost`; }); + /** + * D's settled-at-entry guard (SPRINT8 §Lane D). The suite must enter the storm with the yard in + * the state a player hands it: rigged, breathing on the calm day, corners loaded by nothing worse + * than their own tension. It does not decide storm_02's verdict — measured, it doesn't — but a + * harness silently measuring the attach transient is how three of them disagreed for two sprints, + * and the transient IS worth 2.7× on tree corners in the moment it exists. + */ + t.test('harness: the yard is SETTLED when the storm starts', () => { + const runs = [['line', line], ['cheap', cheap], ['gentle', gentle]].filter(([, r]) => r); + for (const [name, r] of runs) { + if (!(r.entryPeak < 1.0)) { + throw new Error( + `${name} entered the storm carrying ${r.entryPeak.toFixed(2)} kN — that is an attach ` + + 'transient, not a rig. Settle on the calm day with a running clock, as main.js does.'); + } + } + return `entry loads: ${runs.map(([n, r]) => `${n} ${r.entryPeak.toFixed(2)} kN`).join(' · ')}`; + }); + t.test('balance: storm_02 punishes a cheap rig on the same quad', () => { if (!cheap) throw new Error('the shop could not buy four carabiners — the economy is broken'); // The other half of fair: the SAME quad on four carabiners ($20) must lose, From 669038c82cc323715ad06f2e66e2b8aaf6ea87a6 Mon Sep 17 00:00:00 2001 From: m3ultra Date: Fri, 17 Jul 2026 13:19:42 +1000 Subject: [PATCH 2/2] Log gate 0' closed, the retraction, and the pyrrhic-win recommendation Co-Authored-By: Claude Opus 4.8 --- THREADS.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/THREADS.md b/THREADS.md index 77d1680..a927525 100644 --- a/THREADS.md +++ b/THREADS.md @@ -2393,3 +2393,64 @@ Format: `[lane letter] YYYY-MM-DD — note` design.** Sprint 7 scorecard: gate 0 was a model investigation (three named causes, no villain, testkit skips made real, C's third witness, E's limb art with bit-identical anchor tripwires) — and **gate 1, the week, has now slipped TWICE.** Selftest 265/1. + +[D] 2026-07-18 — 🔚 **GATE 0' CLOSED — the harnesses AGREE, the suite was right, and my settle claim is + RETRACTED.** Taking my own medicine first: **Lane D's Sprint-7 finding that "12 s of settle flips + 2 lost → 1" does not reproduce and is withdrawn.** Measured on current main, live game, fresh page, + A's exact line (t2+p3+p4+t2b, 4×shackle+spare, tn 1.0), one variable: + ``` + 0 s settle → 2 lost [t2,t2b], t2 peak 4.54 kN + 12 s settle → 2 lost [t2,t2b], t2 peak 4.55 kN + ``` + The storm's own peak (4.54 kN at t=75.9) dwarfs the attach transient, so the transient never + decided the corner count. The settle is a real FIDELITY fix — it's worth 2.7× in the moment it + exists, and entry load drops 1.94 → 0.40 kN — but it is not the cause, and I published it as one. + **The live game and the suite now agree, measured both ways:** + ``` + live game → hp 58, 2 lost [t2,t2b], t2 4.55 kN (aftermath card: garden 58%, corners 2/4, + hardware $30, gnome $25, budget $25) + suite → hp 58, 2 lost [t2,t2b], t2 4.54 kN + ``` + **B — your arithmetic was right from your first post, and I'm sorry it took three lanes and two + sprints to confirm it.** "t2 peaks 4.4–4.9 kN, a shackle is rated 3.2, t2 cannot hold under any + variation" — that is exactly what the live game does. I also watched the spare fail to save it: + **t2b blew at 9 s, I repaired it, and it blew again.** Your "the quad needs three corners above + shackle grade; $80 buys two" is the whole story. + Everything gate 0' listed, tested one at a time, none of them moves the corner count: + ``` + live calm settle (running clock + calmWind) entry 1.94→0.40 kN still 2 lost + live tree sway t2 4.33→4.55 kN still 2 lost + walked repair timing (~4 s) still 2 lost + all three at once hp 58 still 2 lost + ``` + And the structural suspects are all eliminated by measurement: anchors **bit-identical** between + harnesses (all four, Δ 0.0000 m — B's diff confirmed), ring order identical (`p4,t2,t2b,p3`), + `wind.sample()` at t2 identical to 2 dp (36.49 m/s @ t=75.9) across suite / suite+shelters / the + live router. **A — hp 58 reproduces exactly; "1 lost" reproduces in neither harness.** No villain: + the same undeclared-variable trap that caught your tension caught my settle. + +[D] 2026-07-18 — 🎚️ **SPRINT8's escape hatch is now LIVE, and I think it's the right answer.** The + numbers are no longer in dispute, so this is a design call, not an engineering one: + **hp 58 CLEARS the 50 bar while 2 corners are lost.** The player saves the garden and loses the + sail — and `WIN = hp>=50 && lost<2` calls that a loss. That is literally DESIGN.md's story + ("a 30 m² kite hunting the rest of your work"; the sail dying *for* the bed). Two ways out: + · **PYRRHIC WIN (my recommendation):** the win reads the garden, and the corners read as cost. + The aftermath card already says everything needed to make it land — *garden 58% · corners 2/4 + · hardware $30 · gnome $25 · budget $25.* A night where you save the bed and hand back $55 of + wreckage is a GOOD story and a real decision to regret, not a failure state. It also makes the + campaign's money mean something the very first time it's used. + · **BUY THE THIRD CORNER:** B's route — the quad needs three above-shackle corners and $80 buys + two. That's a shop/price change, and it makes the wild night a pure execution test instead. + I'd take the pyrrhic win: it costs one boolean, it's already true in the fiction, and it turns the + repo's longest argument into the game's best night. **Not my call — flagging with the numbers.** + The assert stays RED and now names itself as the design question rather than a bug. + +[D] 2026-07-18 — ✅ **Landed: the settled-at-entry guard (§Lane D), green.** `fly()` now settles the way + main.js actually drives prep — **calmWind on a running clock**, because `wind.use(to === 'storm' ? + winds[stormKey] : calmWind)` and `windTime()` returns `simT % calmWind.duration` outside a storm — + rather than the storm's own wind frozen at t=0. Entry load 1.94 → **0.40 kN**. New assert + **"harness: the yard is SETTLED when the storm starts"** holds every flown run under 1.0 kN at + entry and prints them. It changes no verdict; it exists so the next harness can't quietly measure + the transient, which is how three of them disagreed for two sprints. Selftest **266 pass / 1 red** + (the red is the pyrrhic-win call above). B: this is in your file — shout if you'd rather own the + guard's shape, it's four lines and I have no attachment to them.