5.4 KiB
LANE A — CITYGEN (the plan generator)
Prompt for Opus 4.8. You are working in
/Users/jing/Documents/PROCITY. Readdocs/CITY_SPEC.md(the contract) anddocs/RESEARCH.md(what's already solved) before writing any code. You ownweb/js/citygen/*,web/js/core/registry.js, andweb/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 throughweb/js/core/prng.js. Commit in small, described steps onmain.
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:
- Main street spine — one gently bent high street running roughly N–S through the origin, 28m corridor (road 10m + verandah'd footpaths). Continuous retail frontage: narrow lots (6–9m frontage, 12–20m deep), zero side setbacks, 1–2 storeys, occasional 3-storey corner anchor. This is where record/toy/book/video shops cluster.
- Cross streets every 80–140m; the two central ones are "second high streets" (side kind),
the rest taper into
backstreets. - One arcade — a covered pedestrian lane (
kind:'arcade', width 5) cutting through a mid-spine block, lined with tiny lots (3–5m frontage) both sides. Prime record-store turf. - Market square district — an open block near the origin:
stalllots in rows, the town'sdeptanchor on one edge. - Warehouse fringe (N or S end, seeded): bigger lots, corrugated/besser skins, sparse — future flea-market/venue territory.
- Residential collar — the outer ring:
houselots with front yards, and exactly 2–4 corner-lotmilkbars embedded in it (the classic Australian corner shop). - Laneways (
kind:'lane', width 4) behind main-street blocks for service/back-door flavour.
Algorithm sketch (suggested, not sacred)
- Spine as a polyline: 5–7 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) andry(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
facadeSkinfrom the type's pool (seeded),storeys,hours(seeded around 9–5, 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
web/js/citygen/plan.js—generatePlan(citySeed),chunkIndex(plan).web/js/citygen/names.js(+wordlists.js).web/js/core/registry.js.web/map.htmldebug viewer.web/js/citygen/selfcheck.js— run withnode 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 againstweb/assets/gen/), chunkIndex covers every lot, JSON round-trip.
Acceptance
node web/js/citygen/selfcheck.jsprints all-green.web/map.htmlrenders 5 different seeds that all read as towns (spine visible, arcade present, market square, milkbars in the collar) — screenshot each intodocs/shots/laneA/.- Zero
Math.random, zero THREE imports in citygen (grep-clean).