Closes the gap Fable caught (hud.js:11 still hardcoded REAL_TOWNS). The real-town list now comes from E's web/assets/towns/index.json (procity-towns-index/1), so it grows with the town pack instead of being edited in hud.js. Verified fresh Chromium. Nothing else touched -- tram/ground work is done and verified. Fail-soft by construction: createHUD is synchronous, so the picker is built immediately from the fallback literal and UPGRADED IN PLACE when the index resolves -- if the index never arrives, a working list is already on screen. - index present -> real names/states/shop counts: "Katoomba, NSW · 80 shops", "Fremantle, WA · 80 shops", "Newtown, NSW · 72 shops", "Bendigo, VIC · 36 shops", "Castlemaine, VIC · 24 shops" (roads:false -> "· marched"). - ?noassets -> the fetch is SKIPPED entirely (asset law; the shell doesn't load town caches there either). Measured: 0 index fetches, fallback list, 0 errors. - anything wrong with the index -> the fallback simply stands. Exercised the exact chain: 404, network error, malformed JSON, towns:[], towns-not-an-array, junk entries (no key) -> none throws, none rebuilds from junk; only a good index rebuilds. REAL_TOWNS_FALLBACK is retained ONLY as that fallback (comment says so), no longer the source of truth. Verified live: booting katoomba_real shows "Katoomba, NSW · 80 shops" pre-selected; fits (193px of 230px max); 0 page errors; navigation unchanged (onchange is set once on the <select>, so rebuilding options never clears it). -> F: unchanged from R19 -- NO shell seam (selector is entirely hud.js, already built by createHUD) and it lives inside #pc-hud so setVisible(false) hides it for the beta money shot. The gating call is still yours. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e3917dd35b
commit
4b0144989b
@ -1,5 +1,41 @@
|
||||
# LANE B — NOTES (measured)
|
||||
|
||||
## Round 21 (Fable's ROUND21 → Lane B) — selector from E's towns index (ledger #2, the missed R20 #5)
|
||||
|
||||
Closed the R20 gap Fable caught (`hud.js:11` still hardcoded `REAL_TOWNS`). The real-town list now comes
|
||||
from **E's `web/assets/towns/index.json`** (`procity-towns-index/1`), so it grows with the town pack instead
|
||||
of being edited in `hud.js`. Verified fresh Chromium. Nothing else touched — the tram/ground work is done.
|
||||
|
||||
### How it works (fail-soft by construction)
|
||||
`createHUD` is synchronous, so the picker is **built immediately from the fallback literal** and then
|
||||
**upgraded in place** when the index resolves. That ordering *is* the fail-soft: if the index never arrives,
|
||||
the working list is already on screen.
|
||||
- **Index present** → real names, states and shop counts: `Katoomba, NSW · 80 shops`, `Fremantle, WA · 80
|
||||
shops`, `Newtown, NSW · 72 shops`, `Bendigo, VIC · 36 shops`, `Castlemaine, VIC · 24 shops`. (`roads:false`
|
||||
towns would read `· marched`; none today.)
|
||||
- **`?noassets`** → the fetch is **skipped entirely** (asset law — the shell doesn't load town caches there
|
||||
either, so reading the index would be pointless as well as illegal). Measured: **0 index fetches**,
|
||||
fallback list, 0 errors.
|
||||
- **Anything wrong with the index** → the fallback simply stands. Exercised the exact chain against every
|
||||
nasty case; **none throws, none rebuilds from junk**:
|
||||
|
||||
| index case | result |
|
||||
|---|---|
|
||||
| 404 / missing · network error · malformed JSON | fallback stands ✅ |
|
||||
| `towns: []` · `towns` not an array · junk entries (no `key`) | fallback stands ✅ |
|
||||
| good index | rebuilds from index ✅ |
|
||||
|
||||
`REAL_TOWNS_FALLBACK` is retained **only** as that fallback (comment says so) — it is no longer the source
|
||||
of truth. Verified live: booting `katoomba_real` shows `Katoomba, NSW · 80 shops` pre-selected; the picker
|
||||
fits (193 px of its 230 px max); 0 page errors; navigation unchanged (`onchange` is set once on the
|
||||
`<select>`, so rebuilding the options never clears it).
|
||||
|
||||
**→ Lane F:** unchanged from R19 — **no shell seam**, the selector is entirely `hud.js` (already built by
|
||||
`createHUD`), and it lives inside `#pc-hud` so `setVisible(false)` hides it for the beta money shot. Your
|
||||
R19 gating call (always-in-HUD vs `?dbg`) is still yours; one line either way.
|
||||
|
||||
---
|
||||
|
||||
## Round 20 (Fable's ROUND20 → Lane B) — the tram ruling + ground polish (ledger #3/#4, v4.0-beta)
|
||||
|
||||
### 1. The tram ruling (ledger #3) — route by SHOP ADJACENCY, fence per-town
|
||||
|
||||
@ -6,9 +6,12 @@
|
||||
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'];
|
||||
// R21 (v4.0-beta, ledger #2): the real-roads towns come from E's index (web/assets/towns/index.json), so the
|
||||
// list grows with the town pack instead of being edited here. This literal is now only the FAIL-SOFT fallback
|
||||
// — used when the index is missing/unparseable, or under ?noassets (where the shell doesn't fetch town caches
|
||||
// either, so reading the index would be both pointless and against the asset law). Zero errors either way.
|
||||
const REAL_TOWNS_FALLBACK = ['katoomba_real', 'newtown_real', 'fremantle_real', 'bendigo_real', 'castlemaine_real'];
|
||||
const TOWNS_INDEX = 'assets/towns/index.json';
|
||||
|
||||
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}
|
||||
@ -53,24 +56,46 @@ 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. ──
|
||||
// ── town selector (R19; R21 reads E's towns index) — synthetic + OSM fixtures + real-road towns.
|
||||
// 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. The real-town list is built SYNCHRONOUSLY from the fallback (so the picker always
|
||||
// works, including under ?noassets and if the index 404s), then upgraded in place once E's index.json
|
||||
// resolves — which also gives real names, states and shop counts. ──
|
||||
(() => {
|
||||
const sel = $('pc-town-select'); if (!sel) return;
|
||||
const params = new URLSearchParams(location.search);
|
||||
const cur = params.get('plansrc') === 'osm' ? (params.get('town') || '') : '';
|
||||
const NOASSETS = params.get('noassets') != null && params.get('noassets') !== '0';
|
||||
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 opt = (key, label) => { const o = document.createElement('option'); o.value = key; o.textContent = label; if (key === cur) o.selected = true; return o; };
|
||||
const build = (realTowns) => {
|
||||
sel.textContent = '';
|
||||
sel.appendChild(opt('', '▸ Synthetic — procedural'));
|
||||
for (const k of osmTownKeys().filter((k) => !k.endsWith('_real'))) sel.appendChild(opt(k, `${cap(k)} · OSM fixture`));
|
||||
for (const t of realTowns) sel.appendChild(opt(t.key, t.label));
|
||||
if (cur && !Array.from(sel.options).some((o) => o.value === cur)) sel.appendChild(opt(cur, `${cap(cur)} · (current)`)); // unknown current town → still shown
|
||||
};
|
||||
build(REAL_TOWNS_FALLBACK.map((k) => ({ key: k, label: `${cap(k)} · real roads` })));
|
||||
sel.onchange = () => { // set once; rebuilding the options below never clears it
|
||||
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
|
||||
};
|
||||
if (NOASSETS) return; // asset law: no fetch — the fallback list stands
|
||||
fetch(TOWNS_INDEX, { cache: 'force-cache' })
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((j) => {
|
||||
const towns = j && Array.isArray(j.towns) ? j.towns.filter((t) => t && t.key) : null;
|
||||
if (!towns || !towns.length) return; // keep the fallback, silently
|
||||
build(towns.map((t) => ({
|
||||
key: t.key,
|
||||
label: `${t.town || cap(t.key)}${t.state ? ', ' + t.state : ''}`
|
||||
+ (t.shops != null ? ` · ${t.shops} shops` : '')
|
||||
+ (t.roads === false ? ' · marched' : ''),
|
||||
})));
|
||||
})
|
||||
.catch(() => {}); // index missing/blocked → fallback stands, zero errors
|
||||
})();
|
||||
|
||||
const ray = new THREE.Raycaster();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user