PROCITY/docs/LANES/ROUND5_INSTRUCTIONS.md

251 lines
16 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 — Round 5 lane instructions (from Fable, integrator)
> **⚠ SUPERSEDED by [ROUND6_INSTRUCTIONS.md](ROUND6_INSTRUCTIONS.md) (2026-07-14).** Round 5 is
> complete — v1.1 tagged (`3a831fe`); B/C/D/E/F delivered, Lane A's task carried to round 6.
> Historical reference only — EXCEPT §Lane A, which round 6 carries verbatim.
Date: 2026-07-14 · Written after **v1.0 tagged** (`2698b40`, all 6 QA gates green, 6-auditor
GO). You are an Opus 4.8 agent assigned ONE lane (read your session prompt). Read this file,
your `docs/LANES/LANE_<X>_*.md` spec, your `<X>-progress.md`, `docs/V2_IDEAS.md`, and any
NOTES addressed to you before touching code.
Repo state: everything committed on `main` at `2698b40` = `v1.0`. `tools/qa.sh --strict`
GREEN 4/4 (selfcheck 1301/1301, fingerprint `0x3fa36874`, consistency 0 warnings, manifest
`--depot` 0 errors, 23 GLBs live). Tag is local; pushing is John's.
## Theme: v1.1 close-out + v2 foundations
v1 shipped: seed → walkable 90s-AU town, every door opens, keepers, crowds, hours, day/night.
Round 5 does two things at once:
1. **v1.1 (small, this week's polish):** land the ONE v1 loose end — Lane C re-maps the two
assets Lane E shipped late in R4 (`record_crate` fixed, `counter_till` new) — plus the
hero-prop lowpoly bake. F tags `v1.1`.
2. **v2 foundations (the real round):** each lane starts its highest-leverage V2_IDEAS item,
**behind a flag, default-off**. The parking-lot doc was written so v2 is wiring, not
rework — this round proves that.
## The v2 prime law (read twice)
**Default boot must stay v1-identical.** Every v2 feature lands behind a query flag or config
default-off. `GOLDEN.hash` (`0x3fa36874`) must not move unless Lane A intentionally versions
it (it should not this round). `qa.sh --strict` green at every commit. If your feature is on
and something breaks, that's your bug; if your feature is OFF and anything changes, that's a
land-blocker.
## Integrator decisions (so nobody blocks)
1. **v2 starts with the content firehose** (V2_IDEAS #1): Lane C ports `dig.js` (the
crate-riffle interaction) running on the EXISTING procedural stock via the `stockAdapter`
seam. Real GODVERSE/BaseGod stock + economy is round 6+ — do NOT wire external databases
or thriftgod's `mint()` this round.
2. **Hero-prop bake-to-lowpoly is UN-PARKED** (was decision R4#2). Lane C's measurements
(record interior ~273k tris; `drinks_fridge` 77k, `counter_till` 32k) justify it. Lane E
bakes: target **≤8k tris per hero prop** with baked base-colour (bake THEN simplify —
R3 taught us raw TRELLIS shell-soup won't simplify; a voxel-remesh + texture re-bake will).
3. **OSM plan import uses cached/fixture data ONLY.** Lane A builds the second plan source
against thriftgod's `city_source.json` cache (or a checked-in fixture extract). NO live
Overpass/network calls from the repo — the asset-free/zero-network law extends to citygen.
4. **fal.ai spend is AUTHORIZED (John, 2026-07-14: account topped up to $22).** Lane E runs
the fallback gens — glass-case + bus-shelter, plus one optional magazine-rack attempt —
at Hunyuan v2 ~1417¢/gen, ≤2 attempts each (expected total well under $1). Nothing else
on fal without a per-item reason logged in AUDIT.md. Pushing stays John's. Audio stays
greenfield-parked (needs an asset-sourcing decision from John).
5. **v1.1 tag**: Lane F tags after C1 (re-map) and E1 (bake) land and the tour still looks
right. v2 flags do NOT need to be done for v1.1.
## Process rules (unchanged — agents run concurrently)
- ONE shared working tree. `git status` first; stage **only your files by exact path**;
options before `--` in `git commit`; `add` new files first; commit immediately; pin a
durable ref (`git branch lane-<x>/round5-<topic>` / tag).
- Leave `qa.sh --strict` GREEN. Update `<X>-progress.md`; cross-lane asks go in your
NOTES file, not in others' code. F owns `web/index.html` + `tools/` seams — document your
call sites for F instead of editing (the D→F setExposure pattern from R3/R4).
- `procityprogress.txt` / `settings.local.json` stay untracked; don't commit them.
- Real repo path `/Users/m3ultra/Documents/procity`. Source repos for ports:
`~/Documents/90sDJsim` (dig.js), `~/Documents/thriftgod` (city_source/plan import),
`~/Documents/dealgod/GODVERSE.md` (read-only reference this round).
## Sequencing
```
C1 (re-map counter_till + record_crate) ─┐
E1 (hero-prop bake ≤8k) ──→ C verifies ─┼─→ F1 (tag v1.1)
A2 (plan-source seam) · B2 (window shader) · C2 (dig.js) · D2 (streamed roster) · E2 (depot grow)
└────────── all behind flags ─────────→ F2 (v2 smoke + runbook)
```
C1 is quick — do it first so F can tag v1.1 early. All v2 items are parallel and independent.
---
## Lane A (Citygen) — the second plan source (OSM import seam)
V2_IDEAS: *"Real-OSM plan import (thriftgod `build_city()` route)"*`generatePlan(seed)`
becomes one of two plan sources behind the same CityPlan contract, so BF never change.
1. **Extract the contract.** Define/verify a single `CityPlan` producer interface (whatever
`generatePlan` returns IS the contract — document its exact shape in CITY_SPEC if not
already precise: nodes/edges/blocks/lots/shops/registry fields, hours, openLate, seeds).
Add a `planSource` selector: default `'synthetic'` (today's path, byte-identical output —
golden hash law), new `'osm'` behind `?plansrc=osm`.
2. **Build the OSM source.** Port the relevant parts of thriftgod `server.py:433 build_city()`:
read a **checked-in fixture** extracted from thriftgod's `city_source.json` cache (pick one
real AU town's worth of shops, a few hundred max — commit the fixture under
`web/assets/fixtures/` or `tools/fixtures/`), then dedupe → suburb inference → parody
names → seeded hours → emit a valid CityPlan. Deterministic: same fixture + seed → same
plan (hash it).
3. **Prove it downstream.** Boot the shell with `?plansrc=osm`: streets stream, doors open,
interiors build, selfcheck-style invariants hold (write an osm-mode selfcheck variant or
parameterize `selfcheck.js` by source — do NOT touch the synthetic golden hash). Screenshot
the imported town for docs.
4. Document the seam in LANE_A_NOTES for F's determinism-gate extension (F2).
**Acceptance**: default boot byte-identical (golden `0x3fa36874` untouched); `?plansrc=osm`
boots a real-data town end-to-end with invariants green; fixture committed, zero network;
qa green.
## Lane B (Streetscape) — the Vuntra window trick (interior-mapping shader)
V2_IDEAS: *"parallax interior-mapping shader so a passing pedestrian sees a plausible room
through the glass without building it."* You own the facade glass + atlas — this is yours.
1. **Interior-mapping shader** on shop window glass, behind `?winmap=1`: classic
single-cube parallax interior mapping (seeded per-shop room: wall/floor colours from the
shop's skin palette, a couple of shelf silhouettes). Must respect §3.5: **closed shops at
night show dark rooms** (reuse `window.PROCITY.isOpen`); open shops glow warm.
2. **Keep it batched.** One shader/material on the existing glass geometry (or a clone at the
same draw count) — per-instance variation via attributes/uniforms, NOT per-shop materials.
Re-measure worst continuous-walk view with citizens: **≤300 draws** flag-on.
3. **Determinism + fallback**: seeded from `shop.seed`; flag-off path byte-identical to v1
(same materials, same draws). `?noassets=1` + winmap must not fetch anything (procedural
only).
4. Screenshot day + night (open vs closed) → `docs/shots/laneB/` for the v2 gallery.
**Acceptance**: flag-off identical to v1; flag-on ≤300 draws worst view, rooms visible
through glass with parallax, closed shops dark at night; zero fetches; qa green.
## Lane C (Interiors) — v1.1 re-map, then the dig
1. **v1.1 re-map (DO FIRST — F tags v1.1 on it).** Lane E's R4 late shipment
(LANE_E_NOTES → Lane C):
- `record_crate` is FIXED (sourceless-texture slots stripped; verified loading in the
vendored GLTFLoader). Your `crate`/`recordBin` mapping already points at it — remove the
"fails to load" fail-soft comment, verify the bins actually upgrade now.
- `counter_till` is NEW (`procity_fit_counter_till_01.glb`, 1.61×0.64 m, 1.15 m tall,
base-origin): **re-add `counter` → `'counter_till'`** to `KIND_TO_GLB` (NOT the old 4 m
`counter`/balcao). Decide what happens to your separate `cash_register` counter-top
attach on counters that now have a modelled till — avoid double tills (keep
cash_register for shop types where it reads better, or drop the attach when the GLB
counter is live; your call, document it).
- Keeper stand pose must still work on the GLB counter (the counter contract).
- Re-run sweep + soak (same bar: 0 throws/leaks, <50 ms), update the contact sheet,
commit tell F (v1.1).
2. **Port `dig.js` — the crate riffle (v2 headline, behind `?dig=1`).** Source:
`~/Documents/90sDJsim/web/world/dig.js` (self-contained, `onBuy/getCash/spawnClerk/
spawnFittings` callback API, ported across 3 games before). Scope for this round:
- Walk to a record bin in a record store riffle interaction (flip through sleeves) using
the EXISTING procedural stock via your `stockAdapter(shop, slotKind)` seam the sleeves
you already generate become the riffled items. Seeded order per bin.
- Buy/economy: STUB it (a toast + remove-from-bin is fine; `getCash` returns a constant).
Real stock/economy is round 6+ (integrator decision #1).
- Keep it interior-mode-only and leak-free: entering/leaving the dig disposes cleanly
(extend your soak to enter a dig per interior when the flag is on).
- F owns the input/mode seam in the shell document the exact hook you need
(e.g. `interiorMode.onInteract(fitting)`) in LANE_C notes; F wires it (F2). Build against
your test page in the meantime.
3. Re-measure the record interior after E1's baked props land (was ~273k tris) record the
new number in LANE_C_GLB_VALIDATION.md.
**Acceptance**: C1 committed early, bins + counter GLB-live, no double till, soak green
F tags v1.1; `?dig=1` riffles a real bin on procedural stock, leak-free, flag-off identical;
qa green.
## Lane D (Citizens) — implement the chunk-streamed roster
Your R3 design note (LANE_D_NOTES) was design-only by instruction. v2 un-parks it: implement,
behind `?roster=stream`.
1. **Implement per your own design**: chunk-local identity keying (same seed + chunk same
residents), windowed spawn/despawn driven by Lane B's `ctx.onChunkBuilt/onChunkDisposed`
hooks (they exist B shipped them in R2), global near-cap across live chunks, ownership
hand-off, and the v1 path untouched when the flag is off.
2. **Prove the wins**: (a) street density stays constant as you walk arbitrarily far from
spawn (v1's fixed roster thins out show a before/after count at distance);
(b) determinism same seed, same walk same identities encountered; (c) leak-free over
30 chunk builds/disposes (renderer.info back to baseline); (d) budget worst view with
the streamed roster v1's numbers (impostor/near-cap machinery unchanged).
3. **Hours-aware density (small, if time allows)**: streamed spawns read the day fraction the
way v1 does; optionally bias spawn weights toward open shops at night (the queue/patronage
sim stays v2-later don't build schedules this round).
4. Document the flag + any shell call-site change for F in LANE_D_NOTES (F wires index.html).
**Acceptance**: flag-off identical to v1 (determinism + golden identities); flag-on constant
far-field density, deterministic, leak-free, within budget; design note updated to as-built;
qa green.
## Lane E (Assets) — the bake pass + depot growth
1. **Hero-prop bake-to-lowpoly (un-parked, DO FIRST — C re-measures on it).** The 6 heavy
TRELLIS props (`drinks_fridge` 77k, `milkshake_mixer`, `arcade_cabinet` 25k,
`listening_booth`, `novelty_record`, `counter_till` 32k): voxel-remesh (or manifold
retopo) simplify to **8k tris** re-bake base-colour (+normal if cheap) from the
original onto the new UVs. R3's lesson stands: simplify-only won't work on shell-soup;
the re-bake is the point. Pipeline it (`pipeline/bake_lowpoly.py` or a Blender headless
script) so it's repeatable; keep originals in a gitignored dir; record before/after in
AUDIT.md. Eyeball every prop (no melted labels/faces) contact sheet. Re-publish to the
depot; `validate --depot` 0 errors. Tell C to re-measure.
2. **Depot growth for v2 (second priority).** From `~/Documents/3D=models/` pick a shortlist
(~812) that serves this round's v2 features dig-scene props (crate variants, price-gun,
listening-station headphones), street-life props for D's roster (shopping bags, prams),
B's window-room silhouettes if any read better as real minis. Normalize to GLB law
(metres, +Y up, base origin, 5k where they're not focal), thumbs, manifest, publish,
AUDIT provenance. Do NOT add manifest entries no lane consumes check the other lanes'
round-5 notes or ask in yours.
3. **fal.ai fallback run (NOW AUTHORIZED — decision #4).** Generate `glass-case` and
`bus-shelter` via fal Hunyuan v2 (the two TRELLIS glass rejects), plus ONE optional
magazine-rack attempt if the first two go well. 2 attempts per prop; expected total
<$1 of the $22 balance. Then the normal funnel: normalize (bake-to-lowpoly if heavy per
task 1) eyeball thumbnails (no melted glass/legible branding) publish manifest.
Log every paid gen + cost in AUDIT.md provenance. If a prop fails both attempts, it keeps
its primitive do not burn extra attempts.
4. **Not doing** (still parked): MESHGOD_BATCH re-runs beyond the above, audio assets.
**Acceptance**: 6 hero props 8k tris with baked textures live on the depot, visually clean,
C's interior re-measure confirms the drop; glass-case/bus-shelter resolved (shipped or
documented-failed at 2 attempts) with costs logged; new shortlist normalized + published
with consumers identified; validate --depot 0 errors; qa green.
## Lane F (Integration) — v1.1 tag, v2 seams, gates that grow
1. **Tag v1.1** once C1 + E1 land: verify the record/milkbar/opshop interiors live (GLB
counter + till story correct, bins upgraded, baked props look right), re-capture ONLY the
two interior tour shots if visibly improved, qa green `git tag v1.1` (local; John
pushes).
2. **Wire the v2 flag seams** as lanes document them (you own `web/index.html` + `tools/`):
A's `?plansrc=`, B's `?winmap=`, C's `?dig=1` interact hook (this one is real work the
interior-mode input seam; C specifies, you own the mode state machine), D's
`?roster=stream`. Keep every flag default-off and independent.
3. **Extend the QA harness for v2** (V2_IDEAS "determinism across engines"):
- determinism gate: pin plan-hash + first-interior-hash per (seed, plansrc) synthetic
stays `0x3fa36874`; add the osm fixture hash when A lands.
- a **flags-off regression check**: boot with no flags, assert plan hash + a settled
draws/tris snapshot within tolerance of v1 numbers (catches "my flag leaks when off").
- a v2 smoke: boot each flag on, walk 2 chunks, enter 1 interior (+1 dig if C landed),
0 console errors. Wire into qa.sh as a non-strict (warning) gate this round; strict
next round.
4. **Runbook**: add a §v2 flags table (flag owner state smoke result) to LANE_F_NOTES;
archive the R3/R4 instruction files' "current" claims (one line at top: superseded by R5).
5. Ongoing: shared-tree marshal if two lanes collide on a file, you arbitrate.
**Acceptance**: v1.1 tagged on a green tree; all landed v2 flags wired, independent,
default-off proven by the flags-off regression check; v2 smoke running in qa.sh (warn-level);
runbook current.
---
*— Fable (integrator). The four hinges (stockAdapter, plan-source contract, shop.seed law,
B's mode machine) are now load-bearing if your task fights one of them, stop and leave me a
note instead of bending it. Commit atomically, pin your refs, leave it green.*