PROCITY/README.md
m3ultra 6afd1ed7a3 Lane F R37 (v8 wave 0, 0.3): THE BUDGET OF RECORD SETTLED — 292 draws / 124,793 tris, and the 280-vs-191 fight was about the CAMERA BEARING
Two sessions disagreed by 90 draws and neither was wrong. Worst draw per BEARING, 12 bearings x
102 stations down the same spine, one tree, one box: 0deg 284 · 30deg 248 · 60deg 208 · 90deg 177 ·
120deg 196 · 150deg 214 · 180deg 211 · 210deg 207 · 240deg 178 · 270deg 182 · 300deg 223 ·
330deg 267. 110 draws of spread from the camera direction alone, on identical positions -- and the
peak sits at yaw 0, the DEFAULT ARGUMENT of DBG.teleport(x, z, ry = 0). The city-patterns audit
took the default and landed on the worst bearing (280, reproduced at 282-292); the animal spike
supplied a yaw and landed mid-range (191, reproduced at 205-215). Correct numbers, different
quantities. Lane B's independent R37 walk (276 draws / 99,880 tris) and mine (282 / 99,878) agree
on TRIANGLES TO 2 -- the same frame through two harnesses.

THE PIN, default boot, synthetic golden seed, tree 7bd96e5, walked the spine:
  292 draws of 300 (NIGHT, 31 live chunks -- the R+1 dispose window at maximum)
  120,145 tris on synthetic; 124,793 on fitzroy_real, the corpus worst
  ?classic=1: 269 draws / 90,580 tris
  margin: EIGHT draws (the audit said "about twenty") and ~75k tris
The worst-draw frame and the worst-tri frame are NOT the same frame. Real towns are draw-cheap and
tri-dear; the synthetic town is the opposite. The audit's "99k-124k" tri range is confirmed and
pinned at its top.

Also measured: viewport is IRRELEVANT (1280x720 and 2560x1440 identical to the byte -- culling is
frustum-shaped, not pixel-shaped) · the R+1 dispose residue is worth +19 draws, not the 60-80
folklore (the rest is place and bearing) · CitizenSim's per-frame rig-acquisition budget means any
one-render-per-station sampler photographs a half-dressed street · ?r=3 measures 307 draws and
BREACHES the 300 gate through an ungated override (filed, not fixed) · ?shadows=1 costs +38 draws
and +38% tris · Lane B's kerb change independently confirmed at +0 draws / -8 tris at the worst
frame, by a harness B did not write.

THE SHARED INSTANCE CAP: 256, one InstancedMesh, town-wide. Measured +1 draw at every N from 32 to
2048, and N x tris/instance ALWAYS submitted (the instance bounding sphere spans the town). Draws
are not the constraint; triangles are. 256 x a 2-4 tri cell = 512-1,024 tris = 0.5% of budget;
0.01 ms/frame to rewrite every matrix. Binding companions: <=24 tris/instance at the cap, and spend
it PROXIMITY-FIRST -- anchor supply runs from synthetic's 246 poles over 6.8 km to bendigo_real's
7,325 over 188 km, so 256 sprayed town-wide there is one instance per 730 m.

Honest limit stated where the number is quoted: headless SwiftShader cannot change a draw or
triangle count (both are counted in JS, and the viewport control proves it), but frame TIME remains
unmeasured on any real GPU and no v8 proposal may claim a millisecond figure.

Re-pinned in LANE_F_NOTES 37; the four other docs that assert the number are flagged with what each
line should now say rather than edited in another author's file. README flags table gains ?verge=0
(Lane B's control, F's file) and a row naming the ?r=3 breach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 17:19:24 +10:00

196 lines
15 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.

# PROCITY
A standalone, procedurally generated, fully walkable 90s-Australian shopping town — every
building enterable (the Vuntra City trick), every interior seeded and themed: record stores,
op shops, toy shops, book barns, video rental, pawnbrokers, milk bars, market stalls. Built on
the proven house stack (three.js r175 vendored, plain JS, no build step) and the asset DNA of
90sDJsim + thriftgod. v1 is the *system*; the GODVERSE/BaseGod real-item content plugs in later.
## Run
```
cd web && python3 -m http.server 8130
# http://localhost:8130 (game shell — Lane B)
# http://localhost:8130/map.html (2D plan debugger — Lane A)
```
## The manual — how to play (private test)
Click **START** to lock the mouse. Then:
| input | does |
|---|---|
| **W A S D** + mouse | walk / look (first person) |
| walk into a shop door | enter the interior (open shops only — check the hours tooltip) |
| **E** (inside a shop) | open the crate at a record bin — then it's cursor-driven: click sleeves to riffle, click BUY |
| **E** (at the counter) · **C** (anywhere) · **SLEEP** (bottom bar) | sell a find to the keeper (he pays under the **guide band**, never over) · open THE CRATE — your collection, in shops too now · sleep = save, the day advances, stock rotates |
| read the **guide** stamp on every price card | the 90s price-guide band ("guide $824"). An item **asking under its band is a gem** — buy it, sell it to a keeper, pocket the margin. Bought slots stay gone for the day (the bin remembers) |
| **☆ WANT IT** on a pull card | start hunting it — keepers gossip ("saw one in Castlemaine"), and buying one clears the hunt. **Switching towns costs a day** — the 23 towns are a map now, not a dropdown |
| **⚖ LIST** in the crate | consign a find to **GodBay** — the auction hammers overnight (sleep or ride somewhere). One hammer per item, ever: keeper cash now, or the gavel tomorrow |
| wake up (sleep or arrive) | **the morning paper** rides the wake/arrival toast: "⚖ SOLD 'Milk Bar Dreams' — hammer $8, $7 after fees" (or "PASSED IN — it comes home"). The news is a toast, never saved — the ledger is your cash and crate |
| click a book spine / toy box | pull-and-buy card (real price stickers in godverse shops) |
| walk out the door / **Esc** | leave the shop (Esc also frees the mouse anywhere) |
| **M** | 2D town map (street only) |
| **[** / **]** | step time of day back / forward · **T** pauses the clock |
| **P** | capture screenshots (street) |
| HUD dropdown (top-left) | switch towns — synthetic, fixtures, 23 real AU towns, godverse |
Worth trying: `?classic=1` (the original v2 town, byte-identical forever) · `?noassets=1`
(zero-network boot) · `?stock=real` in a godverse town (Monster Robot Party's actual crate) ·
`?seed=<n>` (any number = a different town). Night is when the gigs happen — step time with `]`.
## Read first
- **`docs/CITY_SPEC.md`** — the treaty: schema, units, seeds, budgets, file ownership.
- **`docs/V7_THE_GAME.md`** — the CURRENT epoch (ratified): persistence, the collection, the
gem thrill. `docs/V6_LIVING_STREET.md` runs in parallel (mocap NPCs, footpaths, cars).
- **`docs/THE_FRONTIER.md`** — the honest map of what is not figured out yet.
- **`docs/RESEARCH.md`** — what the parent repos already solved, with exact paths to port from.
## The lanes (parallel Opus 4.8 sessions)
Each lane is a self-contained prompt. To run one, start an Opus session in this repo and say:
**"Read docs/LANES/LANE_X_….md and execute it."** AE are parallel-safe (disjoint files,
standalone test pages); F integrates after they land.
| lane | mission | owns |
|---|---|---|
| [A — CITYGEN](docs/LANES/LANE_A_CITYGEN.md) | seed → CityPlan data (streets, blocks, lots, shops) + 2D map | `js/citygen`, `js/core/registry.js`, `map.html` |
| [B — STREETSCAPE](docs/LANES/LANE_B_STREETSCAPE.md) | chunk-streamed instanced 3D town + game shell | `index.html`, `js/world` |
| [C — INTERIORS](docs/LANES/LANE_C_INTERIORS.md) | every door opens: seeded themed shop interiors | `js/interiors`, `interior_test.html` |
| [D — CITIZENS](docs/LANES/LANE_D_CITIZENS.md) | rigged-near / impostor-far NPCs + shopkeepers | `js/citizens`, `citizens_test.html`, `models/` |
| [E — ASSETS](docs/LANES/LANE_E_ASSETS.md) | audit ultra's library, normalize GLBs, skins, manifest | `pipeline/`, `assets/` |
| [F — INTEGRATION](docs/LANES/LANE_F_INTEGRATION.md) | wire AE into one seed-to-town game + QA gates | (after AE) |
## Already in the repo
- `web/vendor/` — three.js r175 + addons (vendored from 90sDJsim; no CDN at runtime).
- `web/assets/gen/` — 69 generated texture skins (25 shopfront facades, 10 skies, 10 grounds,
8 wallpapers, 16 interior surfaces) inherited from thriftgod + 90sDJsim, style-locked.
- `web/js/core/` — frozen shared modules: `prng.js` (seeded-everything law), `loaders.js`
(fail-soft promise-cached GLB/texture + 3GOD depot), `canvas.js` (text planes).
## House laws (short form — CITY_SPEC has the fine print)
Seeded everything, `Math.random` banned in gen code · the game must run with zero assets ·
instancing from day one · canvas text, no font files · characters = shared base rigs + one
clip bank, canonical sources live outside this repo · 🟢 web-ok licensing only.
## v3.1 — THE FLIP: the town shows its best by default
The Friday-night layer soaked through alpha, beta, and the v3.0 release — so **v3.1 flips it on by
default**, and the mature v2 flags flip with it. Boot the plain URL and each town seeds **24 venues**
(pub, band room, RSL) with a **full seeded week of gigs**, town-wide **posters**, and a Friday night that
reads: a **4-piece band** (trio + a **seated drummer**) with real instruments, a mixed **crowd**, an
outdoor **queue**, a per-venue **cover charge**, muffled **gig spill** + **walla and applause**,
town-wide **streetlamp pools**, and **identity continuity** — the ped you followed in is in the crowd.
Plus **weather**, the **tram**, and the parallax interior-**window shader**, all live by default.
**The covenant moved, it didn't disappear.** `?classic=1` boots the **exact v2 town** — byte-identical
forever (synthetic golden `0x3fa36874`, zero gig layer, zero fetch delta). That is the new frozen
regression target; the default boot is deterministic to the gig golden `0xb1d48ea1`. Asset-free
(`?noassets=1`) composes with the default boot and stays silent-and-fine.
**Flags** — the four headliners are **ON by default**, each with a `=0` opt-out; `?classic=1` is the
master off:
| flag | default | what |
|---|---|---|
| `?classic=1` | off | **the exact v2 boot** — all four below off, byte-identical to the frozen baseline |
| `?gigs=0` | **on** | the gig district — venues, week schedule, band/crowd/queue, cover, posters, night pass |
| `?weather=0` · `?tram=0` · `?winmap=0` | **on** | seeded weather · the tram loop · the parallax interior-window shader |
| `?roster=v1` · `?pop=N` | stream | chunk-streamed citizen density is the baseline; `?roster=v1` restores the fixed roster |
| `?dig=0` · `?stock=real` | dig on | crate-riffle on record bins · real GODVERSE sleeves in the stock |
| `?verge=0` | **off** | reverts the ground to the **pre-kerb-ruling** geometry (bitumen painted to the full corridor). John's v8 ruling made the 9 m band between kerb and shopfront **footpath**; this flag keeps the old geometry available forever as the gate's falsifiability control — the poster-clearance leg is asserted to FAIL under it |
| `?r=N` · `?shadows=0\|1` | auto | streaming radius and the sun-shadow pass. **Auto is load-bearing**: a big town (>120 shops or >200 street edges) gets radius 2 with shadows off. Forcing `?r=3` on the default town measures **307 draws**, i.e. over the 300 gate — these overrides are diagnostics, not supported boots |
| `?live=1` · `?live=<url>` | **off** | **tier 2** — live stock from the GODVERSE server (sold-in-the-real-shop means gone in the game). Opt-in by design: absent ⇒ byte-identical to tier 1, and the game never asks a question it has no reason to expect an answer to |
| `?noassets=1` · `?plansrc=osm` · `?mute=1` · `?dbg=1` | off | asset-free (zero network) · OSM plan source · silence · the `window.DBG` capture/soak harness |
| `?plansrc=osm&town=<key>` | — | boot a **real Australian town**: fixtures `melbourne`/`katoomba`/`silverton`, or a real OSM cache `bendigo_real`/`castlemaine_real`/`fremantle_real`/`katoomba_real`/`newtown_real` (ODbL, `web/assets/towns/SOURCES.md`). **REAL ROADS:** a cache with `roads[]` (schema v2) builds the CityPlan street graph from the town's **actual OSM streets**; without `roads[]` a cache marches onto synthetic avenues. Or just **pick a town from the HUD selector** — no URL needed |
## v4.0 — THE REAL MAP: the towns *are* the product
**23 real Australian towns — 1,210 real shops, every state and territory — on their actual streets.** Not
"inspired by": Fitzroy's shops sit on Fitzroy's roads, at their real positions, because the pipeline eats
OpenStreetMap and marches it through the same CityPlan contract the procedural town uses. **Pick one from
the in-HUD selector and walk it.** Everything built across v1v3 comes along unchanged — the venues and their
seeded week of gigs, the 4-piece band with the seated drummer, the crowd that follows you into the pub, the
interiors behind every door, weather, the tram — because they all hang off the plan contract and never had
to know the geography got real.
The towns run from **meccas** (Fitzroy, 160 shops) to the **thin tail** (12), at real retail density: the
secondhand heroes keep their footfall while the street around them gets ~4× busier — texture, never a
takeover. Venues bias to the retail heart. The tram only runs where it fronts a real high street (elsewhere
it was honestly "a highway bus", so it's fenced). Everything stays **deterministic from the seed**, silent
under `?noassets=1`, and **`?classic=1` still boots the exact v2 town, byte-identical forever** (`0x3fa36874`).
**The tour:** [`docs/shots/release_v4/contact.html`](docs/shots/release_v4/) — the Fremantle hero, the
Fitzroy mecca, the thin tail (labelled honestly: the floor is shops-per-street-metre, and Toowoomba is
below it), the synthetic control, and the `?classic=1` covenant made visible. Charter:
[`docs/V4_REAL_MAP.md`](docs/V4_REAL_MAP.md) · data + licences (ODbL):
[`web/assets/towns/SOURCES.md`](web/assets/towns/SOURCES.md).
## v5.0-alpha — THE FIRST REAL CRATE: the shops get real too
**v4 made the towns real. v5 makes the shops real** — and the first one is standing.
Boot `?plansrc=osm&town=redhill_godverse&stock=real`, walk down **Musgrave Road** into **Monster Robot
Party** (147 Musgrave Rd, Red Hill, Brisbane — a real record shop), and riffle its crate. The records in
it are **that shop's real stock**: 120 real records off its real point-of-sale, real covers, real titles,
real prices. Buy one and the wallet debits what the sticker says. It's gone for the session and back next
boot — because until the live tier lands, the crate is a **file**, not a shop's ledger.
**Stock is a data tier now**, exactly like assets — each rung fully playable, each degrading softly to
the one below, so the game never depends on a server to be a game:
| tier | what | deterministic? |
|---|---|---|
| **0 · parody** | seeded canvas sleeves (v1) — any boot, always works | yes — the QA baseline |
| **1 · static real** | per-shop atlases baked from the shop's own POS | yes — atlases are files |
| **2 · live GODVERSE** | `?live=1` — live stock off the real till: **sell a record in the shop and it's gone from the game crate** | **no — by design** |
**The offline law:** the server enriches, never gates. `?noassets=1` still fetches **zero bytes** and plays
silent-and-fine; a shop whose atlas is missing falls soft to parody rather than borrowing someone else's
crate; and **`?classic=1` still boots the exact v2 town, byte-identical forever** (`0x3fa36874`). The
world stays seeded and golden-gated — only the *stock inside it* is data.
**Provenance:** item photos are each shop's own product shots, 🟢 in-house — **flagged before any public
or commercial release**, and every atlas carries that flag where the QA gate must print it. Real titles
and artists are an in-house amendment to the no-real-trademarks law, on the same footing. **Nothing an
in-game purchase does touches real inventory** — v5.0 ships sell-sandboxed by ruling; a real reservation
tier is chartered for v5.x, not assumed. Charter: [`docs/V5_REAL_SHOP.md`](docs/V5_REAL_SHOP.md) ·
economy design: [`docs/G3_ECONOMY_DESIGN.md`](docs/G3_ECONOMY_DESIGN.md).
| `?seed=N` | `20261990` | town seed |
**Release tour:** [`docs/shots/release_v3_1/contact.html`](docs/shots/release_v3_1/) — the money shots on
the default boot, plus a `?classic=1` before/after. **QA:** `tools/qa.sh --strict` (classic regression +
default-boot gate + smokes + no-giants) `--soak` (the full-week soak); the seated drummer passes the
no-giants gate at the seated stature band `[0.9,1.5] m`.
## v7.0 — THE GAME: you're a crate-digger in 90s Australia
v4 made the towns real, v5 made the shops real — **v7 makes it a game.** Boot the plain URL on a
fresh browser and you arrive HUNTING: the splash names a seeded first want and your pocket money
("You're hunting HILLS HOIST — $191 in your pocket"), three toast-beats walk you through the first
dig, and from there the loop is live:
**dig** (E at a record bin — the crate riffle) → **read the guide** (every price card carries a 90s
price-guide band; an item asking *under* its band is a gem, and spotting one is the whole skill) →
**☆ WANT** what you're hunting (keepers gossip seeded rumors — "saw one in Castlemaine") →
**travel** (switching towns costs a day; the 23 towns are a strategy map) → sell to the **keeper**
(instant cash, always under guide — the no-pump law) or **⚖ LIST on GodBay** (a seeded offline
auction that hammers overnight; one hammer per item, ever — no retry farm) → **SLEEP** (= save; the
day advances, parody/mint stock rotates per (shop, day), bought slots come back) → wake to **the
morning paper** ("⚖ SOLD — hammer $22, $19 after fees"). Repeat, richer and one gem wiser.
The machinery under it: a versioned localStorage save carrying THE DELTA ONLY (cash · collection ·
day · town · wants · pulls · listings — the world always regenerates from seed, so a corrupt save
can cost you your stuff, never the town), JSON export/import for moving machines, and an economy
fenced by gates: sell < guide-band structurally, auction EV 0.8×mid (random flips bleed; only
finds profit), save/load byte-determinism, rotation seeded by (citySeed, shop, day).
**`?classic=1` still boots the pure v2 town zero save, zero game DOM, zero Storage calls,
instrumented every run** and `?game=0` opts out of the layer on any boot. Charter:
[`docs/V7_THE_GAME.md`](docs/V7_THE_GAME.md) (ratified 2026-07-17, risk verdicts appended at the
close) · economy: [`docs/G3_ECONOMY_DESIGN.md`](docs/G3_ECONOMY_DESIGN.md) · the tour (day one to
first gem, 6 frames, no staging): [`docs/shots/v7_tour/`](docs/shots/v7_tour/).