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>
This commit is contained in:
parent
a1b7d468a4
commit
75d5f9a4bf
44
THREADS.md
44
THREADS.md
@ -8205,3 +8205,47 @@ anchors are your GLB), but the tooling is now waiting, not TODO.
|
|||||||
the S16 law pins move WITH the ruling, which is them working), hud already reads NIGHTS.length
|
the S16 law pins move WITH the ruling, which is them working), hud already reads NIGHTS.length
|
||||||
so the papers tell the truth by construction. SPRINT17 gate 0 lands it; arc 2 (THE BOOK OF
|
so the papers tell the truth by construction. SPRINT17 gate 0 lands it; arc 2 (THE BOOK OF
|
||||||
CLIENTS) begins in the same sprint. The play half of gate 6 stands as ever.
|
CLIENTS) begins in the same sprint. The play half of gate 6 stands as ever.
|
||||||
|
|
||||||
|
[D] 2026-07-21 — ⚖️→🔧 **GATE 0.1 LANDED: THE SEVEN-NIGHT WEEK IS WIRED. The wildnight flies
|
||||||
|
again, night 5, over the backyard.** John's ruling, in code, fast — A, rebase away. The
|
||||||
|
ladder as wired:
|
||||||
|
1 gentle × backyard — the tutorial
|
||||||
|
2 southerly × backyard — hail arrives
|
||||||
|
3 earlybuster × site_02 — the corner block's debut (new yard)
|
||||||
|
4 southerly × swing_lawn — the swing lawn's debut (new yard, survived storm)
|
||||||
|
5 WILDNIGHT × backyard — RESTORED. the marquee storm, the pyrrhic-win canon
|
||||||
|
6 icenight × backyard — the designed loss (gardenBeyondSaving rode the night to slot 6)
|
||||||
|
7 soaker × site_02 — the fabric bet closes the week
|
||||||
|
**The slot, and why it's the law and not taste:** the wildnight flies over a yard the player
|
||||||
|
knows — it always did. Backyard at night 5 is a yard rigged three times by then, so the storm
|
||||||
|
is the only new variable (one-variable law at the marquee slot); the pinned separation lives
|
||||||
|
in backyard_01's own site data, so the pin flies unchanged; and 30.0 directly before 28.5
|
||||||
|
restores the icenight's pre-S16 opening line "less wind than last night" as a TRUE sentence —
|
||||||
|
that brief moved with the facts twice now (S16 out, S17 back), and a new d.test pin
|
||||||
|
(wildIdx === iceIdx−1) makes any future re-order re-word it in the same commit or go red.
|
||||||
|
**The restored night's brief** (the Hendersons' voice, selling the pyrrhic stake — "the garden
|
||||||
|
made it; the sail didn't" promised as a WIN before a dollar is spent): *"The Hendersons rang
|
||||||
|
from Cairns — the forecast made the news up there. This is the big one, the night the whole
|
||||||
|
week was pointing at. Their words: they'd rather fly home to a wrecked sail over a living bed
|
||||||
|
than a tidy yard and a dead one. Spend the sail if you have to. Save the garden."*
|
||||||
|
**The pins moved with the ruling — that's them working:** d.test's four S16 law pins are
|
||||||
|
retitled "week law (S16, holds at seven)" and every assert body survived UNTOUCHED (they were
|
||||||
|
slot-free rules, which was the point); one NEW pin carries the ruling itself (wildnight IN the
|
||||||
|
week, over backyard_01, directly before the icenight — re-evicting it is a John question, not
|
||||||
|
a re-order). a.test's week suite re-pinned to the seven-shape: NIGHTS.length 7, wildnight at
|
||||||
|
slot 5, icenight's flag at slot 6 (neither new nor final — its dawn breaks book warranty on
|
||||||
|
the soaker's morning), broke-on-final-night now walks NIGHTS.length so it TRACKS the final
|
||||||
|
night by construction, the flagged-night rep test finds the flag instead of hardcoding slot 4.
|
||||||
|
**A, your files, my edits, flagged (revert-and-tell-me standard, same as S16):** a.test week
|
||||||
|
asserts as above, and — the one real ruling I made in your territory — **gradeFor's clean bar
|
||||||
|
is now DERIVED** (held >= savable nights, i.e. NIGHTS minus beyondSaving = 6). My own S16
|
||||||
|
filing said a seven-night week needs gradeFor re-ruled in the same commit as the ladder; this
|
||||||
|
is that commit, and held>=5 'clean' would have printed THE WEEK HELD over a savable garden
|
||||||
|
that died. The scraped/solvent boundary (>=3) is untouched — moving it is a wording-taste
|
||||||
|
call and it's yours; there's no lie in leaving it.
|
||||||
|
**Verified:** selftest **474/0/0** (473 baseline + the new ruling pin — the count moved
|
||||||
|
exactly by what was added, nothing else). Verified live on :8827, my own eyes: splash "Seven
|
||||||
|
nights, seven jobs, one wallet", job sheet NIGHT 1 OF 7 with seven pips, cold-rigged night 1
|
||||||
|
on carabiners, invoice MORNING · NIGHT 1 OF 7, night 2 sheet NIGHT 2 OF 7 / ●◆····· /
|
||||||
|
southerly brief — hud read NIGHTS.length throughout, zero hud edits needed, as designed.
|
||||||
|
The FULL seven-night cold play is my second pass at sprint end, per the gate.
|
||||||
|
|||||||
@ -215,39 +215,45 @@ export default async function run(t) {
|
|||||||
|
|
||||||
// --- the week (SPRINT8 gate 1) -------------------------------------------
|
// --- the week (SPRINT8 gate 1) -------------------------------------------
|
||||||
|
|
||||||
t.test('the week is six escalating nights, each a storm and a site', () => {
|
t.test('the week is seven escalating nights, each a storm and a site', () => {
|
||||||
// SPRINT16 gate 4 (D's wiring): five → six. The swing lawn debuts night 4
|
// SPRINT16 gate 4 (D's wiring): five → six. SPRINT17 gate 0.1 (D again,
|
||||||
// under Tuesday's southerly; the soaker closes the week over the corner
|
// wiring JOHN'S RULING 2026-07-21): six → SEVEN — the wildnight restored
|
||||||
// block. The deeper pins — one-variable law, survived-storm debut, the
|
// at night 5, between the swing lawn and the icenight. The swing lawn
|
||||||
// measured soaker pairing — live in d.test.js next to the wiring's owner.
|
// debuts night 4 under Tuesday's southerly; the soaker still closes the
|
||||||
assertEq(NIGHTS.length, 6);
|
// week over the corner block. The deeper pins — one-variable law,
|
||||||
|
// survived-storm debut, the measured soaker pairing, the wildnight's
|
||||||
|
// known-yard slot — live in d.test.js next to the wiring's owner.
|
||||||
|
assertEq(NIGHTS.length, 7);
|
||||||
// SPRINT10: a night is {storm, site} now. nightAt() normalises either shape.
|
// SPRINT10: a night is {storm, site} now. nightAt() normalises either shape.
|
||||||
assertEq(nightAt(0).storm, 'storm_01_gentle', 'night one must be the one that cannot hurt you');
|
assertEq(nightAt(0).storm, 'storm_01_gentle', 'night one must be the one that cannot hurt you');
|
||||||
assertEq(nightAt(3).storm, 'storm_03_southerly', 'night four repeats the southerly — the yard is the only new thing');
|
assertEq(nightAt(3).storm, 'storm_03_southerly', 'night four repeats the southerly — the yard is the only new thing');
|
||||||
assertEq(nightAt(3).site, 'site_03_swing_lawn', 'night four is the swing lawn');
|
assertEq(nightAt(3).site, 'site_03_swing_lawn', 'night four is the swing lawn');
|
||||||
assertEq(nightAt(4).storm, 'storm_02b_icenight', 'night five is still the ice night');
|
assertEq(nightAt(4).storm, 'storm_02_wildnight', "night five is the wildnight — restored, John's seven-night ruling");
|
||||||
assertEq(nightAt(5).storm, 'storm_06_soaker', 'night six is the fabric bet');
|
assertEq(nightAt(4).site, 'backyard_01', 'and it flies the backyard — the yard its separation is pinned to');
|
||||||
assertEq(nightAt(5).site, 'site_02_corner_block', "and it flies over the corner block — C's measured pairing");
|
assertEq(nightAt(5).storm, 'storm_02b_icenight', 'night six is the ice night');
|
||||||
|
assertEq(nightAt(6).storm, 'storm_06_soaker', 'night seven is the fabric bet');
|
||||||
|
assertEq(nightAt(6).site, 'site_02_corner_block', "and it flies over the corner block — C's measured pairing");
|
||||||
assertEq(nightAt(2).site, 'site_02_corner_block', 'night three moves to the corner block');
|
assertEq(nightAt(2).site, 'site_02_corner_block', 'night three moves to the corner block');
|
||||||
assertEq(nightAt(0).site, 'backyard_01', 'the retainer nights are the backyard');
|
assertEq(nightAt(0).site, 'backyard_01', 'the retainer nights are the backyard');
|
||||||
// SPRINT13 gate 1.4: the unsavable-garden flag is night 5's alone, and a
|
// SPRINT13 gate 1.4: the unsavable-garden flag is the icenight's alone, and
|
||||||
// night without it is presumed savable — new nights can't inherit the excuse.
|
// a night without it is presumed savable — new nights can't inherit the
|
||||||
assertEq(nightAt(4).gardenBeyondSaving, true, 'night 5 is beyond saving BY DESIGN — canon, ruled');
|
// excuse. The flag rides the NIGHT, not the slot: it moved 5 → 6 with the ladder.
|
||||||
for (const i of [0, 1, 2, 3, 5]) assertEq(nightAt(i).gardenBeyondSaving, false, `night ${i + 1} is savable`);
|
assertEq(nightAt(5).gardenBeyondSaving, true, 'night 6 is beyond saving BY DESIGN — canon, ruled');
|
||||||
|
for (const i of [0, 1, 2, 3, 4, 6]) assertEq(nightAt(i).gardenBeyondSaving, false, `night ${i + 1} is savable`);
|
||||||
const w = createWeek();
|
const w = createWeek();
|
||||||
assertEq(w.night, 1); assertEq(w.bank, 80);
|
assertEq(w.night, 1); assertEq(w.bank, 80);
|
||||||
assertEq(w.site, 'backyard_01', 'week.site tracks the ladder');
|
assertEq(w.site, 'backyard_01', 'week.site tracks the ladder');
|
||||||
assert(!w.isFinalNight);
|
assert(!w.isFinalNight);
|
||||||
for (let i = 0; i < 2; i++) w.advance();
|
for (let i = 0; i < 2; i++) w.advance();
|
||||||
assertEq(w.site, 'site_02_corner_block', 'and reaches the corner block on night three');
|
assertEq(w.site, 'site_02_corner_block', 'and reaches the corner block on night three');
|
||||||
for (let i = 0; i < 2; i++) w.advance();
|
for (let i = 0; i < 3; i++) w.advance();
|
||||||
assertEq(w.night, 5);
|
|
||||||
assert(!w.isFinalNight, 'the ice night is no longer the last — its dawn breaks have a morning to book on now');
|
|
||||||
w.advance();
|
|
||||||
assertEq(w.night, 6);
|
assertEq(w.night, 6);
|
||||||
assert(w.isFinalNight, 'night six is the last');
|
assert(!w.isFinalNight, 'the ice night is still not the last — its dawn breaks keep their morning to book on');
|
||||||
w.advance();
|
w.advance();
|
||||||
assertEq(w.night, 6, 'the ladder does not walk off its own end');
|
assertEq(w.night, 7);
|
||||||
|
assert(w.isFinalNight, 'night seven is the last');
|
||||||
|
w.advance();
|
||||||
|
assertEq(w.night, 7, 'the ladder does not walk off its own end');
|
||||||
});
|
});
|
||||||
|
|
||||||
t.test('money persists across nights, and the bank is next night\'s shop', () => {
|
t.test('money persists across nights, and the bank is next night\'s shop', () => {
|
||||||
@ -311,7 +317,7 @@ export default async function run(t) {
|
|||||||
assertEq(new Set(clients).size, 3, 'three clients: the retainer, the one-off, and the swing lawn');
|
assertEq(new Set(clients).size, 3, 'three clients: the retainer, the one-off, and the swing lawn');
|
||||||
assert(clients[2] !== clients[0], 'night 3 is somebody else');
|
assert(clients[2] !== clients[0], 'night 3 is somebody else');
|
||||||
assertEq(nightAt(2).site, 'site_02_corner_block', "and it's their corner block");
|
assertEq(nightAt(2).site, 'site_02_corner_block', "and it's their corner block");
|
||||||
assertEq(clients[5], clients[2], 'night six is the corner block client again — a return booking, not a stranger');
|
assertEq(clients[NIGHTS.length - 1], clients[2], 'the closer is the corner block client again — a return booking, not a stranger');
|
||||||
});
|
});
|
||||||
|
|
||||||
t.test('an unbriefed night is a job with no letterhead, not a crash', () => {
|
t.test('an unbriefed night is a job with no letterhead, not a crash', () => {
|
||||||
@ -381,10 +387,15 @@ export default async function run(t) {
|
|||||||
// WEEK HELD — everything's still where you put it" over four dead gardens.
|
// WEEK HELD — everything's still where you put it" over four dead gardens.
|
||||||
// Outlasting the week is not surviving it, and the end card is the last
|
// Outlasting the week is not surviving it, and the end card is the last
|
||||||
// thing this game says to anyone.
|
// thing this game says to anyone.
|
||||||
assertEq(gradeFor(5), 'clean', 'five held is the only clean week');
|
// SPRINT17 gate 0.1 (D, the same-commit demand from the S16 filing): the
|
||||||
|
// clean bar is derived — every SAVABLE garden held. Seven nights minus the
|
||||||
|
// icenight's designed loss is 6; held>=5 'clean' would have printed THE
|
||||||
|
// WEEK HELD over a savable garden that died.
|
||||||
|
assertEq(gradeFor(6), 'clean', 'six held — every savable garden — is the only clean week');
|
||||||
|
assertEq(gradeFor(5), 'scraped', 'five of six savable is no longer clean — the bar moved with the ladder');
|
||||||
assertEq(gradeFor(4), 'scraped');
|
assertEq(gradeFor(4), 'scraped');
|
||||||
assertEq(gradeFor(3), 'scraped');
|
assertEq(gradeFor(3), 'scraped');
|
||||||
assertEq(gradeFor(1), 'solvent', 'one garden out of five is not a triumph');
|
assertEq(gradeFor(1), 'solvent', 'one garden out of six is not a triumph');
|
||||||
assertEq(gradeFor(0), 'solvent');
|
assertEq(gradeFor(0), 'solvent');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -398,17 +409,19 @@ export default async function run(t) {
|
|||||||
assertEq(s.outcome, 'gameover', 'you cannot rig four corners, so the week is over');
|
assertEq(s.outcome, 'gameover', 'you cannot rig four corners, so the week is over');
|
||||||
|
|
||||||
// On the last night there is no next shop to be unable to afford, so being
|
// On the last night there is no next shop to be unable to afford, so being
|
||||||
// broke is just being broke — the week still finished. SPRINT16: the last
|
// broke is just being broke — the week still finished. SPRINT16 moved the
|
||||||
// night is SIX now, and night five going broke is a real game over again —
|
// last night to six; SPRINT17's seven-night ruling moves it again — the
|
||||||
// both directions pinned so the re-ordering can't quietly move the rule.
|
// walk below reads NIGHTS.length, so this pin TRACKS the final night by
|
||||||
|
// construction, and the second-to-last-night control stays a real game
|
||||||
|
// over — both directions pinned so a re-ordering can't quietly move the rule.
|
||||||
const w2 = createWeek();
|
const w2 = createWeek();
|
||||||
for (let i = 0; i < NIGHTS.length - 1; i++) w2.advance();
|
for (let i = 0; i < NIGHTS.length - 1; i++) w2.advance();
|
||||||
assert(w2.isFinalNight, 'precondition: the walk reached the final night');
|
assert(w2.isFinalNight, 'precondition: the walk reached the final night');
|
||||||
assertEq(w2.settle(ruin, def, 80).outcome, 'win', 'the final night always ends the week, not the run');
|
assertEq(w2.settle(ruin, def, 80).outcome, 'win', 'the final night always ends the week, not the run');
|
||||||
const w3 = createWeek();
|
const w3 = createWeek();
|
||||||
for (let i = 0; i < 4; i++) w3.advance();
|
for (let i = 0; i < NIGHTS.length - 2; i++) w3.advance();
|
||||||
assertEq(w3.settle(ruin, def, 80).outcome, 'gameover',
|
assertEq(w3.settle(ruin, def, 80).outcome, 'gameover',
|
||||||
'night five is not the final night any more — going broke there ends the run');
|
'the second-to-last night is not the final night — going broke there ends the run');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gate 2 acceptance: both sites load from data, and the corner block is not
|
// Gate 2 acceptance: both sites load from data, and the corner block is not
|
||||||
@ -678,16 +691,20 @@ export default async function run(t) {
|
|||||||
|
|
||||||
// The pin that matters most: same rig, opposite garden — identical movement.
|
// The pin that matters most: same rig, opposite garden — identical movement.
|
||||||
// hp and win are absent from the movement BY CONSTRUCTION, which is what
|
// hp and win are absent from the movement BY CONSTRUCTION, which is what
|
||||||
// makes night 5's designed loss free without an exemption to go stale.
|
// makes the icenight's designed loss free without an exemption to go stale.
|
||||||
const a1 = createWeek().settle(cleanRun({ hp: 100, win: true }), s16def, 0);
|
const a1 = createWeek().settle(cleanRun({ hp: 100, win: true }), s16def, 0);
|
||||||
const b1 = createWeek().settle(cleanRun({ hp: 0, win: false }), s16def, 0);
|
const b1 = createWeek().settle(cleanRun({ hp: 0, win: false }), s16def, 0);
|
||||||
assertEq(a1.repDelta, b1.repDelta, 'rep never reads the garden');
|
assertEq(a1.repDelta, b1.repDelta, 'rep never reads the garden');
|
||||||
});
|
});
|
||||||
|
|
||||||
t.test("SPRINT16: night 5's designed loss costs NO rep — a clean rig still earns it", () => {
|
t.test("SPRINT16: the icenight's designed loss costs NO rep — a clean rig still earns it", () => {
|
||||||
|
// S17: the walk finds the flagged night instead of hardcoding slot 4 — the
|
||||||
|
// icenight moved to night 6 with the seven-night ruling and this pin is
|
||||||
|
// about the FLAG, not the slot.
|
||||||
|
const flaggedIdx = NIGHTS.findIndex((_, i) => nightAt(i).gardenBeyondSaving);
|
||||||
const w = createWeek();
|
const w = createWeek();
|
||||||
for (let i = 0; i < 4; i++) w.advance();
|
for (let i = 0; i < flaggedIdx; i++) w.advance();
|
||||||
assertEq(nightAt(4).gardenBeyondSaving, true, 'precondition: the flagged night');
|
assertEq(nightAt(flaggedIdx).gardenBeyondSaving, true, 'precondition: the flagged night');
|
||||||
// Garden lost by design, steel held, nothing of theirs broken: that was
|
// Garden lost by design, steel held, nothing of theirs broken: that was
|
||||||
// the whole job tonight (the verdict's own words), and the number says so.
|
// the whole job tonight (the verdict's own words), and the number says so.
|
||||||
const s = w.settle(cleanRun({ hp: 0, win: false }), s16def, 0);
|
const s = w.settle(cleanRun({ hp: 0, win: false }), s16def, 0);
|
||||||
@ -737,7 +754,7 @@ export default async function run(t) {
|
|||||||
}
|
}
|
||||||
assertEq(items, 0, 'no sheet and no invoice ever carried a warranty line');
|
assertEq(items, 0, 'no sheet and no invoice ever carried a warranty line');
|
||||||
assert(w.rep > REP.START, `the week ends rep-positive (★${w.rep})`);
|
assert(w.rep > REP.START, `the week ends rep-positive (★${w.rep})`);
|
||||||
assertEq(w.rep, REP.MAX, 'six clean nights ride the clamp to the top of the scale');
|
assertEq(w.rep, REP.MAX, 'seven clean nights ride the clamp to the top of the scale');
|
||||||
assertEq(w.log[NIGHTS.length - 1].warrantiesToDate, 0, 'and the end card has no receipts to show');
|
assertEq(w.log[NIGHTS.length - 1].warrantiesToDate, 0, 'and the end card has no receipts to show');
|
||||||
w.reset();
|
w.reset();
|
||||||
assertEq(w.rep, REP.START, 'reset() is a new outfit — rep goes back to neutral');
|
assertEq(w.rep, REP.START, 'reset() is a new outfit — rep goes back to neutral');
|
||||||
@ -1057,8 +1074,8 @@ export default async function run(t) {
|
|||||||
assert(hailed.verdict !== rained.verdict, 'hail and rain deaths must not read identically');
|
assert(hailed.verdict !== rained.verdict, 'hail and rain deaths must not read identically');
|
||||||
});
|
});
|
||||||
|
|
||||||
t.test("night 5's loss is TAUGHT, not blamed: beyondSaving rewrites the garden verdicts only", () => {
|
t.test("the icenight's loss is TAUGHT, not blamed: beyondSaving rewrites the garden verdicts only", () => {
|
||||||
// SPRINT13 gate 1.4, the night-5 ruling. Measured game-true (B's pay table,
|
// SPRINT13 gate 1.4, the icenight ruling (night 5 then, night 6 since S17). Measured game-true (B's pay table,
|
||||||
// my probe agreeing): the icenight garden cannot reach the win line at any
|
// my probe agreeing): the icenight garden cannot reach the win line at any
|
||||||
// price — bare 0.0, best buyable 27.7. So week.js flags the night, and the
|
// price — bare 0.0, best buyable 27.7. So week.js flags the night, and the
|
||||||
// verdict must stop implying a better rig existed. The worst liar was
|
// verdict must stop implying a better rig existed. The worst liar was
|
||||||
@ -1310,7 +1327,7 @@ export default async function run(t) {
|
|||||||
// --- SPRINT13 gate 1.4: THE PINNED SEPARATION TARGET ----------------------
|
// --- SPRINT13 gate 1.4: THE PINNED SEPARATION TARGET ----------------------
|
||||||
//
|
//
|
||||||
// The ruling, RESTATED after B's skew finding (THREADS [A], twice — read the
|
// The ruling, RESTATED after B's skew finding (THREADS [A], twice — read the
|
||||||
// second entry): on the shipped week's wild night — night 4, THIS yard — the
|
// second entry): on the shipped week's wild night — night 5 since S17's ruling, THIS yard — the
|
||||||
// best line START_BUDGET can buy must HOLD and WIN comfortably (>60), a bare
|
// best line START_BUDGET can buy must HOLD and WIN comfortably (>60), a bare
|
||||||
// bed must LOSE (<50), and the spread must read as most of a plant state
|
// bed must LOSE (<50), and the spread must read as most of a plant state
|
||||||
// (≥25 HP), because the win line and the invoice are the game's real stakes.
|
// (≥25 HP), because the win line and the invoice are the game's real stakes.
|
||||||
|
|||||||
@ -20,8 +20,9 @@ import { needsLadder as realNeedsLadder, createLadder } from '../ladder.js';
|
|||||||
import { assert, assertEq, assertClose, assertLess, fixedLoop } from '../testkit.js';
|
import { assert, assertEq, assertClose, assertLess, fixedLoop } from '../testkit.js';
|
||||||
import { FIXED_DT } from '../contracts.js';
|
import { FIXED_DT } from '../contracts.js';
|
||||||
import { loadStorm, createWind } from '../weather.js';
|
import { loadStorm, createWind } from '../weather.js';
|
||||||
// SPRINT16 gate 4: the six-night law pins at the bottom of this file read the
|
// SPRINT16 gate 4 / SPRINT17 gate 0.1: the week-law pins at the bottom of this
|
||||||
// ladder. week.js is contracts-only underneath, so this stays headless-safe.
|
// file read the ladder. week.js is contracts-only underneath, so this stays
|
||||||
|
// headless-safe.
|
||||||
import { NIGHTS, nightAt } from '../week.js';
|
import { NIGHTS, nightAt } from '../week.js';
|
||||||
|
|
||||||
const DT = FIXED_DT;
|
const DT = FIXED_DT;
|
||||||
@ -1139,14 +1140,19 @@ export default async function run(t) {
|
|||||||
assertEq(p.carrying, null, 'and consumes it');
|
assertEq(p.carrying, null, 'and consumes it');
|
||||||
});
|
});
|
||||||
|
|
||||||
// ================================================================ SPRINT16 gate 4
|
// ==================================== SPRINT16 gate 4 → SPRINT17 gate 0.1
|
||||||
// THE SIX-NIGHT LAW, pinned next to its owner. a.test pins the ladder's
|
// THE WEEK'S LAW, pinned next to its owner. Written as the SIX-night law in
|
||||||
// shape (which storm, which site, which slot); these pin the RULES the
|
// S16; John ruled SEVEN (2026-07-21 — the wildnight restored, my own gate-6
|
||||||
// ordering must obey, so a future re-order that keeps six nights but breaks
|
// filing's alternative), and the pins moved WITH the ruling, which is them
|
||||||
// the law goes red with the law's own words. Every threshold here is
|
// working, not breaking: every rule below is slot-free, so restoring the
|
||||||
|
// wildnight at night 5 changed no assert body — only this prose, plus one
|
||||||
|
// NEW pin for the ruling itself (last in the block). a.test pins the
|
||||||
|
// ladder's shape (which storm, which site, which slot); these pin the RULES
|
||||||
|
// the ordering must obey, so a future re-order that keeps seven nights but
|
||||||
|
// breaks the law goes red with the law's own words. Every threshold here is
|
||||||
// ROADMAP principle 5 or a measured THREADS receipt, not taste.
|
// ROADMAP principle 5 or a measured THREADS receipt, not taste.
|
||||||
|
|
||||||
t.test('SPRINT16 six-night law: exactly one new site, exactly one new storm, never the same night', () => {
|
t.test('week law (S16, holds at seven): exactly one new site, exactly one new storm, never the same night', () => {
|
||||||
const nights = NIGHTS.map((_, i) => nightAt(i));
|
const nights = NIGHTS.map((_, i) => nightAt(i));
|
||||||
const PRE_S16_SITES = ['backyard_01', 'site_02_corner_block'];
|
const PRE_S16_SITES = ['backyard_01', 'site_02_corner_block'];
|
||||||
const PRE_S16_STORMS = ['storm_01_gentle', 'storm_02_wildnight', 'storm_02b_icenight',
|
const PRE_S16_STORMS = ['storm_01_gentle', 'storm_02_wildnight', 'storm_02b_icenight',
|
||||||
@ -1161,7 +1167,7 @@ export default async function run(t) {
|
|||||||
'and they are NOT the same night — a player who loses must be able to name what beat them');
|
'and they are NOT the same night — a player who loses must be able to name what beat them');
|
||||||
});
|
});
|
||||||
|
|
||||||
t.test('SPRINT16 six-night law: the new site debuts under a storm already survived', () => {
|
t.test('week law (S16, holds at seven): the new site debuts under a storm already survived', () => {
|
||||||
const idx = NIGHTS.findIndex((_, i) => nightAt(i).site === 'site_03_swing_lawn');
|
const idx = NIGHTS.findIndex((_, i) => nightAt(i).site === 'site_03_swing_lawn');
|
||||||
assert(idx > 0, 'the swing lawn is in the rotation and is not night one');
|
assert(idx > 0, 'the swing lawn is in the rotation and is not night one');
|
||||||
const debut = nightAt(idx);
|
const debut = nightAt(idx);
|
||||||
@ -1171,7 +1177,7 @@ export default async function run(t) {
|
|||||||
+ 'at the swing lawn\'s debut the yard is the ONLY new variable');
|
+ 'at the swing lawn\'s debut the yard is the ONLY new variable');
|
||||||
});
|
});
|
||||||
|
|
||||||
t.test("SPRINT16 six-night law: the soaker flies the corner block — C's measured pairing", () => {
|
t.test("week law (S16, holds at seven): the soaker flies the corner block — C's measured pairing", () => {
|
||||||
// THREADS [C] 2026-07-20: the backyard's buyable geometry caps hail cover
|
// THREADS [C] 2026-07-20: the backyard's buyable geometry caps hail cover
|
||||||
// over the bed at ~31% — best membrane line there reads 37.5, a bet with
|
// over the bed at ~31% — best membrane line there reads 37.5, a bet with
|
||||||
// no win in it. The soaker over anything but site_02 needs a re-measure
|
// no win in it. The soaker over anything but site_02 needs a re-measure
|
||||||
@ -1185,7 +1191,7 @@ export default async function run(t) {
|
|||||||
assert(debutIdx < soakerIdx, 'and the yard was taught on an earlier night — the storm is the only new variable');
|
assert(debutIdx < soakerIdx, 'and the yard was taught on an earlier night — the storm is the only new variable');
|
||||||
});
|
});
|
||||||
|
|
||||||
t.test('SPRINT16 six-night law: the beyond-saving night is neither new nor final', () => {
|
t.test('week law (S16, holds at seven): the beyond-saving night is neither new nor final', () => {
|
||||||
const flagged = NIGHTS.map((_, i) => nightAt(i)).filter((n) => n.gardenBeyondSaving);
|
const flagged = NIGHTS.map((_, i) => nightAt(i)).filter((n) => n.gardenBeyondSaving);
|
||||||
assertEq(flagged.length, 1, 'exactly one designed loss in the week');
|
assertEq(flagged.length, 1, 'exactly one designed loss in the week');
|
||||||
assertEq(flagged[0].storm, 'storm_02b_icenight', 'and it is the ice night — the flag is a measured fact about');
|
assertEq(flagged[0].storm, 'storm_02b_icenight', 'and it is the ice night — the flag is a measured fact about');
|
||||||
@ -1193,6 +1199,34 @@ export default async function run(t) {
|
|||||||
const idx = NIGHTS.findIndex((_, i) => nightAt(i).gardenBeyondSaving);
|
const idx = NIGHTS.findIndex((_, i) => nightAt(i).gardenBeyondSaving);
|
||||||
assert(idx !== NIGHTS.length - 1,
|
assert(idx !== NIGHTS.length - 1,
|
||||||
'the designed loss must not be the final night: its dawn breaks need a morning to book warranty on '
|
'the designed loss must not be the final night: its dawn breaks need a morning to book warranty on '
|
||||||
+ '(A filed the night-5-final gap in THREADS — the six-night order is what closes it)');
|
+ "(A filed the night-final gap in THREADS — the week's ordering closes it, at six AND at seven)");
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("SPRINT17 seven-night law: the wildnight is IN the week, over a yard the player knows", () => {
|
||||||
|
// JOHN'S RULING (2026-07-21), pinned so a future re-order can't quietly
|
||||||
|
// re-evict the marquee storm: gate 6 asked six-without-it or seven-with-it,
|
||||||
|
// and the answer was seven. Three facts carry the ruling:
|
||||||
|
const nights = NIGHTS.map((_, i) => nightAt(i));
|
||||||
|
const wildIdx = nights.findIndex((n) => n.storm === 'storm_02_wildnight');
|
||||||
|
assert(wildIdx >= 0,
|
||||||
|
"the wildnight flies — its S16 eviction was RULED wrong (THREADS [I] 2026-07-21); "
|
||||||
|
+ 'removing it again is a John question, not a re-order');
|
||||||
|
// The one-variable law at its slot: the wildnight flies over a yard the
|
||||||
|
// player has already rigged — it always did (the pinned separation lives
|
||||||
|
// in backyard_01's own site data, so the storm is the only thing new).
|
||||||
|
const wild = nights[wildIdx];
|
||||||
|
assertEq(wild.site, 'backyard_01',
|
||||||
|
"the wildnight flies the backyard — the pinned separation is THAT yard's site data; "
|
||||||
|
+ 'flying it elsewhere needs the separation re-measured FIRST, like the soaker pin above');
|
||||||
|
assert(nights.slice(0, wildIdx).some((n) => n.site === wild.site),
|
||||||
|
'and the yard was rigged on an earlier night — at the marquee storm the yard must not also be new');
|
||||||
|
// The icenight's brief opens "Less wind than last night" — TRUE only while
|
||||||
|
// the wildnight (peak 30.0) sits immediately before it (28.5). If this
|
||||||
|
// ordering changes, that brief's first sentence moves in the same commit
|
||||||
|
// (it already moved twice: S16 out, S17 back — the sentence follows the facts).
|
||||||
|
const iceIdx = nights.findIndex((n) => n.storm === 'storm_02b_icenight');
|
||||||
|
assertEq(wildIdx, iceIdx - 1,
|
||||||
|
"the wildnight sits directly before the icenight — the icenight's brief says 'less wind than "
|
||||||
|
+ "last night', and the paper never lies: re-order these and re-word that brief in the same commit");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* (SPRINT16 gate 4: the NIGHTS ladder and the two new briefs are Lane D's
|
* (SPRINT16 gate 4: the NIGHTS ladder and the two new briefs are Lane D's
|
||||||
* edits, by the sprint's explicit grant — A owns everything else here.)
|
* edits, by the sprint's explicit grant — A owns everything else here.)
|
||||||
*
|
*
|
||||||
* Six nights, one wallet. This is deliberately a thin wrapper around
|
* Seven nights, one wallet. This is deliberately a thin wrapper around
|
||||||
* the phase machine rather than a new system: main.js already knows how to run
|
* the phase machine rather than a new system: main.js already knows how to run
|
||||||
* forecast → prep → storm → aftermath, and the week's whole job is to answer
|
* forecast → prep → storm → aftermath, and the week's whole job is to answer
|
||||||
* three questions between rounds — which storm is tonight, how much money do you
|
* three questions between rounds — which storm is tonight, how much money do you
|
||||||
@ -23,8 +23,9 @@ import { START_BUDGET, HARDWARE } from './contracts.js';
|
|||||||
* 2 Southerly Buster gust 20.0, hail — the change, and hail, arrive
|
* 2 Southerly Buster gust 20.0, hail — the change, and hail, arrive
|
||||||
* 3 Early Buster gust 20.0, hail — same teeth, change comes EARLY
|
* 3 Early Buster gust 20.0, hail — same teeth, change comes EARLY
|
||||||
* 4 Southerly again gust 20.0, hail — the storm you know, the yard you don't
|
* 4 Southerly again gust 20.0, hail — the storm you know, the yard you don't
|
||||||
* 5 Ice Night gust 28.5, hail — the most wind of the week, and worse ice
|
* 5 Wild Night gust 30.0, hail — the marquee storm: the most wind of the week
|
||||||
* 6 Pea-Hail Soaker gust 15.2, fine hail — the night the default fabric is wrong
|
* 6 Ice Night gust 28.5, hail — less wind, worse ice, and the bed is already lost
|
||||||
|
* 7 Pea-Hail Soaker gust 15.2, fine hail — the night the default fabric is wrong
|
||||||
* Night 3 is the trap: it looks like night 2 on the forecast card and isn't.
|
* Night 3 is the trap: it looks like night 2 on the forecast card and isn't.
|
||||||
*
|
*
|
||||||
* SPRINT10: a night is now a STORM and a SITE. Night 3 moves to the corner block
|
* SPRINT10: a night is now a STORM and a SITE. Night 3 moves to the corner block
|
||||||
@ -48,11 +49,11 @@ import { START_BUDGET, HARDWARE } from './contracts.js';
|
|||||||
* swing lawn enters the rotation at night 4 under the southerly — the ONE
|
* swing lawn enters the rotation at night 4 under the southerly — the ONE
|
||||||
* variable rule (ROADMAP principle 5), applied literally: at its debut the only
|
* variable rule (ROADMAP principle 5), applied literally: at its debut the only
|
||||||
* new thing is the yard, because the storm is Tuesday's, already survived. The
|
* new thing is the yard, because the storm is Tuesday's, already survived. The
|
||||||
* soaker flies night 6 over the corner block — a yard night 3 taught — because
|
* soaker closes the week over the corner block — a yard night 3 taught — because
|
||||||
* C MEASURED the pairing (THREADS [C] 2026-07-20): the backyard's buyable
|
* C MEASURED the pairing (THREADS [C] 2026-07-20): the backyard's buyable
|
||||||
* geometry caps hail cover over the bed at ~31%, so over there the membrane
|
* geometry caps hail cover over the bed at ~31%, so over there the membrane
|
||||||
* bet has no win in it. Exactly one night is the new site (4), exactly one
|
* bet has no win in it. Exactly one night is the new site (4), exactly one
|
||||||
* flies the new storm (6), and they are not the same night.
|
* flies the new storm (the closer), and they are not the same night.
|
||||||
*
|
*
|
||||||
* ⚠️ THE STRUCTURAL CONSEQUENCE, SAID OUT LOUD (D, filed in THREADS for John's
|
* ⚠️ THE STRUCTURAL CONSEQUENCE, SAID OUT LOUD (D, filed in THREADS for John's
|
||||||
* gate 6): six slots cannot hold seven nights. Five shipped storms + the
|
* gate 6): six slots cannot hold seven nights. Five shipped storms + the
|
||||||
@ -61,13 +62,28 @@ import { START_BUDGET, HARDWARE } from './contracts.js';
|
|||||||
* and every other candidate is load-bearing (gentle = the tutorial and the
|
* and every other candidate is load-bearing (gentle = the tutorial and the
|
||||||
* calm-day preload; southerly = the hail intro and the early buster's
|
* calm-day preload; southerly = the hail intro and the early buster's
|
||||||
* referent; early buster = site_02's debut, which the soaker's pairing needs;
|
* referent; early buster = site_02's debut, which the soaker's pairing needs;
|
||||||
* icenight = the beyond-saving flag, canon). THE WILD NIGHT (storm_02) is out
|
* icenight = the beyond-saving flag, canon). THE WILD NIGHT (storm_02) was out
|
||||||
* of the rotation. Its storm file, its tests, and the pinned backyard
|
* of the rotation for exactly one integration. The honest fix named in that
|
||||||
* separation all stand — it is un-flown by the week, not deleted. If that
|
* filing was a SEVEN-night week — one entry re-added here, a ruling, not a
|
||||||
* eviction is wrong, the honest fix is a SEVEN-night week (arc 2's own range
|
|
||||||
* is "five-to-seven"), which is one entry re-added here — a ruling, not a
|
|
||||||
* refactor.
|
* refactor.
|
||||||
*
|
*
|
||||||
|
* ⚖️ SPRINT17 gate 0.1 (D, wiring JOHN'S RULING, 2026-07-21): SEVEN NIGHTS —
|
||||||
|
* the wildnight comes back. The slot is between the swing lawn and the
|
||||||
|
* icenight, and the one-variable law places it, not taste: the wildnight has
|
||||||
|
* flown the backyard since Sprint 4 (the pinned separation lives in THAT
|
||||||
|
* yard's site data), so at night 5 the player is back on a yard they have
|
||||||
|
* rigged three times and the storm is the only new thing. The slot also keeps
|
||||||
|
* every S16 law intact by construction — the swing lawn still debuts under a
|
||||||
|
* survived storm (night 4), the icenight is still neither new nor final
|
||||||
|
* (night 6, its dawn breaks book warranty on the soaker's morning), and the
|
||||||
|
* week's arc reads full / full / full / new-yard / won-but-tattered /
|
||||||
|
* the-designed-loss / the-fabric-bet: the pyrrhic win now sits directly
|
||||||
|
* before the night that cannot be won, which is the escalation the six-night
|
||||||
|
* week had to fake with wording. The icenight's brief reverts to its pre-S16
|
||||||
|
* sentence ("less wind than last night") because that fact is TRUE again —
|
||||||
|
* wildnight peaks 30.0, icenight 28.5. gradeFor's clean bar moves with the
|
||||||
|
* ladder in this same commit (my own S16 filing's demand — see gradeFor).
|
||||||
|
*
|
||||||
* Copy lives here as data, per SPRINT11's rule: client flavour is data, not code.
|
* Copy lives here as data, per SPRINT11's rule: client flavour is data, not code.
|
||||||
*/
|
*/
|
||||||
export const NIGHTS = [
|
export const NIGHTS = [
|
||||||
@ -106,31 +122,51 @@ export const NIGHTS = [
|
|||||||
+ 'her dad reckons the crossbar\'s bolted right through, tie to that and save yourself a '
|
+ 'her dad reckons the crossbar\'s bolted right through, tie to that and save yourself a '
|
||||||
+ 'post. The veggie bed is her school project, so it comes home alive.',
|
+ 'post. The veggie bed is her school project, so it comes home alive.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// SPRINT17 gate 0.1 — THE WILDNIGHT, RESTORED (John's ruling: seven
|
||||||
|
// nights). Back at the Hendersons', the yard the player has rigged three
|
||||||
|
// times, so the storm is the only new thing — the one-variable law at the
|
||||||
|
// marquee slot. This is the pyrrhic-win canon (the pinned separation:
|
||||||
|
// best buyable line WINS at ~55-64 hp, tattered; bare bed loses), and the
|
||||||
|
// brief sells exactly that stake in the clients' own voice: they're
|
||||||
|
// ringing from Cairns about the BED, and they price the sail as the thing
|
||||||
|
// that spends itself protecting it. "The garden made it; the sail didn't"
|
||||||
|
// is the ending this night is FOR — the copy promises it as a win.
|
||||||
|
storm: 'storm_02_wildnight', site: 'backyard_01',
|
||||||
|
client: 'the Hendersons', addr: '14 Kurrajong St — the backyard',
|
||||||
|
brief: 'The Hendersons rang from Cairns — the forecast made the news up there. This is the big '
|
||||||
|
+ 'one, the night the whole week was pointing at. Their words: they\'d rather fly home to a '
|
||||||
|
+ 'wrecked sail over a living bed than a tidy yard and a dead one. Spend the sail if you '
|
||||||
|
+ 'have to. Save the garden.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
storm: 'storm_02b_icenight', site: 'backyard_01',
|
storm: 'storm_02b_icenight', site: 'backyard_01',
|
||||||
client: 'the Hendersons', addr: '14 Kurrajong St — the backyard',
|
client: 'the Hendersons', addr: '14 Kurrajong St — the backyard',
|
||||||
// SPRINT16 gate 4 wording note (D): this brief used to open "Less wind than
|
// SPRINT17 gate 0.1 wording note (D): "Less wind than last night" is the
|
||||||
// last night" — true when the wild night (gust 30) sat at night 4. The wild
|
// pre-S16 sentence, back because the FACT is back — the wildnight (gust
|
||||||
// night left the six-night rotation (see the ladder comment), so the ice
|
// 30.0) sits at night 5 again, immediately ahead of this one (28.5). The
|
||||||
// night is now the most wind the week throws; the sentence moved with the
|
// S16 six-night week had no louder night before this, so the brief said
|
||||||
// facts. The teaching half — level with the player that the bed is already
|
// "the most wind of the week" for one integration; the sentence moves
|
||||||
// lost — is untouched.
|
// with the facts, both directions. The teaching half — level with the
|
||||||
brief: 'The most wind of the week and golf-ball ice with it. Last night before they land. '
|
// player that the bed is already lost — is untouched, as ever.
|
||||||
+ 'Nobody\'s pretending the seedlings ride this one out — keep the sail on its feet and '
|
brief: 'Less wind than last night and more ice, which is worse for the bed and easier on the rig. '
|
||||||
+ 'the ice off the windows.',
|
+ 'Last night before they land. Nobody\'s pretending the seedlings ride this one out — keep '
|
||||||
// SPRINT13 gate 1.4 — A's ruling, DESIGN CANON: night 5's garden is BEYOND
|
+ 'the sail on its feet and the ice off the windows.',
|
||||||
// SAVING, by design, at any price. Measured game-true (B's pay table and
|
// SPRINT13 gate 1.4 — A's ruling, DESIGN CANON: the icenight's garden is
|
||||||
|
// BEYOND SAVING, by design, at any price (ruled at night 5; the S17
|
||||||
|
// seven-night ladder flies it night 6 — the flag rides the NIGHT, not the
|
||||||
|
// slot). Measured game-true (B's pay table and
|
||||||
// A's probe, agreeing): bare bed 0.0 DEAD; best buyable garden 27.7; best
|
// A's probe, agreeing): bare bed 0.0 DEAD; best buyable garden 27.7; best
|
||||||
// clean 15.3 — every line in the game sits under WIN's hp >= 50, wallet or
|
// clean 15.3 — every line in the game sits under WIN's hp >= 50, wallet or
|
||||||
// bank. The week's arc WANTS this: full / full / full, won-but-tattered
|
// bank. The week's arc WANTS this: full / full / full, won-but-tattered
|
||||||
// (night 4, the pinned separation), then the night you cannot save — the
|
// (night 5, the pinned separation, restored S17), then the night you cannot save — the
|
||||||
// icenight is where the job stops being about the bed and becomes about
|
// icenight is where the job stops being about the bed and becomes about
|
||||||
// your steel (the line that would hold it needs $105 the shop caps at
|
// your steel (the line that would hold it needs $105 the shop caps at
|
||||||
// 3.20 kN shackles against 3.22/3.25 peaks — C's few-hundredths whisker,
|
// 3.20 kN shackles against 3.22/3.25 peaks — C's few-hundredths whisker,
|
||||||
// kept on purpose). This flag exists so surfaces TEACH the loss instead of
|
// kept on purpose). This flag exists so surfaces TEACH the loss instead of
|
||||||
// implying a better rig existed: verdictFor reads it (main.js) and stops
|
// implying a better rig existed: verdictFor reads it (main.js) and stops
|
||||||
// blaming placement; the brief above levels with the player before they
|
// blaming placement; the brief above levels with the player before they
|
||||||
// spend a dollar. If a retune ever makes night 5 winnable, delete this
|
// spend a dollar. If a retune ever makes this night winnable, delete this
|
||||||
// flag in the same commit or the verdict lies in the other direction.
|
// flag in the same commit or the verdict lies in the other direction.
|
||||||
gardenBeyondSaving: true,
|
gardenBeyondSaving: true,
|
||||||
},
|
},
|
||||||
@ -270,7 +306,7 @@ export const PAY = {
|
|||||||
* A broke-but-repaired-by-dawn night is NEUTRAL by design: not clean (it
|
* A broke-but-repaired-by-dawn night is NEUTRAL by design: not clean (it
|
||||||
* broke), not a warranty (you fixed it mid-storm on your own ladder — that IS
|
* broke), not a warranty (you fixed it mid-storm on your own ladder — that IS
|
||||||
* the job). And rep NEVER reads the garden — hp/win/gardenBeyondSaving are not
|
* the job). And rep NEVER reads the garden — hp/win/gardenBeyondSaving are not
|
||||||
* inputs, so night 5's designed loss cannot cost rep by construction rather
|
* inputs, so the icenight's designed loss cannot cost rep by construction rather
|
||||||
* than by exemption. a.test pins that from both directions.
|
* than by exemption. a.test pins that from both directions.
|
||||||
*
|
*
|
||||||
* Money: `multiplier(rep)` scales the callout fee at QUOTE TIME — quote() and
|
* Money: `multiplier(rep)` scales the callout fee at QUOTE TIME — quote() and
|
||||||
@ -338,10 +374,24 @@ export const BROKE_BELOW = HARDWARE[0].cost * 4;
|
|||||||
* client's garden died four times", and until this was measured nobody knew the
|
* client's garden died four times", and until this was measured nobody knew the
|
||||||
* game could even produce that ending.
|
* game could even produce that ending.
|
||||||
*
|
*
|
||||||
* @param {number} held gardens saved, 0..5
|
* SPRINT17 gate 0.1 (D, in A's file, flagged — revert-and-tell-me standard):
|
||||||
|
* the clean bar is now DERIVED, not the constant 5. My own S16 filing said a
|
||||||
|
* seven-night week needs gradeFor re-ruled in the same commit as the ladder,
|
||||||
|
* and this is that commit. The S16 coincidence — gradeFor(5)='clean' landing
|
||||||
|
* exactly on the six-night week's best outcome (5 won + the designed loss) —
|
||||||
|
* breaks at seven nights: held>=5 would print "THE WEEK HELD — everything's
|
||||||
|
* still where you put it" over a savable garden that died, which is the end
|
||||||
|
* card lying, the one thing it exists not to do. So 'clean' is the RULE the
|
||||||
|
* constant was standing in for: every garden the week let you save. The
|
||||||
|
* scraped/solvent boundary (>=3) is untouched — moving it is a taste call on
|
||||||
|
* A's wording and there's no lie in leaving it (3 of 6 savable is still "the
|
||||||
|
* least wrong call most nights"); re-rule it if you want, it's yours.
|
||||||
|
*
|
||||||
|
* @param {number} held gardens saved, 0..savable
|
||||||
*/
|
*/
|
||||||
export function gradeFor(held) {
|
export function gradeFor(held) {
|
||||||
if (held >= 5) return 'clean';
|
const savable = NIGHTS.filter((_, i) => !nightAt(i).gardenBeyondSaving).length;
|
||||||
|
if (held >= savable) return 'clean';
|
||||||
if (held >= 3) return 'scraped';
|
if (held >= 3) return 'scraped';
|
||||||
return 'solvent';
|
return 'solvent';
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user