PROCITY/docs/V9_THE_CITY_IS_THE_GAME.md

35 KiB
Raw Blame History

FABLE'S VERIFICATION (2026-07-26). John's reframe, designed by a 3-take panel (take-the-map- away / go-up-and-out-the-back / the-unmarked-door), each adversarially judged, then synthesised. I re-measured the two load-bearing claims myself before circulating this, and both hold exactly:

  1. The street names. Counted independently across every shipped cache: 19,132 road ways, 17,835 carrying a name — 93.2%, matching the synthesis to the digit. And the drop is real: plan_osm.js:363 builds each way as rawWays.push({ kind: roadEdgeKind(rd.kind), pl }) — it keeps the kind and the geometry and discards rd.name. Every real street in twenty-three Australian towns is sitting in this repo with its name on it, and the game has never said one out loud.
  2. The interior budget correction, which contradicts our own depth map and is the more urgent of the two. index.html:349 ships useGLB: !NOASSETS, so the default boot is the GLB path, and C-progress.md:841 records that sweep at GLB-on 344 against the ≤350 interior cap. The interior margin is SIX draws, not 191. The depth map's "draw budget is not the problem either" cited the GLB-off 168 and is wrong. This goes into the house record today regardless of what happens to the rest of this document — any interior work, in any epoch, is spending against six.

Scope honesty, stated up front: enterable upper floors are an EPOCH, not a wave. player.js:64 and interior_mode.js:485 both write y = EYE unconditionally every frame on every path — there is no gravity, no floor query, no current-storey state, and no data structure anywhere carries a floor height. Anything containing "levels" or "stairs you walk up" is proposing a locomotion rewrite plus a new ground truth. Everything else John asked for is affordable now.

Companion: SALE_RED_TICKET_DAY.md owns the ROUND and the LAWS (the Band Floor Law); this document owns the HUNT and the CITY.

THE CITY IS THE GAME

Synthesis — the v8 city-patterns review, three takes reconciled

Fable → John. Measurements in this document were re-run against the tree at d59df8e, seed 20261990, all 23 shipped caches + the synthetic. Where I contradict a judge or the depth map, I say so and show the number.


1. THE ANSWER

Yes. And the reason it's the right move is that the city is already deeper than the game knows.

Your reframe — "you have to even know where to find the store first to get the sale item" — asks for a city with places in it. PROCITY generates places. It has generated them since v2. Nothing reads them:

Generated Where Consumed by
Six DISTRICT_KINDS, 7 districts on the synthetic (mainstreet 332 shops · backstreets 95 · market 41 · arcade 17 · warehouse 4 · residential 4) registry.js:8, plan.js:160-292 grass verges + one plaza quad (ground.js:58,165), ambient anchors (audio.js:170). Nothing else.
A 42 m covered arcade, 17 shops, 2 blocks, a measured 4.99 m walkable lane, already awning-roofed by accident plan.js:270-283, buildings.js:634-647 paved (ground.js:140-147). No props, no signage, no roof, no reverb.
Two 280 m service laneways behind the main-street blocks, 36 yard lots plan.js:328-339 gum trees, magpie song. Zero shops.
storeys on every shop — 566 of 1,345 corpus shops are 23 storeys plan.js:89, plan_osm.js:275 box height (buildings.js:567) and one ambient. The facade stops at 3.0 m. No upper window exists anywhere in the codebase.
lot.frontEdge — which street every shop is on plan.js:78 one consumer, gigs.js:82.
roads[].name on every shipped cache — 17,835 named ways of 19,132 (93.2%) the caches themselves nothing. plan_osm.js:362-366 keeps kind and drops the name.

That last row is the finding of this review and I re-derived it independently: adelaide 1191/1099/319 distinct, bendigo 1552/1408/322, fitzroy 1571/1437/190, katoomba 484/457/166, castlemaine 397/370/141. Every real street in twenty-three Australian towns is already sitting in the repo with its name on it, and the game has never once said one out loud.

And it is recoverable without touching citygen. Plan nodes are snapped projected way points (plan_osm.js:361-372, SNAP=3), so plan edges lie on the named ways. Measured, all 23 caches, median distance from a plan-edge midpoint to its nearest named way:

0.03 m 0.90 m. Worst town katoomba at 0.90 m. 97.8% of corpus shops (1,192 / 1,219) resolve to a street name.

That is not an inference. I recovered the plan's internal shift by centroid-align + nearest-waypoint vote and re-measured; the numbers above are mine. There is a cleaner one-line upgrade — write norm.shift = {shx, shz} at plan_osm.js:510, which lands in the caller-supplied opts.report sink, never on the plan object — so it moves no golden and breaks no classic byte. Take the one line. But the design does not depend on it.

So: the city is the game is cheap, because the city is already built and only the exploration layer is missing. The expensive part is exactly one thing, and I'll name it now rather than bury it:

Vertical movement is fiction until someone rewrites locomotion in both modes. player.js:64 and interior_mode.js:485 both write y = EYE unconditionally, every frame, on every path. There is no gravity, no floor query, no current-storey state, and no data structure anywhere carries a floor height. Any proposal containing "levels", "stairs" or "upper floors as places you enter" is proposing that rewrite plus a new ground truth. And the interior draw margin is six, not one-nine-oneC-progress.md:841 reads "full drawSweep unchanged (GLB-off 168 · GLB-on 344, both pass)", and index.html:349 ships useGLB: !NOASSETS, so the default boot is the GLB path at 344/350. The depth map's line "DRAW BUDGET IS NOT THE PROBLEM EITHER" cites the GLB-off sweep and is wrong. That correction goes into the house record today regardless of what happens to the rest of this.

Enterable upper floors are an epoch. They are not this epoch. Everything else you asked for is affordable now.


2. THE SHAPE — "KNOW YOUR TOWN"

One proposal, four layers, in dependency order. Layers 13 are information design and cost zero draws. Layer 4 is the only geometry and it rides meshes that already exist.

Layer 1 — THE ADDRESS LAYER (the vocabulary)

A new pure module, address.js. No THREE, no fetch, no plan mutation.

createAddresses(plan, cache|null) → {
  streetOf(edgeId)   → "Templeton Street" | null
  localityOf(shopId) → { street, district, block, side, label }
  cohort(predicate)  → shopId[]          // the clue sizer — see Layer 3
}
  • 23 cache towns: street name, resolved by nearest named way to the edge midpoint (≤8 m tolerance).
  • Synthetic: no roads cache and only 22 edges, so the label comes from district.kind + block — "the market end", "the arcade", "the backstreets". Same field, different supplier, one consumer contract.

Consumers, all cheap: the HUD tooltip (hud.js, one template literal), the map's street labels, the notebook, the morning paper, keeper rumours.

This is the prerequisite, not the bonus. VERTICALITY's judge marked it as such and was right: without it, CLEARANCE collapses to one type-cell on 21 of 24 towns. UNMARKED needs it harder still — 7 of the synthetic's 12 hidden shops share a name with 110 others (Rotation ×10, Quick Cash Pawn ×11), so a ledger entry that says only a name points at ten buildings. An address is the only way a shop with no sign can be written down at all.

Layer 2 — THE FOG (the keystone)

minimap.js draws only shops the player has walked past. Discovery is player state, saved.

Today minimap.js:52-63 draws every lot in the plan, coloured by shop type, from the first frame, with no gating. One M keypress renders all 493 synthetic shops — all 77 record shops, all 92 op shops — before the player has walked a metre. Measured against what that is worth: passing every shopfront once costs 5,330 m on the synthetic, 9,622 m on fitzroy, 5,797 m on northbridge — at WALK 4.6 m/s against lighting.js:91's 240-second day, 4.8 / 8.7 / 5.3 game-days of walking. The map hands over five to nine game-days of legwork for one keystroke, on all 24 towns, forever.

And the payoff is bigger than the cost saving. Replicating minimap.js's own transform, a shop rectangle renders at:

bowral castlemaine katoomba adelaide fitzroy synthetic
full-extent (today) 0.8×1.1 px 1.4×1.8 1.4×1.9 1.7×2.3 2.7×3.5 5.2×10.2
framed to a 200 m known window 29×39 px 29×39 29×39 29×39 29×39 21×41
gain 35× 21× 21× 17× 11× 4×

The same line of code that hides what you don't know makes legible what you do. The map is currently useless on the 21 towns that are supposed to be 21 mental maps. Fog fixes that as a side effect.

Cost: zero draws, zero triangles — the map is a 2D canvas overlay and hud.js:361's counter (renderer.info.render.calls) never sees it. Zero new plan fields. Classic-gatable by construction: createMinimap(plan) at index.html:204 becomes createMinimap(plan, game ? knownProvider : null), and a null provider is today's exact behaviour — the same pattern ground.js already uses at index.html:178-185, so minimap.js needs no flag test at all.

Two corrections to the depth map's own fog proposal:

  1. Discovery is a proximity probe, not chunks.onChunkBuilt. Real towns are all BIG_CITY (index.html:151) so R=2 — the outermost live chunk centre is 128 m out, its far corner 181 m. Chunk-built discovery would hand you shops three blocks away through a wall. Use a 25 m squared-distance probe over the live doorRects on the existing frame % 6 cadence (hud.js:337). Same cost class, honest semantics.
  2. Key the fog by TOWN, not by townKey. I verified: on all 23 cache towns, shops[].id / lot / type / name and every lot geometry are byte-identical across seeds — only storeys and hours re-jitter. So a re-seed is the same Katoomba with different secrets, and the map you learned should survive it. Keying by town also bounds the save by construction (≤ 25 registered keys, no LRU, no cap needed), which kills NOMAP's K5 and UNMARKED's cap problem in one move. The synthetic is the exception — its shop ids are seed-dependent (493 shops at 20261990, 479 at seed 7) — so it keys default@<seed>. One rule, one stated exception.

Layer 3 — THE HUNT (what the fog is for)

A once-per-day sale, announced at dawn in the existing morning paper, addressed rather than pinned. Full round in §3.

The director's job is one thing: size the clue. It picks a target from the set of shops that some clue cell resolves to ≤4 doors within a ≤350 m span, and emits the smallest cell that works. Measured share of a town's shops that are clueable under that rule:

bendigo · braddon · darwin launceston · castlemaine · marrickville · bowral newtown · redhill · newcastle · northbridge glebe · westend · hobart adelaide · fitzroy · geelong · fremantle · katoomba brunswick synthetic
100% 9093% 8389% 7778% 6974% 51% 32%

Median real town: ~85% of shops are pointable-at in four doors or fewer. Median best clue cell on a real town: 1 door. On the synthetic: 9 doors, p90 20. That difference is structural and I'll deal with it in §5 and §8.

Layer 4 — THE UNMARKED (the reward)

Roughly 6% of a town's shops ship with no sign — 89 across the corpus, 12 on the synthetic, 212 per town by clamp(round(shops×0.06), 2, 12). You find them by cue: a crate of records on the footpath, an A-frame, a roller door half up, a bloke in the doorway. They enter the notebook by address, because they have no name.

The seam that makes this free: on a fogged map, an unmarked shop's absence is indistinguishable from an undiscovered shop's absence. UNMARKED's own judge killed the map-omission idea because on the synthetic — the only town where the map is legible — omitting one lot from a terrace punches a 16.8 px hole, 6.5× the lot width, and you can count the notches without walking. With Layer 2 in place there is nothing to count: every undiscovered lot is a hole. The fog doesn't just permit the hidden layer, it is the only thing that makes it honest.

The same seam retires VERTICALITY's KILL 2 ("the minimap already hands over the clue") and NOMAP's Gate 1 problem. Every one of the three takes has a kill that fog closes. That is the argument for this shape over any of the three alone.

Layer 5 — UPSTAIRS, VISIBLE ONLY (the bridge to the next epoch)

Give the 566 multi-storey shops a first-floor window band, drawn into the existing merged windowGeos mesh with the existing per-vertex aHours shader. +0 draws, ~24 triangles per shop. makeInstanced sizes to list.length with no cap (buildings.js:749-753); windowGeos merges to one mesh per chunk (:800-806). Today buildings.js:581 caps the facade skin at FACADE_H = 3.0 and puts both windows at y=1.55, so storeys 2 and 3 are a flat tinted box with a parapet — while audio.js:190-193 already seeds a radio-through-a-window ambient onto every shop with storeys ≥ 2. The game already sounds like someone lives above the shop and looks like nobody does.

No stair. No enterable room. Nothing that touches the two Y-clamps. This is v8's own name — SOMEONE LIVES HERE — paid for at zero draws.

The seams, stated

address ──(labels)──▶ hud tooltip · map · paper · keeper rumour · notebook
address ──(cohort sizer)──▶ director            [the clue can't be written without it]
fog     ──(known set)──▶ map draw filter + map framing + notebook
fog     ──(structural)──▶ unmarked              [no hole to count]
unmarked──(no name)──▶ address                  [the only identifier it has]
upstairs──(address)──▶ "above 42 Templeton St"  [free, later]

One save field (known), one new pure module (address.js), one canvas rewrite (minimap.js), one template literal (hud.js).


3. THE ROUND

Castlemaine. 22 shops. 8 shop-bearing streets. Day 14. You have $180 and a wantlist with three things on it.

The clock, from the code. lighting.js:91 DAY_SECONDS = 240 over 6 segments = 40 real seconds per segment. lighting.js:22 HOUR_NUM = [6.5, 9, 12.5, 15.5, 18.5, 22]. Registry trade hours are 1018 (registry.js:40), so on 20 of 24 towns a record shop is open in segments 3 and 4 only — 80 real seconds of trading. That is the hard constraint every hunt design in this review either respected or died on.

06:30 — DAWN, segment 1. The paper lands. One line:

CLOSING DOWN — EVERYTHING HALF PRICE — THE RECORD SHOP ON TEMPLETON STREET

Two terms: type + street. Measured on castlemaine, that cell holds 1 shop (town median 2, town max 3). The clue is exact. The question is whether you know where Templeton Street is.

06:3012:30 — segments 1 and 2, 80 real seconds, the doors are shut. This is the hunt, and it is free time you already spend walking. At WALK 4.6 m/s that's 368 m; at RUN 8.8 it's 704 m. Measured, distance from the town's shop-centroid to a given shop: on castlemaine 45% of shops are inside 368 m, 73% inside 704 m. Corpus range 1185% inside 368 m.

  • You've walked Templeton before. It's on your fogged map, labelled. You go. 4090 seconds. You're standing outside at 11:50 with a segment in hand.
  • You haven't. The map shows the streets you've learned and a name you've never seen. Three outs, in order of cost:
    1. Ask a keeper. Any open shop, any keeper: "Templeton? Off the north end of Barker, past the hall." Free, rides the existing rumour seam, and it is the most SOMEONE-LIVES-HERE thing in the whole design.
    2. The paper's second line — a landmark from the address layer's nearest named cross-street: "opposite the Town Hall". Costs nothing; the director emits it only when the target street is undiscovered.
    3. Walk. Every street you learn is banked forever, on every future visit, at every seed. The loss is a day, not a save.
  • You get it wrong. The clue cell is ≤4 doors by gate, so worst case you check four shopfronts. Measured p90 spread inside a clue cohort: 0 m on eleven towns, 105466 m on nine, and the gate rejects anything over 350 m.

12:30 — MIDDAY, segment 3. Doors open. You're in. The sale stock is the shop's normal seeded rotation at half band. You dig — riffle, pull, read the sleeve, check it against THE BIBLE. Under band is a gem; that skill is unchanged and it is still the core.

Scarcity is the stock, not a stopwatch. dig.js:417-421 reads opts.gone once and computes offers once, at card-open — so "half the good ones are already gone" works evaluated when you walk in, and does not work as a timer ticking while the card is open. Arrive in segment 3 with a full sale; arrive in segment 4 and a seeded fraction is gone. That is the whole pressure and it costs one union into an existing set (interior_mode.js:187).

18:00 — the shop shuts. If you never found it, you lost the day. Tomorrow's paper has a different sale, in a different part of town, and the streets you burned today are permanently on your map. You did not lose a run. You bought a map with a day.

Total round: 240 real seconds. Clue resolves to ≤4 doors. 368704 m of pre-open walking. 80 seconds of trading. Win = you knew the town, or you learned it.

The two numbers that constrain everything above — and that killed two of the three takes' round designs:

  • NOMAP's director radius (remaining_segments × 40 s × 4.6 m/s × 0.7) leaves a median of 1 shop reachable on marrickville, 23 on braddon and redhill. The radius narrows the pool below the clue's cohort before the clue speaks. Cut; announce at dawn instead, which is what §7 already did, and let the whole town be live.
  • UNMARKED's BLIND DOCKET puts the target 2,217 m from the roster on hobart, 1,971 m on fremantle — 3.1× the entire round's RUN budget in a straight line, on a mode with an 80-second window. Impossible on 7 towns, and on 9 more the "unnamed" shop is already printed in the paper. Cut entirely.

4. WHAT IT COSTS

THE MINIMUM VERSION — one round, no geometry, proves the fun

Lane File Change
F address.js (new) pure; edge→named-way resolution + shift recovery; synthetic district/block fallback; cohort() sizer. Zero fetch beyond the cache the shell already holds — needs one additive export, getTownCache(key), because index.html:130 hands the JSON to registerTownCache and drops its own reference.
A plan_osm.js:510 one line: norm.shift = { shx, shz } into the caller-supplied opts.report. Not on the plan. No golden moves. Optional — the vote recovers it without this.
B minimap.js fog filter + known-extent framing + street labels + an offscreen static cache (today drawStatic() re-fills every lot every frame; adding up to 322 fillText calls per frame would turn a free overlay into a CPU text pass — invalidate on discovery change only). ~40 lines.
B hud.js tooltip prints NAME — Templeton Street. One template literal. Plus the 25 m discovery probe on the existing frame % 6 cadence.
B buildings.js:453-478 the type label into the sign atlas. The cell is 512×168; the name renders at ≤74 px centred at py+84, and drawCrateMark owns py+128…py+152. There is ~42 px of clear canvas in the top band (py+13 … py+55). Draw the registry label there at ~30 px. +0 draws, +0 tris. Without it, "the op shop on Barker Street" is unfalsifiable from the footpath: drawSign renders shop.name only, opshop and stall sign colours are byte-identical (fixture_plan.js:29-37), and only 518% of real-town shop names contain a type word (median 13%). Every later clue term depends on this.
F save.js known optional block, town-keyed, exact-and-loud validator matching save.js:72-99, .slice() in adopt().
F the game layer the dawn director + the paper line + the notebook DOM.

Zero new draws. Zero new triangles. Zero plan fields. No Lane C, D or E. One round.

Then, in order

Round What Lanes Why then
+1 The unmarked layer — UNMARKED §1§5 with all nine corrections (TYPE_W.stall = 0; hoist the atlas alloc to ensureAtlas()buildings.js:452-462 allocates lazily inside drawSign, so 49 chunks across the corpus where every shop is hidden would hit new THREE.CanvasTexture(null); crate to d/2 + 2.4, outboard of the awning post line at d/2 + 2.0; discovery hook inside the successful-entry branch, after index.html:491 and :503, not on the :489 dispatch) B + F Needs the address layer to have a name for the nameless, and the fog to hide the hole.
+2 The first-floor window band on 566 multi-storey shops B +0 draws, ~24 tris/shop. This is v8's own headline, paid.
+3 The arcade, treated as an arcade — district-keyed, synthetic only (roof lights, tiled floor, reverb, E's 5.86 m longbench) + a mouth so it stops being a graph orphan (measured: arcade edge 14's endpoints are degree 1 and 1; the announcement is 14.7 m of bare ground) B + E 17 shops, one town, but it is the only place on the synthetic where a type-plus-place clue resolves to a set you can scan: record|arcade = 9 hosts, 32 m spread, 7 s end to end.
+4… (a different epoch) Enterable upstairs C + F + B Priced in §5.

5. THE HARD PROBLEMS, ANSWERED

Replayability in a deterministic world

The town is the constant; the contents are the variable. Verified across katoomba, fitzroy and castlemaine: re-seeding a cache town leaves shop ids, lots, types and names byte-identical — only storeys and hours re-jitter (plan_osm.js:274, seedFor(citySeed,'osmshop',id)). And save.js:172 already treats a town change as travel, so you keep cash and collection.

So ?seed=N is the same Katoomba, different secrets: the same streets, the same shopfronts, a different unmarked set, a different sale roster, different upstairs. Your map survives; your knowledge of what's behind the doors does not. That is exactly the right split for a game about knowing your town, and it is why the fog keys by town.

Two honest limits. On small towns the hidden-set reshuffle is weak — 13 of 24 towns pick from N=2 with one dominant candidate, so a Jaccard-based "different secrets" gate goes red on bendigo, braddon, launceston, redhill and glebe. That gate must be size-aware or the reserved slot must take a seeded draw. And the synthetic re-rolls everything including shop ids, so it is a different town at every seed, not the same one.

Findable vs frustrating

Answered by arithmetic, not by feel. The director sizes the clue; the gate enforces the size.

  • Two terms (street + type) on a real town: median cohort 14, p90 218, 1989% uniquely identified. Without the street term: median 349.
  • Adding side-of-street: median 14, 2789% unique, worst tails cut (katoomba 18→15, fitzroy 10→7, brunswick 12→9).
  • 51100% of a real town's shops (median ~85%) are clueable at ≤4 doors within ≤350 m.

The gate must assert the search, not the answer. VERTICALITY's G7 ("the clue's candidate set has ≥2 members") is a floor on the wrong quantity and goes green on an 87-door clue — measured, toy|mainstreet on the synthetic sends you to 87 doors over a 770 m spread, 167 seconds of walking. Rewrite as a ceiling on doors-the-clue-sends-you-to, in [1, 4], with span ≤350 m, over 200 seeded days × 24 towns. As corrected it fails on today's data, which is the point.

The failure mode is then never "I wandered for four minutes and lost." It is "I haven't learned that part of town yet" — with three outs and a permanent gain either way.

The interiors machinery's one-room assumption

Don't touch it this epoch. The minimum version's interior delta is exactly zero: a hidden shop, a sale shop and a normal shop all call the same buildInterior(shop).

When it does come due, the price is known and it is not what the depth map said:

  • The blocker is not disposectx.disposeAll() (interiors.js:242-248) tracks every geometry/material/texture regardless of sub-group, so N rooms through one Ctx dispose correctly today. binUnderAim and stockInfo both traverse, so a second room's crates are diggable for free.
  • The blocker is not draws — but the margin is six, not one-nine-one. C-progress.md:841: GLB-off 168, GLB-on 344, both against ≤350. index.html:349 ships useGLB: !NOASSETS. The 159 the depth map cites is the GLB-off sweep. Correct the house record.
  • The blocker is the grid. layout.js:337 makes ONE makeGrid(W,D); interior_mode.js:322-330 rejects any position with |x| > W/2 or |z| > D/2 before consulting occupancy. A fully built, fully lit second room outside that rectangle is unreachable.
  • Full bill: one merged grid with a doorway cell the Manhattan carve at layout.js:408 may not close · a second path guarantee (:391-408 flood-fills one spawn to one counter) · per-room keeper resolution (interior_mode.js:396 is .find, not .filter) · a second exits[] entry with toStreet:false and an opener — the contract is already honoured at :335, which makes it a vacuous-gate trap: ship the door without the opener and a naive "the room has 2 exits" gate passes while nothing on earth opens it · a shell variant, because shell.js:113-114 calls buildShopfront unconditionally and would give a first-floor room ground-floor glazing, an EXIT plate, and a backdrop canvas painting a kerb at row 74 · plus, for anything called "stairs", the two Y-clamps and a floor-height ground truth no structure carries, and the seven module singletons enter()/exit() own.

≈ one Lane C round + one Lane F round for a second room. An epoch for stairs.

The 8-draw margin

The pin is 292 draws at night, 282/283 at noon (F-progress.md:9,962). Everything in layers 13 is DOM, 2D canvas, or data: zero draws, and the count should be identical, not merely under budget. Gate on renderer.info.render.calls matched exactly, at night and at noon — the window band is emissive at night and lands on the worst frame.

The standing refusal holds and should be written as law: no new per-chunk mesh class, ever. Measured live chunks: 25 live / 49 in the dispose window on every real town (all BIG_CITY, R=2); 23/42 on the synthetic. At 8 draws of margin, +1 draw × 49 chunks is not a cost, it is a breach. All depth geometry rides the existing merged lists — boxMats, awnings, footGeos, windowGeos, doorGeos, signQuads — the way the R38 yards change did. makeInstanced sizes to list.length with no cap; that mechanism is verified and it is what makes layers 4 and 5 free.


6. THE CUT LIST

Cut Measured reason
CLEARANCE (VERTICALITY R4) The clue sends you to 18 doors at the median, 87 at the max — the design counted correct answers, not searches, because the player can't see which hosts have an upstairs. And minimap.js:52-58 already hands over the clue's three attributes. Re-found it or leave it out.
THE BLIND DOCKET (UNMARKED §6) Blind shop to nearest roster shop: 2,217 m hobart, 1,971 m fremantle, 1,538 m newtown against a 704 m RUN budget. Impossible on 7 towns. On 9 others the blind shop is already inside the named clique, so the paper names the shop it says has no name.
The laneway leg 2 shops in the entire corpus front a lane edge (both launceston), 0 on the synthetic. The synthetic's two laneways are graph orphans (endpoint degrees 1 and 1) and the east lane runs under seven arcade lots that overhang its carriageway by up to 1.63 m — lotCollider would eject the player from the lane.
The merged-grid back room (VERTICALITY R5) Surplus depth behind a max-depth room: p50 = 0.10 m, p75 1.96, p90 3.22. Only 31 of 493 shops (6.3%) clear 4 m. It is a lottery on the archetype roll, and it is the only piece that touches the path guarantee.
Real-town arcade and lane flavour 'arcade' on a real town is OSM pedestrian|footway|path|steps|cycleway990 edges, 39,276 m of ordinary suburban footpath. Roofing it roofs 39 km of pavement. Arcade treatment keys off district.kind, synthetic only. Make it a code rule.
District-keyed skins on real towns plan_osm.js:305 is one unconditional addDistrict('mainstreet'). All 24 caches: 1 district, all lots use:'shop'. Code rule, not a caveat.
The facade-skin clue term 47 files in web/assets/gen/, no colour or material name anywhere in the codebase, and skins are shared across types (facade-corrugated in pawn/stall/band_room). Leaks type without adding information.
Physical corner street signs +1 draw × up to 49 live chunks against 8 of margin. Already refused; endorsed.
Map omission for unmarked shops Punches a hole up to 16.8 px wide — 6.5× a lot width — in a contiguous terrace on the synthetic, the one town where the map is legible and the default and classic boot. Fog handles it structurally; omission does not.
The segments-remaining director radius Median shops reachable from a random shopfront: marrickville 1, braddon 2, redhill 3. Narrows the pool below the clue before the clue speaks.
The window card as a cue 28 px in a 168 px cell on a 0.25 m quad = 0.042 m of letter, legible to ~3.8 m — inside the free 6 m tooltip at hud.js:302. Demote to dressing. One ranged cue exists: the crate.

7. WHERE IT SITS

This is v9 — THE CITY IS THE GAME — with a one-round probe landing inside v8.

Three reasons.

  1. v8 is texture; this is mechanic. Ambient audio, per-town character, the magpie, backyards — every v8 item makes the city feel more like a place. This makes it function as one. Folding a new win condition into an epoch about atmosphere leaves the epoch un-nameable, and PROCITY names its epochs for a reason.
  2. The probe belongs in v8 because it decides whether v9 exists. Fog + the address layer + the type label is one round, zero draws, zero geometry, and it is falsifiable on its own: does a fogged, street-named town make you want to walk it? If the answer is no, v9 doesn't happen and we spent one round. If the answer is yes, v9 has a proven core before anyone models a polygon. Never build the geometry first.
  3. v8's own headline is already the bridge. SOMEONE LIVES HERE is answered — literally, on 566 buildings, at +0 draws — by the first-floor window band. audio.js:190-193 has been asserting that upper floors are inhabited since it shipped. Land the band in v8 and the epoch keeps its name. Then v9 makes the city knowable. Then, if it earns it, a later epoch makes those upper floors enterable, and by then the window is already there to walk up to.

v8 wave 2: the address layer · the fog · the type label · the first-floor window band. v9 — THE CITY IS THE GAME: the notebook · the unmarked · the sale director · the arcade.


8. WHAT NEEDS JOHN

Six taste calls. Nobody else can make them.

1. Does the default boot become a real town? This is the big one. The synthetic is 493 shops on 22 streets, with 304 distinct names and a 17-way collision (The Toy Nook ×17, Marbles & Kites ×14, The Book Cellar ×12). Measured: 32% of its shops are clueable at ≤4 doors, against 51100% (median ~85%) on the real towns. Its best clue cell is a median of 9 doors. It is a magnificent strip and it is not a town you can learn. A bare index.html boots it today; ?classic=1 freezes it byte-identical forever regardless, so the covenant is not at risk. But if the win condition is knowing your town, the town the game opens on should be one that can be known. (A smaller version of the same call: let the synthetic's hunt be district-scale — "somewhere in the backstreets" — an honest browse rather than a broken precision hunt.)

2. How harsh is the fog? Three settings, in ascending mercy: (a) shops you've walked within 25 m of · (b) shops you've actually entered · (c) streets you've walked, with every shop on a walked street revealed. (c) is the most map-like and the most forgiving — you learn a town in streets, not in shopfronts — and it makes the address layer the unit of knowledge, which is tidy. (b) is brutal and would take a very long time to fill in. My instinct is (c); it's your call.

3. Does the clue name the type or the shop? "the record shop on Templeton Street" teaches you the city. "Rotation, somewhere up Templeton" teaches you the shops. Type-clues make the town the thing you learn; name-clues make the roster the thing you learn. They are different games and the arithmetic supports either.

4. Does an unmarked shop ever get a name? Or does it stay, forever, "the one with the crate out front, Lyttleton Street" — a place you know by where it is and not by what it's called. The second is better writing and it is also the harder sell to a player who wants a list.

5. Is being late a discount or a loss? Arrive in segment 4 and the good ones are gone (gone grows with arrival time, seeded) — or the sale is simply over and the door is locked. The first is a difficulty curve; the second is a deadline. The code supports both at the same cost.

6. Do keepers give directions? It is the anti-frustration valve, it costs almost nothing, it rides the existing rumour seam — and it is the single most someone lives here thing in this entire document. It is also the moment the city stops being an opponent and becomes a place with people in it who are on your side. That's a tonal decision, not an engineering one.


Two things for the house record, regardless of what you decide

  1. The interior draw margin is six, not one-nine-one. C-progress.md:841 — GLB-off 168, GLB-on 344, budget ≤350, and index.html:349 makes GLB-on the default boot. The depth map's "draw budget is not the problem" line is measured off the wrong sweep and has to come out before it authorises a room somebody can't afford.
  2. Street names are already in the repo. 17,835 named ways of 19,132, 93.2%, on 23 towns, recoverable to a median of 0.030.90 m with no citygen change and no golden move. Whatever the shape ends up being, that data should stop being dropped on the floor.