Lane D S16 gate 4.1+4.2: swing lawn re-scored (zero deltas), THE SIX-NIGHT WEEK wired
- site_03 re-scored on the post-ruling tree, S15 loop, FUNNEL ON: every number reproduces the S14 card to the digit (81 quads, 11 clean/16 marginal, cheapest honest $65 h3,t2,t2b,p1 85.3 FULL, bare 83.7, best clean 90.0, sep +6.3, best-reading 91.8 MARGINAL). Nothing moved; nothing to fix; B's PIN REFUSED (measured) card renders the finding on this yard. - NIGHTS is six: gentle, southerly, earlybuster(s02), SOUTHERLY over the SWING LAWN (new site, survived storm), icenight (flag intact, no longer final), SOAKER over the CORNER BLOCK (C's measured pairing). Two new briefs: Ruby's swing set sells the crossbar trap; the Vasilaros return sells the cloth default the forecast argues against. - THE STRUCTURAL CONSEQUENCE, filed loudly: six slots cannot hold five old storms + a survived-storm debut + the soaker. The WILD NIGHT leaves the rotation (every other candidate is load-bearing); storm file, tests and the pinned backyard separation all stand. If the eviction is wrong the fix is a seven-night week — one entry, a ruling. - week asserts updated (a.test): six nights, final-night=6 both directions on the broke rule, three clients with night 6 a RETURN booking. - four six-night-law pins in d.test: one new site / one new storm / never same night; survived-storm debut; soaker pairing (C's receipt in the message); beyond-saving night neither new nor final. - hud surfaces read NIGHTS.length (splash, morning header, end card) — flagged for A; hardcoded 'five' would lie on every paper. - selftest 473/0/0 (469 + 4); mutations M1 soaker→backyard, M2 debut→wildnight, M3 flag→final each RED with the law's own words, restored, green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1073ac97e8
commit
d8e7087c2c
@ -19,7 +19,15 @@ import { forecastLines, leadFor } from './weather.js';
|
|||||||
// SPRINT16 — the end card compares the final rep against neutral. A constant,
|
// SPRINT16 — the end card compares the final rep against neutral. A constant,
|
||||||
// not state: the HUD still only READS. week.js has no DOM and no THREE, so
|
// not state: the HUD still only READS. week.js has no DOM and no THREE, so
|
||||||
// this import costs nothing it didn't already cost main.js.
|
// this import costs nothing it didn't already cost main.js.
|
||||||
import { REP } from './week.js';
|
import { NIGHTS, REP } from './week.js';
|
||||||
|
|
||||||
|
// SPRINT16 gate 4 (Lane D, flagged for A): the week is data — NIGHTS.length —
|
||||||
|
// and these surfaces used to hardcode "five". A six-night week whose splash,
|
||||||
|
// morning header and end card still said 5 would be the paper lying, so every
|
||||||
|
// count below reads the ladder. The word map keeps the splash's prose voice;
|
||||||
|
// an unmapped length falls back to the digit, which is honest if inelegant.
|
||||||
|
const NIGHT_COUNT = NIGHTS.length;
|
||||||
|
const NIGHT_WORD = { 5: 'Five', 6: 'Six', 7: 'Seven' }[NIGHT_COUNT] ?? String(NIGHT_COUNT);
|
||||||
|
|
||||||
const clamp01 = (v) => (v < 0 ? 0 : v > 1 ? 1 : v);
|
const clamp01 = (v) => (v < 0 ? 0 : v > 1 ? 1 : v);
|
||||||
const kmh = (ms) => ms * 3.6;
|
const kmh = (ms) => ms * 3.6;
|
||||||
@ -533,7 +541,7 @@ export function createHud(d) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="premise">Rig shade sails, then keep the client's garden alive
|
<p class="premise">Rig shade sails, then keep the client's garden alive
|
||||||
through the storm. Five nights, five jobs, one wallet.</p>
|
through the storm. ${NIGHT_WORD} nights, ${NIGHT_WORD.toLowerCase()} jobs, one wallet.</p>
|
||||||
<div class="sect">THE CONTROLS</div>
|
<div class="sect">THE CONTROLS</div>
|
||||||
<div class="keys">
|
<div class="keys">
|
||||||
${[
|
${[
|
||||||
@ -921,9 +929,10 @@ showForecast({ key, def, site, tomorrowDef }, wk, onGo) {
|
|||||||
// scraped one, and a third for the ending nobody knew existed until the
|
// scraped one, and a third for the ending nobody knew existed until the
|
||||||
// economy was measured: solvent, with the gardens dead behind you.
|
// economy was measured: solvent, with the gardens dead behind you.
|
||||||
const COPY = {
|
const COPY = {
|
||||||
clean: ['THE WEEK HELD', "Five nights. Everything's still where you put it.",
|
clean: ['THE WEEK HELD', `${NIGHT_WORD} nights. Everything's still where you put it.`,
|
||||||
"Nobody thanks you for the storm that did nothing. That's the job."],
|
"Nobody thanks you for the storm that did nothing. That's the job."],
|
||||||
scraped: ['FIVE NIGHTS, FIVE MORNINGS', 'You made the least wrong call five times running.',
|
scraped: [`${NIGHT_WORD.toUpperCase()} NIGHTS, ${NIGHT_WORD.toUpperCase()} MORNINGS`,
|
||||||
|
`You made the least wrong call ${NIGHT_WORD.toLowerCase()} times running.`,
|
||||||
'Some of it held. You know which bits didn\'t.'],
|
'Some of it held. You know which bits didn\'t.'],
|
||||||
solvent: ['STILL TRADING', 'The books balanced. The garden didn\'t.',
|
solvent: ['STILL TRADING', 'The books balanced. The garden didn\'t.',
|
||||||
'You can be paid all week and still have nothing to point at.'],
|
'You can be paid all week and still have nothing to point at.'],
|
||||||
@ -953,8 +962,8 @@ showForecast({ key, def, site, tomorrowDef }, wk, onGo) {
|
|||||||
<div class="endtext">
|
<div class="endtext">
|
||||||
<h1>${head}</h1>
|
<h1>${head}</h1>
|
||||||
<h2>${sub}</h2>
|
<h2>${sub}</h2>
|
||||||
<div class="row"><span>nights survived</span><b>${nights}/5</b></div>
|
<div class="row"><span>nights survived</span><b>${nights}/${NIGHT_COUNT}</b></div>
|
||||||
<div class="row"><span>gardens held</span><b>${s.held ?? 0}/5</b></div>
|
<div class="row"><span>gardens held</span><b>${s.held ?? 0}/${NIGHT_COUNT}</b></div>
|
||||||
<div class="row"><span>in the bank</span><b>$${s.bankAfter}</b></div>
|
<div class="row"><span>in the bank</span><b>$${s.bankAfter}</b></div>
|
||||||
${s.rep != null ? `<div class="row"><span>reputation</span><b>${repFmt(s.rep)}</b></div>` : ''}
|
${s.rep != null ? `<div class="row"><span>reputation</span><b>${repFmt(s.rep)}</b></div>` : ''}
|
||||||
${s.warrantiesToDate ? `<div class="row"><span>warranty jobs booked</span><b>${s.warrantiesToDate}</b></div>` : ''}
|
${s.warrantiesToDate ? `<div class="row"><span>warranty jobs booked</span><b>${s.warrantiesToDate}</b></div>` : ''}
|
||||||
@ -1071,7 +1080,7 @@ showForecast({ key, def, site, tomorrowDef }, wk, onGo) {
|
|||||||
|
|
||||||
card.innerHTML = `<div class="card jobcard">
|
card.innerHTML = `<div class="card jobcard">
|
||||||
${letterhead}
|
${letterhead}
|
||||||
<h1>MORNING${w ? ` · NIGHT ${w.night} OF 5` : ''}</h1>
|
<h1>MORNING${w ? ` · NIGHT ${w.night} OF ${w.nights ?? NIGHT_COUNT}` : ''}</h1>
|
||||||
<h2>${r.subtitle}</h2>
|
<h2>${r.subtitle}</h2>
|
||||||
${rows}
|
${rows}
|
||||||
${money}
|
${money}
|
||||||
|
|||||||
@ -215,17 +215,25 @@ export default async function run(t) {
|
|||||||
|
|
||||||
// --- the week (SPRINT8 gate 1) -------------------------------------------
|
// --- the week (SPRINT8 gate 1) -------------------------------------------
|
||||||
|
|
||||||
t.test('the week is five escalating nights, each a storm and a site', () => {
|
t.test('the week is six escalating nights, each a storm and a site', () => {
|
||||||
assertEq(NIGHTS.length, 5);
|
// SPRINT16 gate 4 (D's wiring): five → six. The swing lawn debuts night 4
|
||||||
|
// under Tuesday's southerly; the soaker closes the week over the corner
|
||||||
|
// block. The deeper pins — one-variable law, survived-storm debut, the
|
||||||
|
// measured soaker pairing — live in d.test.js next to the wiring's owner.
|
||||||
|
assertEq(NIGHTS.length, 6);
|
||||||
// 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(4).storm, 'storm_02b_icenight', 'night five is the ice night');
|
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(4).storm, 'storm_02b_icenight', 'night five is still the ice night');
|
||||||
|
assertEq(nightAt(5).storm, 'storm_06_soaker', 'night six is the fabric bet');
|
||||||
|
assertEq(nightAt(5).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 rest 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 night 5's alone, and a
|
||||||
// night without it is presumed savable — new nights can't inherit the excuse.
|
// night without it is presumed savable — new nights can't inherit the excuse.
|
||||||
assertEq(nightAt(4).gardenBeyondSaving, true, 'night 5 is beyond saving BY DESIGN — canon, ruled');
|
assertEq(nightAt(4).gardenBeyondSaving, true, 'night 5 is beyond saving BY DESIGN — canon, ruled');
|
||||||
for (let i = 0; i < 4; i++) assertEq(nightAt(i).gardenBeyondSaving, false, `night ${i + 1} is savable`);
|
for (const i of [0, 1, 2, 3, 5]) 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');
|
||||||
@ -234,9 +242,12 @@ export default async function run(t) {
|
|||||||
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 < 2; i++) w.advance();
|
||||||
assertEq(w.night, 5);
|
assertEq(w.night, 5);
|
||||||
assert(w.isFinalNight, 'night five is the last');
|
assert(!w.isFinalNight, 'the ice night is no longer the last — its dawn breaks have a morning to book on now');
|
||||||
w.advance();
|
w.advance();
|
||||||
assertEq(w.night, 5, 'the ladder does not walk off its own end');
|
assertEq(w.night, 6);
|
||||||
|
assert(w.isFinalNight, 'night six is the last');
|
||||||
|
w.advance();
|
||||||
|
assertEq(w.night, 6, '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', () => {
|
||||||
@ -292,14 +303,15 @@ export default async function run(t) {
|
|||||||
assert(j.brief && j.brief.length > 20, `night ${i + 1} has a brief worth reading`);
|
assert(j.brief && j.brief.length > 20, `night ${i + 1} has a brief worth reading`);
|
||||||
assert(j.site, `night ${i + 1} has a yard`);
|
assert(j.site, `night ${i + 1} has a yard`);
|
||||||
}
|
}
|
||||||
// The week's shape, which is DATA and not decoration: four nights on
|
// The week's shape, which is DATA and not decoration: the Hendersons'
|
||||||
// retainer at one yard while they're away, and ONE short-notice callout to
|
// retainer, one short-notice callout to a stranger's corner block, the
|
||||||
// a stranger's corner block. Night 3 landing differently is the whole point
|
// Delaneys' swing lawn — and the corner block calling BACK, which is
|
||||||
// of the ladder — a different site MEANS a different client.
|
// SPRINT16's whole thesis (your work follows you) written into the ladder.
|
||||||
const clients = NIGHTS.map((_, i) => nightAt(i).client);
|
const clients = NIGHTS.map((_, i) => nightAt(i).client);
|
||||||
assertEq(new Set(clients).size, 2, 'two clients: the retainer and the one-off');
|
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');
|
||||||
});
|
});
|
||||||
|
|
||||||
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', () => {
|
||||||
@ -386,10 +398,17 @@ 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.
|
// broke is just being broke — the week still finished. SPRINT16: the last
|
||||||
|
// night is SIX now, and night five going broke is a real game over again —
|
||||||
|
// both directions pinned so the re-ordering can't quietly move the rule.
|
||||||
const w2 = createWeek();
|
const w2 = createWeek();
|
||||||
for (let i = 0; i < 4; i++) w2.advance();
|
for (let i = 0; i < NIGHTS.length - 1; i++) w2.advance();
|
||||||
assertEq(w2.settle(ruin, def, 80).outcome, 'win', 'night five always ends the week, not the run');
|
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');
|
||||||
|
const w3 = createWeek();
|
||||||
|
for (let i = 0; i < 4; i++) w3.advance();
|
||||||
|
assertEq(w3.settle(ruin, def, 80).outcome, 'gameover',
|
||||||
|
'night five is not the final night any more — 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
|
||||||
@ -711,15 +730,15 @@ export default async function run(t) {
|
|||||||
t.test('SPRINT16 negative control: a clean week books ZERO warranty items and ends rep-positive', () => {
|
t.test('SPRINT16 negative control: a clean week books ZERO warranty items and ends rep-positive', () => {
|
||||||
const w = createWeek();
|
const w = createWeek();
|
||||||
let items = 0;
|
let items = 0;
|
||||||
for (let n = 0; n < 5; n++) {
|
for (let n = 0; n < NIGHTS.length; n++) {
|
||||||
items += w.quote(s16def).warranty.length; // counted off the sheet…
|
items += w.quote(s16def).warranty.length; // counted off the sheet…
|
||||||
items += w.settle(cleanRun(), s16def, 0).warranty.length; // …and the invoice
|
items += w.settle(cleanRun(), s16def, 0).warranty.length; // …and the invoice
|
||||||
w.advance();
|
w.advance();
|
||||||
}
|
}
|
||||||
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, 'five clean nights ride the clamp to the top of the scale');
|
assertEq(w.rep, REP.MAX, 'six clean nights ride the clamp to the top of the scale');
|
||||||
assertEq(w.log[4].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');
|
||||||
});
|
});
|
||||||
|
|||||||
@ -20,6 +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
|
||||||
|
// ladder. week.js is contracts-only underneath, so this stays headless-safe.
|
||||||
|
import { NIGHTS, nightAt } from '../week.js';
|
||||||
|
|
||||||
const DT = FIXED_DT;
|
const DT = FIXED_DT;
|
||||||
|
|
||||||
@ -1135,4 +1138,61 @@ export default async function run(t) {
|
|||||||
assertEq(repaired, 0, 're-rig fires with a spare');
|
assertEq(repaired, 0, 're-rig fires with a spare');
|
||||||
assertEq(p.carrying, null, 'and consumes it');
|
assertEq(p.carrying, null, 'and consumes it');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ================================================================ SPRINT16 gate 4
|
||||||
|
// THE SIX-NIGHT LAW, pinned next to its owner. 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 six 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.
|
||||||
|
|
||||||
|
t.test('SPRINT16 six-night law: exactly one new site, exactly one new storm, never the same night', () => {
|
||||||
|
const nights = NIGHTS.map((_, i) => nightAt(i));
|
||||||
|
const PRE_S16_SITES = ['backyard_01', 'site_02_corner_block'];
|
||||||
|
const PRE_S16_STORMS = ['storm_01_gentle', 'storm_02_wildnight', 'storm_02b_icenight',
|
||||||
|
'storm_03_southerly', 'storm_03b_earlybuster'];
|
||||||
|
const newSiteNights = nights.filter((n) => !PRE_S16_SITES.includes(n.site));
|
||||||
|
const newStormNights = nights.filter((n) => !PRE_S16_STORMS.includes(n.storm));
|
||||||
|
assertEq(newSiteNights.length, 1, 'exactly ONE night is the new site');
|
||||||
|
assertEq(newSiteNights[0].site, 'site_03_swing_lawn');
|
||||||
|
assertEq(newStormNights.length, 1, 'exactly ONE night flies the new storm');
|
||||||
|
assertEq(newStormNights[0].storm, 'storm_06_soaker');
|
||||||
|
assert(newSiteNights[0] !== newStormNights[0],
|
||||||
|
'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', () => {
|
||||||
|
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');
|
||||||
|
const debut = nightAt(idx);
|
||||||
|
const earlier = NIGHTS.slice(0, idx).map((_, i) => nightAt(i).storm);
|
||||||
|
assert(earlier.includes(debut.storm),
|
||||||
|
`night ${idx + 1} flies ${debut.storm}, which must have flown on an earlier night — `
|
||||||
|
+ '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", () => {
|
||||||
|
// 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
|
||||||
|
// no win in it. The soaker over anything but site_02 needs a re-measure
|
||||||
|
// FIRST; this pin is where a re-pairing finds that out.
|
||||||
|
const soaker = NIGHTS.map((_, i) => nightAt(i)).find((n) => n.storm === 'storm_06_soaker');
|
||||||
|
assert(soaker, 'the soaker is in the week');
|
||||||
|
assertEq(soaker.site, 'site_02_corner_block',
|
||||||
|
'storm_06 over any other yard is unmeasured — fly gardenfly over the new pairing before moving this');
|
||||||
|
const debutIdx = NIGHTS.findIndex((_, i) => nightAt(i).site === soaker.site);
|
||||||
|
const soakerIdx = NIGHTS.findIndex((_, i) => nightAt(i).storm === 'storm_06_soaker');
|
||||||
|
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', () => {
|
||||||
|
const flagged = NIGHTS.map((_, i) => nightAt(i)).filter((n) => n.gardenBeyondSaving);
|
||||||
|
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].site, 'backyard_01', 'THIS yard under THIS storm (bare 0.0, best buyable 27.7, win at 50)');
|
||||||
|
const idx = NIGHTS.findIndex((_, i) => nightAt(i).gardenBeyondSaving);
|
||||||
|
assert(idx !== NIGHTS.length - 1,
|
||||||
|
'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)');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* SHADES — the week. Lane A owns this file.
|
* SHADES — the week. Lane A owns this file.
|
||||||
|
* (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.)
|
||||||
*
|
*
|
||||||
* Five nights, one yard, one wallet. This is deliberately a thin wrapper around
|
* Six 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
|
||||||
@ -20,30 +22,51 @@ import { START_BUDGET, HARDWARE } from './contracts.js';
|
|||||||
* 1 Sea Breeze gust 10.5, no hail — the tutorial: nothing can hurt you
|
* 1 Sea Breeze gust 10.5, no hail — the tutorial: nothing can hurt you
|
||||||
* 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 Wild Night gust 30.0, hail — the one the whole repo tuned for
|
* 4 Southerly again gust 20.0, hail — the storm you know, the yard you don't
|
||||||
* 5 Ice Night gust 28.5, hail — less wind, more ice
|
* 5 Ice Night gust 28.5, hail — the most wind of the week, and worse ice
|
||||||
|
* 6 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. Nights 1/2/4/5 are the backyard;
|
* SPRINT10: a night is now a STORM and a SITE. Night 3 moves to the corner block
|
||||||
* night 3 moves to the corner block — the same "looks like night 2 and isn't"
|
* — the same "looks like night 2 and isn't" beat, sharpened: it's a new yard
|
||||||
* beat, sharpened: it's a new yard you've never rigged, its carport is a trap,
|
* you've never rigged, its carport is a trap, and its storm carries the
|
||||||
* and its storm carries the southerly change that makes the venturi scream. The
|
* southerly change that makes the venturi scream. The plain-string form is
|
||||||
* plain-string form is still accepted (defaults to backyard_01) so nothing that
|
* still accepted (defaults to backyard_01) so nothing that reads NIGHTS[i] as
|
||||||
* reads NIGHTS[i] as a storm key breaks.
|
* a storm key breaks.
|
||||||
*
|
*
|
||||||
* SPRINT11: a night is now a JOB. Same ladder, same yards — what's new is the
|
* SPRINT11: a night is now a JOB. What's new is the frame DESIGN.md has had
|
||||||
* frame DESIGN.md has had from line one: *"work arrives as callouts — each one a
|
* from line one: *"work arrives as callouts — each one a site with existing
|
||||||
* site with existing conditions, a client brief, a budget, and a forecast
|
* conditions, a client brief, a budget, and a forecast window."* You are not
|
||||||
* window."* You are not surviving five nights; you are working five jobs.
|
* surviving the week; you are working its jobs.
|
||||||
*
|
*
|
||||||
* The week that fell out of the data, once it was asked to name a client: the
|
* The week that fell out of the data, once it was asked to name a client: the
|
||||||
* Hendersons are away (SPRINT11's own line — they leave for Cairns), so four of
|
* Hendersons are away (SPRINT11's own line — they leave for Cairns), so the
|
||||||
* the five nights are the same yard, on retainer, through a bad week. Night 3 is
|
* backyard nights are the same yard, on retainer, through a bad week. A
|
||||||
* a different site, so it is necessarily a different client — a short-notice
|
* different site is necessarily a different client.
|
||||||
* callout to a yard you have never rigged, on the night the change comes early.
|
*
|
||||||
* That is not flavour bolted on: it is what `site: site_02_corner_block` already
|
* SPRINT16 gate 4 (Lane D, by the sprint's explicit grant): SIX nights. The
|
||||||
* MEANT, finally said out loud. The corner block lands harder because it's a
|
* swing lawn enters the rotation at night 4 under the southerly — the ONE
|
||||||
* stranger's place and you're there at short notice.
|
* 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
|
||||||
|
* soaker flies night 6 over the corner block — a yard night 3 taught — because
|
||||||
|
* 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
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* ⚠️ 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
|
||||||
|
* soaker is six distinct storms; the swing lawn's debut must REPEAT a storm
|
||||||
|
* the player already survived; so exactly one shipped storm leaves the week —
|
||||||
|
* and every other candidate is load-bearing (gentle = the tutorial and the
|
||||||
|
* 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;
|
||||||
|
* icenight = the beyond-saving flag, canon). THE WILD NIGHT (storm_02) is out
|
||||||
|
* of the rotation. Its storm file, its tests, and the pinned backyard
|
||||||
|
* separation all stand — it is un-flown by the week, not deleted. If that
|
||||||
|
* 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.
|
||||||
*
|
*
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
@ -72,17 +95,29 @@ export const NIGHTS = [
|
|||||||
+ 'to tie off to.',
|
+ 'to tie off to.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
storm: 'storm_02_wildnight', site: 'backyard_01',
|
// SPRINT16 gate 4 — THE NEW SITE, and nothing else new: Tuesday's southerly
|
||||||
client: 'the Hendersons', addr: '14 Kurrajong St — the backyard',
|
// over D's swing lawn. The brief does what "plenty to tie off to" did for
|
||||||
brief: 'Back at the Hendersons\', and this is the night the whole week was pointing at. Rig it like '
|
// the carport — the client's own voice sells the trap (the crossbar between
|
||||||
+ 'they\'re watching, because Friday they will be.',
|
// the swing apexes is a dead-level rail and NOT an anchor; the apexes are
|
||||||
|
// 0.45 steel that bills $140) — and the kid gives the bed its reason.
|
||||||
|
storm: 'storm_03_southerly', site: 'site_03_swing_lawn',
|
||||||
|
client: 'the Delaneys', addr: '31 Ferndale Ave — the swing lawn',
|
||||||
|
brief: 'Same southerly as Tuesday, over a lawn you\'ve never rigged. The swing set is Ruby\'s — '
|
||||||
|
+ '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.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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',
|
||||||
brief: 'Less wind than last night and more ice, which is worse for the bed and easier on the rig. '
|
// SPRINT16 gate 4 wording note (D): this brief used to open "Less wind than
|
||||||
+ 'Last night before they land. Nobody\'s pretending the seedlings ride this one out — keep '
|
// last night" — true when the wild night (gust 30) sat at night 4. The wild
|
||||||
+ 'the sail on its feet and the ice off the windows.',
|
// night left the six-night rotation (see the ladder comment), so the ice
|
||||||
|
// night is now the most wind the week throws; the sentence moved with the
|
||||||
|
// facts. The teaching half — level with the player that the bed is already
|
||||||
|
// lost — is untouched.
|
||||||
|
brief: 'The most wind of the week and golf-ball ice with it. Last night before they land. '
|
||||||
|
+ 'Nobody\'s pretending the seedlings ride this one out — keep the sail on its feet and '
|
||||||
|
+ 'the ice off the windows.',
|
||||||
// SPRINT13 gate 1.4 — A's ruling, DESIGN CANON: night 5's garden is BEYOND
|
// SPRINT13 gate 1.4 — A's ruling, DESIGN CANON: night 5's garden is BEYOND
|
||||||
// SAVING, by design, at any price. Measured game-true (B's pay table and
|
// SAVING, by design, at any price. 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
|
||||||
@ -99,6 +134,20 @@ export const NIGHTS = [
|
|||||||
// 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,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// SPRINT16 gate 4 — THE NEW STORM, over a yard night 3 taught. C's pairing
|
||||||
|
// receipt (THREADS [C] 2026-07-20): the soaker MUST fly the corner block —
|
||||||
|
// the backyard caps hail cover at ~31% and the bet has no win there. The
|
||||||
|
// brief sells the WRONG default the way the carport got sold: fine hail
|
||||||
|
// through a 2 mm weave is exactly the night cloth loses, and the client is
|
||||||
|
// the one telling you it's easy. The forecast (C's stones/rain lines) is
|
||||||
|
// where the truth argues back — the brief stays in character.
|
||||||
|
storm: 'storm_06_soaker', site: 'site_02_corner_block',
|
||||||
|
client: 'the Vasilaros place', addr: '2 Bight Rd — the corner block',
|
||||||
|
brief: 'The Vasilaros place again — they asked for you by name this time. Hardly any wind in it, '
|
||||||
|
+ 'just rain and a fine rattle of hail; the kind of night you could rig in your sleep. '
|
||||||
|
+ 'Whatever you flew last time will do, they said.',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user