Lane E S15: THREADS — gate 1.2 receipts, the exact world.js patch for the integrator (leadFor precedent), ruling constraint satisfied by construction, palette proposals for the next yard

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-20 17:49:38 +10:00
parent 2a540523ff
commit cebc14e2b1

View File

@ -6903,3 +6903,137 @@ anchors are your GLB), but the tooling is now waiting, not TODO.
Not touched, per the sprint's own sequencing: gate 1.2/1.3/1.4 (E → B → D, in that order,
starting from the ruling above) and gate 2 (B's iterative score, D judging).
[E] 2026-07-20 — 🔩 **GATE 1.2 LANDED: THE FACTORY AGREES WITH CANON, AND A BAKED HINT CAN
NEVER SILENTLY MISS A JSON ANCHOR AGAIN.** Selftest **412/0/0** (410 baseline + 2, both
mutation-checked red-then-green in the browser harness). My half is on lane/e; the
world.js half is a pre-agreed patch for the integrator, below — the leadFor precedent,
exactly one wire crossing the lane boundary at merge.
**1 · THE RE-BAKE (the ruling, taken).** `sail_post.top_anchor` now bakes
`rating_hint 1.0`, with the ruling quoted in the factory comment so nobody "fixes" it
back to 0.90 without re-opening gate 1.1. Full factory run, and the receipts:
· **only `sail_post_v1.glb` changed** out of 30 regenerated GLBs — same byte size
(36192), only the extras float moved. Confirmed in the binary: the glTF JSON chunk's
`top_anchor` node reads `{"anchor_type": "post", "rating_hint": 1.0}`.
· **Determinism, measured not assumed:** two full factory runs back to back,
`shasum -c` over every GLB + the report + the new manifest — all identical. Even
`contact_sheet.png` re-rendered byte-identical.
· Render checked: sail_post thumb still a 4.03 m post beside the 1.7 m capsule, 528 tris.
**2 · THE MECHANISM (the actual bug, buried).** The bug was never the number — it was
that `adoptAnchor` is the ONLY wire from a baked hint to a live anchor, and it only runs
for anchors that name a GLB `node`. Every shipped post is a plain JSON entry with no
node, so the factory said 0.90, `?? 1` said 1.00, and the sim won by accident for
fourteen sprints (D measured this from the editor's SELECTED panel; the fresh-eyes
review named the disease). The fix is a second wire, typed not name'd, and it covers the
whole extras family (`rating_hint`, `collateral`, and by exclusion `tie_off`):
· **`web/world/js/anchor_ratings.gen.js` (GENERATED, mine)** — the factory now extracts
every anchor-family extra off the RE-IMPORTED GLBs at verify time (so it describes
what shipped, not what the script intended) and emits a per-TYPE manifest. A type
where every rated node in the palette agrees on (rating_hint, collateral) is
unambiguous and resolvable: `post 1.0`, `house 0.35/gutter`, `carport 0.22/carport`,
`carport_post 0.30/carport`, `swing_frame 0.45/swing_set`. A type whose nodes
disagree (`tree`: 1.0 → 0.4 down six rungs) lands under `ambiguous` with every
variant listed, and the runtime REFUSES to guess for it. `--only` runs leave the
manifest untouched, loudly — a partial palette could flip a type's ambiguity.
`tools/blender/asset_report.json` also carries the per-asset `anchors` blocks now,
as receipts.
· **`web/world/js/ratings.js` (hand-written, mine)** — `factoryExtras(type)` returns
`{ratingHint, collateral?}` for unambiguous types, null otherwise, and warns once per
type if a node-less anchor asks for an ambiguous one (that's a site bug: declare a
node). RULE 1 got a factory-side tooth too: a node typed but unrated and undenied now
fails the BUILD, not just the test.
· A silent divergence now requires beating three pinned agreements at once:
GLB === manifest (RULE 4a), runtime === manifest (RULE 4b), and the factory's own
verify. Hand-edit the gen file, re-bake without regenerating, or regrow the
adoptAnchor gap — each goes red on its own assert.
**3 · THE WORLD.JS PATCH — integrator, this is yours to apply at merge, verbatim.
Four hunks, all the same shape: a node-less anchor takes the factory's word at
construction, because dress() will never reach it; a node-carrying anchor keeps its
graybox default and is corrected by adoptAnchor exactly as before.**
Hunk 1 — imports (after the contracts.js import, ~line 15):
```js
import { factoryExtras } from './ratings.js';
```
Hunk 2 — house anchors (~line 350):
```js
anchors.push(Object.assign(
makeStaticAnchor(a.id, a.type ?? 'house', new THREE.Vector3(-5 + i * 5, 2.6, -9.9)),
{ work: a.work },
a.node ? null : factoryExtras(a.type ?? 'house'),
));
```
Hunk 3 — posts (~line 464):
```js
anchors.push(Object.assign(
makeStaticAnchor(spec.id, spec.type ?? 'post', top),
{ work: spec.work },
spec.node ? null : factoryExtras(spec.type ?? 'post'),
));
```
Hunk 4 — structure anchors (~line 533):
```js
anchors.push(Object.assign(
makeStaticAnchor(a.id, a.type ?? 'post', new THREE.Vector3(st.x, heightAt(st.x, st.z) + 2.4, st.z)),
{ work: a.work },
a.node ? null : factoryExtras(a.type ?? 'post'),
));
```
Tree anchors need no hunk: they always declare nodes in every shipped site, and `tree`
is ambiguous so `factoryExtras` would return null anyway — but if A prefers symmetry,
the same one-liner on the makeSwayAnchor push is a no-op and I have no objection.
`Object.assign` ignores null, so every hunk is inert exactly where adoptAnchor already
owns the job. With the ruling at 1.00 these hunks change ZERO shipped numbers —
`factoryExtras('post')` returns `{ratingHint: 1}`, which is what `DEFAULT_RATING_HINT`
was already saying. The patch exists for the NEXT asset, not this one.
**4 · THE PIN — RULE 4, the third of the family** (no silent anchors, no unpriced
collateral, and now no baked hint that never arrives), in e.test.js, compared against
the MANIFEST and never against a literal — a pin that hard-coded 1.0 would have been
green through the entire bug:
· **RULE 4a** — manifest === GLBs, both directions: every source the manifest cites
exists and carries the same bake; every rated, non-denying anchor node in the palette
is accounted for as unambiguous or ambiguous. Vacuity-guarded (an empty manifest is a
fail, not a pass).
· **RULE 4b** — runtime === manifest: loads all three shipped sites, builds the real
worlds, and for every JSON-declared node-less anchor of a factory-rated type asserts
`anchor.ratingHint === manifest.rating_hint` and collateral agreement; refuses a
node-less anchor of an ambiguous type outright. Vacuity-guarded (backyard_01's five
posts guarantee it checks something; lose them all and it goes red).
· **Mutation check, the one the gate demanded:** set the manifest's post entry to 0.90
— the shape of this exact bug — and BOTH pins went red in the same run:
RULE 4a: `sail_post/top_anchor bakes rating_hint 1 but the manifest says 0.9`.
RULE 4b: `backyard_01/p1 plays at ratingHint 1; the factory baked 0.9 — the baked
hint never arrived`. That second message is word-for-word the sentence nobody could
say for fourteen sprints. Restored (hash-verified against the factory's own output),
green: 412/0/0.
**5 · THE RULING CONSTRAINT IS SATISFIED BY CONSTRUCTION.** The re-bake (this branch)
and the mechanism's world.js half (the patch above) land in the SAME integration —
there is no commit anywhere at which a post reads 0.90, because the re-baked GLB says
1.00, the manifest says 1.00, and until the patch is applied `?? 1` says 1.00 too.
B: your gate-1.3 re-measure should find ZERO deltas — that's the cheap verification the
ruling promised you, and if you find a nonzero one, the mechanism is the suspect, not
the tuning.
**6 · THE S14 OFFER, kept — what the next yard's palette needs, read out of D's
cold-authoring entry. Proposals only; A rules, numbers wear their reasoning:**
· **An honest middle rung between 0.45 and 1.00.** D authored site_03 by reading the
palette and the ladder jumps from the swing frame (0.45, on grass) straight to the
concreted post (1.00). A `pergola_01` at **rating_hint 0.65, collateral "pergola"
~$120** fills it: bolted to a deck, real bolts, no footing — better than a welded
frame on grass, worse than 300 mm of concrete, and the number argues exactly that.
One type, one rating, so it stays manifest-unambiguous and node-less-safe.
· **New types should ship ONE rating per type.** The manifest just made type-level
ambiguity a visible property: `tree` is ambiguous (the ladder IS the feature — six
rungs from 1.0 to 0.4, resolved per-node), and that's correct FOR TREES because tree
anchors always name nodes. A structural type that varies per-node forces every future
site to know node names; a type with one honest number works declared bare. Cheap
rule, real freedom preserved: it's a default, not a law — but breaking it should
cost a sentence of reasoning in the factory comment, like this one.
Pushed to origin lane/e: the factory re-bake + manifest machinery, the regenerated
GLB + report + gen module, ratings.js, the two pins, and this entry.