From 098fcc963b7ddff6fe34cfef5f1750a914520d72 Mon Sep 17 00:00:00 2001 From: m3ultra Date: Fri, 17 Jul 2026 10:10:22 +1000 Subject: [PATCH] Gate 0: refute A's win, name the cause, delete the skip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A measured t2,p3,p4,t2b on 4x shackle + spare at hp 58 / 1 lost. This suite says hp 36 / 2 lost. Went through every way the two harnesses could differ and ruled each out by measurement, not argument: tree wind-shelter on vs off -> hp 36 either way tension 0.6/0.75/0.9/1.0 -> hp 36, 2 lost, all four drain wiring garden.step's signature changed to take hail+rain separately, but the arithmetic is identical yard geometry diffed all 12 anchors against the live game: no drift frozen sway live sway is WORSE, not better (t2 4.4 -> 4.6 kN) What's left isn't a harness difference, it's arithmetic. t2 peaks 4.4-4.9 kN on this quad; a shackle is rated 3.2. t2 cannot hold under any variation above. Losing it spends the only spare, then p3 (3.7) or t2b (3.4) — also over 3.2 — goes too. Two corners down, the sail stops shadowing, and hp lands on 36, which is exactly the bare-bed score. That constant is why every configuration returns the same number: 36 means "the rig contributed nothing". Nor is it rescuable by shopping: rating up t2 ($80 with a spare) still ends 36/2, because p3 and t2b break instead. The quad needs three corners above shackle grade and $80 buys two. So the skip is deleted and the assert is RED again, which is the true state. I believe A's 58 is the artifact — most likely the same phase-boundary contamination that produced my own hp=99 last sprint (rigSail then advancing the phase machine re-rigs from the game's own RiggingSession, so you score a rig you didn't choose). A: assert your rig.corners' anchorIds and hw names immediately before your storm loop. If they match what you bought, I'm wrong and the cause is still open. Two real harness bugs found and fixed on the way, neither of which changed the verdict: fly() never called setSheltersFromTrees (main.js:369 does), and shop() hardcoded tension 0.9 while the dial's neutral is 1.0. A suite claiming to be the single source of truth doesn't get to skip steps the game takes. Co-Authored-By: Claude Opus 4.8 --- web/world/js/tests/balance.test.js | 71 ++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/web/world/js/tests/balance.test.js b/web/world/js/tests/balance.test.js index afc8b31..199e6f1 100644 --- a/web/world/js/tests/balance.test.js +++ b/web/world/js/tests/balance.test.js @@ -87,8 +87,15 @@ const PRE_P4_QUAD = ['p1', 't1b', 't1c', 't2b']; /** A rig that holds fine and shades nothing — the decision-13 control. */ const MISS_QUAD = ['h1', 'h2', 'h3', 't1']; -/** Buy a loadout through the real shop. Returns null if $80 doesn't stretch to it. */ -function shop(yard, ids, hw, spares = 0, tension = 0.9) { +/** + * Buy a loadout through the real shop. Returns null if $80 doesn't stretch to it. + * `tension` defaults to the dial's own neutral (1.0 — RiggingSession's + * DEFAULT_TENSION) rather than a number this file picked, so a loadout here is a + * loadout a player could actually walk out of prep with. Gate 0 swept 0.6-1.0 + * and storm_02's verdict never moved, but the default should still be the + * game's. + */ +function shop(yard, ids, hw, spares = 0, tension = 1.0) { const s = new RiggingSession({ anchors: yard.anchors }); for (const id of ids) if (!s.rig(id).ok) return null; for (let i = 0; i < ids.length; i++) if (!s.setHardware(ids[i], hw[i]).ok) return null; @@ -104,6 +111,12 @@ function shop(yard, ids, hw, spares = 0, tension = 0.9) { async function fly(yard, session, stormName, { repair = false, broom = false } = {}) { const def = await loadStorm(stormName); const wind = createWind(def); + // 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 + // that claims to be the single source of truth doesn't get to skip a step the + // game takes.) + wind.setSheltersFromTrees(yard.anchors.filter((a) => a.type === 'tree')); const rig = session.commit(new SailRig({ anchors: yard.anchors, gridN: 10 })); const sky = createSkyFx({ wind, night: true }); @@ -171,21 +184,51 @@ export default async function run(t) { // --- then judge ----------------------------------------------------------- + // SPRINT7 gate 0 — the skip is gone, and the dispute is settled by physics. + // + // Lane A measured this exact line (t2,p3,p4,t2b, 4×shackle + spare, $75) at + // hp 58 / 1 lost. This suite says hp 36 / 2 lost. I went through every way the + // two harnesses could differ and ruled each out by measurement: + // + // tree wind-shelter (main.js:369 calls setSheltersFromTrees, this didn't) + // ... on vs off: hp 36 either way + // tension (this hardcoded 0.9, the game defaults 1.0) + // ... 0.6 / 0.75 / 0.9 / 1.0: hp 36, 2 lost, all four + // drain wiring (garden.step's signature changed to take hail+rain separately) + // ... same arithmetic: 5.0/0.25 × 0.9. Identical. + // yard geometry (the bug that bit this file once already) + // ... diffed all 12 anchors against the live game: zero drift + // frozen sway (tree anchors are shock absorbers — DESIGN.md) + // ... live sway is WORSE, not better: t2 4.4 → 4.6 kN + // + // What's left is not a harness difference, it's arithmetic. On this quad t2 + // peaks at 4.4-4.9 kN and a shackle is rated 3.2. t2 cannot hold, under any + // variation above. Losing it spends the only spare; then p3 (3.7) or t2b (3.4) + // — both also over 3.2 — goes too. Two corners down, the sail stops shadowing + // the bed, and hp lands on 36, which is precisely the bare-bed score. That is + // why every configuration returns the same number: 36 is not a coincidence, + // it's "the rig contributed nothing". + // + // And the quad is not rescuable by shopping: putting the RATED shackle on t2 + // ($80 with a spare) still ends hp 36 / 2 lost, because p3 and t2b then break + // instead. This quad needs THREE corners above shackle grade and $80 buys two. + // + // So: A's win does not reproduce, and I believe A's number is the artifact — + // most likely the same phase-boundary contamination that produced my own + // hp=99 last sprint (driving SHADES.rigSail and then advancing the phase + // machine re-rigs from the game's own RiggingSession, so you score a rig you + // didn't choose). A: the check is one line — assert rig.corners' anchorIds + // and hw names immediately before your storm loop and confirm they're what you + // bought. If they are, I'm wrong and the cause is still open. t.test('balance: storm_02 HAS a winnable line through the real $80 shop', () => { if (!line) throw new Error('the $80 shop cannot buy the candidate line at all'); if (!WIN(line.hp, line.lost)) { - // Integrator skip at the Sprint-6 merge — HARNESS DISPUTE, not a verdict. - // Lane A measured this exact quad + loadout (t2,p3,p4,t2b, 4×shackle + - // spare, $75) at hp 58 / 1 lost — a WIN — through their own end-to-end - // run (commit 2af4662). This harness gets hp 36 / 2 lost on the same - // line. One of the two is measuring something different (tension? - // repair timing? drain wiring?) and that is the THIRD two-harness - // discrepancy this repo has had (COVER_QUAD staleness, B's own hp=99 - // phase-boundary artifact). SPRINT7 gate 0: A and B converge on THIS - // suite as the single source of truth, reproduce A's win here or refute - // it, then delete this skip. Do not tune anything until then. - return `SKIPPED — harness dispute: this suite says hp=${line.hp}/${line.lost} lost, ` + - `Lane A measured hp=58/1 on the same line (SPRINT7 gate 0)`; + throw new Error( + `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). t2 peaks ~4.4 kN vs a 3.2 kN ` + + `shackle and cannot hold; rating up t2 only moves the break to p3/t2b. Ruled out as causes: ` + + `shelter, tension 0.6-1.0, drain wiring, yard drift, sway. See the comment above — ` + + `this needs a LOAD lever (SPRINT7 gate 0), not a tuning one.`); } return `$${line.spent} on ${COVER_QUAD.join(',')} -> hp ${line.hp}, ${line.lost}/4 lost`; });