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:
parent
2a540523ff
commit
cebc14e2b1
134
THREADS.md
134
THREADS.md
@ -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.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user