From afef9abe0bd5bda73c39b9f293179b41daaee388 Mon Sep 17 00:00:00 2001 From: type-two Date: Sat, 25 Jul 2026 20:03:13 +1000 Subject: [PATCH 1/2] Lane B S18 gate 1: commit(rig, {at}) carries the mid-storm clock, so seeding it cannot be forgotten MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A's emergency flow is inherit -> commit -> put the dead corner down -> roll, and it goes through session.commit(rig) by design (the public-moves discipline). That door could not seed the sail's clock, so getting it right needed a SECOND call after commit — a step a caller can forget, on the one night where forgetting it understates every load ~4x and reads as "triage is easy" rather than as a bug. opts forward straight to attach; omitted, the ordinary night is byte-for-byte unchanged (clock at 0), pinned both ways. Selftest 535/0/0 (522 baseline + 13). --- web/world/js/rigging.js | 16 +++++++++++++--- web/world/js/rigging.selftest.js | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/web/world/js/rigging.js b/web/world/js/rigging.js index f7c474d..c7782bf 100644 --- a/web/world/js/rigging.js +++ b/web/world/js/rigging.js @@ -541,8 +541,18 @@ export class RiggingSession { this.picks = ring.map((a) => byId.get(a.id)); } - /** Hand the finished rig to the sim. Mirrors contracts.js sailRig.attach(). */ - commit(rig) { + /** + * Hand the finished rig to the sim. Mirrors contracts.js sailRig.attach(). + * + * `opts` is forwarded to `attach` untouched, which exists for exactly one + * reason (SPRINT18, Lane C's finding): an emergency callout's storm is already + * 30 seconds old, and `attach({at, wind})` is what stops the cloth flying the + * wrong sixty seconds of it. Without this the only way to seed the clock would + * be a SECOND call after commit — a step a caller can forget, on the one night + * where forgetting it silently understates every load by 4x. The session's + * public-moves discipline and a correct clock should not be a choice. + */ + commit(rig, opts = undefined) { if (!this.canStart) throw new Error(`sail needs ${MAX_CORNERS} corners, have ${this.picks.length}`); // gate 2, the promise the sprint doc names ("enforces the budget cap at // commit"): by now the refusals above have made these states unreachable @@ -570,7 +580,7 @@ export class RiggingSession { // shade cloth + C's 0.40 downdraft drop p1 to 1.08 kN, under a $5 // carabiner's rating, which is the $10 that closes the $90 -> $80 gap.) if (rig.setFabric) rig.setFabric(this.fabric); - return rig.attach(this.picks.map((p) => p.anchorId), this.picks.map((p) => p.hw), this.tension); + return rig.attach(this.picks.map((p) => p.anchorId), this.picks.map((p) => p.hw), this.tension, opts); } /** diff --git a/web/world/js/rigging.selftest.js b/web/world/js/rigging.selftest.js index bf37ef3..2a95098 100644 --- a/web/world/js/rigging.selftest.js +++ b/web/world/js/rigging.selftest.js @@ -605,6 +605,25 @@ test('gate 1: ⚠️ the refund cannot pay you for a stranger\'s steel — the k return `inherited steel refunds $0; the $${RATED.cost} the player bought refunds, and only that`; }); +test('gate 1: commit carries the mid-storm clock through to the sail', () => { + // The emergency flow is inherit -> commit -> put the dead corner down -> roll. + // If commit cannot pass `at`, seeding the clock becomes a separate call on the + // one night where forgetting it understates every load ~4x (C's finding). + const s = session(); + assert(s.inherit(INHERITED).ok, 'inherit refused'); + const rig = s.commit(new SailRig({ anchors: ANCHORS, gridN: 10 }), { at: 30 }); + assert(rig.t === 30, + `commit({at:30}) left the cloth at t=${rig.t}. It is now flying the storm's FIRST sixty seconds ` + + 'under a sky flying its last sixty, and nothing about that is visible from outside'); + assert(rig.clockSkew === 0, `a freshly seeded commit already reads ${rig.clockSkew}s of skew`); + // and the ordinary night is untouched — no opts, clock at 0 + const plain = session(); + for (const id of ['h1', 'h3', 'p1', 'p2']) plain.rig(id); + assert(plain.commit(new SailRig({ anchors: ANCHORS, gridN: 10 })).t === 0, + 'an ordinary commit no longer starts its storm at 0'); + return 'commit(rig, {at:30}) seeds the cloth to 30s; a plain commit still starts at 0'; +}); + test('gate 1: an inherited rig still obeys the client, and refuses a rig it cannot build', () => { const s = session(); s.setConstraints([HOUSE_BAN]); From 26d41e53e8c785ace254f138aae6acd2fba5e575 Mon Sep 17 00:00:00 2001 From: type-two Date: Sat, 25 Jul 2026 20:03:35 +1000 Subject: [PATCH 2/2] =?UTF-8?q?Lane=20B=20S18:=20[B]=20THREADS=20closing?= =?UTF-8?q?=20=E2=80=94=20535/0/0,=20the=20count=20delta,=20and=20A's=20ou?= =?UTF-8?q?tstanding=20two-line=20pickup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- THREADS.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/THREADS.md b/THREADS.md index 7e7d1c9..dffd2d3 100644 --- a/THREADS.md +++ b/THREADS.md @@ -9738,3 +9738,30 @@ anchors are your GLB), but the tooling is now waiting, not TODO. the played game exactly — that is the part of C's warning that does not apply here. C's warning that DOES apply, and I took it: the triage table above was re-measured pre-flown with `clockSkew` 0, so the economy is not tuned against a 4.12× understated load. + +[B] 2026-07-25 — ✅ **CLOSING: 535/0/0 (522 baseline + 13), pushed. The one thing I could not land + myself, restated because it is the whole of gate 2:** `main.js`'s `rigSail` still drops the + fabric (checked against origin/lane/a 397d943 — `async function rigSail(anchorIds, hwChoices, + tension = 1.0)` and `onCommit: (ids, hw, tension) => …`), so **the shipped sim is still flying + the waterproof membrane on every night while every card says shade cloth.** My side of the seam + is landed and proven in `dev_rigging.html`; the two-line pickup is in my gate-2 entry above. + A or the integrator: please take it — it is the difference between the fix existing and the fix + reaching the player. + + **Count delta, per file, for the integrator's sum:** sail.selftest +6 (3 knife/failCorner, + 3 clock), rigging.selftest +4 (3 inherit, 1 commit-clock), balance.test +3 (2 gate 2, 1 gate 3), + sweep.selftest ±0 (the margin-rule test REWRITTEN in place to pin the dwell boundary — same + count, opposite ruling). Node-verified standalone: sail 53/53, rigging 33/33, sweep 4/4. + + **Two asserts mutation-checked, both directions:** drop `session.fabric` from the commit seam → + the seam assert reddens with its own diagnostic; set `coeff = PRESSURE_COEFF * (1 - 0)` → the + flight assert reddens, and it correctly reddens S16's pre-existing `fabric decides p1` too. + + **Still open, filed not fixed (each with its mechanism named):** `balance.test`'s `fly()` settles + 12 s and then restarts the storm loop's `t` at 0 — the phantom-settle skew, now explained by C's + clock finding rather than by "cause unknown"; its own gate, because moving it moves a dozen + pinned balance numbers. The SCORE IT card should lead with the per-corner pairing rather than the + total (gate 3: two different $40s exist on one quad and one of them breaks). And A's ordering + note: `attach({at, wind})` pre-flies all four corners, so a corner that arrived down is failed + after the pre-fly rather than during it — measured difference cb1 0.80 vs 0.83 kN, identical + verdict, and I will take the broken set into `attach` the moment anyone wants it exact.