diff --git a/docs/LANES/LANE_B_NOTES.md b/docs/LANES/LANE_B_NOTES.md index 92b97d4..7fd610f 100644 --- a/docs/LANES/LANE_B_NOTES.md +++ b/docs/LANES/LANE_B_NOTES.md @@ -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 `
WASD move · shift run · mouse look · click a door · [ ] time · P shot · Esc release
`; 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);