# LANE B — NOTES (measured) ## 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 `tram.js` `spinePolyline` now walks from each dead-end of the main subgraph, **greedily taking the unused main edge that fronts the most shops** (`lot.frontEdge`), and keeps the best-scoring path (ties → longer). Component logic isn't needed — a walk can't leave its component. **Synthetic + fixtures are byte-identical, by construction and by measurement.** A synthetic main node has ≤2 mains so there's never a choice, and its two dead-ends tie exactly ⇒ strict `>` keeps the pre-R20 start ⇒ the same route. Verified by running the old and new logic against the same plan: **identical polylines** (Boolarra Heads: 7 points, 6 edges, 810 m, 172 shops fronted, 2 stops — unchanged). | town | shops | shops fronting mains | OLD route | NEW route | stops | tram | |---|---|---|---|---|---|---| | synthetic | 493 | 172 | 172 | **172** (810 m) | 2 | RUNS (unchanged) | | fixture melbourne / katoomba | 95 / 19 | 0 / 0 | 0 | 0 (378 m) | 3 | RUNS (unchanged) | | fixture silverton | 12 | — (0 mains) | — | — | 0 | no-op (unchanged) | | **REAL newtown** | 64 | 32 | **0** | **8** (1924 m) | 16 | **RUNS** | | **REAL castlemaine** | 22 | 11 | 1 | **6** (2184 m) | 10 | **RUNS** | | REAL katoomba | 69 | 7 | **0** | 3 (3784 m) | — | **FENCED** | | REAL fremantle | 79 | 4 | **0** | 2 (3054 m) | — | **FENCED** | | REAL bendigo | 35 | 7 | **0** | 1 (4376 m) | — | **FENCED** | The alpha tram fronted **0 shops on 4 of 5 real towns** — it really was a highway bus. Shop-adjacency improves every town, but on katoomba/fremantle/bendigo the best main chain still fronts <5 shops, so per Fable's fallback the tram is **fenced per-town**. **The fence keys off the REAL-ROADS signal (`edges > 200`), never shop count alone** — because the marched fixtures legitimately front **0 shops on their mains** (the marched spine is bare; the avenues carry the shops), so a naive shop-count fence would have killed their v2 tram. Synthetic (22 edges) and fixtures (15) are an order of magnitude under the threshold and can never be fenced. Threshold: `shopsFronted < 5`. ### 2. Bug found + fixed en route: phantom tram stops on real graphs The stop projection mapped **every shelter in the town** onto the route with no distance gate — harmless on synthetic (2 shelters, both on the spine), nonsense on a real graph: **Newtown produced 149 stops** (~9 min of pure dwell at points the tram never passes). Added a **30 m** gate (a shelter sits `halfRoad+1.7` ≈ ≤16 m off its own centreline, so every genuine stop survives): **newtown 149 → 16, castlemaine 37 → 10**; synthetic **2** and fixtures **3** unchanged. ### 3. Ground polish at real intersections (ledger #4) — measured, NO fix needed Real Katoomba: **325 degree-3+ junctions, 28 under 30°, worst 4.3°** (near-parallel). Viewed at grazing eye level (the worst case for z-fighting) the worst junction reads **clean — no z-fight, no gaps**: continuous tarmac, correct footpath/kerb. Why it holds: the layers are already y-separated (road `0.0` / footpath `0.02` / kerb `0.06`) so nothing co-plane-fights across layers, and road-on-road overlap at junctions is **invisible by design** — `OVERLAP = 1.5` makes strips overrun their nodes on purpose, and they share one skin, one y and one merged draw, so a UV mismatch can't read on uniform asphalt. - *Cosmetic only (v4.0 candidate, not a defect):* at ultra-acute (<10°) junctions the footpath/kerb strips of the two near-parallel roads double up on the sliver between them. Reads busy, not broken. ### 4. Selector polish (ledger #5) — BLOCKED on E E's `web/assets/towns/index.json` hasn't landed (`build_towns.py` still WIP), so `REAL_TOWNS` stays hardcoded this round. It's a one-line swap to `fetch('assets/towns/index.json')` when E lands. **→ Lane F (B→F tram verdict + selector):** tram **runs** on synthetic, all fixtures, **newtown** and **castlemaine**; **fenced** on **katoomba, fremantle, bendigo** (`routeInfo.fenced` + `reason` are exposed for your smoke). Selector gating (always-on HUD vs `?dbg`) is your call — one line either way. **→ Lane A (finding, with measurements):** only a small slice of shops front a **main** edge on real towns — katoomba **7/69**, fremantle **4/79**, bendigo **7/35**, newtown 32/64, castlemaine 11/22. The real high street is coming out classified **`side`**, which is *why* the tram must be fenced on three towns. If your main/side classifier promoted shop-dense ways to `main`, the fence would lift and the tram would find the actual high street. Worth a look beyond the tram: **`onMain` also gates `gigs.js pickVenues` pub placement**. --- ## 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 `