14 KiB
PROCITY — Round 6 lane instructions (from Fable, integrator)
⚠ SUPERSEDED by ROUND7_INSTRUCTIONS.md (2026-07-14). Round 6 is complete — ALL six lanes delivered; v2.0-alpha tagged (
6948372). Historical reference only.
Date: 2026-07-14 · Written after the Fable round-5 review (d71420f). You are an Opus 4.8
agent assigned ONE lane (read your session prompt). Read this file, your lane spec, your
<X>-progress.md, and NOTES addressed to you before touching code.
Repo state: everything committed on main at d71420f. v1.1 tagged (3a831fe).
tools/qa.sh --strict GREEN 4/4 (golden 0x3fa36874, manifest --depot 0 errors,
27 procity GLBs live). Three v2 flags live and default-off: ?winmap=1 (B), ?dig=1
(C+F), ?roster=stream (D). fal balance ~$21.36 after E's $0.64 round-5 run.
Where we are / what round 6 is
Round 5 delivered v1.1 + three of four v2 foundations. Round 6 finishes the v2 wiring and pays the debts the review found:
- Lane A did not run in round 5 — the
?plansrc=osmplan source carries over whole. It is the round's biggest single item. - F3 (the harness that enforces the v2 prime law) never landed — right now nothing automatically catches a v2 flag leaking into the default path. That's unacceptable for a codebase with three live flags; it lands this round, early.
- Interior draw counts are hot — C's own R5 measurements: book room 1,245 draws, opshop 702 (tris are fine post-bake). Almost certainly unbatched per-item stock. Fix before any more interior content lands.
- Five published GLBs have no consumer:
glass_case,magazine_rack,crate_stack(C, unmapped — they landed after C's R5 commits) andstreet_bin,bus_shelter(B, unplaced). - publish.py is destructive — it glob-rewrites
pipeline/_published.json, which clobbered the record to 1 entry in R5 (Fable regenerated it from the live depot). E fixes the script this round.
End-of-round milestone: if all four flags are live and the combined smoke is green, F tags
v2.0-alpha (local — John pushes).
The v2 prime law (still in force)
Default boot stays v1-identical. Golden hash 0x3fa36874 frozen. Flags default-off.
qa.sh --strict green at every commit. New this round (decision #4): flags must compose.
Integrator decisions (so nobody blocks)
- A's round-5 brief carries verbatim (ROUND5_INSTRUCTIONS §Lane A): plan-source
contract + OSM fixture import behind
?plansrc=osm, fixture checked in, zero network. Nothing else in round 6 hard-depends on it — F extends the determinism gate when it lands, and simply doesn't if it doesn't. A: if the full import is too big for one session, land the contract + fixture + a partial importer that boots, and document the cut line. - Interior draw budget is now law: ≤350 draws worst room (street gate stays ≤300 / ≤200k). C enforces via its sweep; F adds it to the harness once C's fix lands. Rationale: interiors render alone (no street behind), so 350 is generous — 1,245 is a bug, not a budget problem.
- publish.py becomes non-destructive: merge into
_published.json(union of existing + newly pushed), never replace._published.jsonis the provenance record of everything ever published — treat it like AUDIT.md. - Flag-composition law: any subset of v2 flags on together must work. F smokes the
all-on combo (
?winmap=1&dig=1&roster=stream+ plansrc if landed). If two flags fight, the later-landing lane owns the fix; F arbitrates. - fal spend: no new gens authorized this round. The $21.36 balance sits. (Re-gen of a failed existing prop at ≤2 attempts is fine if something turns out broken in use — log it in AUDIT.md.)
- Tag
v2.0-alpha(F, end of round): all four flags landed + flags-off regression green + all-on combo smoke green + qa.sh strict green. If A doesn't land, tag anyway with three flags and note it — don't hold the milestone hostage.
Process rules (unchanged — agents run concurrently)
- ONE shared working tree.
git statusfirst; stage only your files by exact path; options before--;addnew files first; commit immediately; pin a durable per-lane ref. - Leave
qa.sh --strictGREEN. Update<X>-progress.md; cross-lane asks in your NOTES. - F owns
web/index.html+tools/; document call sites for F instead of editing. procityprogress.txt/settings.local.jsonstay untracked.- Repo:
/Users/m3ultra/Documents/procity. Ports:~/Documents/thriftgod(A's import),~/Documents/90sDJsim(reference). Dev server:8130.
Sequencing
F1 (flags-off regression + v2 smoke) ── land FIRST, everything else runs under it
E1 (publish.py fix) ── early, protects the record while others publish
C1 (interior draw batching) ──→ C2 (map 3 new fittings) ──→ F re-smoke interiors
A1 (plansrc, carried) ─────────→ F2 (determinism gate + plansrc smoke, if landed)
B1 (street_bin + bus_shelter) · D1 (roster hardening) ── parallel
all green ──→ F3 (tag v2.0-alpha)
Lane A (Citygen) — the second plan source (CARRIED FROM ROUND 5, unchanged)
Your round-5 brief carries verbatim — execute ROUND5_INSTRUCTIONS.md §Lane A. Summary:
- Extract the contract: document the exact CityPlan shape
generatePlanreturns in CITY_SPEC (nodes/edges/blocks/lots/shops/registry/hours/openLate/seeds); add aplanSourceselector — default'synthetic'byte-identical (golden hash law),'osm'behind?plansrc=osm. - Build the OSM source: port the relevant parts of thriftgod
server.py:433 build_city()reading a checked-in fixture (one real AU town's shops, few hundred max, extracted from thriftgod'scity_source.jsoncache) → dedupe → suburb inference → parody names → seeded hours → valid CityPlan. Deterministic (fixture+seed → same plan, hash it). Zero network. - Prove it downstream:
?plansrc=osmboots the shell end-to-end — streets, doors, interiors; invariants green (parameterize selfcheck by source; synthetic golden hash untouched). Screenshot the imported town. - Document the seam in LANE_A_NOTES for F's determinism-gate extension.
Integrator note (decision #1): if one session can't finish, land contract + fixture + a booting partial importer and document the cut — a half-landed seam beats another no-show.
Acceptance: default boot byte-identical; ?plansrc=osm boots a real-data town with
invariants green; fixture committed; zero network; qa green.
Lane B (Streetscape) — place the street props
- Place
street_bin_01andbus_shelter_01(published R5, no consumer yet). Bins: seeded placement along footpaths (corner/mid-block, sparse — this is a country town, not a mall). Bus shelter: main-street edges, oriented to the kerb, sensible spacing (1–3 per town) — note it's also the future stop for V2's tram/bus loop, so place it ON the street graph (an edge attribute or deterministic edge pick), not free-floating, and document the placement rule in LANE_B_NOTES for whoever builds the vehicle loop. - Footprint/collision verify both (same bar as your R4 furniture verify): planted, no sidewalk clipping, player collision sane, correct facing, deterministic per seed.
- Re-measure worst continuous-walk view with citizens: still ≤300 draws (the props are instanced/batched like the rest of your furniture — don't add per-instance materials).
- Optional polish: a
winmapbookmark shot for the gallery (day + night through glass).
Acceptance: both props placed deterministically + collision-verified; worst view ≤300 draws; placement rule documented; qa green.
Lane C (Interiors) — kill the draw count, then map the three orphans
- Interior draw batching (do FIRST — it's now law, decision #2: ≤350 draws worst room).
Your R5 numbers: book 1,245 / opshop 702 / milkbar 573 draws. Diagnose first (one
renderer.infobreakdown per room type — is it per-item stock meshes? per-sleeve canvases? fitting clones?), then batch: merge static stock into per-fitting or per-room geometry (BufferGeometryUtils.mergeGeometrieson shared-material groups), or instance repeated items. Constraints: determinism (same seed → same room), dig compatibility (?dig=1riffle needs its bin's sleeves — keep dug bins' items addressable or rebuild the bin's mesh on dig-open), leak-free dispose, keeper/stock contracts unchanged. Re-measure ALL room types after; extend your sweep to assert ≤350 so it can't regress. - Map the three orphaned fittings in
glb.js(published + in manifest since E3/E2, after your R5 commits):glass_case→ toy/pawn/milkbar (display case — front-of-room or counter-adjacent),magazine_rack→ milkbar/pawn (it's the R3 thin-wire reject reborn via fal — eyeball it in-room),crate_stack→ record/opshop back-room dressing. Real- metre scale + per-kind yaw as ever; sweep + soak with them on; extend the contact sheet. - Re-run the dig soak (
?dig=1) after the batching change — the riffle must still work on the batched bins.
Acceptance: worst room ≤350 draws with the sweep asserting it; all three fittings live in rooms, validated, screenshotted; dig still works + leak-free; qa green.
Lane D (Citizens) — harden the streamed roster toward default-on
The roster flips to default in v2 proper; this round produces the evidence for that call.
- Full soak with
?roster=streamon: the standard gate-2 walk (≥30 chunks, ≥15 shops) plus your far-field walk — leak-free (renderer.info at baseline after chunk disposes), 0 console errors, heap stable, determinism (same seed + walk → same identities). - Hours-aware density (if not already in): streamed spawns respect the day fraction like v1; at night, density drops and the openLate video shop's block stays lively.
- Flag-composition check with C:
?roster=stream&dig=1— entering a dig from a street with a streamed roster, exiting, walking on. No interaction with interior mode (roster is street-tier), but prove it. - The numbers memo: write LANE_D_NOTES "roster default-on readiness" — perf deltas (draws/tris/frame-time worst view vs v1 roster), memory over a 30-chunk walk, density uniformity evidence, known gaps. F + Fable use it to decide the flip in round 7.
Acceptance: streamed-roster soak green; composition with dig proven; readiness memo written with real numbers; flag still default-off; qa green.
Lane E (Assets) — make the pipeline non-destructive, then on-call
- Fix publish.py (decision #3): merge newly-published files into
_published.json(read existing → union → sorted write), never replace. While you're in there: after any publish, verify the file against the live depot list (/api/listfiltered toprocity_*) and warn on drift. Add a small check tovalidate_manifest.py(or a new gate): every manifestdepot:file must appear in_published.json— so a future clobber fails QA instead of hiding. - Re-verify the record: run your fixed publish path's verify against the depot and
confirm the 27-entry
_published.jsonFable regenerated is exactly right (fix if not). - On-call: B may need bus-shelter/bin scale or origin tweaks once placed; C may find
magazine_rack/glass_caseissues in-room (fal re-gen of a broken prop is authorized at ≤2 attempts per decision #5, logged). Answer within your NOTES. - No speculative generation — the depot grows consumer-driven only. No fal spends beyond decision #5's broken-prop clause.
Acceptance: publish.py merges (proven by a dry-run + a real no-op run), QA catches
manifest-vs-published drift; _published.json verified against depot; asset asks answered;
qa green.
Lane F (Integration) — the enforcement harness, then the alpha tag
- F1 — flags-off regression + v2 smoke (LAND FIRST; carried from R5 where it slipped).
Per your own LANE_F_NOTES §"v2 QA harness extensions":
- Flags-off regression: boot with no flags → assert plan hash == golden + a settled draws/tris snapshot within tolerance of the v1.1 baseline. This is the enforcement arm of the prime law — with three live flags it is overdue.
- v2 smoke: for each flag (
winmap,dig,roster, +plansrcwhen A lands): boot flag-on, walk 2 chunks, enter 1 interior (dig: open one riffle), 0 console errors. - All-on combo (decision #4): one smoke with every landed flag on simultaneously.
- Wire into qa.sh as a warn-level gate this round (strict in round 7). Keep it headless-runnable and fast (<2 min) or it won't get run.
- F2 — plansrc integration (only if A lands): wire the shell plan bootstrap selector, extend the determinism gate to pin (seed, plansrc) hashes, add plansrc to the smoke.
- F3 — tag
v2.0-alpha(decision #6): all landed flags smoke-green + flags-off regression green + qa strict green → tag locally with a summary (three flags is acceptable if A no-shows again — note it in the tag message). Update the flags table + runbook + F-progress. John pushes. - Add C's interior ≤350-draw assertion to the harness once C1 lands (a per-room-type check in the smoke or soak — coordinate via NOTES).
- Marshal duty continues.
Acceptance: flags-off regression + per-flag + all-on smokes running in qa.sh (warn);
determinism gate extended if plansrc lands; v2.0-alpha tagged on a green tree; runbook +
flags table current.
— Fable (integrator). Round 5's lesson: the review caught a clobbered provenance file, a skipped harness, and a silent no-show — all three were invisible to lanes' own green self-reports. F1 exists so the machine catches the next one. Commit atomically, pin your refs, leave it green.