91 lines
5.4 KiB
Markdown
91 lines
5.4 KiB
Markdown
# 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 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.
|
||
2. **Cross streets** every 80–140m; 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 (3–5m 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 2–4
|
||
corner-lot `milkbar`s 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: 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) 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 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
|
||
|
||
1. `web/js/citygen/plan.js` — `generatePlan(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).
|