diff --git a/docs/CITY_SPEC.md b/docs/CITY_SPEC.md index 7e46e96..0825011 100644 --- a/docs/CITY_SPEC.md +++ b/docs/CITY_SPEC.md @@ -225,6 +225,26 @@ night in v1; interior-mapping shader is a stretch goal). > broadens off-spine if the warehouse fringe is uniformly narrow). This moved `plan.posters`, so the gig > golden re-pinned `0x4f4a549d → 0xb1d48ea1`; synthetic `0x3fa36874` still frozen. The v3.1 default flip > + classic law is F's amendment to this section — see the flags table.)* +> +> *(**Recorded amendment — ROUND20, ledger #2:** the R16 clearance bias above **widened to every venue +> kind**. R16 exempted `pub` because on the synthetic/marched towns a pub's `+Z` is always open; on a real +> road graph at E's widened density it is not (newtown: a pub whose facade overhung a cross street). On the +> synthetic town every pub/rsl candidate already clears ⇒ no-op there, gig golden `0xb1d48ea1` frozen.)* +> +> *(**Recorded amendment — ROUND21 ledger #1 + ROUND22 ledger #2: the venue cluster bias.** D's relocation +> finding: a venue a kilometre from the retail cluster is a gig with no passing crowd (katoomba's pub had +> **1** shop within 160 m; its RSL had 13). `pickVenues` now draws each venue from a **cluster field** — +> candidates with ≥ `CLUSTER_MIN` (3) other shops within `CLUSTER_R` (160 m) — and lets the kind flavours +> bias *within* that field rather than override it. Venues under the floor: **11 of 15 → 0** across the +> five R21 towns; Newtown's RSL moved 2,228 m → 29 m from the hub. **Real towns only, by construction:** +> the bias reads `plan.source === 'osm'`, which the synthetic generator never sets, so it cannot move +> `0x3fa36874`/`0xb1d48ea1` — measured, not assumed (4 synthetic candidates per seed sit under the floor, +> so a blanket filter *would* have moved them). **ROUND22 graded fallback:** at pack scale three towns have +> no candidate at the floor at all (`ballarat`/`toowoomba`/`newtown_godverse` top out at 2 within 160 m). +> R21 dropped the bias entirely there, stranding venues at 0 neighbours; it now falls back to the **densest +> available** candidates instead. Fires only where the floor is unreachable ⇒ no already-pinned town moved. +> **Acceptance at pack scale: 20 of 23 towns have every venue ≥ `CLUSTER_MIN`; those three sit at their +> town's genuine ceiling** — the carved-out case, stated rather than papered over.)* > **Prime flag law.** The gig layer is a **post-hoc augmentation** applied by the selector when the gig > flag is on. With it off, `generatePlanFor(seed, src)` returns the byte-identical v2 base plan — **all v2 @@ -268,6 +288,13 @@ plain shop" so osm towns still get venues): | `band_room` | `grunge` → `gig-grunge` | warehouse-district fringe | `[18,23]` | single-storey tin shed | | `rsl` | `covers` → `gig-covers` | **off**-spine (not main, not warehouse) | `[16,23]` | carpet-and-bistro club | +**The cluster field comes first (ROUND21/22 — real towns only).** On an osm plan every kind rule in the +table runs **inside** the retail cluster. Order per venue: cluster field (≥`CLUSTER_MIN` other shops within +`CLUSTER_R`, or the densest available if none reach the floor) → the kind flavour → the facade-clearance +filter → `farthestFirst` shortlist → seeded pick, each step degrading to the previous set rather than to +the whole town. A fringe band_room *in the cluster* beats a fringe band_room a kilometre from anyone. +Synthetic plans skip the field entirely (`plan.source` is unset) ⇒ goldens frozen. See the amendment note. + Venue hours all close `23` (open through DUSK+NIGHT so the gig runs and D's surge fills the room; drains at close per the closing-time ruling above). Venues are the *only* gig-plan shops allowed to close ≥22 besides the openLate video (selfcheck exempts them). `genreKey` is set from the registry kind→genre map @@ -309,8 +336,9 @@ frontage** — seated on the **+Z street facade** (B's canon: `buildings.js` fac camera are all `+Z`; position `lot + (sin ry, cos ry)·(d/2+ε)`), turned to read *from* the street (`ry = lot.ry + π` so its printed face points `+Z` at the queue-side camera — **ROUND15 ledger #1**; before R15 it was on the `−Z` back and invisible from the street). So the poster never sits over a back-street's -bitumen, **`pickVenues` prefers a band_room/rsl lot whose facade clears every carriageway by ≥ `POSTER_CLEAR`** -(ROUND16 ledger #6 — exported from the barrel; the selfcheck asserts the same floor). Plus a seeded run along +bitumen, **`pickVenues` prefers a lot whose facade clears every carriageway by ≥ `POSTER_CLEAR`** +(ROUND16 ledger #6, **widened from band_room/rsl to every venue kind in ROUND20** — exported from the +barrel; the selfcheck asserts the same floor). Plus a seeded run along the spine — 2 per main edge (or, when a town has no main spine, every edge), at `t ∈ [0.2,0.8]` of the block, on **either verge**; a candidate that would overhang a **real** crossing carriageway is skipped (ROUND19, E's finding), and the run is **capped at `POSTER_PER_SHOP × shops`** (ROUND20) — a sparse real town has few shops but a big edge @@ -406,6 +434,23 @@ synthetic town, so its gig golden is frozen). Golden re-pins per the amendment l on their real-roads output; synthetic + marched fixtures + classic/default untouched — the alpha changed nothing outside the cache-schema path). +> **FINAL at `v4.0` (ROUND22 ledger #6, the epoch close).** This section — schema v2, the lift, the +> connectivity rulings above, the venue cluster bias and poster cap in §v3 — is the authoritative real-map +> contract at the tag. Changes are an amendment + a golden re-pin in the same commit, as always. +> +> **The alpha's five became the epoch's 23** (22 real towns + Lane G's `newtown_godverse`; **1,210 shops**), +> and every ruling written above held at that scale with **no new hardening**: **one connected component and +> three venues on every one of the 23 towns**, fitzroy's **160 shops** — 2× anything the pipeline had seen — +> included. All **36 goldens** (`REAL_TOWN_GOLDENS` + `REAL_TOWN_GIG_GOLDENS`, 23 each) are pinned; selfcheck +> runs **161,300** checks green, cross-process deterministic. Read "all five towns" above as the measurement +> of record *when the ruling was written* — the ruling itself is now pack-wide. +> +> **The honest tail, carried not hidden:** 6.4% of shops (77 of 1,210) don't seat, every drop attributed by +> cause, dominated by overflow where a town has more shops than its road graph has frontage (daylesford 19%, +> the pack's thinnest graph; fitzroy 13%). See `V4_REAL_MAP.md` risk #6. The R20 ruling stands: **drop and +> count beats teleport.** The frozen set is unmoved by the entire epoch: synthetic `0x3fa36874`, gig +> `0xb1d48ea1`, marched fixtures, classic. + ## Shop types v1 (registry lives in `web/js/core/registry.js`, Lane A writes it, all lanes read) > **`web/js/core/registry.js` is the machine-readable source of truth** — import `SHOP_TYPES` for diff --git a/docs/V4_REAL_MAP.md b/docs/V4_REAL_MAP.md index 4c75c9e..c3c0184 100644 --- a/docs/V4_REAL_MAP.md +++ b/docs/V4_REAL_MAP.md @@ -50,3 +50,36 @@ re-fetch; only public POI/way data, never private-individual data. 4. Road simplification fidelity — too coarse reads wrong, too fine costs edges; A owns the knob. 5. Settled non-risks: facade clearance on osm towns (R17), interior types (C proved 18 real `shop=` values degrade cleanly), plan-side determinism (E's pipeline is byte-identical re-run). + +### Retired-or-carried at the v4.0 close (ROUND22 ledger #6 — Lane A, line by line) + +Verdicts against the **full 23-town pack** (1,210 shops), not against the 5-town sample the list was +written for. "Retired" means a law now enforces it and the pack proved the law holds with headroom. + +1. **RETIRED — enforced, with headroom.** `MAX_TOWN_SPAN_M` (5 km) is validator-enforced, so a breach + fails loudly at load, not silently in the plan. Widest in the pack: **geelong at 3,829 m — 77% of the + ceiling**; no town came close to the law. Mega-strips are now a *pipeline curation* question (E's), and + the generator's fence is the backstop it was designed to be. +2. **RETIRED — the floor never bound.** `MIN_TOWN_SHOPS` is 6; the thinnest town in the pack is + **darwin at 12 — 2× the floor**. No cache was rejected for sparsity. (Whether a 12-shop town *reads* + alive is a different question and a live one — it's D's thin-tail spot-check this round, not a + generator risk.) +3. **RETIRED on the plan side; CARRIED for the runtime lanes.** This was the alpha's real risk and it + paid off exactly as intended — the irregular graphs surfaced fragmentation (R19), capacity (R20) and + venue stranding (R21), each fixed-or-fenced and written down. At 23-town scale A's side needed **no + new hardening**: 1 connected component and 3 venues on every town, all invariants green, fitzroy's 160 + shops included. **Carried** only where it hasn't been measured yet: C's door-soak, D's fitzroy audit + and F's matrix are this round's verifies — the plan being green is not the same as the town rendering + and simulating green. +4. **RETIRED — the knob is at a measured setting.** Junction-protected Douglas–Peucker settled it + (katoomba 105 → 11 components; cull/bridge → 1 component at 100% main-net, and now **1 component on + all 23**). The fidelity/edge-count trade has a floor that holds across the pack; it is no longer an + open question, and any future move re-pins goldens under the amendment law. +5. **Unchanged** — still settled, now on 23 towns rather than 5. +6. **NEW, CARRIED — thin-graph seating capacity.** The pack's honest tail: **6.4% of shops don't seat** + (77 of 1,210), all counted by cause, dominated by overflow where a town has more shops than its road + graph has frontage. Outliers: **daylesford 19%** (32 shops on 171 roads — the thinnest graph in the + pack by 2×) and **fitzroy 13%**. `RGAP` is already 0.5 m and `NODE_CLEAR` 3 m; the only lever left is + spilling overflow onto a *different* street, which buys a few percent by putting a shop on a road it + isn't on. Ruling stands from R20: **drop and count beats teleport.** Carried into v4.x as a known, + measured, per-cause-attributed tail — not a defect, and not hidden.