Sprint 13 gate 1.4 RE-RULED on B's skew finding: the pin measures the game now, and the target is restated

- a.test's separation flight had the phantom 12 s settle B caught (SailRig
  samples wind at its INTERNAL clock; the storm flew 12 s off the authored
  curve). Settle deleted — commit->attach->storm from t=0, C's corrected bench
  pattern; venturi wired from site data so the flight can never assume a yard.
- Game-true the line reads 63.6, not 68.3 — FULL (>66) was met by the harness,
  not the game, and the re-sweep says nothing buyable reaches 66 (the twin-post
  covering quad tears at 6.9 kN). Target RESTATED to the game's real stakes,
  with headroom: held >60 and 0/4 (win line 50; measured 63.6), bare <50
  (35.7), separation >=25 (27.9). Field names kept — B's gardenfly reads them.
- DESIGN CANON in the site block: the wild night's garden is WON SCARRED,
  never FULL; the week's arc is full/full/full, won-tattered, beyond-saving.
- p5 _why table corrected to game-true numbers (first sweep ran ~4 HP hot).
This commit is contained in:
type-two 2026-07-18 14:11:58 +10:00
parent b5643922a4
commit 1c0fcc757b
2 changed files with 110 additions and 54 deletions

View File

@ -115,38 +115,46 @@
"", "",
"WHY (5.5, -2) at h 2.6 and not somewhere kinder: the LOW top is the point - it twists the", "WHY (5.5, -2) at h 2.6 and not somewhere kinder: the LOW top is the point - it twists the",
"p1+p2+p3+p5 quad so it spills the gusts (small+twisted survives, DESIGN.md), and the", "p1+p2+p3+p5 quad so it spills the gusts (small+twisted survives, DESIGN.md), and the",
"position is swept against the $80 wallet:", "position is swept against the $80 wallet. NUMBERS CORRECTED to the game-true chain after",
"B's 12s-skew finding (no phantom settle; the first sweep's hp column ran ~4 HP hot):",
" (4.5,-2) h2.6 -> p1 3.35 kN, needs RATED, line costs $90 DO NOT", " (4.5,-2) h2.6 -> p1 3.35 kN, needs RATED, line costs $90 DO NOT",
" (5,-2.5) h2.8 -> p5 6.20, p1 3.75 -> $90 DO NOT", " (5,-2.5) h2.8 -> p5 6.20, p1 3.75 -> $90 DO NOT",
" (5.5,-2) h2.8 -> p2 3.08, p1 3.06 (4% margin on shackle) - legal but knife-edged", " (5.5,-2) h2.6 -> p5 5.10, p2 3.08, p1 2.96, p3 1.81 -> $75, hp 63.6 WIN <- here",
" (5.5,-2) h2.6 -> p5 4.88, p2 3.14, p1 2.91, p3 1.82 -> $75, hp 68.3 FULL <- here", " (6,-2) h2.6 -> shadow drops DO NOT",
" (6,-2) h2.6 -> shadow drops, hp 65.8 tattered DO NOT", "A second, LOWER-still or wider post cannot buy FULL either: the twin-post covering quad",
"The separation block below pins the recipe; a.test flies it. Full cover STAYS unbuyable:", "(p6 at (-3,-2) h2.6 + p5 + p1 + p2, 100% cover) pulls 6.9/6.7/6.2 kN and tears - full",
"the covering quads (p1+p2+p4+p5 and up) still pull 6.4-6.9 kN and still tear - the", "cover honestly exceeds the wild night at ANY buyable price, which is decision 2's tension",
"smallest full-cover quad stays >45 m2 (a.test's floor holds), so the tradeoff decision 2", "doing its job. The separation block below pins the recipe; a.test flies it game-true.",
"protects is intact. And the ICE night still refuses the $80 wallet: same line needs $105", "The smallest full-cover quad stays >45 m2 (a.test's floor holds). And the ICE night still",
"there (p1 3.25, p2 3.22 vs a shackle's 3.20 - the same few-hundredths miss as C's q3),", "refuses the $80 wallet: same line needs $105 there (p1 3.25, p2 3.22 vs a shackle's 3.20",
"so night 5 stays the night the bank pays for, exactly as ruled in SPRINT9." "- the same few-hundredths miss as C's q3), so night 5 stays the night the bank pays for."
] ]
} ], } ],
"separation": { "separation": {
"_why": [ "_why": [
"SPRINT13 gate 1.4 — A's ruling on C's proposed target, AMENDED to the numbers the real game", "SPRINT13 gate 1.4 — A's ruling on C's proposed target, RULED TWICE and honest the second",
"measures (ruled on D's funnel-corrected picture, not C's original headline). C proposed:", "time. C proposed: 'on a wild night the best buyable line reads FULL (>66) and a bare bed",
"'on a wild night the best buyable line reads FULL (>66) and a bare bed reads DEAD (<33)'.", "reads DEAD (<33)'. First ruling amended the bare half (nowhere does a bare bed read DEAD;",
"Adopted in spirit, amended in both halves:", "35.7 is tattered, and tuning storm_02 until a slogan lands is the fudge gate 1 exists to",
" - The bare half becomes 'a bare bed LOSES the night (hp < 50, the win line)'. Nowhere in", "kill) and kept FULL — then B's audit rewrite caught that FULL was only ever met through the",
" the shipped game does a bare bed read DEAD on the wild night (35.7, tattered) — D's", "assert's own 12 s phantom settle (SailRig samples wind at its INTERNAL clock: game-true",
" check — and tuning storm_02's damage budget until a slogan lands is exactly the fudge", "63.8/63.6, skewed 68.4/68.3). The geometry was re-swept game-true: nothing buyable reaches",
" gate 1 exists to kill. The felt boundary is the WIN line, and 35.7 fails it honestly.", "66 (the twin-post covering quad tears at 6.9 kN; every FULL-flavoured candidate is also",
" - The FULL half binds THIS yard, because the shipped week's only wild night is here", "knife-edged under B's 15% margin rule). So the target is RESTATED to the boundaries the",
" (week.js night 4). C's passing example was site_02, whose wild night the corner block", "game actually stakes, with real headroom, instead of pinning another knife edge:",
" never plays — and with its venturi live the example dies anyway (D's landmine).", " - the best buyable line HOLDS (0/4) and WINS COMFORTABLY: hp > 60 (win line is 50;",
"This block is the pin: the recipe below is the measured best $80 line, a.test flies it", " game-true 63.6 - a red means the wild night's best line regressed, not noise),",
"through the real chain (session -> commit -> attach -> skyfx) every run, and B's audit can", " - a bare bed LOSES: hp < 50 (game-true 35.7),",
"read the same recipe instead of inventing one. A target nothing checks is decoration —", " - the spread reads as most of a plant state: >= 25 HP (game-true 27.9).",
"the repo's oldest lesson, and the reason this is data and not a THREADS sentence." "DESIGN CANON, written down on purpose: the wild night's garden is WON SCARRED, never FULL.",
"The week's garden arc is full/full/full on nights 1-3, won-but-tattered on night 4, and",
"beyond saving on night 5 (see week.js night 5) - the state bands are the yard's face, but",
"the stakes the player plays for are the WIN line and the invoice, and those are what pin.",
"This block is the pin: a.test flies the recipe through the real chain (session -> commit ->",
"attach -> skyfx, NO settle, from t=0) every run, and B's audit reads the same recipe",
"(heldMustExceed/bareMustLoseBelow are B's field names too - renaming them breaks gardenfly).",
"A target nothing checks is decoration - the repo's oldest lesson."
], ],
"stormKey": "storm_02_wildnight", "stormKey": "storm_02_wildnight",
"tension": 1.0, "tension": 1.0,
@ -156,8 +164,9 @@
{ "anchor": "p2", "hw": "shackle" }, { "anchor": "p2", "hw": "shackle" },
{ "anchor": "p3", "hw": "shackle" } { "anchor": "p3", "hw": "shackle" }
], ],
"heldMustExceed": 66, "heldMustExceed": 60,
"bareMustLoseBelow": 50 "bareMustLoseBelow": 50,
"separationAtLeast": 25
}, },
"shed": { "model": "shed_01_v1", "x": 11.8, "z": 6.2, "rotYDeg": -90 }, "shed": { "model": "shed_01_v1", "x": 11.8, "z": 6.2, "rotYDeg": -90 },

View File

@ -221,6 +221,10 @@ export default async function run(t) {
assertEq(nightAt(4).storm, 'storm_02b_icenight', 'night five is the ice night'); assertEq(nightAt(4).storm, 'storm_02b_icenight', 'night five is the ice night');
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 rest are the backyard');
// 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.
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`);
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');
@ -797,6 +801,39 @@ 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", () => {
// SPRINT13 gate 1.4, the night-5 ruling. Measured game-true (B's pay table,
// 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
// verdict must stop implying a better rig existed. The worst liar was
// 'uncovered' ("the hail fell where your sail wasn't") on a night where no
// WHERE saves it.
const dmg = { hail: 90, rain: 5 };
const held = verdictFor({ hp: 25, lost: [], win: false, dmg, pondPeak: 0, pondDumped: 0, beyondSaving: true });
assertEq(held.mode, 'beyond-saving');
assert(/BEYOND SAVING/.test(held.verdict), `says so plainly: "${held.verdict}"`);
assert(/held|did it/i.test(held.verdict), 'and a 4/4 hold is CREDITED — keeping the steel was the job');
assert(!/wasn't|skimp/i.test(held.verdict), 'no placement-blame, no skimp-blame');
// Losing your sail on the unsavable night is still yours to own.
const shackle = { name: 'shackle', rating: 3200 };
const torn = verdictFor({ hp: 5,
lost: [{ hw: shackle, anchorId: 'p1', anchor: { ratingHint: 1 } }],
win: false, dmg, pondPeak: 0, pondDumped: 0, beyondSaving: true });
assertEq(torn.mode, 'beyond-saving');
assert(/shackle at P1/.test(torn.verdict), `still names the steel that went: "${torn.verdict}"`);
// The flag defaults OFF and changes nothing anywhere else: the two garden
// deaths keep their opposite sentences (mutation check — delete the branch
// and the first assert above reds; pass the flag everywhere and THIS one does).
const uncov = verdictFor({ hp: 20, lost: [], win: false, dmg: { hail: 70, rain: 10 }, pondPeak: 0, pondDumped: 0 });
assertEq(uncov.mode, 'uncovered', 'a savable night keeps its honest placement lesson');
// And a WIN on a flagged night ignores the flag — week.js says the flag
// would be stale, and a stale excuse must never eat a real win.
const win = verdictFor({ hp: 90, lost: [], win: true, dmg, pondPeak: 0, pondDumped: 0, beyondSaving: true });
assertEq(win.mode, 'clean');
});
t.test('a clean hold reads as a clean hold, and the broom gets its credit', () => { t.test('a clean hold reads as a clean hold, and the broom gets its credit', () => {
const clean = verdictFor({ hp: 96, lost: [], win: true, dmg: { hail: 2, rain: 2 }, pondPeak: 0, pondDumped: 0 }); const clean = verdictFor({ hp: 96, lost: [], win: true, dmg: { hail: 2, rain: 2 }, pondPeak: 0, pondDumped: 0 });
assertEq(clean.mode, 'clean'); assertEq(clean.mode, 'clean');
@ -1016,19 +1053,33 @@ export default async function run(t) {
// --- SPRINT13 gate 1.4: THE PINNED SEPARATION TARGET ---------------------- // --- SPRINT13 gate 1.4: THE PINNED SEPARATION TARGET ----------------------
// //
// The ruling (THREADS [A], on D's funnel-corrected picture): on the shipped // The ruling, RESTATED after B's skew finding (THREADS [A], twice — read the
// week's wild night — night 4, THIS yard — the best line START_BUDGET can buy // second entry): on the shipped week's wild night — night 4, THIS yard — the
// must read FULL (>66) and WIN, and a bare bed must LOSE (<50). The recipe is // best line START_BUDGET can buy must HOLD and WIN comfortably (>60), a bare
// DATA (site.separation) so B's audit reads the same one, and this flight is // bed must LOSE (<50), and the spread must read as most of a plant state
// the pin: real shop at the real budget, real commit()->attach() (D's // (≥25 HP), because the win line and the invoice are the game's real stakes.
// skipped-attach landmine — session.commit(rig) IS the attach path, verified // FULL (>66) was the first ruling and it died honestly: it was only ever met
// by C's probe2 agreement), real skyfx exposure, the real garden model out of // through this flight's own 12 s phantom settle — SailRig samples wind at its
// garden.js. Flown OUTSIDE t.test() because Suite.test cannot await — the // INTERNAL clock, so a settled rig flies the storm 12 s off the authored
// two async-test ghosts above are the precedent. // curve while the sky flies t=0..90 (B's three-way probe: game-true 63.8 /
// skewed 68.4 / shape-only 64.3). The settle is GONE: in the real game the
// rig does not exist before commit, and commit→attach→storm is one keypress
// (C's bench correction, same day, same bug).
//
// The recipe is DATA (site.separation) so B's audit reads the same one, and
// this flight is the pin: real shop at the real budget, real
// commit()->attach() (D's skipped-attach landmine — session.commit(rig) IS
// the attach path), real skyfx exposure, the real garden model out of
// garden.js. Wind is wired the way main.js wires a site (venturi from site
// data — backyard's list is empty, but the wiring must not ASSUME that — plus
// tree shelters); switch to C's windForSite at integration, it is the same
// wiring behind one door. Flown OUTSIDE t.test() because Suite.test cannot
// await — the two async-test ghosts above are the precedent.
// //
// Sway is frozen (balance.test's reasoning): the recipe is all posts, whose // Sway is frozen (balance.test's reasoning): the recipe is all posts, whose
// real sway is constant anyway, so frozen here means "identical to the game", // real sway is constant anyway, so frozen here means "identical to the game",
// not "conveniently calm". // not "conveniently calm". (C's landmine 2 — a frozen TREE under-reads — is
// exactly why the pinned recipe staying post-only matters to this harness.)
const sep = site.separation; const sep = site.separation;
let sepRun = null; let sepRun = null;
if (sep && typeof document !== 'undefined') { if (sep && typeof document !== 'undefined') {
@ -1039,7 +1090,6 @@ export default async function run(t) {
}); });
const trees = frozen.filter((a) => a.type === 'tree'); const trees = frozen.filter((a) => a.type === 'tree');
const stormDef = await loadStorm(sep.stormKey); const stormDef = await loadStorm(sep.stormKey);
const calmDef = await loadStorm(CALM_STORM);
const session = new RiggingSession({ anchors: frozen }); // the REAL budget const session = new RiggingSession({ anchors: frozen }); // the REAL budget
const shopLog = []; const shopLog = [];
@ -1056,16 +1106,12 @@ export default async function run(t) {
session.setTension(sep.tension); session.setTension(sep.tension);
const spent = START_BUDGET - session.budget; const spent = START_BUDGET - session.budget;
const rig = session.commit(new SailRig({ anchors: frozen, gridN: 10 })); const rig = session.commit(new SailRig({ anchors: frozen, gridN: 10 }));
// NO settle here, deliberately — see the header. The rig flies the storm
const calmWind = createWind(calmDef); // from t=0 on a fresh internal clock, exactly like a committed rig does.
calmWind.setSheltersFromTrees(trees);
let prepT = 0;
for (let i = 0; i < Math.round(12 / FIXED_DT); i++) {
rig.step(FIXED_DT, calmWind, prepT % 3); prepT += FIXED_DT;
}
const flyNight = (sail) => { const flyNight = (sail) => {
const wind = createWind(stormDef); const wind = createWind(stormDef);
wind.setVenturi(site.wind?.venturi ?? []); // main.js:the funnel is SITE data
wind.setSheltersFromTrees(trees); wind.setSheltersFromTrees(trees);
const camera = new THREE.PerspectiveCamera(60, 1.6, 0.1, 400); const camera = new THREE.PerspectiveCamera(60, 1.6, 0.1, 400);
camera.position.set(0, 2, 8); camera.position.set(0, 2, 8);
@ -1092,7 +1138,7 @@ export default async function run(t) {
} }
} }
t.test('PINNED: the wild night separates — best buyable line FULL and winning, bare bed losing', () => { t.test('PINNED: the wild night separates — best buyable line holds and wins, bare bed loses', () => {
if (!sep) throw new Error('backyard_01 lost its separation block — the target is decoration again'); if (!sep) throw new Error('backyard_01 lost its separation block — the target is decoration again');
if (typeof document === 'undefined') return t.skip('needs DOM (skyfx)'); if (typeof document === 'undefined') return t.skip('needs DOM (skyfx)');
if (sepRun.err) throw new Error(`separation flight died: ${sepRun.err}`); if (sepRun.err) throw new Error(`separation flight died: ${sepRun.err}`);
@ -1100,16 +1146,17 @@ export default async function run(t) {
assert(sepRun.spent <= START_BUDGET, `recipe costs $${sepRun.spent} — must be buyable night 1`); assert(sepRun.spent <= START_BUDGET, `recipe costs $${sepRun.spent} — must be buyable night 1`);
assertEq(sepRun.lost, 0, 'the pinned line HOLDS the wild night — 0/4 lost'); assertEq(sepRun.lost, 0, 'the pinned line HOLDS the wild night — 0/4 lost');
assert(sepRun.held.hp > sep.heldMustExceed, assert(sepRun.held.hp > sep.heldMustExceed,
`held bed reads ${sepRun.held.hp.toFixed(1)} — the target is FULL (>${sep.heldMustExceed}). ` + `held bed reads ${sepRun.held.hp.toFixed(1)} — the target is comfortably WON ` +
`Measured 68.3 at landing; a red here means a retune moved the wild night and the ` + `(>${sep.heldMustExceed}; the win line is 50). Game-true 63.6 at landing — FULL (>66) was the ` +
`separation ruling needs re-litigating in THREADS, not a quiet threshold bump.`); `first ruling and it was only ever met through this flight's own 12 s clock skew (B's finding). ` +
assertEq(sepRun.held.state, 'full', 'and the plants SAY full — the state the player sees'); `A red here means a retune moved the wild night: re-litigate in THREADS, no quiet threshold bump.`);
assert(sepRun.held.state !== 'dead', 'the held bed is ALIVE — tattered is the wild night speaking');
assertLess(sepRun.bare.hp, sep.bareMustLoseBelow, assertLess(sepRun.bare.hp, sep.bareMustLoseBelow,
`bare bed must LOSE the night (<${sep.bareMustLoseBelow}) — measured 35.7 at landing`); `bare bed must LOSE the night (<${sep.bareMustLoseBelow}) — game-true 35.7 at landing`);
// The felt spread, stated so a red prints the whole story: // The felt spread, stated so a red prints the whole story:
assert(sepRun.held.hp - sepRun.bare.hp > 25, assert(sepRun.held.hp - sepRun.bare.hp >= (sep.separationAtLeast ?? 25),
`separation ${(sepRun.held.hp - sepRun.bare.hp).toFixed(1)} — rigging must matter by a full state, ` + `separation ${(sepRun.held.hp - sepRun.bare.hp).toFixed(1)} — rigging must matter by most of a ` +
`not a garden-bonus margin (measured 32.6 at landing)`); `plant state, not a garden-bonus margin (game-true 27.9 at landing)`);
}); });
t.test('sway() returns an absolute position, not an offset', () => { t.test('sway() returns an absolute position, not an offset', () => {