EVERY CRATE DIFFERENT — measured: 15/15 keyed crates distinct, max shared titles between any two = 0. 14 mint atlases (5 record, 6 book, 3 toy) + Monster Robot's real crate + the manifest. MINT (ledger #2): the G3 note promoted — random.Random(shop_id) over an ID-ORDERED pool of real dealgod listings. The shop id IS the seed, so two shops can never draw the same crate. No TABLESAMPLE, no unseeded random: the two things that disqualified thriftgod's own mint() from tier 1 (G3 §2). Re-mint + re-bake is byte-identical. Only keyed shops whose type the loader can fetch get one (record/book/toy, C §7.1); the 13 keyed opshop/video shops get none and stay parody — an atlas there could never load, and the manifest is what makes that silent instead of a 404. MANIFEST (ledger #6, C §7.2d): stock_godverse/index.json — 15 shops, 1 real / 14 mint. Derived by reading the atlases back off disk, so it cannot drift from them: the manifest IS the directory. Real crate re-emitted with sourcing:"real" + C §7.2c's condition/sleeve_cond (my snapshot always carried them; I dropped them at emit — that was the hole in C's price card). Atlas bytes unmoved. THE ONE RED — mint ids vs E's SLOT_ID_PREFIX (E, one line). E asserts `sku_` on every atlas under stock_godverse/; mint items have no POS sku and carry `mint_<dealgod listing id>`, so all 14 fail. C §7.2a's RULE is satisfied — derive from the source's own stable key, never position, unique across ALL packs (measured: 344 ids, 0 clash with the generic packs, 0 positional). What §7.2a specifies as sku_<POS sku> it specifies "for a real-stock (godverse) pack"; mint is §7.2b's other thing, written a round later, and the id form was never revisited. I did not just emit `sku_` to go green: a POS sku and a dealgod listing id are DIFFERENT NAMESPACES, and this round's own standing note says no code or gate may compare a bare number across two id spaces — it exists because enterShop(31) entered the wrong shop. Putting both under `sku_` recreates that bug inside the economy: tier-2's sold-means-gone looks up POS skus, and sku_6031122 (a dealgod id) is indistinguishable by shape from a sku a POS must resolve. §7.2a's own reason #2 is exactly about ids being looked up across snapshots. Fix: make the prefix sourcing-aware (sku_ for real, mint_ for mint) — E's constant and error text are the only places. THREE MORE FINDINGS, none blocking: [E] FITS_1024=64 cannot be satisfied — 1024²/256² = 16 cells, so 64 items need 4 atlases and C caps a shop at 2; any 33–64 item pack MUST be 2048². Latent (my packs are 16→1×1024² and 120→2×2048²); the honest bound is 32. [C/F] the ≤32 MB ceiling doesn't hold at beta scale because the LRU it assumes hasn't shipped — F's boot preload loads EVERY keyed shop's pack, measured 46.1 MB/town resident; it's why mint crates are 16 items on one 1024² sheet. [C] "the crate menu, baked statically" can only be metadata: one base = one index = ≤2 atlases = 128 items, and Monster Robot has 357 crates / 24,646 records — so crate_menu ships the real crate list (labels + counts) and per-crate STOCK needs a per-crate base, i.e. a C contract line. MINT HONESTY: sourcing:"mint", an attribution that says "not this shop's stock", and NO POS-claim field at all — structural, not editorial. Records require a real Discogs artist (dealgod's `brand` on a record listing is the SELLER — the first bake had "Red Eye Records" as an artist; a shop is not an act) and use discogs_master_title where present, so Egg Records reads "MC5 — High Time $50", not "Pre Loved Record - Elton John - Breaking Hearts". Books/toys take `brand` per C's author/brand convention — sometimes the maker, sometimes the listing store. Real, imperfectly typed, and never claimed as that shop's. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| docs | ||
| pipeline | ||
| priorwork | ||
| tools | ||
| web | ||
| .gitignore | ||
| B-progress.md | ||
| C-progress.md | ||
| D-progress.md | ||
| E-progress.md | ||
| F-progress.md | ||
| G-progress.md | ||
| README.md | ||
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)
Read first
docs/CITY_SPEC.md— the treaty: schema, units, seeds, budgets, file ownership.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." A–E are parallel-safe (disjoint files, standalone test pages); F integrates after they land.
| lane | mission | owns |
|---|---|---|
| A — CITYGEN | seed → CityPlan data (streets, blocks, lots, shops) + 2D map | js/citygen, js/core/registry.js, map.html |
| B — STREETSCAPE | chunk-streamed instanced 3D town + game shell | index.html, js/world |
| C — INTERIORS | every door opens: seeded themed shop interiors | js/interiors, interior_test.html |
| D — CITIZENS | rigged-near / impostor-far NPCs + shopkeepers | js/citizens, citizens_test.html, models/ |
| E — ASSETS | audit ultra's library, normalize GLBs, skins, manifest | pipeline/, assets/ |
| F — INTEGRATION | wire A–E into one seed-to-town game + QA gates | (after A–E) |
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 2–4 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 |
?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 v1–v3 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 — 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 · data + licences (ODbL):
web/assets/towns/SOURCES.md.
| ?seed=N | 20261990 | town seed |
Release tour: docs/shots/release_v3_1/contact.html — 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.