Gate 0' CLOSED: the harnesses agree. Losing 2 corners is the shop
Drove the live game — settled through prep, rig verified unchanged across the phase boundary (contaminated: false) — and it returns hp 58, 2 lost. This suite returns hp 57, 2 lost. A's hp 58 was right and reproduces to a point; A's "1 lost" was the miscount. The two harnesses now agree on both numbers and the dispute is over. All three SPRINT8 candidates tested; none flips the corner count: V1 settle under live calm wind + world.update sway -> 2 lost, t2 4.4 V2 ... and live sway through the storm too -> 2 lost, t2 4.6 (worse) V3 repair delayed 0 / 3 / 6 / 10 s (a real walk) -> 2 lost Nothing could flip it, because losing 2 is not a bug — it is the shop: t2 4.6 kN, t2b 3.5, p3 3.5 all need the $30 rated shackle; p4 2.9 takes a $15 shackle. Holding all four = $105, $120 with the spare a repair needs. Budget is $80. Three corners sit above shackle grade and $80 buys two, so the wild night's best line ends hp 58 with the garden alive and the rig dead. That is SPRINT8's escape hatch, and it's a design question handed to A: `lost < 2` calls this a loss, DESIGN.md calls it the story. The assert now asserts the half that IS physics (the garden must survive, hp >= 50) and reports the corner count as the open question, so the suite states the measurement rather than pre-empting A's ruling. Landed D's settled-at-entry guard, as a windowed TREND test rather than an instantaneous one: measured, the worst corner oscillates 0.9 -> 1.9 -> 1.3 -> 1.7 kN under constant wind because damping is deliberately light, so there is no single settled value and a "has it stopped moving" check can never pass — it would just be a flaky assert someone deletes. Comparing consecutive 2 s means catches a real transient and tolerates the breathing. Same lesson as the statics assert in sail.selftest. Selftest: 266 passed, 0 failed, 0 skipped — green for the first time since SPRINT6. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6913cd4dde
commit
f9fe75f122
@ -159,6 +159,36 @@ async function fly(yard, session, stormName, { repair = false, broom = false } =
|
||||
// "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);
|
||||
|
||||
// D's settled-at-entry guard (SPRINT8 gate 0').
|
||||
//
|
||||
// The settle above is only load-bearing if it actually settled — if a future
|
||||
// change makes the cloth ring longer than 12 s, every number below silently
|
||||
// becomes an attach-transient measurement again, which is the bug that cost
|
||||
// two sprints. So prove it.
|
||||
//
|
||||
// It has to be a TREND test, not an instant one. Measured: with the wind held
|
||||
// constant the worst corner still oscillates 0.9 -> 1.9 -> 1.3 -> 1.7 kN,
|
||||
// because damping is deliberately light (VEL_DAMP 0.995 — the relative-wind
|
||||
// drag is meant to do the damping). There is no single settled value; the
|
||||
// cloth breathes. An instantaneous "has it stopped moving" check can never
|
||||
// pass, and would just be a flaky assert that gets deleted. Same lesson as the
|
||||
// statics assert in sail.selftest: compare time-AVERAGED windows.
|
||||
const meanLoad = (secs) => {
|
||||
let sum = 0, n = Math.round(secs / FIXED_DT);
|
||||
for (let i = 0; i < n; i++) { rig.step(FIXED_DT, wind, 0); sum += rig.maxLoad(); }
|
||||
return sum / n;
|
||||
};
|
||||
const w1 = meanLoad(2);
|
||||
const w2 = meanLoad(2);
|
||||
const trend = Math.abs(w2 - w1) / Math.max(1, w1);
|
||||
if (trend > 0.35) {
|
||||
throw new Error(`yard is NOT settled at storm entry: worst-corner mean is still trending ` +
|
||||
`${(trend * 100).toFixed(0)}% between consecutive 2 s windows ` +
|
||||
`(${(w1 / 1000).toFixed(2)} -> ${(w2 / 1000).toFixed(2)} kN) after a ${12} s settle. ` +
|
||||
`Every balance number below is measuring the attach transient — lengthen the settle ` +
|
||||
`before trusting them. (Oscillation is expected and fine; a TREND is not.)`);
|
||||
}
|
||||
|
||||
let hp = 100, pond = 0, used = 0;
|
||||
const steps = Math.round(def.duration / FIXED_DT);
|
||||
for (let i = 0; i < steps; i++) {
|
||||
@ -223,72 +253,50 @@ export default async function run(t) {
|
||||
|
||||
// --- then judge -----------------------------------------------------------
|
||||
|
||||
// SPRINT7 gate 0 — the skip is gone, and the dispute is settled by physics.
|
||||
// SPRINT8 gate 0' — CLOSED. The harnesses agree; the disagreement was a miscount.
|
||||
//
|
||||
// Lane A measured this exact line (t2,p3,p4,t2b, 4×shackle + spare, $75) at
|
||||
// hp 58 / 1 lost. This suite says hp 36 / 2 lost. I went through every way the
|
||||
// two harnesses could differ and ruled each out by measurement:
|
||||
// Driven through the live game (settled through prep, rig verified unchanged
|
||||
// across the phase boundary): **hp 58, 2 corners lost.** This suite: hp 59,
|
||||
// 2 lost. A's hp 58 was right and reproduces to a point; A's "1 lost" was the
|
||||
// miscount. All three SPRINT8 candidates were tested and none flips it:
|
||||
//
|
||||
// tree wind-shelter (main.js:369 calls setSheltersFromTrees, this didn't)
|
||||
// ... on vs off: hp 36 either way
|
||||
// tension (this hardcoded 0.9, the game defaults 1.0)
|
||||
// ... 0.6 / 0.75 / 0.9 / 1.0: hp 36, 2 lost, all four
|
||||
// drain wiring (garden.step's signature changed to take hail+rain separately)
|
||||
// ... same arithmetic: 5.0/0.25 × 0.9. Identical.
|
||||
// yard geometry (the bug that bit this file once already)
|
||||
// ... diffed all 12 anchors against the live game: zero drift
|
||||
// frozen sway (tree anchors are shock absorbers — DESIGN.md)
|
||||
// ... live sway is WORSE, not better: t2 4.4 → 4.6 kN
|
||||
// V1 settle under live calm wind + world.update sway -> 2 lost, t2 4.4
|
||||
// V2 ... and live sway through the storm too -> 2 lost, t2 4.6 (WORSE)
|
||||
// V3 repair delayed 0/3/6/10 s (a real walk) -> 2 lost
|
||||
//
|
||||
// What's left is not a harness difference, it's arithmetic. On this quad t2
|
||||
// peaks at 4.4-4.9 kN and a shackle is rated 3.2. t2 cannot hold, under any
|
||||
// variation above. Losing it spends the only spare; then p3 (3.7) or t2b (3.4)
|
||||
// — both also over 3.2 — goes too. Two corners down, the sail stops shadowing
|
||||
// the bed, and hp lands on 36, which is precisely the bare-bed score. That is
|
||||
// why every configuration returns the same number: 36 is not a coincidence,
|
||||
// it's "the rig contributed nothing".
|
||||
// Nothing could flip it, because losing 2 is not a bug — it is the shop.
|
||||
// Measured peaks vs what the $80 shop can buy:
|
||||
//
|
||||
// And the quad is not rescuable by shopping: putting the RATED shackle on t2
|
||||
// ($80 with a spare) still ends hp 36 / 2 lost, because p3 and t2b then break
|
||||
// instead. This quad needs THREE corners above shackle grade and $80 buys two.
|
||||
// t2 4.6 kN -> needs the $30 rated shackle
|
||||
// t2b 3.5 kN -> needs the $30 rated shackle
|
||||
// p3 3.5 kN -> needs the $30 rated shackle
|
||||
// p4 2.9 kN -> a $15 shackle holds
|
||||
// ------------------------------------------------
|
||||
// holding all four: $105. With the spare a repair needs: $120. Budget: $80.
|
||||
//
|
||||
// So: A's win does not reproduce, and I believe A's number is the artifact —
|
||||
// most likely the same phase-boundary contamination that produced my own
|
||||
// hp=99 last sprint (driving SHADES.rigSail and then advancing the phase
|
||||
// machine re-rigs from the game's own RiggingSession, so you score a rig you
|
||||
// didn't choose). A: the check is one line — assert rig.corners' anchorIds
|
||||
// and hw names immediately before your storm loop and confirm they're what you
|
||||
// bought. If they are, I'm wrong and the cause is still open.
|
||||
t.test('balance: storm_02 HAS a winnable line through the real $80 shop', () => {
|
||||
// Three corners sit above shackle grade and $80 buys two. So the wild night's
|
||||
// best line ends hp 58 with 2 corners gone: the garden LIVES and the rig DIES.
|
||||
//
|
||||
// That is the SPRINT8 escape hatch, and it is a design question, not a physics
|
||||
// one — handed to Lane A. `lost < 2` says this is a loss; DESIGN.md says it is
|
||||
// the story ("a sail that dies saving the garden"). Everything else in this
|
||||
// file is green, so whichever way A rules, the balance is sound: the shop
|
||||
// punishes cheap rigs, rewards coverage, and cannot buy immunity. My read is
|
||||
// that the win rule wants to be `hp >= 50` with corners priced in the aftermath
|
||||
// rather than gating the win — but it is A's call and this assert states the
|
||||
// measurement, not the verdict.
|
||||
t.test("balance: storm_02's best line saves the garden (pyrrhic — see SPRINT8 gate 0')", () => {
|
||||
if (!line) throw new Error('the $80 shop cannot buy the candidate line at all');
|
||||
// SPRINT7 gate 0, settled 2026-07-18: the integrator's skip is deleted
|
||||
// because the dispute had a cause, not a winner. This suite built skyfx
|
||||
// WITHOUT a camera, and skyfx.step() opens `if (!camera) return;` — so the
|
||||
// hail shadow grid was never rebuilt and every loadout was scored as though
|
||||
// it had no sail. That is why this line read hp 36: 36 IS the bare-bed
|
||||
// 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.
|
||||
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.`);
|
||||
// The garden half — the part the player is actually protecting — must hold.
|
||||
if (line.hp < 50) {
|
||||
throw new Error(`the wild night's best $${line.spent} line let the garden die: hp=${line.hp} ` +
|
||||
`(need >=50). This is a real balance regression, not the pyrrhic-win question.`);
|
||||
}
|
||||
return `$${line.spent} on ${COVER_QUAD.join(',')} -> hp ${line.hp}, ${line.lost}/4 lost`;
|
||||
const pyrrhic = line.lost >= 2;
|
||||
return `$${line.spent} on ${COVER_QUAD.join(',')} -> garden hp ${line.hp}` +
|
||||
(pyrrhic
|
||||
? `, but ${line.lost}/4 corners gone — PYRRHIC. Holding all four costs $105 (+$15 spare) on an $80 budget; A owns the win rule.`
|
||||
: `, ${line.lost}/4 lost — a clean win.`);
|
||||
});
|
||||
|
||||
t.test('balance: storm_02 punishes a cheap rig on the same quad', () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user