From afef9abe0bd5bda73c39b9f293179b41daaee388 Mon Sep 17 00:00:00 2001 From: type-two Date: Sat, 25 Jul 2026 20:03:13 +1000 Subject: [PATCH] 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]);