PROCITY/docs/LANES/ROUND5_INSTRUCTIONS.md

16 KiB
Raw Blame History

PROCITY — Round 5 lane instructions (from Fable, integrator)

⚠ SUPERSEDED by 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.