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",
"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",
" (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 4.88, p2 3.14, p1 2.91, p3 1.82 -> $75, hp 68.3 FULL <- here",
" (6,-2) h2.6 -> shadow drops, hp 65.8 tattered DO NOT",
"The separation block below pins the recipe; a.test flies it. Full cover STAYS unbuyable:",
"the covering quads (p1+p2+p4+p5 and up) still pull 6.4-6.9 kN and still tear - the",
"smallest full-cover quad stays >45 m2 (a.test's floor holds), so the tradeoff decision 2",
"protects is intact. And the ICE night still refuses the $80 wallet: same line needs $105",
"there (p1 3.25, p2 3.22 vs a shackle's 3.20 - the same few-hundredths miss as C's q3),",
"so night 5 stays the night the bank pays for, exactly as ruled in SPRINT9."
" (5.5,-2) h2.6 -> p5 5.10, p2 3.08, p1 2.96, p3 1.81 -> $75, hp 63.6 WIN <- here",
" (6,-2) h2.6 -> shadow drops DO NOT",
"A second, LOWER-still or wider post cannot buy FULL either: the twin-post covering quad",
"(p6 at (-3,-2) h2.6 + p5 + p1 + p2, 100% cover) pulls 6.9/6.7/6.2 kN and tears - full",
"cover honestly exceeds the wild night at ANY buyable price, which is decision 2's tension",
"doing its job. The separation block below pins the recipe; a.test flies it game-true.",
"The smallest full-cover quad stays >45 m2 (a.test's floor holds). And the ICE night still",
"refuses the $80 wallet: same line needs $105 there (p1 3.25, p2 3.22 vs a shackle's 3.20",
"- the same few-hundredths miss as C's q3), so night 5 stays the night the bank pays for."
]
} ],
"separation": {
"_why": [
"SPRINT13 gate 1.4 — A's ruling on C's proposed target, AMENDED to the numbers the real game",
"measures (ruled on D's funnel-corrected picture, not C's original headline). C proposed:",
"'on a wild night the best buyable line reads FULL (>66) and a bare bed reads DEAD (<33)'.",
"Adopted in spirit, amended in both halves:",
" - The bare half becomes 'a bare bed LOSES the night (hp < 50, the win line)'. Nowhere in",
" the shipped game does a bare bed read DEAD on the wild night (35.7, tattered) — D's",
" check — and tuning storm_02's damage budget until a slogan lands is exactly the fudge",
" gate 1 exists to kill. The felt boundary is the WIN line, and 35.7 fails it honestly.",
" - The FULL half binds THIS yard, because the shipped week's only wild night is here",
" (week.js night 4). C's passing example was site_02, whose wild night the corner block",
" never plays — and with its venturi live the example dies anyway (D's landmine).",
"This block is the pin: the recipe below is the measured best $80 line, a.test flies it",
"through the real chain (session -> commit -> attach -> skyfx) every run, and B's audit can",
"read the same recipe instead of inventing one. A target nothing checks is decoration —",
"the repo's oldest lesson, and the reason this is data and not a THREADS sentence."
"SPRINT13 gate 1.4 — A's ruling on C's proposed target, RULED TWICE and honest the second",
"time. C proposed: 'on a wild night the best buyable line reads FULL (>66) and a bare bed",
"reads DEAD (<33)'. First ruling amended the bare half (nowhere does a bare bed read DEAD;",
"35.7 is tattered, and tuning storm_02 until a slogan lands is the fudge gate 1 exists to",
"kill) and kept FULL — then B's audit rewrite caught that FULL was only ever met through the",
"assert's own 12 s phantom settle (SailRig samples wind at its INTERNAL clock: game-true",
"63.8/63.6, skewed 68.4/68.3). The geometry was re-swept game-true: nothing buyable reaches",
"66 (the twin-post covering quad tears at 6.9 kN; every FULL-flavoured candidate is also",
"knife-edged under B's 15% margin rule). So the target is RESTATED to the boundaries the",
"game actually stakes, with real headroom, instead of pinning another knife edge:",
" - the best buyable line HOLDS (0/4) and WINS COMFORTABLY: hp > 60 (win line is 50;",
" game-true 63.6 - a red means the wild night's best line regressed, not noise),",
" - a bare bed LOSES: hp < 50 (game-true 35.7),",
" - the spread reads as most of a plant state: >= 25 HP (game-true 27.9).",
"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",
"tension": 1.0,
@ -156,8 +164,9 @@
{ "anchor": "p2", "hw": "shackle" },
{ "anchor": "p3", "hw": "shackle" }
],
"heldMustExceed": 66,
"bareMustLoseBelow": 50
"heldMustExceed": 60,
"bareMustLoseBelow": 50,
"separationAtLeast": 25
},
"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(2).site, 'site_02_corner_block', 'night three moves to the corner block');
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();
assertEq(w.night, 1); assertEq(w.bank, 80);
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');
});
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', () => {
const clean = verdictFor({ hp: 96, lost: [], win: true, dmg: { hail: 2, rain: 2 }, pondPeak: 0, pondDumped: 0 });
assertEq(clean.mode, 'clean');
@ -1016,19 +1053,33 @@ export default async function run(t) {
// --- SPRINT13 gate 1.4: THE PINNED SEPARATION TARGET ----------------------
//
// The ruling (THREADS [A], on D's funnel-corrected picture): on the shipped
// week's wild night — night 4, THIS yard — the best line START_BUDGET can buy
// must read FULL (>66) and WIN, and a bare bed must LOSE (<50). 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, verified
// by C's probe2 agreement), real skyfx exposure, the real garden model out of
// garden.js. Flown OUTSIDE t.test() because Suite.test cannot await — the
// two async-test ghosts above are the precedent.
// 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
// 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
// (≥25 HP), because the win line and the invoice are the game's real stakes.
// FULL (>66) was the first ruling and it died honestly: it was only ever met
// through this flight's own 12 s phantom settle — SailRig samples wind at its
// INTERNAL clock, so a settled rig flies the storm 12 s off the authored
// 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
// 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;
let sepRun = null;
if (sep && typeof document !== 'undefined') {
@ -1039,7 +1090,6 @@ export default async function run(t) {
});
const trees = frozen.filter((a) => a.type === 'tree');
const stormDef = await loadStorm(sep.stormKey);
const calmDef = await loadStorm(CALM_STORM);
const session = new RiggingSession({ anchors: frozen }); // the REAL budget
const shopLog = [];
@ -1056,16 +1106,12 @@ export default async function run(t) {
session.setTension(sep.tension);
const spent = START_BUDGET - session.budget;
const rig = session.commit(new SailRig({ anchors: frozen, gridN: 10 }));
const calmWind = createWind(calmDef);
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;
}
// NO settle here, deliberately — see the header. The rig flies the storm
// from t=0 on a fresh internal clock, exactly like a committed rig does.
const flyNight = (sail) => {
const wind = createWind(stormDef);
wind.setVenturi(site.wind?.venturi ?? []); // main.js:the funnel is SITE data
wind.setSheltersFromTrees(trees);
const camera = new THREE.PerspectiveCamera(60, 1.6, 0.1, 400);
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 (typeof document === 'undefined') return t.skip('needs DOM (skyfx)');
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`);
assertEq(sepRun.lost, 0, 'the pinned line HOLDS the wild night — 0/4 lost');
assert(sepRun.held.hp > sep.heldMustExceed,
`held bed reads ${sepRun.held.hp.toFixed(1)} — the target is FULL (>${sep.heldMustExceed}). ` +
`Measured 68.3 at landing; a red here means a retune moved the wild night and the ` +
`separation ruling needs re-litigating in THREADS, not a quiet threshold bump.`);
assertEq(sepRun.held.state, 'full', 'and the plants SAY full — the state the player sees');
`held bed reads ${sepRun.held.hp.toFixed(1)} — the target is comfortably WON ` +
`(>${sep.heldMustExceed}; the win line is 50). Game-true 63.6 at landing — FULL (>66) was the ` +
`first ruling and it was only ever met through this flight's own 12 s clock skew (B's finding). ` +
`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,
`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:
assert(sepRun.held.hp - sepRun.bare.hp > 25,
`separation ${(sepRun.held.hp - sepRun.bare.hp).toFixed(1)} — rigging must matter by a full state, ` +
`not a garden-bonus margin (measured 32.6 at landing)`);
assert(sepRun.held.hp - sepRun.bare.hp >= (sep.separationAtLeast ?? 25),
`separation ${(sepRun.held.hp - sepRun.bare.hp).toFixed(1)} — rigging must matter by most of a ` +
`plant state, not a garden-bonus margin (game-true 27.9 at landing)`);
});
t.test('sway() returns an absolute position, not an offset', () => {