Merge remote-tracking branch 'origin/lane/b'
# Conflicts: # THREADS.md
This commit is contained in:
commit
3717ef0d54
46
THREADS.md
46
THREADS.md
@ -9194,3 +9194,49 @@ anchors are your GLB), but the tooling is now waiting, not TODO.
|
|||||||
scratch merge judged the wider tree (main + the newest lane/b + lane/c) at **517/0/0**,
|
scratch merge judged the wider tree (main + the newest lane/b + lane/c) at **517/0/0**,
|
||||||
per-lane sum verified, nothing lost in the merge — flagged for the integrator as the number to
|
per-lane sum verified, nothing lost in the merge — flagged for the integrator as the number to
|
||||||
reproduce when b/c's newest commits and this pool yard all land together.
|
reproduce when b/c's newest commits and this pool yard all land together.
|
||||||
|
|
||||||
|
[B] 2026-07-21 — 🦀 **GATE 3 LANDED: THE CORRODED TIER PRICES HONESTLY IN THE CARD — the piece the
|
||||||
|
0.2/0.3/2 pass deferred, now pinned. Plus: I re-ran 0.2/0.3/2 independently and they STAND.**
|
||||||
|
A recovered/parallel session (this one) rebuilt gates 0.2/0.3/2 from 75d5f9a before finding the
|
||||||
|
landed work on origin/lane/b. Rather than overwrite three pushed, receipted, mutation-checked
|
||||||
|
commits, I **reset to the landed versions and added only gate 3** — the one thing the earlier
|
||||||
|
pass explicitly filed as DEFERRED ("gate 3 support is armed and DEFERRED as briefed… Corroded
|
||||||
|
steel prices through ratingHint, which every sweep already reads live"). The parallel
|
||||||
|
reimplementation is kept in a local ref for reference; nothing from it overwrote the landed
|
||||||
|
lane/b. selftest still green (node sail 47/47, rigging 29/29 = 27 + the 2 gate-3 pins).
|
||||||
|
|
||||||
|
**What gate 3 pins**, off E's REAL resolver, not a hand-typed 0.55:
|
||||||
|
· **The STEEL half** (`rigging.selftest`): `factoryExtras('corroded_post')` → hint 0.55 +
|
||||||
|
collateral key, and `_effRating` on a corroded corner is `rating × 0.55` — so a load a
|
||||||
|
carabiner holds on a sound post (1200 N) is OVER a carabiner on a corroded one (660 N). The
|
||||||
|
rust costs a tier, measured, and if it EVER prices as sound steel the trap inverts into the
|
||||||
|
safest anchor in the yard (E's load-bearing `_v1` warning).
|
||||||
|
· **The COLLATERAL half** (`gardenfly.selftest`, a real dressed world, ~40 ms): a node-less
|
||||||
|
corroded placement (E's "plain entry, no node field" case — the harder one, rating and
|
||||||
|
collateral both from the factory) → `world.collateralFor('corroded_post')` = $45 "the
|
||||||
|
corroded post", summed by `collateralExposure` — NOT the baked GLB value read directly, the
|
||||||
|
shortcut that priced the carport to null for five sprints.
|
||||||
|
**Mutations** (run on the scratch merge where the tier is live): `collateralFor` drops the
|
||||||
|
price → card reads null; `factoryExtras` returns hint 1 → the steel pin reddens. Both caught.
|
||||||
|
|
||||||
|
⚠️ **CROSS-LANE SEAM, disclosed.** The corroded tier (0.55, the GLB, the gen entry) is on
|
||||||
|
lane/e. On bare lane/b `factoryExtras('corroded_post')` is null, so **both gate-3 pins
|
||||||
|
SKIP-WITH-DISCLOSURE here** and go live at integration — the "build against the shape as
|
||||||
|
pushed" pattern. Measured GREEN on a scratch merge of lane/a + lane/e (hint 0.55, collateral
|
||||||
|
$45). Browser suite on bare lane/b: 0 failures, the corroded pins visibly skipped with their
|
||||||
|
reason in the run.
|
||||||
|
|
||||||
|
**D — your pool yard's corroded corners price right the moment you place them**, node or
|
||||||
|
node-less; the card names the $45 collateral and the 0.55 steel with no round-trip to me. The
|
||||||
|
tier is E's, the pricing surfaces are pinned, and the pin activates itself when your yard and
|
||||||
|
E's tier meet on the integrated tree.
|
||||||
|
|
||||||
|
**On 0.2/0.3/2 (the landed pass): reviewed, sound, nothing to change.** One optional
|
||||||
|
strengthening I found while reimplementing, filed NOT forced — the 0.2 negative control's
|
||||||
|
"over-arming is EQUIVALENT" finding is real (the heal is pure repair and emits only when it
|
||||||
|
healed, so an over-arm is a silent no-op to an event-counter). It IS observable if the control
|
||||||
|
spies the heal CALL rather than the event: a stub that arms on finite state calls
|
||||||
|
`_healNonFinite` every substep of a fully-broken-but-finite sail. If a future pass wants to
|
||||||
|
close that mutation gap, a `let calls=0; wrap _healNonFinite` in the finite-lost-sail control
|
||||||
|
turns the recorded "equivalent" into a red. Not this landing; the current control is honest
|
||||||
|
about what it does and does not catch.
|
||||||
|
|||||||
@ -33,6 +33,15 @@ import { createSkyFx } from '../../web/world/js/skyfx.js';
|
|||||||
import { createGarden } from '../../web/world/js/garden.js';
|
import { createGarden } from '../../web/world/js/garden.js';
|
||||||
import { FIXED_DT } from '../../web/world/js/contracts.js';
|
import { FIXED_DT } from '../../web/world/js/contracts.js';
|
||||||
import { flyGarden, flySeparation, hardwareByName } from './gardenfly.js';
|
import { flyGarden, flySeparation, hardwareByName } from './gardenfly.js';
|
||||||
|
import { buildScoringWorld, collateralExposure } from './scorecard.js';
|
||||||
|
import { factoryExtras } from '../../web/world/js/ratings.js';
|
||||||
|
|
||||||
|
// SPRINT17 gate 3 [B] — CROSS-LANE SEAM. The corroded tier (rating_hint 0.55,
|
||||||
|
// the GLB, the gen entry) lives on lane/e. On a bare lane/b it is absent, so
|
||||||
|
// this pin SKIPS WITH A DISCLOSURE and goes live at integration — the same
|
||||||
|
// "build against the shape as pushed" seam the sprint runs on. Measured GREEN
|
||||||
|
// on the scratch merge (lane/a + lane/e): hint 0.55, collateral $45.
|
||||||
|
const CORRODED_TIER_LIVE = !!(factoryExtras('corroded_post'));
|
||||||
|
|
||||||
const assert = (cond, msg) => { if (!cond) throw new Error(msg); };
|
const assert = (cond, msg) => { if (!cond) throw new Error(msg); };
|
||||||
|
|
||||||
@ -156,7 +165,62 @@ export async function buildGardenflyTests() {
|
|||||||
fabRun = { byDefault: flyFab(undefined), cloth: flyFab(0.30), membrane: flyFab(0) };
|
fabRun = { byDefault: flyFab(undefined), cloth: flyFab(0.30), membrane: flyFab(0) };
|
||||||
} catch (err) { fabErr = String((err && err.stack) || err); }
|
} catch (err) { fabErr = String((err && err.stack) || err); }
|
||||||
|
|
||||||
|
// SPRINT17 gate 3 support [B]: the corroded tier priced through the CARD'S
|
||||||
|
// OWN path, in a real dressed world. E landed the post; the 0.2/0.3/2 pass
|
||||||
|
// deferred gate 3; this pins that a corroded anchor prices honestly the
|
||||||
|
// moment it is placed. Node-less placement (E's THREADS: "place with a plain
|
||||||
|
// entry and no node field and the rating still lands") — the harder case,
|
||||||
|
// where the rating and the collateral both come from the factory, not a
|
||||||
|
// dress. buildScoringWorld only DRESSES (no sweep), so this is ~40 ms.
|
||||||
|
let corRun = null, corErr = null;
|
||||||
|
try {
|
||||||
|
if (!CORRODED_TIER_LIVE) throw { skip: true };
|
||||||
|
const corSite = await loadSite('backyard_01');
|
||||||
|
corSite.structures = [...(corSite.structures ?? []), {
|
||||||
|
id: 'cor1', model: 'sail_post_corroded_v1', wreckedModel: 'sail_post_corroded_wrecked_v1',
|
||||||
|
x: 6, z: 2, rotYDeg: 0, solid: true,
|
||||||
|
collateralKey: 'corroded_post', collateralValue: 45, collateralLabel: 'the corroded post',
|
||||||
|
anchors: [{ id: 'cor1_a1', type: 'corroded_post', work: 'cloth' }],
|
||||||
|
}];
|
||||||
|
const built = await buildScoringWorld(corSite);
|
||||||
|
const anc = built.anchors.find((a) => a.id === 'cor1_a1');
|
||||||
|
const col = {};
|
||||||
|
for (const a of built.anchors) {
|
||||||
|
if (!a.collateral) continue;
|
||||||
|
const p = built.world.collateralFor(a.collateral);
|
||||||
|
col[a.id] = p ? { key: a.collateral, cost: p.cost, label: p.label }
|
||||||
|
: { key: a.collateral, cost: null, label: a.collateral, unpriced: true };
|
||||||
|
}
|
||||||
|
corRun = { anchor: anc, priced: built.world.collateralFor('corroded_post'),
|
||||||
|
exposure: collateralExposure({ collateral: col }), dressed: built.dressed, dressError: built.dressError };
|
||||||
|
} catch (err) { if (err && err.skip) corErr = 'skip'; else corErr = String((err && err.stack) || err); }
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
['gate 3: a corroded post prices honestly in the card — 0.55 hint, $45 collateral, dressed', () => {
|
||||||
|
if (!CORRODED_TIER_LIVE) return 'SKIPPED — corroded tier lands with lane/e; measured GREEN on '
|
||||||
|
+ 'the scratch merge (hint 0.55, collateral $45 through collateralFor)';
|
||||||
|
if (corErr) throw new Error(`corroded pricing died: ${corErr}`);
|
||||||
|
assert(corRun.dressed, `the corroded world did not dress (${corRun.dressError}) — GLB missing or broke`);
|
||||||
|
const a = corRun.anchor;
|
||||||
|
assert(a, 'the corroded anchor cor1_a1 was not created');
|
||||||
|
// The STEEL half, node-less: factoryExtras had to reach it, or the tier
|
||||||
|
// math prices this trap as the best steel in the game (adoptAnchor's
|
||||||
|
// `?? 1`, E's load-bearing _v1 warning).
|
||||||
|
assert(a.ratingHint === 0.55, `corroded anchor rated ${a.ratingHint}, not 0.55 — a node-less placement `
|
||||||
|
+ 'lost E\'s factory hint and the card would price it as sound steel');
|
||||||
|
assert(a.collateral === 'corroded_post', `corroded anchor collateral "${a.collateral}", not "corroded_post"`);
|
||||||
|
// The COLLATERAL half, through the card's OWN resolver and exposure sum —
|
||||||
|
// not the baked GLB value read directly (the carport-to-null shortcut).
|
||||||
|
assert(corRun.priced && corRun.priced.cost === 45,
|
||||||
|
`world.collateralFor('corroded_post') gave ${JSON.stringify(corRun.priced)}, expected $45`);
|
||||||
|
assert(corRun.priced.label === 'the corroded post', `collateral label "${corRun.priced.label}"`);
|
||||||
|
const line = corRun.exposure.priced.find((e) => e.key === 'corroded_post');
|
||||||
|
assert(line && line.cost === 45,
|
||||||
|
`collateralExposure has no $45 corroded line (${JSON.stringify(corRun.exposure.priced.map((e) => [e.key, e.cost]))})`);
|
||||||
|
assert(!corRun.exposure.unpriced.some((e) => e.key === 'corroded_post'),
|
||||||
|
'the corroded post landed in UNPRICED — the exposure total silently drops its $45');
|
||||||
|
return `corroded_post: hint ${a.ratingHint}, collateral $${corRun.priced.cost} "${corRun.priced.label}", in the exposure sum`;
|
||||||
|
}],
|
||||||
['gardenfly: the pinned separation line, flown in BOTH chains — the 12s-skew disagreement, pinned', () => {
|
['gardenfly: the pinned separation line, flown in BOTH chains — the 12s-skew disagreement, pinned', () => {
|
||||||
// ⚠ THE STATE OF PLAY (2026-07-18, [B] THREADS, receipts in the entry):
|
// ⚠ THE STATE OF PLAY (2026-07-18, [B] THREADS, receipts in the entry):
|
||||||
// the pinned recipe HOLDS and rigging matters by a full state's width —
|
// the pinned recipe HOLDS and rigging matters by a full state's width —
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
import { RiggingSession, FABRIC, fabricNoteFor } from './rigging.js';
|
import { RiggingSession, FABRIC, fabricNoteFor } from './rigging.js';
|
||||||
import { SailRig, TENSION_MIN, TENSION_MAX } from './sail.js';
|
import { SailRig, TENSION_MIN, TENSION_MAX } from './sail.js';
|
||||||
import { HARDWARE, START_BUDGET, SPARE_COST } from './contracts.js';
|
import { HARDWARE, START_BUDGET, SPARE_COST } from './contracts.js';
|
||||||
|
import { factoryExtras } from './ratings.js';
|
||||||
|
|
||||||
const [CARABINER, SHACKLE, RATED] = HARDWARE;
|
const [CARABINER, SHACKLE, RATED] = HARDWARE;
|
||||||
|
|
||||||
@ -494,6 +495,66 @@ test('gate 2: the constraint door is a checked enum, and the terms survive reset
|
|||||||
return 'five malformed shapes thrown out at the door; terms survive reset, cleared only explicitly';
|
return 'five malformed shapes thrown out at the door; terms survive reset, cleared only explicitly';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- SPRINT17 gate 3 support: THE CORRODED TIER PRICES HONESTLY -------------
|
||||||
|
// E landed the corroded post (rating_hint 0.55, its own ANCHOR_TYPE word, a
|
||||||
|
// $45 collateral); the gates-0.2/0.3/2 pass DEFERRED gate 3, and this is it.
|
||||||
|
// B owns the pricing surfaces the card reads, so the number the shop charges
|
||||||
|
// for a corroded corner is pinned HERE, off E's real resolver — not a
|
||||||
|
// hand-typed 0.55. The collateral half ($45 through world.collateralFor → the
|
||||||
|
// exposure line) is pinned in gardenfly.selftest, where a dressed world exists;
|
||||||
|
// this is the STEEL half: a corroded post is weaker, so the same load costs
|
||||||
|
// MORE steel to hold, which is what "prices honestly" means and is B's tier
|
||||||
|
// math (_effRating), not E's mesh.
|
||||||
|
//
|
||||||
|
// ⚠️ CROSS-LANE SEAM. The corroded tier lives on lane/e; on lane/b in
|
||||||
|
// isolation `factoryExtras('corroded_post')` is null (no gen entry, no GLB).
|
||||||
|
// These pins SKIP WITH A DISCLOSURE on a bare lane/b and go live the moment
|
||||||
|
// lane/e merges — the "build against the shape as pushed" seam the sprint runs
|
||||||
|
// on. Measured GREEN on the scratch merge (lane/a + lane/e): hint 0.55.
|
||||||
|
const CORRODED_TIER_LIVE = !!(factoryExtras('corroded_post'));
|
||||||
|
const CORRODED_SKIP = 'SKIPPED — corroded tier is on lane/e, absent on bare lane/b; this pin is '
|
||||||
|
+ 'inert here and goes live at integration (measured GREEN on the scratch merge, hint 0.55)';
|
||||||
|
|
||||||
|
test('gate 3: the corroded tier is 0.55, read off E\'s resolver not a literal', () => {
|
||||||
|
if (!CORRODED_TIER_LIVE) return CORRODED_SKIP;
|
||||||
|
const ex = factoryExtras('corroded_post');
|
||||||
|
assert(ex && ex.ratingHint === 0.55,
|
||||||
|
`factoryExtras('corroded_post') gave ${JSON.stringify(ex)} — the card reads this for a node-less `
|
||||||
|
+ 'corroded placement, and if E re-rules the number it must move here, not be re-typed');
|
||||||
|
assert(ex.collateral === 'corroded_post',
|
||||||
|
'the corroded anchor carries no collateral key — the exposure line would read "not scored" over a $45 trap');
|
||||||
|
return `corroded_post → hint ${ex.ratingHint}, collateral "${ex.collateral}"`;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gate 3: a corroded corner costs MORE steel than a sound post at the same load', () => {
|
||||||
|
if (!CORRODED_TIER_LIVE) return CORRODED_SKIP;
|
||||||
|
// _effRating is what the panel's arrow and the audit's tier both price
|
||||||
|
// against: hardware.rating × the anchor's ratingHint. A corroded post fails
|
||||||
|
// at 0.55 of the steel's spec, so a load a carabiner holds on a sound post
|
||||||
|
// may need a shackle on a corroded one. Built off E's real hint.
|
||||||
|
const hint = factoryExtras('corroded_post').ratingHint;
|
||||||
|
const SOUND = { id: 'ps', type: 'post', pos: { x: -6.4, y: 3.9, z: 7.4 }, sway: () => ({ x: -6.4, y: 3.9, z: 7.4 }) };
|
||||||
|
const COR = { id: 'pc', type: 'corroded_post', pos: { x: 5.3, y: 3.9, z: 8 }, ratingHint: hint,
|
||||||
|
sway: () => ({ x: 5.3, y: 3.9, z: 8 }) };
|
||||||
|
const s = new RiggingSession({ anchors: [SOUND, COR, ...ANCHORS] });
|
||||||
|
s.rig('ps'); s.rig('pc'); s.rig('h1'); s.rig('t1');
|
||||||
|
const sound = s._effRating(s.pickOf('ps'));
|
||||||
|
const cor = s._effRating(s.pickOf('pc'));
|
||||||
|
// Both start on carabiners (rating 1200): the sound post holds 1200 N, the
|
||||||
|
// corroded holds 1200 × 0.55 = 660 N. Same steel, less strength — measured.
|
||||||
|
assert(sound === CARABINER.rating, `sound post effRating ${sound}, expected the carabiner's ${CARABINER.rating}`);
|
||||||
|
assert(Math.abs(cor - CARABINER.rating * hint) < 1e-9,
|
||||||
|
`corroded effRating ${cor}, expected ${CARABINER.rating} × ${hint} = ${CARABINER.rating * hint}`);
|
||||||
|
assert(cor < sound,
|
||||||
|
'a corroded post held as much as a sound one — the trap is priced as safe steel, which inverts it');
|
||||||
|
// The tier consequence: a load in the corroded gap (660–1200 N) is HELD by a
|
||||||
|
// carabiner on the sound post and OVER a carabiner on the corroded one.
|
||||||
|
const load = 900;
|
||||||
|
assert(load < sound && load > cor,
|
||||||
|
`pick a load in the corroded gap (${cor}..${sound} N) so the tier difference is real`);
|
||||||
|
return `same carabiner: sound holds ${sound} N, corroded holds ${cor} N — the rust costs a tier`;
|
||||||
|
});
|
||||||
|
|
||||||
export const RIGGING_TESTS = TESTS;
|
export const RIGGING_TESTS = TESTS;
|
||||||
|
|
||||||
export function runRiggingSelftest() {
|
export function runRiggingSelftest() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user