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:
parent
83ed848f16
commit
4c2702fc58
@ -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
|
||||
|
||||
@ -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.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:<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 }; },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user