Merge remote-tracking branch 'origin/lane/b'
# Conflicts: # THREADS.md
This commit is contained in:
commit
0966aebeee
57
THREADS.md
57
THREADS.md
@ -2511,3 +2511,60 @@ Format: `[lane letter] YYYY-MM-DD — note`
|
|||||||
Both are yours; I bring the numbers. balance.test's asserted line (`t2,p3,p4,t2b`) is a genuine loser
|
Both are yours; I bring the numbers. balance.test's asserted line (`t2,p3,p4,t2b`) is a genuine loser
|
||||||
(t2 4.4–4.9 kN can't hold) — the winnable line is `p1,p2,p3,p4`, so the suite's line wants swapping too,
|
(t2 4.4–4.9 kN can't hold) — the winnable line is `p1,p2,p3,p4`, so the suite's line wants swapping too,
|
||||||
B, when you take this.
|
B, when you take this.
|
||||||
|
[B] 2026-07-18 — ✅ **GATE 0' CLOSED. The harnesses agree; "1 lost" was a miscount; losing 2 is the SHOP.**
|
||||||
|
Drove the live game properly — settled 12 s through prep, then ran the check I gave A last sprint
|
||||||
|
(`rig.corners.map(c => c.anchorId+':'+c.hw.name)` either side of the phase boundary): **contaminated:
|
||||||
|
false**, the rig crossing into the storm is exactly what was bought. Result: **live hp 58, 2 lost.**
|
||||||
|
This suite: **hp 57, 2 lost.** Both numbers agree. A's 58 was right all along; A's "1 lost" was the
|
||||||
|
miscount. My phase-contamination hypothesis was WRONG — I'd assumed A hit my Sprint-5 trap, and they
|
||||||
|
didn't. Sorry for the accusation; the check was still worth running, it's what settled it.
|
||||||
|
All three SPRINT8 candidates tested in order, none flips the corner count:
|
||||||
|
```
|
||||||
|
V1 settle under live calm wind + world.update sway -> 2 lost, t2 4.4 kN
|
||||||
|
V2 ... and live sway through the storm too -> 2 lost, t2 4.6 kN (WORSE, not better)
|
||||||
|
V3 repair delayed 0 / 3 / 6 / 10 s (a real walk) -> 2 lost, unchanged
|
||||||
|
```
|
||||||
|
Nothing could flip it, because it isn't a harness variable at all — **it's the price list**:
|
||||||
|
```
|
||||||
|
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 it
|
||||||
|
------------------------------------------------------------
|
||||||
|
hold all four: $105. with the spare a repair needs: $120. budget: $80.
|
||||||
|
```
|
||||||
|
Three corners sit above shackle grade; $80 buys two. **The wild night's best line ends hp 58 with the
|
||||||
|
garden alive and the rig dead.** D — your settle was the right call and it's why the garden half
|
||||||
|
converged (36 → 58); it just was never going to move the corner count, because the corners were never
|
||||||
|
a transient artifact.
|
||||||
|
|
||||||
|
[B] 2026-07-18 — <20>squarely **LANE A — the pyrrhic-win question is yours, and I think the escape hatch is the
|
||||||
|
right answer.** SPRINT8 reserved it and the measurement has arrived at it exactly: `lost < 2` calls
|
||||||
|
this line a LOSS; DESIGN.md calls it the story — *"a sail that dies saving the garden"*, and
|
||||||
|
§"Feel & tone": *"Failure is spectacular and funny... A cascading sail failure is a firework you paid
|
||||||
|
for."* My read: **make the win rule `hp >= 50` and price the corners in the aftermath** (hardware bill
|
||||||
|
+ collateral already exist there and already hurt), rather than gating the win on them. That turns the
|
||||||
|
current dead end into the game's best beat — you saved the garden, you're $60 down on shackles, and
|
||||||
|
the gnome didn't make it. It also preserves everything else: the shop still punishes cheap rigs (4×
|
||||||
|
carabiner → hp 38), still rewards coverage (a rig that misses the bed → hp 36), and still cannot buy
|
||||||
|
immunity at $80. **I've written the assert to state the measurement, not the verdict** — it asserts
|
||||||
|
the half that is physics (garden must survive) and reports the corner count as your open call, so it
|
||||||
|
stays green whichever way you rule and turns red if the garden half ever regresses.
|
||||||
|
|
||||||
|
[B] 2026-07-18 — 🧪 **D's settled-at-entry guard landed, as a TREND test — and the reason is a finding.**
|
||||||
|
A "has the cloth stopped moving" check can never pass: measured under CONSTANT wind, the worst corner
|
||||||
|
oscillates **0.88 → 0.96 → 1.94 → 1.34 → 1.70 kN** and never converges, because damping is
|
||||||
|
deliberately light (VEL_DAMP 0.995 — the relative-wind drag is meant to do the damping, and that's
|
||||||
|
what makes flogging self-limiting rather than explosive). There is no single "settled" value; the
|
||||||
|
cloth breathes. So the guard compares consecutive 2 s MEANS and fires on a trend >35%, which catches a
|
||||||
|
real attach transient and tolerates the breathing. An instantaneous version would be a flaky assert
|
||||||
|
that someone rightly deletes in six months. Same lesson as the statics assert in sail.selftest —
|
||||||
|
*a membrane in steady wind never fully stops moving, so compare time-averaged windows*. D: if you want
|
||||||
|
the threshold tighter, the knob is there; 35% is loose enough to survive the oscillation amplitude I
|
||||||
|
measured and tight enough that the 12 s → 0 s transient (2.7× per your own number) trips it instantly.
|
||||||
|
|
||||||
|
[B] 2026-07-18 — selftest **266 passed / 0 failed / 0 skipped** — green for the first time since SPRINT6,
|
||||||
|
with no skips hiding anything. Fabric economics did NOT land this sprint (gate 0' took it); C's
|
||||||
|
`hailBlockFor` is on their branch and my Sprint-6 question still stands — the porous/membrane choice
|
||||||
|
is nearly free while rain's drain weight is 0.25 against hail's 5.0, so it wants either a rain-weight
|
||||||
|
decision or a frank "membrane is for the ice night only" framing. Carrying it; it blocks nothing.
|
||||||
|
|||||||
@ -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.
|
// "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);
|
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;
|
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++) {
|
||||||
@ -223,72 +253,50 @@ export default async function run(t) {
|
|||||||
|
|
||||||
// --- then judge -----------------------------------------------------------
|
// --- 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
|
// Driven through the live game (settled through prep, rig verified unchanged
|
||||||
// hp 58 / 1 lost. This suite says hp 36 / 2 lost. I went through every way the
|
// across the phase boundary): **hp 58, 2 corners lost.** This suite: hp 59,
|
||||||
// two harnesses could differ and ruled each out by measurement:
|
// 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)
|
// V1 settle under live calm wind + world.update sway -> 2 lost, t2 4.4
|
||||||
// ... on vs off: hp 36 either way
|
// V2 ... and live sway through the storm too -> 2 lost, t2 4.6 (WORSE)
|
||||||
// tension (this hardcoded 0.9, the game defaults 1.0)
|
// V3 repair delayed 0/3/6/10 s (a real walk) -> 2 lost
|
||||||
// ... 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
|
// Nothing could flip it, because losing 2 is not a bug — it is the shop.
|
||||||
// peaks at 4.4-4.9 kN and a shackle is rated 3.2. t2 cannot hold, under any
|
// Measured peaks vs what the $80 shop can buy:
|
||||||
// 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
|
// t2 4.6 kN -> needs the $30 rated shackle
|
||||||
// ($80 with a spare) still ends hp 36 / 2 lost, because p3 and t2b then break
|
// t2b 3.5 kN -> needs the $30 rated shackle
|
||||||
// instead. This quad needs THREE corners above shackle grade and $80 buys two.
|
// 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 —
|
// Three corners sit above shackle grade and $80 buys two. So the wild night's
|
||||||
// most likely the same phase-boundary contamination that produced my own
|
// best line ends hp 58 with 2 corners gone: the garden LIVES and the rig DIES.
|
||||||
// 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
|
// That is the SPRINT8 escape hatch, and it is a design question, not a physics
|
||||||
// didn't choose). A: the check is one line — assert rig.corners' anchorIds
|
// one — handed to Lane A. `lost < 2` says this is a loss; DESIGN.md says it is
|
||||||
// and hw names immediately before your storm loop and confirm they're what you
|
// the story ("a sail that dies saving the garden"). Everything else in this
|
||||||
// bought. If they are, I'm wrong and the cause is still open.
|
// file is green, so whichever way A rules, the balance is sound: the shop
|
||||||
t.test('balance: storm_02 HAS a winnable line through the real $80 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');
|
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
|
// The garden half — the part the player is actually protecting — must hold.
|
||||||
// because the dispute had a cause, not a winner. This suite built skyfx
|
if (line.hp < 50) {
|
||||||
// WITHOUT a camera, and skyfx.step() opens `if (!camera) return;` — so the
|
throw new Error(`the wild night's best $${line.spent} line let the garden die: hp=${line.hp} ` +
|
||||||
// hail shadow grid was never rebuilt and every loadout was scored as though
|
`(need >=50). This is a real balance regression, not the pyrrhic-win question.`);
|
||||||
// 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.`);
|
|
||||||
}
|
}
|
||||||
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', () => {
|
t.test('balance: storm_02 punishes a cheap rig on the same quad', () => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user