Lane C S18 gate 1: the honesty gate walks A's EMERGENCIES — storm AND pairing

NIGHTS ∪ POOL grew a third member this sprint, and an emergency is the member with
the most to hide: no forecast card, no quote, and a storm entered at a t nobody
walked up to. Both arms checked per entry — dispatchHonest(def, arriveAt) for the
storm-and-instant, pairingRefusal(storm, site) for the yard, because a read can be
perfectly honest about a storm and still be an invitation to a night nobody can
work, which is worse on a callout the player cannot decline once they are in it.

Read through a NAMESPACE import (board.EMERGENCIES), not a named one, so this file
survives the window where A's spine is on lane/a and not in my tree — a named
import of a not-yet-exported symbol is a hard module error, not a graceful no-op.
A's own reasoning for weather.midStormRead?.(), applied back at them.

Walks a FIXTURE mirroring A's §6 as well as the real table, so the gate is
exercised on known data TODAY rather than reading green off an absent import — the
calm-day vacuity that has now bitten this repo in three separate gates. Verified
against A's actual lane/a tree out of band: their one shipped emergency
(corner_block_emergency, night 5, storm_03_southerly x site_02_corner_block,
arriveAt 30) passes both arms, so the walk binds clean at merge. Three vacuity
guards, one per arm plus the late arrival.

533/0/0 (+1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-25 19:44:11 +10:00
parent 9506f6e79f
commit 062fd00758

View File

@ -32,6 +32,11 @@ import { loadSite, createWorld } from '../world.js';
// without a test edit the day D's pool yard lands.
import { NIGHTS, nightAt } from '../week.js';
import { POOL, exposureOf } from '../board.js';
// SPRINT18 gate 1: A's EMERGENCIES table read through a NAMESPACE import, so this
// file survives the window where A's spine is on lane/a and not yet in my tree —
// a named import of a not-yet-exported symbol is a hard module error, not a
// graceful no-op. A's own reasoning for `weather.midStormRead?.()`, applied back.
import * as board from '../board.js';
// GATE 2.3 — the GAME's own wind wiring, IMPORTED rather than re-typed. A pin
// that copied main.js's two lines would agree with a copy of the game forever,
// including on the day the game itself changed. "Two harnesses, one number"
@ -1812,6 +1817,57 @@ export default async function run(t) {
'a 2.6 stone was dispatched — the vocabulary ceiling stopped biting');
});
t.test('GATE 1: every EMERGENCY is a storm that can be honestly dispatched over that yard', () => {
// The honesty gate walking A's own table — NIGHTS POOL grew a third member
// this sprint, and an emergency is the member with the most to hide: no
// forecast card, no quote, and a storm entered at a t nobody walked up to.
//
// ⚠️ Read through a NAMESPACE import (`board.EMERGENCIES`), not a named one, so
// this file does not hard-error in the sprint-window where A's spine is on
// lane/a and not yet in my tree — the same reasoning A gave for calling
// `weather.midStormRead?.()` that way, applied back at them. And it walks a
// FIXTURE mirroring A's §6 as well as the real table, so the gate is exercised
// on known data TODAY rather than reading green off an absent import: the
// calm-day vacuity that has now bitten this repo in three separate gates.
const fixture = [{
id: 'fixture:corner_block_emergency', night: 5,
storm: 'storm_03_southerly', site: 'site_02_corner_block',
emergency: { arriveAt: 30 },
}];
const real = Array.isArray(board.EMERGENCIES) ? board.EMERGENCIES : [];
const walk = [...fixture, ...real];
assert(walk.length >= 1, 'the emergency walk found nothing at all');
for (const e of walk) {
const at = e.emergency?.arriveAt;
const def = storms[e.storm];
assert(def, `${e.id}: emergency storm '${e.storm}' is not in the loaded storm set — `
+ 'an emergency whose weather nobody can load cannot be dispatched');
assert(Number.isFinite(at), `${e.id}: no numeric emergency.arriveAt`);
// (1) the storm-and-instant gate
const r = dispatchHonest(def, at, e.id);
assert(r.ok, `${e.id} cannot be honestly dispatched at arriveAt=${at}:\n ${r.errors.join('\n ')}`);
// (2) THE PAIRING LAW, extended to the new job type. A read can be perfectly
// honest about a storm and still be an invitation to a night nobody can
// work — and that is worse on a callout, which the player cannot decline
// once they are standing in it.
const why = pairingRefusal(e.storm, e.site);
assert(why === null,
`${e.id}: dispatching ${e.storm} over ${e.site} is refused by measurement — ${why}`);
}
// VACUITY GUARDS — three, because a walk that only ever says yes is decoration.
assert(pairingRefusal('storm_06_soaker', 'backyard_01') !== null,
'the pairing arm of this walk cannot say no — an emergency on a refused yard would pass');
assert(!dispatchHonest(storms[fixture[0].storm], 95, 'late').ok,
'an emergency arriving after its storm ended was accepted');
const sizeless = JSON.parse(JSON.stringify(storms.storm_06_soaker));
delete sizeless.hail.size;
assert(!dispatchHonest(sizeless, 30, 'sizeless').ok, 'the storm arm of this walk cannot say no');
});
t.test('GATE 1: every line of the read is reachable, and each one is earned', () => {
// A vocabulary with an unreachable word is a vocabulary that has never been
// read. Walk the shipped storms and prove each key fires somewhere — and that