PROCITY/docs/LANES/LANE_A_CITYGEN.md
2026-07-14 10:46:40 +10:00

5.4 KiB
Raw Permalink Blame History

LANE A — CITYGEN (the plan generator)

Prompt for Opus 4.8. You are working in /Users/jing/Documents/PROCITY. Read docs/CITY_SPEC.md (the contract) and docs/RESEARCH.md (what's already solved) before writing any code. You own web/js/citygen/*, web/js/core/registry.js, and web/map.html. Do not touch any other lane's files (ownership table in CITY_SPEC). No npm, no build step, no TypeScript — plain ES modules, three.js style of the house repos. All randomness through web/js/core/prng.js. Commit in small, described steps on main.

Mission

Pure-data town generator: generatePlan(citySeed) → CityPlan per the schema in CITY_SPEC, in under 100ms, fully deterministic, JSON-serializable, no THREE import anywhere in this lane except the debug map page. Plus the shop-type registry and a 2D debug map viewer.

Design brief (the town we want)

Not a Manhattan grid, not noise-soup. A 1km² Australian country-town-meets-inner-suburb core:

  1. Main street spine — one gently bent high street running roughly NS through the origin, 28m corridor (road 10m + verandah'd footpaths). Continuous retail frontage: narrow lots (69m frontage, 1220m deep), zero side setbacks, 12 storeys, occasional 3-storey corner anchor. This is where record/toy/book/video shops cluster.
  2. Cross streets every 80140m; the two central ones are "second high streets" (side kind), the rest taper into backstreets.
  3. One arcade — a covered pedestrian lane (kind:'arcade', width 5) cutting through a mid-spine block, lined with tiny lots (35m frontage) both sides. Prime record-store turf.
  4. Market square district — an open block near the origin: stall lots in rows, the town's dept anchor on one edge.
  5. Warehouse fringe (N or S end, seeded): bigger lots, corrugated/besser skins, sparse — future flea-market/venue territory.
  6. Residential collar — the outer ring: house lots with front yards, and exactly 24 corner-lot milkbars embedded in it (the classic Australian corner shop).
  7. Laneways (kind:'lane', width 4) behind main-street blocks for service/back-door flavour.

Algorithm sketch (suggested, not sacred)

  • Spine as a polyline: 57 nodes, x-jitter ±30m. Offset cross streets perpendicular-ish with ±10° jitter. Snap intersections into shared nodes; build the half-edge/block extraction by walking the planar graph (blocks = faces). Keep it simple — the graph is small (<200 edges); an O(n²) face walk is fine.
  • Subdivide each block's street-facing sides into lots by frontage bands per district kind. Every lot records frontEdge (street edge id) and ry (facing rotation) so Lane B knows which way the facade points. Interior leftover = yard/infill.
  • Assign shop types by district weights (registry-driven), with clustering bonuses (a record shop next to an opshop next to a bookshop is the vibe — use a per-block shuffle that favours same-cluster neighbours), then assign facadeSkin from the type's pool (seeded), storeys, hours (seeded around 95, milkbars longer, one weirdo open till late).

Shop names & signs

web/js/citygen/names.js: seeded generator with per-type patterns and 90s-AU flavour (e.g. record: "{Word} Records", "Rotation", "{Suburb} Sound Exchange"; opshop: "St {Name}'s Opportunity Shop", "Vinnies-alike parodies"; milkbar: "{Family} Milk Bar"). 40+ patterns, plus a sign short form (what fits on the signboard). Keep it wordlist-based and deterministic — optionally ship pipeline/gen_names.py that asks m3ultra Ollama (http://100.89.131.57:11434, model qwen3:32b) to expand the wordlists into web/js/citygen/wordlists.js — generation uses only the checked-in wordlists at runtime (no network calls in the game).

Registry (web/js/core/registry.js)

Export SHOP_TYPES exactly covering the table in CITY_SPEC: per type — facade skin pool (filenames that exist in web/assets/gen/), sign style hints, interior archetype id, fittings mix, district weights, hours profile. This file is the shared vocabulary — every other lane imports it, so land it early and keep it flat data.

Debug map (web/map.html)

Standalone page, Canvas 2D (steal the thriftgod REFIDEX vibe): draws streets (width-scaled), blocks, lots coloured by use, shop dots coloured by type with name labels on hover, chunk grid overlay, seed input box + "regen" + "export JSON" buttons. This page is how humans (and Lane F) eyeball a seed. Make it pleasant — it becomes the in-game map later.

Deliverables

  1. web/js/citygen/plan.jsgeneratePlan(citySeed), chunkIndex(plan).
  2. web/js/citygen/names.js (+ wordlists.js).
  3. web/js/core/registry.js.
  4. web/map.html debug viewer.
  5. web/js/citygen/selfcheck.js — run with node web/js/citygen/selfcheck.js (plain node, no deps): asserts determinism (two runs, deep-equal), plan under 100ms, every shop has a lot, every lot has a valid frontEdge, no overlapping lots within a block, every facadeSkin file exists (fs check against web/assets/gen/), chunkIndex covers every lot, JSON round-trip.

Acceptance

  • node web/js/citygen/selfcheck.js prints all-green.
  • web/map.html renders 5 different seeds that all read as towns (spine visible, arcade present, market square, milkbars in the collar) — screenshot each into docs/shots/laneA/.
  • Zero Math.random, zero THREE imports in citygen (grep-clean).