From 391722970a024e48da1cc1a8fe108d6fe6070c7a Mon Sep 17 00:00:00 2001 From: m3ultra Date: Fri, 17 Jul 2026 14:19:29 +1000 Subject: [PATCH] Rule the pyrrhic win: WIN = hp >= 50, corners priced not gated (SPRINT9 dec 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The garden is the client's. The sail is yours. Saving the bed with a rig that tore itself apart is the job done expensively — not a failure. DESIGN.md doesn't call a cascade a loss, it calls it "a firework you paid for", and *paid for* is the point: the aftermath already bills the broken hardware, the collateral, and a week's fee you didn't earn cleanly. Gating the win on corners bills you twice for one night and then lies about it — the same species as the verdict that used to tell a 4/4 hold they'd skimped. B and D arrived here independently with the numbers, and B checked what it does NOT break: cheap rigs still lose (4x carabiner -> hp 38), rigs that miss the bed still lose (hp 36), $80 still cannot buy immunity. C's exhaustive sweep found the only winnable $80 line on the wild night lands exactly here — hp 52, 1 lost. The pyrrhic ending gets its own verdict, and it had to be checked FIRST: `lost >= 2` was unconditionally a cascade-and-a-loss, and under the new rule it can be the best night the game has. Two asserts pin both halves — garden saved + sail destroyed is a win that still names the weakest link; the same wreck WITHOUT the garden is still a plain cascade, because the garden is the whole job. Selftest 276 pass / 0 fail (Lane A 32 -> 34). Co-Authored-By: Claude Opus 4.8 --- web/world/js/main.js | 32 ++++++++++++++++++++++++++++++-- web/world/js/tests/a.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/web/world/js/main.js b/web/world/js/main.js index b18c052..0a747bc 100644 --- a/web/world/js/main.js +++ b/web/world/js/main.js @@ -146,7 +146,17 @@ export function verdictFor({ hp, lost, win, dmg, pondPeak, pondDumped }) { const named = worst ? `${worst.hw.name} at ${worst.anchorId.toUpperCase()}` : ''; const hailKilled = dmg.hail > dmg.rain; - if (lost.length >= 2) { + // SPRINT9 decision 1. This case had to come FIRST because it did not used to + // exist: `lost >= 2` was unconditionally a cascade-and-a-loss, and under the + // new rule it can be the best night the game has. A sail that tore itself + // apart while the bed came through is DESIGN.md's actual story — the firework + // you paid for — and the aftermath's hardware bill is where that payment goes. + if (win && lost.length >= 2) { + return { mode: 'pyrrhic', + verdict: `THE GARDEN MADE IT. The sail didn't — the ${named} went first and took ` + + `${lost.length - 1} more with it. That is what the sail was for.` }; + } + if (!win && lost.length >= 2) { return { mode: 'cascade', verdict: `THE SAIL LOST. The ${named} went first — and took ${lost.length - 1} more with it.` }; } @@ -533,7 +543,25 @@ export async function boot(opts = {}) { if (lost.length >= 2) collateral.push({ what: 'garden gnome', cost: world.gnome.collateralValue }); const s = rigging.summary; const hp = garden.hp; - const win = hp >= 50 && lost.length < 2; + /** + * SPRINT9 decision 1 — Lane A's ruling on the pyrrhic win. It was + * `hp >= 50 && lost.length < 2`; the corner clause is gone. + * + * **The garden is the client's. The sail is yours.** Saving the bed with a + * rig that tore itself apart is the job done expensively — not a failure. + * DESIGN.md doesn't call a cascade a loss, it calls it "a firework you paid + * for", and *paid for* is the whole point: the aftermath already bills you + * for the broken hardware, the collateral, and a week's fee you didn't earn + * cleanly. Gating the win on corners bills you twice for the same night and + * then lies about it — which is the same species as the verdict that used to + * tell a 4/4 hold they'd skimped. + * + * B and D both arrived here independently with the numbers, and B checked + * what it does NOT break: cheap rigs still lose (4× carabiner → hp 38), rigs + * that miss the bed still lose (hp 36), and $80 still cannot buy immunity. + * The corners are priced, not gated. + */ + const win = hp >= 50; const dmg = garden.damage; const { verdict, mode } = verdictFor({ hp, lost, win, dmg, pondPeak, pondDumped }); diff --git a/web/world/js/tests/a.test.js b/web/world/js/tests/a.test.js index c829888..d25119b 100644 --- a/web/world/js/tests/a.test.js +++ b/web/world/js/tests/a.test.js @@ -194,6 +194,35 @@ export default async function run(t) { `verdict should credit the corners that held: "${heldAll.verdict}"`); }); + t.test('the pyrrhic win: a sail that dies saving the garden is a WIN', () => { + // SPRINT9 decision 1, Lane A's ruling. The garden is the client's; the sail + // is yours. This night used to score as a flat LOSS, which was the repo's + // longest-running dead end — C measured the only $80 line on the wild night + // and it lands here. + const carabiner = { anchorId: 'p1', hw: { name: 'carabiner', rating: 1200, cost: 5 } }; + const shackle = { anchorId: 'p3', hw: { name: 'shackle', rating: 3200, cost: 15 } }; + const v = verdictFor({ + hp: 52, lost: [shackle, carabiner], win: true, + dmg: { hail: 40, rain: 8 }, pondPeak: 0, pondDumped: 0, + }); + assertEq(v.mode, 'pyrrhic', 'garden saved + sail destroyed is its own ending, not a cascade'); + assert(/GARDEN MADE IT/.test(v.verdict), `it is a win and must read as one: "${v.verdict}"`); + assert(/carabiner at P1/.test(v.verdict), 'and still names the weakest link that went first'); + }); + + t.test('the same wreck WITHOUT the garden is still a plain cascade', () => { + // The other half of the ruling: corners are priced, not gated — but the + // garden is still the whole job. Lose it and two corners and you lost. + const carabiner = { anchorId: 'p1', hw: { name: 'carabiner', rating: 1200, cost: 5 } }; + const shackle = { anchorId: 'p3', hw: { name: 'shackle', rating: 3200, cost: 15 } }; + const v = verdictFor({ + hp: 20, lost: [shackle, carabiner], win: false, + dmg: { hail: 70, rain: 10 }, pondPeak: 0, pondDumped: 0, + }); + assertEq(v.mode, 'cascade'); + assert(/SAIL LOST/.test(v.verdict), `no garden, no win: "${v.verdict}"`); + }); + t.test('verdict names the weakest link that actually let go', () => { // "…the shackle. I knew about the shackle." The whole point is that the // player recognises the corner they gambled on.