/** * SHADES / HARD YARDS — Lane E owns this file. * * The runtime end of the factory's anchor ratings (SPRINT15 gate 1.2). * * THE BUG THIS CLOSES: `adoptAnchor` is the only place a baked `rating_hint` * ever reached an anchor, and it only runs for anchors that name a GLB `node`. * Every shipped sail post is a plain JSON entry with no `node`, so the * factory's bake never arrived and `?? 1` won — the factory said 0.90, the sim * played 1.00, and nothing anywhere could see the disagreement. Gate 1.1 ruled * the shipped 1.00 canon (every measured number stands on it), and this module * is the mechanism half of that ruling: the same silent divergence is now * impossible for the whole extras family (`rating_hint`, `collateral`, and by * exclusion `tie_off`) on ANY node-less JSON anchor whose type the factory * rates. * * HOW: the factory emits `anchor_ratings.gen.js` from the re-imported GLBs — * per anchor TYPE, the baked extras, but only where every rated node of that * type in the palette agrees (`types`). Types whose nodes disagree (tree: * 1.0 → 0.4 down the trunk) are listed under `ambiguous`, and this module * REFUSES to guess for them — a node-less anchor of an ambiguous type keeps * the default hint and warns loudly, and e.test.js pins that no shipped site * declares one. Three-way agreement is pinned in e.test.js: * GLB === manifest === runtime, so a re-bake that skips regeneration, a * hand-edit to the gen file, or a future adoptAnchor-shaped gap all go red. * * WHO CALLS IT: world.js, at the JSON-anchor creation sites, as * `Object.assign(makeStaticAnchor(...), ..., a.node ? null : factoryExtras(type))` * — node-carrying anchors keep their graybox default and are corrected by * adoptAnchor at dress time, exactly as before; node-less ones take the * factory's word at construction, because dress will never reach them. */ import { FACTORY_ANCHOR_RATINGS } from './anchor_ratings.gen.js'; /** Warn once per type, not once per anchor — a site with four posts of a * future ambiguous type should not print four identical warnings. */ const warned = new Set(); /** * The factory's baked extras for an anchor TYPE, in runtime (camelCase) field * names, ready to `Object.assign` onto an Anchor. * * @param {string} type An ANCHOR_TYPE string (contracts.js). * @returns {{ratingHint: number, collateral?: string}|null} * The extras when the factory rates this type unambiguously; null when it * doesn't rate it at all (caller keeps its default) or rates it ambiguously * (caller keeps its default AND this is almost certainly a site bug — a * node-less anchor of an ambiguous type has no honest rating; declare a * `node` instead. e.test.js refuses it in shipped sites). */ export function factoryExtras(type) { const t = FACTORY_ANCHOR_RATINGS.types[type]; if (t) { const out = { ratingHint: t.rating_hint }; if (t.collateral !== undefined) out.collateral = t.collateral; return out; } if (FACTORY_ANCHOR_RATINGS.ambiguous[type] && !warned.has(type)) { warned.add(type); console.warn( `[ratings] anchor type "${type}" is factory-AMBIGUOUS (its GLB nodes carry ` + `different rating_hints) — a node-less "${type}" anchor falls back to the ` + `default hint, which is the exact silent-divergence bug gate 1.2 buried. ` + `Declare a \`node\` on the anchor so adoptAnchor can carry the real bake.`); } return null; }