Lane B round 19 (v4.0-alpha): in-game town selector (ledger #4, beta pull-forward, non-blocking)

Shipped the town picker in hud.js (which already owns its DOM overlay -> NO index.html seam). A compact
<select> in the HUD (top-left, pointer-events:auto), 9 options: synthetic + 3 OSM fixtures (osmTownKeys
minus _real) + 5 real-road caches (Katoomba/Newtown/Fremantle/Bendigo/Castlemaine, hardcoded REAL_TOWNS --
no manifest yet, beta derives it). Selecting navigates: synthetic clears plansrc/town; a town sets
?plansrc=osm&town=<key>, preserving all other params. Current pre-selected; unknown-current still shown.
Verified end-to-end: picked katoomba_real through the dropdown -> booted real Katoomba (966 edges/323
intersections/3 venues), 0 errors. Lives inside #pc-hud so hud.setVisible(false) hides it for money shots.

-> F (B->F seam): NONE -- self-contained in hud.js (already constructed by createHUD). Gate it behind ?dbg
   with one setVisible call if you prefer; left always-in-HUD as a modest debug-grade feature.

Corner-strips spot-check (R18 failure-item #3): ground fills real intersections OK -- degree-4 junction
(58 exist) shows road strips overlap-filling the junction, lane markings + zebra crossing render, no gross
gap. Acceptable for alpha; finer acute-angle pass = beta.

Budget note -> F (measured vs A's UNCOMMITTED WIP -- caveat, not a defect): F's BIG_CITY one-liner is live
(chunks 50->26). But venue block still ~237k tris at night against the current tree, which carries A's
uncommitted R19 finalization (plan_osm modified; graph grew 799->966 edges, junction-joining in but the
far-island CULL not yet). Fable's ruling: the cull is "half of B's tris problem" -> the <=200k gate needs
F's fix AND A's committed cull. Re-measure the busiest real block after A pins (the deferred tris-post-cull
re-measure genuinely waits on A's commit; the dev server serves other lanes' uncommitted edits).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-16 17:38:59 +10:00
parent 6fc4b40632
commit 73694c97aa
2 changed files with 74 additions and 0 deletions

View File

@ -1,5 +1,49 @@
# LANE B — NOTES (measured)
## Round 19 (Fable's ROUND19 → Lane B) — the town selector (ledger #4, beta pull-forward, non-blocking)
Shipped the in-game town picker; verified fresh Chromium. **No `index.html` seam** — it lives in `hud.js`,
which already owns its DOM overlay.
### `hud.js` town selector
A compact `<select>` in the HUD overlay (top-left, styled to match, `pointer-events:auto` so it's usable
when the pointer is free — Esc first). **9 options**: `▸ Synthetic — procedural` + the 3 OSM fixtures
(`osmTownKeys()` minus `_real`: Melbourne/Katoomba/Silverton) + the 5 real-road caches (Katoomba, Newtown,
Fremantle, Bendigo, Castlemaine — hardcoded `REAL_TOWNS`, since there's no towns manifest yet; beta derives
it from an index). Selecting navigates: synthetic → clears `plansrc`/`town`; a town → `?plansrc=osm&town=<key>`,
**preserving all other params** (seed, flags). Current town pre-selected; an unknown current town is added
so it still shows.
- **Verified end-to-end**: picked `katoomba_real` through the dropdown → it navigated + booted real Katoomba
(966 edges / 323 intersections / 3 venues), selector then reads "Katoomba · real roads". 0 page errors.
- Lives **inside `#pc-hud`**, so `hud.setVisible(false)` hides it for F's money shots (no extra work).
**→ Lane F (B→F "selector seam"): there is NO seam** — the selector is self-contained in `hud.js` (already
constructed by `createHUD`). Nothing to wire in `index.html`. If you want it gated (e.g. `?dbg` only) that's
a one-line `setVisible`/guard call, your choice; I left it always-in-HUD as a modest debug-grade feature.
### Corner-strips spot-check (R18 failure-item #3) — ground fills real intersections OK
Overhead look at a degree-4 real intersection (58 exist on the current graph): the merged road strips
**overlap-fill the junction** (carriageway continuous, lane markings + zebra crossing render, no gross gap).
Acceptable for the alpha; a finer acute-angle / z-fight pass is a beta detail. `ground.js` is road-agnostic
enough for real topology.
### Budget note → F (measured against A's UNCOMMITTED WIP — caveat, not a final finding)
F's `BIG_CITY` one-liner (`e6fcb7b`) is live and working — real Katoomba now streams big-city (radius 2 +
shadows off), chunks **50 → 26**. **But** against the current working tree the venue block is still **~237k
tris** at night — and that tree carries **A's uncommitted R19 finalization WIP** (`plan_osm.js` modified;
the graph grew 799 → 966 edges / 323 intersections = junction-joining in, but the **far-island CULL not yet
applied**). Fable's ruling calls the cull "half of B's tris problem", so **the ≤200k gate needs F's fix AND
A's committed cull together** — re-measure the busiest real block after A pins (that's the deferred
"tris post-cull" re-measure; it genuinely waits on A's commit, per the R18/C lesson that the dev server
serves other lanes' uncommitted edits). Not filing 237k as a defect — it's mid-finalization.
### The uncommitted `docs/shots/v4_alpha/katoomba_mainstreet.png`
Present in the tree since before this R19 session (not a deliberate save of mine — my audit screenshots are
browser-tool captures, uncommitted). Likely a prior B-audit capture. **F should reshoot deterministically
via the tour harness** for the official money shot regardless; treat its provenance as unverified.
---
## Round 18 (Fable's ROUND18 → Lane B) — irregular-geometry audit (ledger #4, v4.0-alpha REAL ROADS)
**The deliverable is the failure list.** Audited `katoomba_real` on E's real roads (schema v2, **484 OSM

View File

@ -4,6 +4,11 @@
// door-click → `procity:enterShop` hand-off. Creates its own DOM overlay so index.html stays thin.
import * as THREE from 'three';
import { osmTownKeys } from '../citygen/index.js'; // R19 town selector: enumerate OSM fixtures
// R19 (v4.0-alpha, beta pull-forward): the real-roads town caches under web/assets/towns/*.json. Hardcoded
// for the alpha — there is no towns manifest yet; beta derives this from an index file. New towns: add here.
const REAL_TOWNS = ['katoomba_real', 'newtown_real', 'fremantle_real', 'bendigo_real', 'castlemaine_real'];
const CSS = `
#pc-hud{position:fixed;inset:0;pointer-events:none;font:13px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;color:#f4efe6;z-index:10}
@ -15,6 +20,10 @@ const CSS = `
#pc-clock{font-size:15px;margin-bottom:3px}
#pc-toast{position:absolute;left:50%;bottom:64px;transform:translateX(-50%);background:rgba(20,16,10,.9);padding:10px 18px;border-radius:10px;font-size:15px;opacity:0;transition:opacity .25s;text-shadow:0 1px 2px #000}
#pc-help{position:absolute;left:12px;bottom:12px;background:rgba(20,16,10,.5);padding:7px 11px;border-radius:8px;opacity:.85}
#pc-townpick{position:absolute;left:10px;top:10px;pointer-events:auto;opacity:.8;transition:opacity .12s}
#pc-townpick:hover{opacity:1}
#pc-townpick select{background:rgba(20,16,10,.7);color:#f4efe6;border:1px solid rgba(255,215,94,.35);border-radius:8px;padding:5px 9px;font:12px/1.3 -apple-system,Segoe UI,Roboto,sans-serif;max-width:230px;cursor:pointer}
#pc-townpick select:focus{outline:1px solid rgba(255,215,94,.6)}
`;
export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop }) {
@ -35,6 +44,7 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop }
<div>chunks <b id="pc-chunks"></b></div>
</div>
<div id="pc-toast"></div>
<div id="pc-townpick"><select id="pc-town-select" title="Load a different town (Esc to release the pointer first)"></select></div>
<div id="pc-help">WASD move · shift run · mouse look · click a door · [ ] time · P shot · Esc release</div>`;
document.body.appendChild(root);
const $ = (id) => root.querySelector('#' + id);
@ -43,6 +53,26 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop }
$('pc-town').textContent = plan.name || '—';
$('pc-seed').textContent = plan.citySeed;
// ── town selector (R19, beta pull-forward) — synthetic + OSM fixtures + real-road caches. Navigates
// to the matching URL params, preserving everything else (seed, flags). Lives in the HUD overlay, so it
// hides with the HUD for money shots; usable when the pointer is free (Esc). No index.html seam. ──
(() => {
const sel = $('pc-town-select'); if (!sel) return;
const params = new URLSearchParams(location.search);
const cur = params.get('plansrc') === 'osm' ? (params.get('town') || '') : '';
const cap = (k) => k.replace(/_real$/, '').replace(/(^|[\s_])\w/g, (c) => c.toUpperCase()).replace(/_/g, ' ');
const add = (key, label) => { const o = document.createElement('option'); o.value = key; o.textContent = label; if (key === cur) o.selected = true; sel.appendChild(o); };
add('', '▸ Synthetic — procedural');
for (const k of osmTownKeys().filter((k) => !k.endsWith('_real'))) add(k, `${cap(k)} · OSM fixture`);
for (const k of REAL_TOWNS) add(k, `${cap(k)} · real roads`);
if (cur && !Array.from(sel.options).some((o) => o.value === cur)) add(cur, `${cap(cur)} · (current)`); // unknown current town → still shown
sel.onchange = () => {
const v = sel.value, p = new URLSearchParams(location.search);
if (!v) { p.delete('plansrc'); p.delete('town'); } else { p.set('plansrc', 'osm'); p.set('town', v); }
location.search = p.toString(); // reload into the chosen town
};
})();
const ray = new THREE.Raycaster();
ray.far = 6;
const centre = new THREE.Vector2(0, 0);