From 4c2702fc58dea895739b12c4143cf7c2d913aeb4 Mon Sep 17 00:00:00 2001 From: m3ultra Date: Fri, 17 Jul 2026 15:42:50 +1000 Subject: [PATCH] =?UTF-8?q?Lane=20B=20R27=20w4:=20poseForShopFront=20?= =?UTF-8?q?=E2=80=94=20and=20the=20real=20towns=20face=20the=20wrong=20way?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The micro-task, done and verified. In doing it I found why F photographed empty road: it is not the pose. 1. THE DELIVERABLE — DBG.poseForShopFront(sel, dist = 8) Poses at a NAMED shop's frontage: door centred, sign band + R26 crate mark in shot, under the verandah. clusterPose() answers "where is the town's HEART", which is the wrong question for a named shop — Red Hill's heart doesn't contain Monster Robot, which is exactly how F got three frames of empty road. Keyed through ONE shop selector: F's R22/R24 resolver lived inside enterShop(), so I extracted it rather than grow a second key scheme beside it. A selector bug resolves the wrong shop AND looks like it worked (F's own first cut proved it), so the id-namespace law now has exactly one home. poseForShopFront(31) → Wholefood Cafe (PLAN id) + ambiguous: "31 is also the godverseShopId of Silky Oak Furnature" poseForShopFront('g:31') → Silky Oak Furnature (the prefix IS the fence) poseForShopFront('g:999999') → {error} — never throws, never guesses Framing proven by projection, not by eye: doorCentreNdcX = 0.0000 exactly, door + sign + mark inside the frustum. Derived from buildings.js, not hardcoded: door is centred on the lot front (off = 0, unlike poseForShop's off = 1.0); sign band = min(h,FACADE_H)-0.35 ± 0.25 ⇒ y 2.40-2.90; mark = y 2.45-2.52; camY 2.0 sits below AWNING_Y 2.95, so a ray rising to a target ≤2.9 can never cross it — the sign is clear BY GEOMETRY at any distance. 2. CORRECTING MY OWN R27 INTEL — I gave F two wrong numbers. I said the mark "rides high (y≈3.3)" and the vinyl blocks 4% of it. Both wrong: the mark is at y 2.45-2.52, and I had scanned y 3.0-3.6 — empty awning air above the sign, which is why it read ~0. Re-measured: the novelty_record disc (top y 2.50) reaches INTO the mark band and clips ~15% of the strip from EVERY offset; moving the camera only changes which 15%. "Shoot head-on" survives, but for the opposite reason to the one I gave. 3. THE FINDING — every real town's shops face away from their street, since R18. From Musgrave Road (Monster Robot's own frontEdge 1316) the shop is a blank grey back wall: no sign, no door, no mark. Measured: redhill_godverse 35 shop lots: 0 facing, 35 backwards (dot = -1.000) katoomba_real 72 shop lots: 0 facing, 72 backwards (dot = -1.000) synthetic correct — always has been dot = -1.000 exactly on every lot in both towns: a convention, not a defect. Katoomba's street_noon — the bookmark the tours have shot for nine rounds — is 46 draws / 19,680 tris of bare road. Root cause: CITY_SPEC contradicts itself and each generator implements one half. CITY_SPEC:71 (lots contract) "World facing = (-sin ry, -cos ry)" ⇒ -Z facade CITY_SPEC:335 (posters, R15) "the +Z street facade (B's canon)" ⇒ +Z facade plan_osm.js:443 obeys the first (its comment: "facade (-Z) faces road"). plan.js, buildings.js, venue.js and the R15 posters obey the second. Synthetic — the town every golden tests — follows B's canon and looks right, which is exactly why nine rounds of real-town work never caught it. The epoch's own signature species: a rule meeting a case written after it. Proven, then REVERTED: one line in A's plan_osm.js (ry = atan2(-nx,-nz)) flips katoomba 0/72 → 72/72 facing, and turns poseForShopFront's camera from 26m out in a paddock into 4m from Musgrave Road — the money shot F specified. Not committed: it is A's file, it moves every real-town plan (goldens re-pin under amendment law), and a self-contradicting spec is Fable's to rule on, not mine to pick a side of. This also re-reads my R22 diagnosis: I recorded "chunk-buildings 348 tris (0.2%)" in katoomba's busiest block and credited the instancing work. 348 tris is ~29 boxes — that was not efficiency, it was the buildings being ABSENT from frame. I read the evidence as a win for five rounds. Co-Authored-By: Claude Opus 4.8 --- docs/LANES/LANE_B_NOTES.md | 78 +++++++++++++++++++++++++ web/js/world/dbg.js | 117 +++++++++++++++++++++++++++++-------- 2 files changed, 171 insertions(+), 24 deletions(-) diff --git a/docs/LANES/LANE_B_NOTES.md b/docs/LANES/LANE_B_NOTES.md index d573344..22390d0 100644 --- a/docs/LANES/LANE_B_NOTES.md +++ b/docs/LANES/LANE_B_NOTES.md @@ -1,5 +1,83 @@ # LANE B — NOTES (measured) +## Round 27 wave 4 — `poseForShopFront` shipped · **and every real town's shops face away from the street** + +**The micro-task is done and verified. In doing it I found why F photographed empty road: it is not +the pose. Every shopfront in every OSM town has faced away from its street since R18, and the tours +have been photographing the backs of buildings for nine rounds. Root cause is a CITY_SPEC that +contradicts itself. The fix is one line in A's `plan_osm.js` — proven, then reverted, because it +isn't mine and it needs a ruling.** + +### 1. The deliverable — `DBG.poseForShopFront(sel, dist = 8)` +Poses at a NAMED SHOP's frontage: door centred, sign band + R26 crate mark in shot, under the verandah. +`clusterPose()` answers "where is the town's HEART", which is the wrong question for a named shop — +Red Hill's heart doesn't contain Monster Robot, which is exactly how F got three frames of empty road. + +Keyed through **one shop selector**: F's R22/R24 resolver lived inside `enterShop()`, so I extracted it +rather than grow a second key scheme beside it — a selector bug resolves the wrong shop *and looks like +it worked* (F's own first cut proved that), so the id-namespace law now has exactly one home. +Verified live, and this is the case that matters: +| call | resolves to | | +|---|---|---| +| `poseForShopFront(31)` | **Wholefood Cafe** (plan id 31) | + `ambiguous:` "31 is also the godverseShopId of Silky Oak Furnature — use 'g:31'" | +| `poseForShopFront('g:31')` | **Silky Oak Furnature** (godverse id 31) | the prefix IS the fence | +| `poseForShopFront('g:999999')` | `{error}` | never throws, never guesses | + +Framing proven by **projection**, not by eye — `doorCentreNdcX = 0.0000` (exactly), door / sign / mark +all inside the frustum. Derived from buildings.js rather than hardcoded: door is centred on the lot +front (so `off = 0`, unlike `poseForShop`'s `off = 1.0`); sign band is `min(h,FACADE_H)−0.35 ± 0.25` ⇒ +**y 2.40–2.90**; the mark is **y 2.45–2.52**; `camY = 2.0` sits below the `AWNING_Y = 2.95` verandah, so +a ray rising to a target ≤ 2.9 can never cross it — the sign is clear *by geometry*, at any distance. + +### 2. Correcting my own R27 intel to F — I gave F two wrong numbers +My R27 note said the mark "rides high (y ≈ 3.3)" and the vinyl landmark blocks only 4% of it. **Both +wrong.** The mark is at y 2.45–2.52; I had ray-scanned y 3.0–3.6, which is empty awning air *above the +sign* — that is why it read ~0. Re-measured on the true band: the `novelty_record` disc (top y **2.50**) +reaches **into** the mark band and clips **~15% of the strip from every offset** — moving the camera only +changes *which* 15%, so there is nothing to dodge. The recommendation (**shoot head-on**) survives, but +for the opposite reason to the one I gave: not because the vinyl is clear of the mark, but because it +never isn't, and the repeating spines read through it anyway. + +### 3. THE FINDING — real-town shopfronts face the paddock, not the street +Standing on Musgrave Road (Monster Robot's own `frontEdge: 1316`) and looking at the shop returns a +**blank grey back wall**: no sign, no door, no windows, no mark. Measured, not eyeballed: +| town | shop lots w/ frontEdge | facing their street | **180° backwards** | +|---|---|---|---| +| `redhill_godverse` | 35 | **0** | **35** (dot = −1.000) | +| `katoomba_real` | 72 | **0** | **72** (dot = −1.000) | +| synthetic (default) | — | — | none — looks right, always has | + +dot = **−1.000 exactly**, every lot, both towns: a convention, not a defect. Katoomba's `street_noon` — +the bookmark the release tours have shot for nine rounds — is **46 draws / 19,680 tris of bare road**. + +**Root cause: CITY_SPEC contradicts itself, and the two generators each implement one half.** +- **CITY_SPEC:71** (the lots contract): *"ry: … a GLB modelled facing −Z ends up with its facade's OUTWARD + normal pointing at frontEdge. **World facing = (−sin ry, −cos ry)**"* → **−Z is the facade.** +- **CITY_SPEC:335** (posters, R15): *"seated on the **+Z street facade** (**B's canon**: buildings.js facade + + venue.js door/queue/camera are all +Z; `lot + (sin ry, cos ry)·(d/2+ε)`)"* → **+Z is the facade.** + +`plan_osm.js:443` obeys the first (`ry = atan2(nx, nz)`, its comment says *"facade (−Z) faces road"*). +`plan.js` (synthetic), `buildings.js`, `venue.js` and the R15 posters obey the second. Synthetic — the +town every golden and every one of us tests — follows B's canon and looks correct, which is precisely +why nine rounds of real-town work never caught it. **This is the epoch's own signature species: a rule +meeting a case written after it.** The +Z canon is the de-facto standard (v1→v5, every synthetic golden, +posters R15 ledger #1); `plan_osm.js` is the lone dissenter. + +**Proven, then reverted.** One line in A's file — `ry = Math.atan2(-nx, -nz)` — flips katoomba from +**0/72 facing → 72/72 facing**, and turns B's `poseForShopFront` camera from **26 m out in a paddock into +4 m from Musgrave Road**: the money shot F specified, shopfront + mark + kerb + a citizen at the door. +I did not commit it. It is A's file, it moves every real-town plan (⇒ real-town goldens re-pin under +amendment law), and a spec that contradicts itself is Fable's to rule on, not mine to pick a side of. + +**This also re-reads my own R22 diagnosis.** I recorded "chunk-buildings **348 tris** (0.2%)" in katoomba's +busiest venue block and credited the atlas/instancing work. 348 tris is ~29 boxes: that was not efficiency, +it was the buildings being **absent from the frame**, and I read the evidence as a win. The number was +right; my reading of it was wrong, for five rounds. + +Files: `web/js/world/dbg.js` (the helper + the extracted selector). No other lane's file touched. + +--- + ## Round 27 (on-call) — the R26 handshake **verified against G's real manifest**; intel for F's #5 money shot R27 owes B nothing (*"B/D: notes current; nothing else owed"*, *"B/D on-call"*). This is on-call work: F's diff --git a/web/js/world/dbg.js b/web/js/world/dbg.js index 03ff545..f6be69d 100644 --- a/web/js/world/dbg.js +++ b/web/js/world/dbg.js @@ -90,6 +90,84 @@ export function installDBG(deps) { // nearest shop matching pred, else nearest shop overall → a pose is always produced const pickShop = (pred) => { const s = byDist(); return poseForShop(s.find(pred) || s[0]); }; + // ── the shop selector: THE ID-NAMESPACE LAW, one implementation ───────────────────────────── + // [Lane F R22/R24 wrote this inside enterShop(); extracted verbatim in R27 w4 so the frontage + // pose below obeys the SAME law instead of growing a second, subtly different key scheme. A + // selector bug that resolves the wrong shop looks like it works — F's first cut proved that — + // so there is exactly one place to get it right.] + // resolveShop(116) → by PLAN id + // resolveShop('g:3962749') → by godverseShopId (the prefix IS the namespace fence) + // resolveShop('record') → first shop of that registry type + // resolveShop('The Exchange Hotel') → exact name, else name substring (case-insensitive) + // resolveShop(shopObject) → passed through + // Resolution order is most-specific-first so an id-shaped string can never be shadowed by a name. + // THE TWO ID SPACES COLLIDE: a GODVERSE shop has a plan id (local, renumbered per town, 1..N) and + // a godverseShopId (the real POS/dealgod id — what the atlas and every G-side query names it by). + // Monster Robot is plan 8 / godverse 3962749. Silky Oak's godverse id is 31 and another shop's + // PLAN id is also 31 — so a bare number is genuinely ambiguous. Bare number ⇒ plan id (unchanged, + // back-compatible); 'g:' ⇒ godverse id, explicitly. When a bare number matches BOTH we say so + // in the return rather than pick quietly. + const resolveShop = (sel) => { + const shops = plan.shops || []; + let s = null, ambiguousWith = null; + if (sel && typeof sel === 'object') { + s = shops.includes(sel) ? sel : (sel.id != null ? shops.find((x) => x.id === sel.id) : null); + } else if (typeof sel === 'number') { + s = shops.find((x) => x.id === sel) || null; + const g = shops.find((x) => x.godverseShopId === sel) || null; + if (s && g && g !== s) ambiguousWith = { godverseShopId: sel, name: g.name }; + if (!s) s = g; + } else if (typeof sel === 'string') { + const q = sel.trim().toLowerCase(); + const gm = /^g(?:odverse)?:(\d+)$/.exec(q); + if (gm) { + const n = Number(gm[1]); + s = shops.find((x) => x.godverseShopId === n) || null; + if (!s) return { error: `no shop with godverseShopId ${n}` }; + } else { + s = shops.find((x) => String(x.id) === q) // id-as-string + || shops.find((x) => (x.name || '').toLowerCase() === q) // exact name + || shops.find((x) => (x.type || '').toLowerCase() === q) // registry type + || shops.find((x) => (x.name || '').toLowerCase().includes(q)); // name substring + } + } + if (!s) return { error: `no shop matching ${JSON.stringify(sel)}` }; + return { shop: s, ambiguousWith }; + }; + const ambiguityNote = (sel, a, verb) => `${sel} is also the godverseShopId of "${a.name}" ` + + `— ${verb} by PLAN id; use 'g:${sel}' for the godverse shop`; + + // ── frontage pose (R27 w4 — Lane F's ask for the godverse tour frames) ────────────────────── + // clusterPose() answers "where is the town's HEART", which is the wrong question for a named + // shop: Red Hill's heart doesn't contain Monster Robot, so three tour frames photographed empty + // road. This poses at a SHOP instead. Measured against buildings.js rather than eyeballed: + // • door — buildings.js centres it on the lot front (dw 1.2, y 0.02–2.2), so off = 0 + // (NOT poseForShop's off = 1.0) puts it on the frame's centre line. + // • sign+mark — sign band is y (min(h,FACADE_H) − 0.35) ± 0.25 ⇒ 2.40–2.90 for a normal shop; + // B's R26 crate mark is the strip at y ≈ 2.45–2.52 (atlas cell rows 128–152). + // • verandah — the awning slab sits at y 2.95 and reaches 2.2 m over the footpath. Any camera + // BELOW it sees the whole sign, because a ray rising from camY < 2.9 to a target + // ≤ 2.9 can never cross 2.95. Hence camY 2.0: eye-level, and clear by geometry. + // The novelty_record landmark (a 1.47 m vinyl disc, top y 2.50) stands ~1.3 m proud of record + // shopfronts and unavoidably clips ~15% of the mark strip from EVERY offset — moving the camera + // only changes WHICH 15%, so there is nothing to dodge and the repeating spines still read. + const poseForShopFront = (sel, dist = 8) => { + const r = resolveShop(sel); + if (r.error) return r; + const lot = lotOf(r.shop); + if (!lot) return { error: `shop "${r.shop.name}" has no lot` }; + const ry = lot.ry || 0, fx = Math.sin(ry), fz = Math.cos(ry); + const half = (lot.d || 8) / 2; + const frontX = lot.x + fx * half, frontZ = lot.z + fz * half; + const out = { + pos: [frontX + fx * dist, 2.0, frontZ + fz * dist], // off = 0 ⇒ the door is the centre line + look: [frontX, 1.9, frontZ], // near-level: door low, sign band high + shop: r.shop.name, id: r.shop.id, godverseShopId: r.shop.godverseShopId ?? null, + }; + if (r.ambiguousWith) out.ambiguous = ambiguityNote(sel, r.ambiguousWith, 'posed'); + return out; + }; + // ── lot- and node-based poses (the district bookmarks aren't all shopfronts) ── const poseForLot = (lot, dist = 11) => { if (!lot) return null; @@ -232,6 +310,17 @@ export function installDBG(deps) { townAnchor: { ...townAnchor, anchored: REAL_ROADS, ref: { x: REF.x, z: REF.z } }, clusterPose: (kinds = ['main']) => streetViewPose(kinds), + // ── R27 w4: the frontage pose, published for Lane F's godverse tour frames ── + // clusterPose finds the town's heart; this finds a NAMED SHOP's frontage — door centred, sign + // band and B's crate mark in shot, under the verandah. Keyed through the one shop selector, so + // it inherits the id-namespace law: `poseForShopFront('g:3962749')` for a godverse shop, + // a bare number for a plan id (and it TELLS you when that number is ambiguous). + // DBG.poseForShopFront('g:3962749') → Monster Robot Party's frontage at 8 m + // DBG.poseForShopFront('Egg Records', 7) → by name, closer + // Returns { pos, look, shop, id, godverseShopId } — the same {pos,look} every bookmark uses — + // or { error } for an unknown selector / a shop with no lot. Never throws, never guesses. + poseForShopFront, + // time-of-day (hands the day cycle to the harness so captures are deterministic) setSegment(seg) { lighting.setPaused(true); lighting.setSegment(seg | 0); return lighting.getClock(); }, @@ -277,33 +366,13 @@ export function installDBG(deps) { // like it worked. Bare number ⇒ plan id (unchanged, back-compatible); `'g:'` ⇒ godverse id, // explicitly. When a bare number matches BOTH, we say so in the return rather than pick quietly. enterShop(sel) { - const shops = plan.shops || []; - let s = null, ambiguousWith = null; - if (typeof sel === 'number') { - s = shops.find((x) => x.id === sel) || null; - const g = shops.find((x) => x.godverseShopId === sel) || null; - if (s && g && g !== s) ambiguousWith = { godverseShopId: sel, name: g.name }; - if (!s) s = g; - } else if (typeof sel === 'string') { - const q = sel.trim().toLowerCase(); - const gm = /^g(?:odverse)?:(\d+)$/.exec(q); - if (gm) { - const n = Number(gm[1]); - s = shops.find((x) => x.godverseShopId === n) || null; - if (!s) return { error: `no shop with godverseShopId ${n}` }; - } else { - s = shops.find((x) => String(x.id) === q) // id-as-string - || shops.find((x) => (x.name || '').toLowerCase() === q) // exact name - || shops.find((x) => (x.type || '').toLowerCase() === q) // registry type - || shops.find((x) => (x.name || '').toLowerCase().includes(q)); // name substring - } - } - if (!s) return { error: `no shop matching ${JSON.stringify(sel)}` }; + const r = resolveShop(sel); + if (r.error) return r; + const s = r.shop; enterShop(s.id, s.name); const out = { entered: s.id, name: s.name, type: s.type, godverseShopId: s.godverseShopId || null }; - if (ambiguousWith) out.ambiguous = `${sel} is also the godverseShopId of "${ambiguousWith.name}" ` + - `— entered by PLAN id; use 'g:${sel}' for the godverse shop`; + if (r.ambiguousWith) out.ambiguous = ambiguityNote(sel, r.ambiguousWith, 'entered'); return out; }, exitShop() { window.dispatchEvent(new CustomEvent('procity:exitShop')); return { exited: true }; },