Gate 0': the harnesses AGREE — and my settle claim is retracted

Ran every remaining suite-vs-game difference one at a time against the real
storm. None of them moves the corner count:

  live calm settle (running clock + calmWind)  entry 1.94 -> 0.40 kN, 2 lost
  live tree sway                               t2 4.33 -> 4.55 kN,    2 lost
  walked repair timing (~4 s)                                          2 lost
  all three together                                          hp 58,   2 lost

Then checked the structural candidates: anchors are bit-identical between
harnesses (all four, 0.0000 m), ring order is identical (p4,t2,t2b,p3), and
wind.sample() at t2 matches to 2 dp (36.49 m/s @ t=75.9) across suite, suite
with shelters, and the live router.

So I re-ran the live game itself, fresh page, A's exact line, tension 1.0:
  live  -> hp 58, 2 lost [t2,t2b], t2 peak 4.55 kN
  suite -> hp 58, 2 lost [t2,t2b], t2 peak 4.54 kN
They agree. The suite was right.

RETRACTED: my Sprint-7 "12 s settle flips 2 lost -> 1". It does not reproduce —
0 s and 12 s both end 2 lost, because the storm's own peak (4.54 kN at t=75.9)
dwarfs the attach transient. The settle is a real fidelity fix and NOT the
corner-count cause. B's arithmetic was right from their first post: t2 pulls
~4.5 kN, a shackle is rated 3.2, and the spare cannot save it (measured: t2b
blew @9 s, repaired, blew again).

Landed anyway, because it is still true and still worth guarding: fly() now
settles the way main.js drives prep (calmWind on a running clock, not the storm
frozen at t=0), and a new "the yard is SETTLED when the storm starts" assert
holds every run under 1.0 kN at entry. 266 pass / 1 red — the red is the
pyrrhic-win design call, not a bug, and it now says so.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-17 13:19:10 +10:00
parent 6913cd4dde
commit a6ffd52209

View File

@ -33,6 +33,8 @@ const [CARABINER, SHACKLE, RATED] = HARDWARE;
/** main.js's rule, duplicated ONLY here so a balance failure names the rule it broke. */
const WIN = (hp, lost) => hp >= 50 && lost < 2;
/** main.js's CALM_STORM — what wind.use() hands the rig outside a storm, and so what settles it. */
const CALM_STORM = 'storm_01_gentle';
const GARDEN_DRAIN = 0.9; // main.js
const W_HAIL = 5.0; // main.js, decision 13 — integration weights
const W_RAIN = 0.25;
@ -112,6 +114,11 @@ function shop(yard, ids, hw, spares = 0, tension = 1.0) {
async function fly(yard, session, stormName, { repair = false, broom = false } = {}) {
const def = await loadStorm(stormName);
const wind = createWind(def);
// The settle below runs on the CALM day, because that is what main.js hands the rig outside a
// storm: `wind.use(to === 'storm' ? winds[stormKey] : calmWind)`.
const calmDef = await loadStorm(CALM_STORM);
const calmWind = createWind(calmDef);
calmWind.setSheltersFromTrees(yard.anchors.filter((a) => a.type === 'tree'));
// main.js:369 does this at boot and this suite didn't — trees shelter the air
// downwind of them, and a quad hanging off tree anchors sits right in it.
// (Measured during gate 0: it doesn't change storm_02's verdict, but a harness
@ -149,15 +156,34 @@ async function fly(yard, session, stormName, { repair = false, broom = false } =
// anchors sitting inside those shadows.
wind.setSheltersFromTrees(yard.anchors.filter((a) => a.type === 'tree'));
// THE SETTLE (gate 0, cause #2, found by Lane D 2026-07-18). A player plays
// through prep, so ~12 s of world.update pass before ENTER and the cloth +
// tree sway reach steady state. A harness that rigs and storms in the same
// tick lands the storm on the ATTACH TRANSIENT instead — measured at 2.7× the
// settled load on tree corners (t2 4.54 kN unsettled vs 1.71 settled), loud
// enough to swamp tension entirely. Holding t=0 keeps the wind at its mild
// ramp start (first gust ≥3 s) — near-calm, like prep. Per D's spec; the
// "assert the yard IS settled at storm entry" guard is theirs to formalize.
for (let i = 0, n = Math.round(12 / FIXED_DT); i < n; i++) rig.step(FIXED_DT, wind, 0);
// THE SETTLE. A player plays through prep, so ~12 s pass before ENTER and the cloth reaches
// steady state. A harness that rigs and storms in the same tick lands the storm on the ATTACH
// TRANSIENT instead.
//
// Now driven the way main.js drives prep, which is the last of the three suite-vs-game
// differences gate 0' was chartered to close: `wind.use(calmWind)` on the phase change, and
// `windTime()` returning `simT % calmWind.duration` — i.e. CALM WIND ON A RUNNING CLOCK, not the
// storm's wind frozen at t=0. Measured, Lane D 2026-07-18, this quad:
// frozen t=0, storm wind → 1.94 kN sitting on the corners at storm entry
// calm wind, clock running → 0.40 kN ← what a player actually walks in with
//
// ⚠️ AND THE HONEST PART: this does NOT change the corner count, and my Sprint-7 claim that it
// did is RETRACTED. Measured on current main, live game, fresh page, one variable:
// 0 s settle → 2 lost [t2,t2b], t2 peak 4.54 kN
// 12 s settle → 2 lost [t2,t2b], t2 peak 4.55 kN
// The storm's own peak (4.54 kN at t=75.9) dwarfs the transient, so the transient never decided
// anything. The settle is a fidelity fix, not the corner-count cause. B's arithmetic was right:
// t2 pulls ~4.5 kN and a shackle is rated 3.2 — the line cannot hold, at any tension, settled or
// not. See THREADS.
{
const settleWind = calmWind || wind;
const period = Math.max(1, (calmDef && calmDef.duration) || 1);
for (let i = 0, n = Math.round(12 / FIXED_DT); i < n; i++) {
rig.step(FIXED_DT, settleWind, (i * FIXED_DT) % period);
}
}
/** Peak corner load the instant the storm starts — the settled-at-entry guard reads this. */
const entryPeak = Math.max(...rig.corners.map((c) => c.load || 0));
let hp = 100, pond = 0, used = 0;
const steps = Math.round(def.duration / FIXED_DT);
@ -189,6 +215,8 @@ async function fly(yard, session, stormName, { repair = false, broom = false } =
lost: rig.corners.filter((c) => c.broken).length,
spent: START_BUDGET - session.budget,
pond: Math.round(pond),
/** kN on the worst corner at storm entry — the settled-at-entry guard's number. */
entryPeak: entryPeak / 1000,
};
}
@ -269,28 +297,58 @@ export default async function run(t) {
// number. With a camera it reads what Lane A measured. Neither harness was
// lying; one of them was flying a yard with no cloth in it.
if (!WIN(line.hp, line.lost)) {
// SPRINT7 gate 0 — CLOSED at the merge, three causes named, none a villain:
// 1. GARDEN (A): skyfx built with no camera → `if (!camera) return;`
// skipped the shadow-grid rebuild → every loadout scored as bare bed.
// hp 36 WAS the no-sail constant. Fixed above (camera in fly()).
// 2. CORNERS (D): the settle. A harness that rigs and storms in the
// same tick measures the ATTACH TRANSIENT — trees at rest, cloth
// still falling into shape — worth 2.7× on tree corners (t2 4.54 kN
// unsettled vs 1.71 settled). A plays through prep, so A's rig was
// settled; fly() wasn't. Fixed below (12 s settle, per D's spec).
// 3. TENSION (A, self-reported): the win was measured at 1.0 and
// published without declaring it; the shop default was 0.9.
// B's elimination sweep (shelters/tension/drain/geometry/sway) was right
// about everything it measured — the transient swamped tension, which is
// why four tensions returned identical numbers. See THREADS 2026-07-18.
// SPRINT8 gate 0' — CLOSED, and this suite was RIGHT. The live game and this harness now
// agree, measured by Lane D 2026-07-18 on current main (fresh page, A's exact line,
// t2+p3+p4+t2b on 4×shackle+spare, tension 1.0):
// live game → hp 58, 2 lost [t2,t2b], t2 peak 4.55 kN
// this suite → hp 58, 2 lost [t2,t2b], t2 peak 4.54 kN
// Every suite-vs-game difference gate 0' listed has now been tested one at a time and NONE
// of them moves the corner count: live-calm settle (entry 1.94→0.40 kN, verdict unchanged),
// live tree sway (t2 4.33→4.55, verdict unchanged), walked repair timing (unchanged).
// Anchors are bit-identical between harnesses, ring order is identical (p4,t2,t2b,p3), and
// wind.sample() at t2 is identical to 2 dp (36.49 m/s @ t=75.9).
//
// RETRACTED, on the record: Lane D's "the settle flips 2 lost → 1" (SPRINT7). It does not
// reproduce — 0 s and 12 s settle both end 2 lost, because the storm's own peak (4.54 kN at
// t=75.9) dwarfs the attach transient. The settle is a real fidelity fix and NOT the
// corner-count cause. A's hp 58 reproduces exactly; A's "1 lost" reproduces in neither
// harness.
//
// What's left is B's arithmetic, which was correct from the first post: t2 pulls ~4.5 kN and
// a shackle is rated 3.2 kN. The corner cannot hold at any tension, and the spare cannot save
// it — the repaired corner simply blows again (measured: t2b blew @9 s, repaired, blew again).
// So this is no longer an engineering question. It is SPRINT8's designed escape hatch:
// hp 58 CLEARS the 50 bar while 2 corners are lost, i.e. the player saves the garden and
// loses the sail — DESIGN.md's own story — and the win rule (`hp>=50 && lost<2`) calls it a
// loss. Either the rule admits a PYRRHIC WIN, or the shop must be able to buy a third
// above-shackle corner ($80 buys two). That is a design call, not a harness bug. See THREADS.
throw new Error(
`no winnable line even settled: ${COVER_QUAD.join(',')} on 4×shackle+spare ` +
`($${line.spent}) ended hp=${line.hp}, lost=${line.lost}/4 (need hp>=50, lost<2). ` +
`If this is red after the settle fix, gate 0 is OPEN again — post the numbers.`);
`no winnable line: ${COVER_QUAD.join(',')} on 4×shackle+spare ($${line.spent}) ended ` +
`hp=${line.hp}, lost=${line.lost}/4 (need hp>=50, lost<2). CONFIRMED against the live game ` +
`(hp 58, 2 lost) — the harnesses agree, so this is the pyrrhic-win design call, not a bug.`);
}
return `$${line.spent} on ${COVER_QUAD.join(',')} -> hp ${line.hp}, ${line.lost}/4 lost`;
});
/**
* D's settled-at-entry guard (SPRINT8 §Lane D). The suite must enter the storm with the yard in
* the state a player hands it: rigged, breathing on the calm day, corners loaded by nothing worse
* than their own tension. It does not decide storm_02's verdict — measured, it doesn't but a
* harness silently measuring the attach transient is how three of them disagreed for two sprints,
* and the transient IS worth 2.7× on tree corners in the moment it exists.
*/
t.test('harness: the yard is SETTLED when the storm starts', () => {
const runs = [['line', line], ['cheap', cheap], ['gentle', gentle]].filter(([, r]) => r);
for (const [name, r] of runs) {
if (!(r.entryPeak < 1.0)) {
throw new Error(
`${name} entered the storm carrying ${r.entryPeak.toFixed(2)} kN — that is an attach ` +
'transient, not a rig. Settle on the calm day with a running clock, as main.js does.');
}
}
return `entry loads: ${runs.map(([n, r]) => `${n} ${r.entryPeak.toFixed(2)} kN`).join(' · ')}`;
});
t.test('balance: storm_02 punishes a cheap rig on the same quad', () => {
if (!cheap) throw new Error('the shop could not buy four carabiners — the economy is broken');
// The other half of fair: the SAME quad on four carabiners ($20) must lose,