Lane B S16: gate 2.3 — separation pins ATTEMPTED and honestly REFUSED, with receipts

Measured post-ruling (garden_probe, funnel ON, 2026-07-20):
  site_02 x earlybuster: bare 82.6 FULL (hail 10.7 + rain 6.8) · best flown 90.2 · gain +7.6
  site_03 x southerly:   bare 83.7 FULL (hail 10.7 + rain 5.6) · best flown 89.8 · gain +6.1
Bare WINS both nights by >30 points; 'bareMustLoseBelow' cannot be written
honestly at any threshold, so per the gate's own rule this is a FINDING, not
a fudged pin. Each yard carries _separation_finding (measured refusal with
numbers + the delete-on-pin instruction); scorecard exposes it and SCORE IT's
NONE PINNED branch becomes PIN REFUSED (measured). New assert: every shipped
yard is separation-JUDGED — pinned XOR refused-with-finding; silence and
stale-finding both red. Balance fight filed in THREADS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-20 20:32:37 +10:00
parent e5df63128a
commit 458e9a5412
5 changed files with 65 additions and 0 deletions

View File

@ -112,6 +112,19 @@ export async function buildGardenflyTests() {
} catch (err) { sepErr = String((err && err.stack) || err); } } catch (err) { sepErr = String((err && err.stack) || err); }
} }
// SPRINT16 gate 2.3 [B]: every shipped yard has been JUDGED on separation —
// either it pins a block (backyard_01) or it records a measured refusal
// (_separation_finding). Read all three here so the assert below can hold
// the invariant: neither is "unjudged", both at once is a stale finding.
let sepStates = null, sepStatesErr = null;
try {
sepStates = [];
for (const name of ['backyard_01', 'site_02_corner_block', 'site_03_swing_lawn']) {
const sd = await loadSite(name);
sepStates.push({ name, pinned: !!sd.separation, finding: !!sd._separation_finding });
}
} catch (err) { sepStatesErr = String((err && err.stack) || err); }
let venRun = null, venErr = null; let venRun = null, venErr = null;
try { try {
const fly = (venturi) => flyGarden({ const fly = (venturi) => flyGarden({
@ -184,6 +197,23 @@ export async function buildGardenflyTests() {
`the siteDef's venturi is not reaching gardenfly's wind (windForSite must receive the SITE def)`); `the siteDef's venturi is not reaching gardenfly's wind (windForSite must receive the SITE def)`);
} }
}], }],
['gate 2.3: every shipped yard is separation-JUDGED — pinned, or measured-and-refused', () => {
// SPRINT16's rule made durable: a yard may pin a separation block or it
// may carry a measured refusal (_separation_finding, the honest "this
// yard cannot separate at $80 under its storm" verdict with receipts) —
// but silence is not an option, and holding both means the finding went
// stale the day the pin landed and must be deleted (its own text says so).
if (sepStatesErr) throw new Error(`site read died: ${sepStatesErr}`);
assert(sepStates.length === 3, `expected 3 shipped yards, read ${sepStates.length}`);
for (const s of sepStates) {
assert(s.pinned || s.finding,
`${s.name} has neither a separation block nor a _separation_finding — the yard is UNJUDGED; ` +
'run gardenfly, pin honestly or refuse honestly (SPRINT16 gate 2.3)');
assert(!(s.pinned && s.finding),
`${s.name} carries BOTH a pin and a refusal finding — the finding is stale, delete it in the pinning commit`);
}
return sepStates.map((s) => `${s.name}: ${s.pinned ? 'PINNED' : 'refused w/ finding'}`).join(' · ');
}],
['gardenfly: default fabric is still the pinned 0.30 cloth, and membrane flies heavier', () => { ['gardenfly: default fabric is still the pinned 0.30 cloth, and membrane flies heavier', () => {
if (fabErr) throw new Error(`fabric flight died: ${fabErr}`); if (fabErr) throw new Error(`fabric flight died: ${fabErr}`);
const { byDefault, cloth, membrane } = fabRun; const { byDefault, cloth, membrane } = fabRun;

View File

@ -243,6 +243,10 @@ export async function scoreSite({ site, stormDef, stormName = null, sepStormDef
bestGarden: bestGarden ? { ids: bestGarden.key, ...bestGarden.g } : null, bestGarden: bestGarden ? { ids: bestGarden.key, ...bestGarden.g } : null,
bestMarginal: bestMarginal ? { ids: bestMarginal.key, ...bestMarginal.g } : null, bestMarginal: bestMarginal ? { ids: bestMarginal.key, ...bestMarginal.g } : null,
separation, sepStormName, sepUnjudged, separation, sepStormName, sepUnjudged,
// SPRINT16 gate 2.3 [B]: a site that ATTEMPTED a pin and honestly refused
// one records why in its JSON; the card prints it so NONE PINNED reads as
// "measured and refused, here's the fight" rather than "nobody tried".
separationFinding: site._separation_finding ?? null,
collateral, collateral,
MARGIN: AUDIT.MARGIN, MARGIN: AUDIT.MARGIN,
}; };

View File

@ -9,6 +9,21 @@
"gardenBed": { "x": 0, "z": 1, "w": 5, "d": 3.5 }, "gardenBed": { "x": 0, "z": 1, "w": 5, "d": 3.5 },
"_separation_finding": [
"SPRINT16 gate 2.3 [B] — a separation pin was ATTEMPTED and honestly REFUSED. Measured",
"post-ruling (garden_probe, funnel ON, storm_03b_earlybuster, 2026-07-20): bare bed 82.6",
"FULL (hail 10.7 + rain 6.8 HP); best flown line 90.2; best gain +7.6 HP. The block's own",
"semantics need 'bare LOSES the night' (bareMustLoseBelow, win line 50) and a bare bed",
"WINS this night by 32 points — the early buster's pea hail simply cannot hurt an open bed",
"enough. Any pin writable today would be a fudge: either a bareMustLoseBelow above the win",
"line (the field name lying) or thresholds tuned until the slogan lands (the S13 fudge the",
"gate exists to kill). THE FINDING: this yard cannot separate held from bare at ANY price",
"under its shipped storm — that is a balance fact about the NIGHT's hail spectrum, not the",
"rig market. The fight is in THREADS [B] 2026-07-20; candidate levers are C's hail",
"spectrum, the win threshold, or per-night garden stakes — A rules. When a retune lands,",
"re-measure, pin like backyard_01's, and DELETE this block in the same commit."
],
"_design": [ "_design": [
"DESIGN.md wants the corner block anchor-poor — 'nowhere to tie off'. An empty yard is just a", "DESIGN.md wants the corner block anchor-poor — 'nowhere to tie off'. An empty yard is just a",
"smaller yard, so the trap is the interesting build: give it something that LOOKS like four free", "smaller yard, so the trap is the interesting build: give it something that LOOKS like four free",

View File

@ -48,6 +48,16 @@
"The bare bed reads 83.7 FULL, which is the bare-beds-win pool item showing up on a", "The bare bed reads 83.7 FULL, which is the bare-beds-win pool item showing up on a",
"third yard - gate-4 material, not this yard's doing and not tuned here." "third yard - gate-4 material, not this yard's doing and not tuned here."
], ],
"_separation_finding": [
"SPRINT16 gate 2.3 [B] — a separation pin was ATTEMPTED and honestly REFUSED, same",
"verdict as site_02's. Measured post-ruling (garden_probe, funnel ON,",
"storm_03_southerly, 2026-07-20): bare bed 83.7 FULL (hail 10.7 + rain 5.6 HP); best",
"flown 89.8; best gain +6.1 HP — D's S14 card numbers reproduced. Bare WINS the",
"southerly by 34 points, so 'bareMustLoseBelow' cannot be written honestly at any",
"threshold. The pea-hail nights cannot hurt an open bed enough for a sail to be the",
"point of the night; that is the NIGHT's balance fact, filed in THREADS [B] 2026-07-20.",
"When the balance ruling lands, re-measure, pin like backyard_01's, delete this block."
],
"yard": { "yard": {
"width": 24, "width": 24,
"depth": 16 "depth": 16

View File

@ -312,6 +312,12 @@ function render(out, s, ms) {
} else if (s.sepUnjudged) { } else if (s.sepUnjudged) {
const cs = card(out, 'SEPARATION TARGET — NOT JUDGED', 'ed-warn'); const cs = card(out, 'SEPARATION TARGET — NOT JUDGED', 'ed-warn');
cs.append(el('div', 'ed-note', s.sepUnjudged)); cs.append(el('div', 'ed-note', s.sepUnjudged));
} else if (s.separationFinding) {
// SPRINT16 gate 2.3 [B]: measured, and honestly REFUSED — different fact
// from "nobody tried", and the card must not flatten the two together.
const cs = card(out, 'SEPARATION TARGET — PIN REFUSED (measured)', 'ed-warn');
cs.append(el('div', 'ed-note',
Array.isArray(s.separationFinding) ? s.separationFinding.join(' ') : String(s.separationFinding)));
} else { } else {
const cs = card(out, 'SEPARATION TARGET — NONE PINNED', 'ed-warn'); const cs = card(out, 'SEPARATION TARGET — NONE PINNED', 'ed-warn');
cs.append(el('div', 'ed-note', cs.append(el('div', 'ed-note',