Lane E S18 gates 4+5: e.test — the tier's pins, and the shared-collateral-key landmine disarmed

Seven new pins, ten new rows (the two assets also add dims/nodes rows).

Gate 4 — the tier:
  · the frame BRACKETS the glass: footprint strictly contains the glasshouse's
    on both axes, head clears the ridge by 0.47 m, all four eyes above it. The
    tell is a measurement because the tell is a position.
  · the 20 m read is METALNESS not lightness: powder-coat <= 0.15, galvanised
    >= 0.6 as the control, and the paint the brighter base colour. An assert
    whose stated reason is the mechanism, so the docstring cannot be "fixed"
    the wrong way (S17's inverted-sign lesson).
  · a NEW RUNG at 0.80, pinned against the MANIFEST not a literal: above the
    pergola, below the post, collateral 'glasshouse', four agreeing sources.
  · the APEX IS REACHABLE — this one was RED by construction before this
    sprint, and its message says why. Plus the corollary: nothing but the
    glasshouse may CLAIM its key.
  · the wreck folds at one end, keeps its height, carries no glass, offers no
    anchor, and is unpriced by ruling.

Gate 5 — D's second-rust-post landmine, disarmed at author time:
  · no shipped yard gives two structures the same resolved collateral key
    (mirrors structKey() exactly), and structure ids are unique — a duplicate
    id makes structures.set() silently replace the first, which then never
    renders, never bills and never wrecks with nothing said anywhere.
  · every collateral an authored anchor names is OWNED by exactly one thing in
    that yard. RULE 2's runtime twin: RULE 2 proves a key is priced somewhere
    in the palette, this proves it is owned in the yard that names it. Zero
    owners is the gutter bug reborn; two is the landmine.

Five negative controls witnessed red in the real harness, then green: the frame
un-bracketed, the paint turned galvanised, the frame claiming the glass's key,
the anchors no longer naming the glass (the pre-sprint state), and a second
corroded post in the pool yard. Counts held at 533 throughout, so nothing
vanished while other things were red.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-25 19:46:06 +10:00
parent 00e6b288c8
commit 0a279b36f3

View File

@ -113,6 +113,16 @@ const ASSETS = [
nodes: ['frame', 'panes', 'door', 'ridge_anchor'] },
{ name: 'glasshouse_01_wrecked', h: [2.15, 2.40],
nodes: ['frame', 'door', 'panes_left', 'shards'] },
// SPRINT18 gate 4 — the glasshouse tier's live half. The height floor is a
// design claim about the asset next to it: 2.70 sits ABOVE the glasshouse's
// 2.40 ceiling, because a net frame that does not clear the ridge is resting
// on the glass it exists to protect. The wreck shares the band — the +x
// portal stands, so a portal frame folds at one end rather than falling.
{ name: 'hail_net_frame_01', h: [2.70, 2.85],
nodes: ['footings', 'legs', 'top_frame', 'net_roll',
'net_anchor_01', 'net_anchor_02', 'net_anchor_03', 'net_anchor_04'] },
{ name: 'hail_net_frame_01_wrecked', h: [2.70, 2.85],
nodes: ['footings', 'legs', 'top_frame', 'net_down'] },
{ name: 'pool_kit_01', h: [1.12, 1.32],
nodes: ['pool_shell', 'pool_water', 'fence_rails', 'pool_gate',
'fence_post_01', 'fence_post_14'] },
@ -1436,5 +1446,313 @@ export default async function run(t) {
'vacuous: no node-less declared anchors found across the shipped sites — this pin checked nothing');
});
// -------------------------------------------------------------------------
// SPRINT18 gate 4 — THE GLASSHOUSE TIER. Sprint 16 shipped the glass and
// priced it at 320; the gate asked for the tier, and reading the gate found
// that the price could not be REACHED. The comment two hundred lines up says
// it in this file's own words: "nothing in the sim wrecks a glasshouse yet —
// the value is inert data". main.js bills off a broken rig corner's baked
// collateral string, and a glasshouse correctly offers no anchors, so the
// most expensive object in the game could not cost anybody a cent.
//
// DESIGN.md line 179 named the missing asset: hail nets rigged OVER glass.
// You tie to the frame that straddles it. The tier's teaching is new — the
// frame's numbers are GOOD, and it is the most dangerous anchor in the game
// because of where it stands. So the tell being pinned below is a POSITION,
// measured off two GLBs, not a defect painted on one.
// -------------------------------------------------------------------------
t.test('the hail-net frame BRACKETS the glass — the tell is a position, and it is measured', () => {
const frame = loaded.get('hail_net_frame_01');
const glass = loaded.get('glasshouse_01');
assert(frame && glass, 'hail_net_frame_01 or glasshouse_01 did not load');
const f = new THREE.Box3().setFromObject(frame.scene);
const g = new THREE.Box3().setFromObject(glass.scene);
// Both assets are built at the origin, so a shared-origin placement is the
// authored case and these comparisons are the real geometry.
assert(f.min.x < g.min.x && f.max.x > g.max.x,
`the frame spans x ${f.min.x.toFixed(2)}..${f.max.x.toFixed(2)} and the glasshouse `
+ `${g.min.x.toFixed(2)}..${g.max.x.toFixed(2)} — the frame must STRADDLE the glass on x. `
+ 'A frame that no longer brackets it is a frame whose anchors bill a building they are '
+ 'not standing over, and the $320 becomes a number with no geometry behind it');
assert(f.min.z < g.min.z && f.max.z > g.max.z,
`the frame spans z ${f.min.z.toFixed(2)}..${f.max.z.toFixed(2)} and the glasshouse `
+ `${g.min.z.toFixed(2)}..${g.max.z.toFixed(2)} — it must straddle on z as well; `
+ 'legs standing ON the glass would be the joke version of this asset');
assert(f.max.y > g.max.y + 0.35,
`the frame heads at y=${f.max.y.toFixed(2)} and the ridge is at ${g.max.y.toFixed(2)}`
+ 'a net frame built to protect glass must CLEAR the ridge with room for the net to '
+ 'drape, or it is resting on the thing it exists to keep hail off');
// Every eye is above the ridge: the load path arcs OVER the glass, which is
// the whole reason a failure here lands on panes.
for (const n of ['net_anchor_01', 'net_anchor_02', 'net_anchor_03', 'net_anchor_04']) {
const o = frame.scene.getObjectByName(n);
assert(o, `${n} missing`);
const p = new THREE.Vector3().setFromMatrixPosition(o.matrixWorld);
assert(p.y > g.max.y,
`${n} sits at y=${p.y.toFixed(2)}, below the ridge (${g.max.y.toFixed(2)}) — every corner `
+ 'of this frame must be above the glass or the tier is not about the glass');
}
});
t.test('the frame\'s 20 m read is METALNESS, not lightness — S17\'s lesson, applied on purpose', () => {
// S17 measured this the hard way on the corroded post: galvanised ships at
// metalness 0.90, and with no environment map a near-mirror has nothing to
// mirror and renders NEAR-BLACK (rgb 49,51,49 at 20 m). So every steel the
// player is allowed to trust is dark at yard distance. This asset's entire
// long-range tell is being the opposite of that, and the mechanism is
// metalness — pin the mechanism, or the docstring gets "fixed" the wrong
// way by whoever repaints it next.
const pick = (asset, node) => {
let m = null;
loaded.get(asset)?.scene.getObjectByName(node)?.traverse((o) => { if (o.material) m = o.material; });
return m;
};
const paint = pick('hail_net_frame_01', 'top_frame');
const gal = pick('sail_post', 'post');
assert(paint && gal, 'could not reach the frame paint or the honest post steel');
assert(paint.metalness <= 0.15,
`the frame paints at metalness ${paint.metalness} — powder-coat is PAINT, and above ~0.15 `
+ 'it starts mirroring an environment that does not exist and goes dark like every other '
+ 'steel. The tell dies silently: the frame is still white in the palette and black on screen');
assert(gal.metalness >= 0.6,
`the honest post's steel is at metalness ${gal.metalness} — the control for this comparison `
+ 'is that trusted steel IS a near-mirror; if that changed, re-measure the whole claim');
const lum = (c) => c.r * 0.2126 + c.g * 0.7152 + c.b * 0.0722;
assert(lum(paint.color) > lum(gal.color) * 1.3,
`frame paint luminance ${lum(paint.color).toFixed(3)} vs galvanised ${lum(gal.color).toFixed(3)}`
+ 'the powder-coat must also be the brighter base colour, so the two halves of the tell '
+ 'agree instead of fighting');
});
t.test('the hail-net frame is a NEW RUNG at 0.80: good steel, worst place, resolvable node-less', () => {
const T = FACTORY_ANCHOR_RATINGS.types;
const entry = T.hail_net_frame;
assert(entry, 'hail_net_frame is not in the ratings manifest — the type cannot resolve '
+ 'node-less, and a site declaring it plainly would play at the default hint (the '
+ 'sail_post bug shape, on the highest-collateral anchor in the game)');
assert(!FACTORY_ANCHOR_RATINGS.ambiguous.hail_net_frame,
'hail_net_frame went manifest-AMBIGUOUS — its four eyes disagree, and the runtime will '
+ 'refuse to guess; they are one frame and must bake one number');
// Pinned against the manifest, never a literal — a literal would stay green
// through a whole reshuffle of the ladder.
assert(entry.rating_hint > T.pergola.rating_hint,
`the frame rates ${entry.rating_hint} and the pergola ${T.pergola.rating_hint} — the frame `
+ 'must out-rate it by a distance: a deck screwed to a house flexes, this is an engineered '
+ 'portal on four footings. De-rating honest steel for looking dangerous is the corroded '
+ 'post\'s lie inverted');
assert(entry.rating_hint < T.post.rating_hint,
`the frame rates ${entry.rating_hint} against the honest post's ${T.post.rating_hint} — at or `
+ 'above the post there is no de-rate left for "32 mm sized for mesh, not for 25 m² of sail"');
// THE FIELD THAT MAKES IT A TIER: what it takes with it is the GLASS.
assert(entry.collateral === 'glasshouse',
`the manifest says this type bills ${JSON.stringify(entry.collateral)} — it must bill `
+ '"glasshouse". That string is the entire tier: the anchor is fine, the thing underneath '
+ 'it is $320, and a node-less declaration has to carry that or the yard is free to break');
assert(entry.sources.length === 4,
`the manifest cites ${entry.sources.length} eyes; the frame has four corners and a net `
+ 'ties at all of them — a missing source means a corner baked something different');
});
// ⚠️ THE GATE, AS ONE MEASUREMENT. Before this sprint this assert was RED by
// construction: no node anywhere in the palette named the glasshouse's key,
// so the collateral apex was unbillable and the 320 was decoration. It is the
// free-failure bug's mirror image — RULE 2 catches a key with no price, and
// this catches a price with no key that can reach it.
t.test('⚠️ the collateral APEX is reachable: some anchor in the palette can actually bill the glass', () => {
const g = loaded.get('glasshouse_01')?.scene.getObjectByName('glasshouse_01');
assert(g, 'glasshouse_01 root missing');
const key = g.userData?.collateral_key;
assert(typeof key === 'string', 'the glasshouse carries no collateral_key to reach');
const billers = [];
for (const [asset, o] of allNodes()) {
const u = o.userData ?? {};
if (u.collateral !== key) continue;
if (typeof u.rating_hint !== 'number' || u.tie_off === false) continue;
billers.push(`${asset}/${o.name}`);
}
assert(billers.length > 0,
`NOTHING in the palette can bill "${key}" ($${g.userData?.collateral_value}). The apex is `
+ 'priced and unreachable: main.js bills collateral off a BROKEN RIG CORNER\'s baked key, '
+ 'a glasshouse rightly offers no anchors, so the most expensive thing in the game costs '
+ 'nobody anything. It needs an adoptable anchor whose collateral names the glass — that '
+ 'is what the hail-net frame is for (SPRINT18 gate 4)');
// ...and the corollary that keeps gate 5 disarmed on my own asset: the
// frame POINTS at the key and must never CLAIM it. structFor() returns the
// FIRST structure matching a key, so a frame carrying collateral_key
// "glasshouse" would, in any yard authored frame-first, bill $320 for the
// panes and then wreck THE FRAME — the bill and the wreck agreeing with
// each other and both wrong, which is the only kind of wrong this repo
// cannot see from the outside.
const claimants = [];
for (const [asset, gltf] of loaded) {
const root = gltf.scene.getObjectByName(asset);
if (root?.userData?.collateral_key === key) claimants.push(asset);
}
for (const c of claimants) {
assert(c.startsWith('glasshouse_01'),
`${c} claims collateral_key "${key}", and it is not the glasshouse. Exactly one structure `
+ 'may OWN a key — the one that owns the price. structFor() takes the first match, so a '
+ 'second claimant means some yard bills the glass and wrecks something else');
}
assert(claimants.length > 0, `vacuous: nothing owns "${key}" at all`);
});
t.test('the net-frame wreck folds at ONE end: keeps its height, carries no glass, offers nothing', () => {
const w = loaded.get('hail_net_frame_01_wrecked');
const i = loaded.get('hail_net_frame_01');
assert(w && i, 'the net frame or its wreck did not load');
const bw = new THREE.Box3().setFromObject(w.scene);
const bi = new THREE.Box3().setFromObject(i.scene);
assert(Math.abs(bw.max.y - bi.max.y) < 0.10,
`the wreck tops out at y=${bw.max.y.toFixed(2)} vs the intact ${bi.max.y.toFixed(2)} — the +x `
+ 'portal stands and holds the eave rails, so a portal frame folds at one end. A wreck lying '
+ 'flat is claiming a failure mode this thing does not have');
assert((bw.max.x - bw.min.x) > (bi.max.x - bi.min.x) + 0.6,
`the wreck spans ${(bw.max.x - bw.min.x).toFixed(2)} m on x against the intact `
+ `${(bi.max.x - bi.min.x).toFixed(2)} — the net has to end up OUT past the footing, or your `
+ 'own debris is tidier than the storm\'s and the tier stops teaching what it is for');
// No glass here. The glasshouse has its own wreck variant and swaps
// independently; shards in both would draw twice the night both swap, and
// would quietly claim the glass's failure as this asset's.
const glassy = [];
w.scene.traverse((o) => {
const m = o.material;
if (m && m.transparent === true && m.opacity < 0.6) glassy.push(o.name);
});
assert(glassy.length === 0,
`the net-frame wreck ships translucent geometry on [${glassy.join(', ')}] — the glass belongs `
+ 'to glasshouse_01_wrecked, which swaps on its own; shards in both double-draw');
// No anchor survives the wreck (the fascia rule, sixth application).
const offers = [];
w.scene.traverse((o) => {
if (typeof o.userData?.rating_hint === 'number' || o.userData?.anchor_type !== undefined) {
offers.push(o.name);
}
});
assert(offers.length === 0,
`the wreck still offers [${offers.join(', ')}] — you cannot clip to a folded frame, and an `
+ 'anchor that outlives its structure is the free-failure bug in a costume');
const wr = w.scene.getObjectByName('hail_net_frame_01_wrecked');
assert(wr?.userData?.broken_variant_of === 'hail_net_frame_01',
'the wreck must name its intact twin so A can pair the swap');
// UNPRICED BY RULING — the bike/jacaranda pattern. One broken corner can
// only name one collateral key, and the player is being taught about the
// glass. Splitting a bill between the frame and the panes with no mechanism
// to split it is exactly the guess the invoice exists to refuse.
assert(wr?.userData?.collateral_value === undefined,
'the net frame is UNPRICED BY RULING — build the mechanism that can bill two things off '
+ 'one broken corner before pricing the second one');
});
// -------------------------------------------------------------------------
// SPRINT18 gate 5 — THE SECOND-RUST-POST LANDMINE (D's filing, E+A).
//
// Two structures in one yard sharing a collateral key is currently ARMED and
// not firing: world.js's structFor() returns the FIRST structure whose key
// matches, main.js's billing loop dedupes by key, and C's scorecard envelope
// dedupes by key again — so the second one is invisible to the bill and to
// the wreck, while board.js's exposureOf() counts per STRUCTURE and prices
// both. D measured the split on a real two-post yard: $115 on the offer card
// against $45 on the failure route.
//
// The pool yard dodges it by shipping exactly one corroded post ON PURPOSE.
// That is a yard author remembering, which is not a defence — and gate 4 just
// made it likelier, because a glasshouse yard carries a structure that OWNS
// the key and a frame whose anchors POINT at it. Two glasshouses with a frame
// each trips this on the first night.
//
// So: the asset/manifest half is to make it impossible to SHIP quietly. The
// runtime half (a per-instance key a site can override) is A's and the exact
// wiring is filed in THREADS.
// -------------------------------------------------------------------------
const shippedSites = [];
for (const name of ['backyard_01', 'site_02_corner_block', 'site_03_swing_lawn',
'site_04_pool_yard']) {
shippedSites.push({ name, site: await loadSite(name) });
}
t.test('⚠️ no shipped yard gives two structures the same collateral key, and ids are unique', () => {
// Mirrors world.js structKey() exactly: site JSON wins, then the baked
// GLB key, then the structure's own id as a last resort.
const resolvedKey = (s) => {
if (s.collateralKey) return s.collateralKey;
const base = String(s.model ?? '').replace(/_v1$/, '');
return loaded.get(base)?.scene.getObjectByName(base)?.userData?.collateral_key ?? s.id;
};
let checked = 0;
for (const { name, site } of shippedSites) {
const structs = site.structures ?? [];
const ids = structs.map((s) => s.id);
const dupeIds = ids.filter((v, i) => ids.indexOf(v) !== i);
assert(dupeIds.length === 0,
`${name}: duplicate structure ids [${[...new Set(dupeIds)].join(', ')}]. world.js keys its `
+ 'structures Map by id, so the second one SILENTLY REPLACES the first — it never renders, '
+ 'never bills, and never wrecks, and nothing anywhere says a word');
const keys = structs.map(resolvedKey);
const dupeKeys = keys.filter((v, i) => keys.indexOf(v) !== i);
assert(dupeKeys.length === 0,
`${name}: two structures resolve to the same collateral key `
+ `"${[...new Set(dupeKeys)].join('", "')}" (keys: [${keys.join(', ')}]). structFor() `
+ 'returns the FIRST match, so the bill and the wreck would both land on the wrong one, '
+ 'agreeing with each other; meanwhile exposureOf() prices per structure and the offer '
+ 'card would quote a total the failure route can never reach (D measured $115 vs $45). '
+ 'Give each instance its own key in the site JSON — and until A lands the per-anchor '
+ 'override, the anchors bake the shared key, so the second one cannot be made to bill '
+ 'at all: see THREADS [E] SPRINT18 gate 5');
checked += keys.length;
}
assert(checked > 0, 'vacuous: no structures found across the shipped yards');
});
// The other half of the same seam, from the asset side: an anchor names the
// collateral it TAKES, and in a real yard something has to own that name. The
// hail-net frame is the first asset where those are different objects, so a
// frame placed in a yard with no glasshouse would bill a building that is not
// there — collateralFor() returns null and the corner is FREE. RULE 2 proves
// the key is priced SOMEWHERE in the palette; this proves it is owned in
// every yard that names it.
t.test('⚠️ every collateral an authored anchor names is OWNED by exactly one thing in that yard', () => {
const types = FACTORY_ANCHOR_RATINGS.types;
let checked = 0;
for (const { name, site } of shippedSites) {
const owners = new Map();
const own = (key, what) => {
if (typeof key !== 'string') return;
owners.set(key, (owners.get(key) ?? 0) + 1);
void what;
};
const houseBase = String(site.house?.model ?? '').replace(/_v1$/, '');
own(site.house?.collateralKey
?? loaded.get(houseBase)?.scene.getObjectByName(houseBase)?.userData?.collateral_key,
'house');
for (const s of site.structures ?? []) {
const base = String(s.model ?? '').replace(/_v1$/, '');
own(s.collateralKey
?? loaded.get(base)?.scene.getObjectByName(base)?.userData?.collateral_key ?? s.id, s.id);
}
// What every authored anchor on a structure will actually bill: the baked
// node's key when it names a node, else the manifest's key for its type.
for (const s of site.structures ?? []) {
const base = String(s.model ?? '').replace(/_v1$/, '');
for (const a of s.anchors ?? []) {
const baked = a.node
? loaded.get(base)?.scene.getObjectByName(a.node)?.userData?.collateral
: types[a.type]?.collateral;
if (typeof baked !== 'string') continue; // bills nothing, by design
assert(owners.get(baked) === 1,
`${name}/${a.id} will bill "${baked}" on failure, and ${owners.get(baked) ?? 0} things `
+ 'in this yard own that key. Zero means collateralFor() returns null and breaking '
+ 'this corner is FREE (the gutter bug, RULE 2\'s runtime twin); more than one means '
+ 'structFor() picks the first and the wreck lands on the wrong structure');
checked += 1;
}
}
}
assert(checked > 0,
'vacuous: no structure anchor in any shipped yard names collateral — this pin checked nothing');
});
for (const { world } of siteWorlds) world.dispose();
}