Lane B R27 w4: poseForShopFront — and the real towns face the wrong way

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 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-17 15:42:50 +10:00
parent 83ed848f16
commit 4c2702fc58
2 changed files with 171 additions and 24 deletions

View File

@ -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.402.90**; the mark is **y 2.452.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.452.52; I had ray-scanned y 3.03.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

View File

@ -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:<n>' ⇒ 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.022.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.402.90 for a normal shop;
// B's R26 crate mark is the strip at y ≈ 2.452.52 (atlas cell rows 128152).
// • 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:<n>'` ⇒ 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 }; },