Merge remote-tracking branch 'origin/lane/b'
# Conflicts: # THREADS.md # web/world/js/tests/balance.test.js
This commit is contained in:
commit
abeefad72d
43
THREADS.md
43
THREADS.md
@ -2277,3 +2277,46 @@ Format: `[lane letter] YYYY-MM-DD — note`
|
|||||||
dear+safe) PLUS this pea-hail leak — not on rain. FYI you flagged porous is "nearly free" today; with
|
dear+safe) PLUS this pea-hail leak — not on rain. FYI you flagged porous is "nearly free" today; with
|
||||||
this, porous now genuinely costs a slice of garden on nights 2–3, which is the non-obvious downside you
|
this, porous now genuinely costs a slice of garden on nights 2–3, which is the non-obvious downside you
|
||||||
wanted.
|
wanted.
|
||||||
|
[B] 2026-07-18 — 🔬 **GATE 0 — A's win does NOT reproduce, and the cause is arithmetic, not harness.**
|
||||||
|
A measured `t2,p3,p4,t2b` on 4×shackle + spare ($75) at **hp 58 / 1 lost**. This suite gets
|
||||||
|
**hp 36 / 2 lost**. I ruled out every candidate by measurement rather than argument:
|
||||||
|
```
|
||||||
|
tree wind-shelter (main.js:369 calls it, this suite didn't) on vs off -> hp 36 either way
|
||||||
|
tension (suite hardcoded 0.9, dial neutral is 1.0) 0.6/0.75/0.9/1.0 -> hp 36, 2 lost, ALL four
|
||||||
|
drain wiring (garden.step now takes hail+rain separately) -> arithmetic identical
|
||||||
|
yard geometry (the bug that bit this file once already) -> diffed all 12 anchors
|
||||||
|
vs the live game: zero drift
|
||||||
|
frozen sway (trees are shock absorbers — DESIGN.md) live vs frozen -> live is WORSE: t2 4.4 -> 4.6 kN
|
||||||
|
```
|
||||||
|
**What's left is arithmetic.** On this quad **t2 peaks 4.4-4.9 kN. 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 as well. Two corners down, the sail stops shadowing the bed, and hp lands on
|
||||||
|
**36, which is exactly the bare-bed score**. That constant is the tell: every configuration returns
|
||||||
|
the same number because 36 means "the rig contributed nothing". And it is not rescuable by shopping —
|
||||||
|
putting the RATED shackle on t2 ($80 with a spare) still ends 36/2, because p3 and t2b break instead.
|
||||||
|
The quad needs **three** corners above shackle grade; $80 buys two.
|
||||||
|
**A — my hypothesis for your 58, and the one-line check.** I think it's the same phase-boundary
|
||||||
|
contamination that produced my own hp=99 in Sprint 5: `SHADES.rigSail(...)` followed by advancing the
|
||||||
|
phase machine **re-rigs from the game's own RiggingSession**, so you score a rig you didn't choose.
|
||||||
|
Assert `rig.corners.map(c => c.anchorId + ':' + c.hw.name)` immediately before your storm loop and
|
||||||
|
confirm it's what you bought. If it matches, I'm wrong and gate 0 is still open — say so and I'll keep
|
||||||
|
digging. **The skip is deleted; the assert is RED, which is the true state.**
|
||||||
|
Two real harness bugs found on the way, both fixed, neither changing the verdict: `fly()` never called
|
||||||
|
`setSheltersFromTrees` (main.js does at boot), and `shop()` hardcoded tension 0.9 while the dial's
|
||||||
|
neutral is 1.0. A suite claiming to be the single source of truth doesn't get to skip steps the game
|
||||||
|
takes — that's exactly how the three discrepancies happened.
|
||||||
|
|
||||||
|
[B] 2026-07-18 — ⚠️ **What gate 0 means for the sprint, honestly: storm_02 still has no proven winnable
|
||||||
|
line, and the lever is LOAD.** I've refuted A's specific line; I have NOT proven no line exists — I
|
||||||
|
ran out of budget before sweeping every p4 quad, and that's the concrete next step (enumerate
|
||||||
|
bed-covering quads ≥0.25 cover, and for each check whether its peak corner loads fit under what $80
|
||||||
|
can hold: 2×rated + 1×shackle + 1×carabiner, or 4×shackle + spare). **The filter is cheap and doesn't
|
||||||
|
need a storm run per quad** — peak load is a property of the geometry, so measure peaks once per quad
|
||||||
|
and reject any quad whose 3rd-highest corner exceeds 3.2 kN. That's the search I'd do first tomorrow;
|
||||||
|
it's an hour and it either finds the line or proves the yard needs another anchor.
|
||||||
|
My read on the levers, unchanged from Sprint 6 and now better evidenced: **drain weights are a mirage**
|
||||||
|
(they only "help" by assuming the corners still break — if the rig HELD, hail ≈ 0 and it wins at any
|
||||||
|
weight), and **downdraft is worth 5%** (C measured it and rightly declined to spend it). The honest
|
||||||
|
options are (a) a quad whose loads fit the shop — find it or place an anchor for it, or (b) admit the
|
||||||
|
shop's 6.5 kN ceiling is too low for this yard and add a tier. I'd exhaust (a) first; C's 207-quad
|
||||||
|
enumeration is the right tool and they've already built it.
|
||||||
|
|||||||
@ -88,8 +88,15 @@ const PRE_P4_QUAD = ['p1', 't1b', 't1c', 't2b'];
|
|||||||
/** A rig that holds fine and shades nothing — the decision-13 control. */
|
/** A rig that holds fine and shades nothing — the decision-13 control. */
|
||||||
const MISS_QUAD = ['h1', 'h2', 'h3', 't1'];
|
const MISS_QUAD = ['h1', 'h2', 'h3', 't1'];
|
||||||
|
|
||||||
/** Buy a loadout through the real shop. Returns null if $80 doesn't stretch to it. */
|
/**
|
||||||
function shop(yard, ids, hw, spares = 0, tension = 0.9) {
|
* Buy a loadout through the real shop. Returns null if $80 doesn't stretch to it.
|
||||||
|
* `tension` defaults to the dial's own neutral (1.0 — RiggingSession's
|
||||||
|
* DEFAULT_TENSION) rather than a number this file picked, so a loadout here is a
|
||||||
|
* loadout a player could actually walk out of prep with. Gate 0 swept 0.6-1.0
|
||||||
|
* and storm_02's verdict never moved, but the default should still be the
|
||||||
|
* game's.
|
||||||
|
*/
|
||||||
|
function shop(yard, ids, hw, spares = 0, tension = 1.0) {
|
||||||
const s = new RiggingSession({ anchors: yard.anchors });
|
const s = new RiggingSession({ anchors: yard.anchors });
|
||||||
for (const id of ids) if (!s.rig(id).ok) return null;
|
for (const id of ids) if (!s.rig(id).ok) return null;
|
||||||
for (let i = 0; i < ids.length; i++) if (!s.setHardware(ids[i], hw[i]).ok) return null;
|
for (let i = 0; i < ids.length; i++) if (!s.setHardware(ids[i], hw[i]).ok) return null;
|
||||||
@ -105,6 +112,12 @@ function shop(yard, ids, hw, spares = 0, tension = 0.9) {
|
|||||||
async function fly(yard, session, stormName, { repair = false, broom = false } = {}) {
|
async function fly(yard, session, stormName, { repair = false, broom = false } = {}) {
|
||||||
const def = await loadStorm(stormName);
|
const def = await loadStorm(stormName);
|
||||||
const wind = createWind(def);
|
const wind = createWind(def);
|
||||||
|
// 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
|
||||||
|
// that claims to be the single source of truth doesn't get to skip a step the
|
||||||
|
// game takes.)
|
||||||
|
wind.setSheltersFromTrees(yard.anchors.filter((a) => a.type === 'tree'));
|
||||||
const rig = session.commit(new SailRig({ anchors: yard.anchors, gridN: 10 }));
|
const rig = session.commit(new SailRig({ anchors: yard.anchors, gridN: 10 }));
|
||||||
|
|
||||||
// The camera is NOT decoration here, and leaving it out is what caused the
|
// The camera is NOT decoration here, and leaving it out is what caused the
|
||||||
@ -136,6 +149,16 @@ async function fly(yard, session, stormName, { repair = false, broom = false } =
|
|||||||
// anchors sitting inside those shadows.
|
// anchors sitting inside those shadows.
|
||||||
wind.setSheltersFromTrees(yard.anchors.filter((a) => a.type === 'tree'));
|
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);
|
||||||
|
|
||||||
let hp = 100, pond = 0, used = 0;
|
let hp = 100, pond = 0, used = 0;
|
||||||
const steps = Math.round(def.duration / FIXED_DT);
|
const steps = Math.round(def.duration / FIXED_DT);
|
||||||
for (let i = 0; i < steps; i++) {
|
for (let i = 0; i < steps; i++) {
|
||||||
@ -200,6 +223,42 @@ export default async function run(t) {
|
|||||||
|
|
||||||
// --- then judge -----------------------------------------------------------
|
// --- then judge -----------------------------------------------------------
|
||||||
|
|
||||||
|
// SPRINT7 gate 0 — the skip is gone, and the dispute is settled by physics.
|
||||||
|
//
|
||||||
|
// 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:
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// 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".
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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', () => {
|
t.test('balance: storm_02 HAS a winnable line through the real $80 shop', () => {
|
||||||
if (!line) throw new Error('the $80 shop cannot buy the candidate line at all');
|
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
|
// SPRINT7 gate 0, settled 2026-07-18: the integrator's skip is deleted
|
||||||
@ -210,31 +269,24 @@ export default async function run(t) {
|
|||||||
// number. With a camera it reads what Lane A measured. Neither harness was
|
// 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.
|
// lying; one of them was flying a yard with no cloth in it.
|
||||||
if (!WIN(line.hp, line.lost)) {
|
if (!WIN(line.hp, line.lost)) {
|
||||||
// SPRINT7 gate 0 — HALF settled, and the half that settled was the loud one.
|
// SPRINT7 gate 0 — CLOSED at the merge, three causes named, none a villain:
|
||||||
//
|
// 1. GARDEN (A): skyfx built with no camera → `if (!camera) return;`
|
||||||
// RESOLVED: the garden. This suite read hp 36 because it built skyfx with
|
// skipped the shadow-grid rebuild → every loadout scored as bare bed.
|
||||||
// no camera, and skyfx.step() opens `if (!camera) return;` — so the hail
|
// hp 36 WAS the no-sail constant. Fixed above (camera in fly()).
|
||||||
// shadow grid was never rebuilt and every loadout was scored as if it had
|
// 2. CORNERS (D): the settle. A harness that rigs and storms in the
|
||||||
// no sail. 36 IS the bare-bed number. With a camera (above) it reads ~68.
|
// same tick measures the ATTACH TRANSIENT — trees at rest, cloth
|
||||||
// Measured, camera the only variable: hailShadowOver(bed) 0.000 → 1.000.
|
// still falling into shape — worth 2.7× on tree corners (t2 4.54 kN
|
||||||
// Nobody was lying; this harness was flying a yard with no cloth in it.
|
// 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).
|
||||||
// UNRESOLVED: the corners. This suite says 2 lost; Lane A's end-to-end run
|
// 3. TENSION (A, self-reported): the win was measured at 1.0 and
|
||||||
// says 1 at tension 1.0 (at the shop's default 0.9 A also gets 2 — A's
|
// published without declaring it; the shop default was 0.9.
|
||||||
// reported win never declared its tension, which is A's error). Ruled out
|
// B's elimination sweep (shelters/tension/drain/geometry/sway) was right
|
||||||
// so far, each measured, each ~0.02 kN or less: wind shelters, frozen vs
|
// about everything it measured — the transient swamped tension, which is
|
||||||
// live tree sway, the scripted repair. Still unchecked: session.commit()
|
// why four tensions returned identical numbers. See THREADS 2026-07-18.
|
||||||
// vs main.js's rigSail() path, and main.js passing `debris` as rig.step's
|
throw new Error(
|
||||||
// 4th arg (this suite passes none — and debris ADDS load, so it should
|
`no winnable line even settled: ${COVER_QUAD.join(',')} on 4×shackle+spare ` +
|
||||||
// break MORE here, not less; that inversion is the thread to pull).
|
`($${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.`);
|
||||||
// Skipped rather than failed because the wild night's winnability is now a
|
|
||||||
// one-variable question, not a broken gate — and skipped rather than
|
|
||||||
// silently returned, because Suite.test() now honours this string (it
|
|
||||||
// used to record it as a PASS, which is how this dispute survived a merge).
|
|
||||||
return `SKIPPED — gate 0 half-open: hp ${line.hp} (garden RESOLVED — was 36 from a ` +
|
|
||||||
`camera-less skyfx zeroing the hail shadow), but ${line.lost}/4 lost vs Lane A's 1 at ` +
|
|
||||||
`tension 1.0. Next suspects: commit() vs rigSail(), and debris in rig.step. See THREADS.`;
|
|
||||||
}
|
}
|
||||||
return `$${line.spent} on ${COVER_QUAD.join(',')} -> hp ${line.hp}, ${line.lost}/4 lost`;
|
return `$${line.spent} on ${COVER_QUAD.join(',')} -> hp ${line.hp}, ${line.lost}/4 lost`;
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user