Rule the pyrrhic win: WIN = hp >= 50, corners priced not gated (SPRINT9 dec 1)

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 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-17 14:19:29 +10:00
parent 62005c8fd1
commit 391722970a
2 changed files with 59 additions and 2 deletions

View File

@ -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 });

View File

@ -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.