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

91 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 `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: 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.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).