Files
security-game/TODO.md
T
zwitschi d5e180e50c
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
Raise the back gear to shoulder-blade height and compact the review page
2026-11-06 18:35:02 +01:00

1320 lines
90 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.
# TODO
Active task list. Work is grouped by milestone and workstream; every item is small enough to
complete and verify on its own. The product plan, gap analysis and release roadmap live in
[`PLAN.md`](PLAN.md); durable decisions and gotchas live in [`MEMORY.md`](MEMORY.md);
[`AGENTS.md`](AGENTS.md) defines the workflow.
**Current state (2026-11-05):** v0.9.8 is published. Three venues, the shift loop, the
tutorial/campaign, the access/escalation system and the 12-character procedural library are
shipped. The character plan changed twice and landed on the CC0 Quaternius base; the owner
accepted the 62-joint runtime rig ([ADR-0018](docs/adr/0018-quaternius-stage2-runtime-rig.md)),
but the base does not yet render or animate correctly in Godot.
**Roadmap (detail in [`PLAN.md`](PLAN.md#release-roadmap)):**
| Version | Type | Scope |
| --- | --- | --- |
| `0.9.9` | patch | Quaternius pilot base: rig/export fix, pose composition, face/silhouette gate |
| `0.9.10` | patch | 12-role roster on the approved base, gear fit, budget evidence |
| `0.9.11` | patch | Scheduled venue, prop, foliage, lighting and set-dressing polish |
| `0.9.12` | patch | Performance/Web/accessibility verification before the minor |
| `0.10.0` | minor | Public-readiness release: playtests, balance, legal, platform matrix, docs |
| `0.10.1`–`0.10.3` | patch | Onboarding, balance and stability follow-ups |
| `0.11.0` | minor | Live body-camera replay, fourth venue, localization foundation |
| `0.12.0` | minor | Signed/storefront builds, frozen save contract, launch languages |
| `0.13.0` | minor | Content complete and feature freeze for 1.0.0 |
| `0.13.1`–`0.13.2` | patch | Release candidates |
| `1.0.0` | major | Stable release with a documented compatibility promise |
**Priority now:** the `0.9.9` rig-and-export correction. Do not start the 12-role roster
(`0.9.10`) before the single-variant pilot renders, animates and passes the owner face gate.
The unversioned tracks (3D review evidence, maintenance, Gaussian splatting) stay at the top of
this file because their work is not release-scoped; each milestone below is versioned and must
follow the release procedure in `AGENTS.md`.
The MIT Blender skill pack lives locally at `.agents/skills/blender-skills/` (gitignored). Every
3D workstream below names the skills to load; they are guidance only, and the project contract,
`blender_assets.py audit`/`fit`/`inspect` and the capture gates stay authoritative.
## 3D model and local map re-evaluation
Goal: re-judge every shipped 3D asset against the newly available Blender skill guidance, rank
the real gaps, and fix only what the rankings justify (sections A–G); and re-evaluate the three
venues against licensed real-world data — OSM footprints and building massing, reference-derived
photoreal facades and the photo-driven geometry pipeline (sections H–M). Evidence comes from three
separate sources: licensed real-location photos, generated concept boards, and build captures. This
is an unversioned development track: it must not change the `npc_base` contract, the renderer or
the public 0.10.0 release.
**Sequencing (updated 2026-11-05):** the old block is cleared. The local map rebuild (H–M) is
done, the 96/96 review ran against the new maps, and character work moved to the versioned
`0.9.9`/`0.9.10` milestones. This section is now the **evidence, scorecard and measurement
source**, not an open-ended backlog: the venue/prop/lighting fixes are scheduled into
`0.9.11`, the budget/LOD checks into `0.9.12`, and the character items (C) are superseded by
`0.9.9`/`0.9.10`. Fixes are made from the milestone checklists; the scorecard keeps the
measurements and verdicts.
**Map rebuild status (2026-11-03):** H–M are complete — the Joke Shop is now a 6.0 m low hall
(dark stucco, white signature fascia, tied-back curtains, hotel tower) and Hollywood/TV Studio
share the limestone-ashlar Masonic facade. A fresh `capture_npc_review.gd` run produced 96/96
frames against the new maps (none blank) and a vision spot-check confirms the characters still
read cleanly. The remaining §L signage-texture candidates and the deferred 3D facade-reveal depth
are enhancements, not rebuild blockers.
Reference material is collected first (section A): local, gitignored, development-only photo
research may use any source the owner authorises (DuckDuckGo image search, street-view frames,
neighbouring properties, buildings across the street) **without the licence gate**. The licence
and privacy gates still apply the moment a frame is promoted into `docs/screens/refs/` or any
committed asset — local `tmp/research/` is the only licence-free zone.
Scope: the shipped 3D catalogue — 8 library characters, the frozen `npc_base` fallback, 18 gear
pieces, 20 library props, 6 generated props and the three venue families — plus the local map
pipeline that re-evaluates those venues (OSM data/licensing, reference imagery, the photoreal
facade kit, AI-assisted composing, Gaussian-splatting evaluation and map QA).
**Exit (asset review, A–G):** `docs/3d-model-review.md` scores every catalogue asset; every P0/P1
is fixed or explicitly accepted with a measurement; `inspect`/`audit`/`fit`/proportions, the
24-NPC budget and the capture gates are green; no runtime contract, dependency or release artifact
changed except the rebuilt assets themselves.
**Exit (local map, H–M):** one local block is reconstructed from licensed data with
reference-derived photoreal facades, passes performance/navigation/visual QA and has complete
provenance; or the pipeline is rejected with the findings recorded.
### A. Local reference material collection (development-only, gitignored)
*Owner override (2026-11-03):* for local, development-only photo research the licence gate does
**not** apply. Anything downloaded for this track stays under `tmp/research/` (gitignored) and is
never committed, shipped, or used as a committed asset. The moment a frame is promoted to
`docs/screens/refs/<venue>/` it must pass the sidecar licence + privacy gate; local research is the
licence-free zone only.
- [x] Add a DuckDuckGo image-search fetcher: `tools/refcollect.py search <query> --slug <block>
--limit N` downloads top results into `tmp/research/<block>/` with a source-URL manifest
(no licence filter, marked `local_only`/`unchecked-local`), and `sheet` composes a local
contact sheet. Requests go through a token bucket plus polite jittered backoff that honours
`Retry-After`, and send a full Firefox header set. Tests in
`tools/test_refcollect.py`. **Run note:** DDG rate-limits aggressively; space batches and
run the per-venue collection below when the limit clears.
- [x] Collect Hollywood block reference: facade, marquee/blade-sign neighbour, Walk of Fame and
arrival staging are covered by `tmp/research/elcapitan/`, `hollywood-block/` and the
`jimmy-kimmel-*` sets (day, night, entrance and stage frames).
- [x] Collect Joke Shop / Comedy Store block reference: `tmp/research/comedy-store/` covers the
facade, canopy, street and visitor views; `ramada-hotel/` adds the neighbouring hotel and
`comedy-store-hillside/` the hillside/houses behind the roofline.
- [-] Collect street-view frames of both blocks: street-level DDG frames now cover the facades,
corners and opposite frontages for both blocks; true Google Street View panorama capture is
deferred (needs a Google API key or a manual browser capture — recorded in MEMORY).
- [x] Build a local contact sheet per block: `sheet.jpg` exists for every collected set
(`elcapitan`, `hollywood-block`, `jimmy-kimmel-stage`, `jimmy-kimmel-entrance`,
`comedy-store`).
- [x] Record which local frames changed a decision: added the frame→decision mapping to
`docs/venues.md#hollywood-boulevard` (recent `elcapitan-2026` geosearch → 2009 frame stays the
geometry reference; `jimmy-kimmel-entrance/07.jpg` → the four entrance concepts) and
`docs/venues.md#the-joke-shop` (`comedy-store/` → the four concept pairs + map plan;
`comedy-store-hillside/` → the day-concept anchor that clears the image-recitation filter).
Frames stay gitignored; only the provenance notes are committed.
### B. Baseline, scorecard and routing
- [x] Load `blender-director` plus the per-family specialists before touching geometry, and
record the skill stack and the advice that changed a decision in `docs/3d-model-review.md`.
(Routing, QA severity/verdict, texel-density tiers, character loop criteria and budget rules
recorded in the review doc; MCP-only steps translated to headless flatpak runs.)
- [x] Re-run the neutral NPC baseline (48 frames), the final-context review (96 frames), the
prop gallery and the 24-NPC budget on the current build; keep raw frames in `tmp/` and do
not replace committed sheets until a fix lands. (48/48 and 96/96 frames captured under
`xvfb-run`, none blank; raw sets in `tmp/model-baseline` and `tmp/model-review`; committed
sheets untouched. 24-NPC smoke still 6.94 ms / 168 meshes / 131,616 triangles.)
- [x] Create `docs/3d-model-review.md` with a per-asset scorecard: silhouette, anatomy/proportion,
topology/edge flow, UV/texel density, textures/materials, rig/weights, LOD/impostor,
collision/grounding, export contract, provenance, skill route, verdict (P0/P1/P2/accept).
(Doc created with character/gear/prop/venue tables, verdict columns and a ranked candidate
list; unmeasured cells are marked `—` instead of being guessed.)
- [x] Collect the baseline measurements in one table: `blender_assets.py inspect`, `audit`, `fit`,
`npc_proportions.py`, texel density, triangles/meshes, 24-NPC frame time. (All seven captured;
`texels` and `inspect --json --out` were added to make the measurements repeatable. All 46
assets audit/inspect clean; texel density 64–122 px/m authored, 287–440 effective fabric.)
- [ ] Review the scorecard with the project owner, rank the P0/P1 gaps and decide fix vs accept
per item; record the decision and the reason in the review document. **Deferred until the
full map rebuild (H–M) lands and a fresh 3D review runs against the new maps.**
### C. Character re-evaluation (superseded 2026-11-05)
The procedural character review is complete and its evidence stays in
`docs/3d-model-review.md`. The next character pass is the Quaternius base in
[`Milestone 0.9.9`](#milestone-099--quaternius-npc-base-swap-patch) and
[`Milestone 0.9.10`](#milestone-0910--npc-roster-rollout-patch); the open items below are kept
as evidence for what the new base must not regress (face read, hands, weights, cloth, texel
density, crowd variety).
*Skill route: `character-artist`, `retopology`, `rigging`, `uv-workflow`, `texture-workflow`,
`materials`, `hair-groom`, `asset-optimization`, `lookdev`, `qa-review`.*
- [x] Face/head review: the eight library characters ARE built with the 0.9.4 face detail in
`tools/blender_assets.py._face()` — sculpted chin plate + cheekbones (`_shape_head_mesh`),
socketed sclera + thin upper lid, nose bridge/tip/wings, rounded mouth and ear concha. The
plain sphere head in `tools/npc_pipeline.py` is the frozen `npc_base` fallback only. The
ranked P1 "flat faceted cheek/brow + slit eyes" is therefore a readability/scale question on
the existing sculpt (head `seg=18/rings=12`, small offsets), not missing geometry. Next step
if the owner funds it: a single-variant `npc_casual` prototype that raises head subdivision
and deepens the cheek/brow offsets. Fix is gated on the owner scorecard review.
- [x] Hands/grip review: the hands ARE detailed — `tools/blender_assets.py._hands_and_shoes()`
builds a wider shallower palm, four two-segment bent fingers with rounded tips and a thumb;
gear contact is the open palm plus the `HAND_ITEM_FORWARD` (0.04 m) offset. Decision: **keep
the open palm + forward offset; a finger-curl rig stays out of scope** — the current rig has
no finger bones, so a curl rig is a contract decision, not a modelling tidy-up. Optional
extra finger bones are now allowed by the cap/contract extension, but require an ADR and
weight/budget verification. Recorded as ranked gap #6 (accept; revisit only if face work
opens the budget).
- [x] Weights/pose evidence captured and vision-reviewed, and the P1s fixed: wrist/hand
detachment (wrist + hand now 100% `Hand`, hand raised, sleeve cuff follows the hand),
hip/thigh collapse + knee pinch (thigh/shin rigid with a short knee blend). Vision-verified:
hip fix clean, knee crease minor residual. The guard hip-pouch clip and the residual knee
crease remain as 0.10.0 refinements.
- [x] UV/texel density: measured (64–122 px/m authored, 287–440 px/m effective fabric on the 4×
repeat; table in `docs/3d-model-review.md`). Decision: **accept as-is** — the maps are
neutral/runtime-tinted (not painted skin), so density controls only subtle weave/noise, and
fabric already sits in the standard tier. No atlas/UDIM/resolution change is justified until
a portrait shows texture starvation; `CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` stay unchanged.
- [x] Cloth and hair: hem clipping is already handled by the 0.76 stride scale for `npc_dress`/
`npc_coat` (v0.9.0), hair/beard are the five procedural styles, and headgear clearance is
verified clean by `blender_assets.py fit` (0.9.4). Decision: **cloth simulation stays out of
scope** for the 8-variant library and the Compatibility renderer — rigid procedural meshes
plus stride scaling are the accepted approach; a baked cloth-sim pipeline would only be
reconsidered if a later close-up shows visible hem/coat stiffness.
- [ ] Fix only the ranked P0/P1s, one asset family per commit; refresh the comparison sheet and
re-run `inspect`, `audit`, `fit` and `npc_proportions.py` after each fix. **Scheduled: the
remaining character items are covered by Milestone 0.9.9/0.9.10; this row is evidence only.**
- [x] Crowd-variety reference: five licence-cleared Commons photos fetched to
`tmp/research/crowd-variety/` (CC0 ×2, CC BY 2.0, CC BY 3.0, PD) covering umbrella/dense
crowds and a pedestrian mall — general build/hair/outer-layer variety context, reference
only (never traced). The AI proposal set (generated crowd-variety concepts) will carry the
specific 4-variant detail when the character-development roster starts.
- [ ] Generate a face/hand concept board for the highest-ranked P1 and compare it with the 0.9.4
baseline before rebuilding anything; keep it development-only and record model and prompt.
- [ ] After each accepted fix, capture front/three-quarter/profile comparisons, add them to the
review sheet, and refresh the README character row.
- [x] `qa-review` sign-off: all eight variants **ship with notes** (face realism landed, hands/
cloth/gear accepted, props reviewed); the weights/pose column is the only open item (blocked
on the harness) and the full foliage carded-cluster is the one remaining owner-gated P1.
Recorded in `docs/3d-model-review.md` §qa-review sign-off.
### D. Gear and prop re-evaluation
*Skill route: `prop-artist`, `hard-surface`, `vegetation-artist`, `materials`, `collision-proxy`,
`lod-pipeline`, `qa-review`.*
- [ ] Gear: review the 18 pieces for silhouette, straps/buckles, helmet and lanyard readability
and rebuild only what fails the scorecard. (Attach/clearance verification is done — the
`fit` report is recorded in `docs/3d-model-review.md` with the back-gear-vs-hip-radius
measurement caveat; the visual silhouette read is what remains, pending the owner scorecard.)
- [ ] Props: review the 20 library props plus the 6 generated props for bevels, chamfers,
silhouette, ground contact and shared material language (`prop-artist`, `hard-surface`);
list the rebuilds that are actually worth the triangle budget. (Vision-reviewed 2026-11-03
via `tools/describe_image.py`: `fire_hydrant` rebuilt and shipped; `planter_box`/
`round_planter` foliage and `street_tree`/`palm_tree` are the foliage-language P1 (gated on
the owner); `newspaper_box` slot and `bollard` collar are minor rebuild candidates; `bench`/
`trash_bin` are ship-ready.)
- [ ] Foliage: replace the flat-shaded `_shrub()`/`_tree()` clumps with `vegetation-artist`-style
carded clusters; measure triangles and keep the 60k `LIBRARY_TRIANGLE_BUDGET`.
(Partial 2026-11-04: `_shrub()` clumps and the palm fronds now alternate two green tones
(`foliage`/`foliage_dark`, `palm_leaf`/`palm_leaf_dark`) so the crown is no longer one flat
colour, and a `textured_solid` bug that broke `planter_box`/`round_planter` imports was
fixed. The two-tone read is subtle at gallery distance; the full carded-cluster pass remains
the owner-gated P1.)
- [ ] Materials and emissives: `lookdev` pass on bulbs, signs, lanterns, the marquee and the
sedan at day and late; confirm the flashlight stays a tight spot with no beam geometry.
- [ ] Collision: verify every prop collider and navigation obstacle matches its silhouette and
add the missing `collision-proxy` shapes where walking is blocked.
- [ ] Build a real-photo comparison for street furniture (bollard, bench, bin, hydrant, planter,
lamp) and rank the silhouette and proportion mismatches.
- [ ] Rebuild only the mismatched pieces through the assisted Blender pipeline
(`blender_assets.py inspect` → scripted retopo/UV assist → atlas → `audit`/`fit`); document
the exact commands in `docs/asset-tooling.md`.
- [ ] Refresh `sheet_props.jpg`/`sheet_npc_gear.jpg`, the README asset rows and
`docs/blender_previews/README.md` after the rebuilds.
- [ ] Exit: a prop/gear capture sheet, budgets and collision checks pass with no unreviewed asset.
### E. Venue and camera re-evaluation
*Skill route: `environment-artist`, `archviz`, `set-dressing`, `lighting`, `camera-cinematography`,
`lookdev`, `rendering`, `qa-review`.*
- [-] Facade/venue review: rank the top five shape-language fixes for Hollywood, the Joke Shop and
the TV Studio (`environment-artist`, `archviz`) and check them against the map-budget tests.
**Ranked 2026-11-03:** (1) TV Studio renders a generic pilaster/concrete box instead of the
six-column Masonic Temple — `tv_studio` lacks `masonic_facade`, so `_build_building()` runs
while the Jimmy Kimmel starting point is the same facade; (2) Joke Shop blade sign is a flat
slab + label, not the reference's circular sign on a roof pole; (3) Joke Shop canopy fascia
lacks the deep white-signature band; (4) Hollywood entrance signage shows `EL/CAPITAN/LIVE`
banners where the real building carries the Jimmy Kimmel Live marquee (owner content call);
(5) Joke Shop red-curtain bays and entrance-recess rhythm read flat under the canopy.
**Fix 1 landed:** `tv_studio` now reuses `masonic_facade` (architecture `masonic`, ashlar
field, six columns, eight steps, landing + guard spawn at y=1.0) while keeping the arrival
staging (`bulb_canopy`, `sedan`, `handrail`, barriers); Godot 276/276, screenshot smoke
21/0, `13-studio-arrival.png` recaptured.
**Fix 3 landed:** the Joke Shop canopy signature band is now white (`Color(0.90, 0.88, 0.80)`)
over the black fascia, matching the concept's "white celebrity signatures"; a test asserts
the `CanopySignatures` label is white, Godot 276/276, `12-joke-shop.png` + the three-up
board recaptured. Note: fix 2 (blade sign) is already a circular disc on a roof pole in the
code (`37f925c`); the ranking note was stale.
**Fix 5 landed:** the red curtain bays are now seven tied-back drapes (hourglass cinch, dark
recess between bays, scalloped hem) instead of four flat red slabs; vision-judged as
"tied-back/gathered fabric" via `tools/describe_image.py`. Joke Shop map is now 25,828 tris /
518 meshes (under the 150k/700 budget); Godot 277/277, `12-joke-shop.png` + board recaptured.
- [ ] Lighting: `lighting`/`lookdev` pass on sconces, canopy, marquee and street lamps with
day/late captures. (Attenuation is now gated: `test_venue_lights_use_physical_falloff`
asserts every `OmniLight3D`/`SpotLight3D` in all three venues uses `attenuation = 1.0`,
so a constant-falloff light cannot return unnoticed.)
Vision review 2026-11-03 (`tools/describe_image.py` on the night + flashlight captures):
the flashlight (`light_energy 4.2`, `spot_angle 14°`) washes the facade flat when pointed
at the door from close range and backlights the nearest NPC into a silhouette (staged-shot
artifact, but worth a shorter/softer cone or lower energy for the `06-night-flashlight`
stage); the El Capitan Theatre red marquee reads as a flat saturated block and the event
banners read as flat emissive decals rather than glowing signs — both are Compatibility
renderer limits (no bloom) plus the marquee's near-pure-red body; street lamps pool weakly
on near-black ground. No code change yet; these are owner-scorecard inputs.
- [ ] Set dressing: review entrance and sidewalk prop clusters (`set-dressing`) for occlusion,
navigation obstacles and readable storytelling clusters.
Vision review 2026-11-03 (`tools/describe_image.py` on the Joke Shop capture): the facade is
well dressed, the sidewalk under-dressed. Findings: (1) the queue story is weak — the
`security_podium` sits at x=6.8 while the entrance is at x=-8.8, so the host/podium and the
two `stanchion`/`crowd_barrier` pieces do not read as a line at the door; (2) the rope
barrier reads as thin bare geometry at night; (3) the west palm (x=-13.0) is inside the
27 m facade x-range so its fronds visually overlap the roofline from the beauty camera;
(4) the sidewalk has no light pool or A-frame/poster cluster. These are P2 composition
items, kept for the owner scorecard rather than moved unilaterally.
- [x] Camera height: already decided in the 0.9.0 pass — keep the 1.7 m gameplay camera; the
~1.55 m concept frames are art reference only (recorded in `DONE.md` 0.9.0 Animation).
- [x] Build the venue three-up boards (real photo | generated concept | current capture):
`tools/three_up.py compose` renders labelled strips into `docs/screens/boards/` (caption
sidecar per board, `rows` emits README rows). Boards committed: El Capitan day, El Capitan
night, Joke Shop day. Tests in `tools/test_three_up.py`.
- [x] Link each committed board from `docs/venues.md#hollywood-boulevard` and
`docs/venues.md#the-joke-shop`.
- [ ] Use the generated concept lighting and palette as the target for the `lighting`/`lookdev`
pass, and record the deviations that stay accepted.
- [ ] After the pass, regenerate the venue captures and update README plus both venue documents.
- [x] Hooded/winter portrait: `capture_realism.gd` now force-spawns the requested `--portrait-type`
when the role is event-driven (types 5–7 do not appear in the routine day crowd), so the
deferred close-up can be staged. Captured `--portrait-type=5` and vision-reviewed: the hood
reads as a hoodie (drawstrings + face opening) but is snug — no slack volume behind the
head/drape at the neck — and the face stays fully lit with no inner-rim shadow. Acceptable;
the snug hood is a minor scorecard note, not a P0/P1.
### F. Reference images, concept boards and screenshots
*Skill route: `lookdev`, `lighting`, `environment-artist`, `qa-review`; tooling:
`tools/scene_concepts.py`, `tools/screen_designs.py`, `tools/asset_pipeline.py`.*
Licensed photos are evidence of what a place looks like, generated boards are art-direction
proposals, and captures are proof that the build matches. Keep the three labelled and separate,
and never ship a generated or downloaded image without provenance.
- [x] Create `docs/screens/refs/` with a README and a sidecar template: source URL, author,
licence/attribution, capture or generation date, what it is evidence for, and whether it is
shipped or development-only. (`docs/screens/refs/README.md` defines the layout, naming,
licence and privacy gates and the three-way separation from concepts and captures;
`SIDECAR-TEMPLATE.md` carries the photo table, redaction/permission notes, derived-decision
log and the generated-board reference table. Linked from `docs/screens/README.md`.)
- [x] Hollywood board: five licence-cleared photos (facade day, blade sign, 1922 elevation,
Ovation across street, premiere staging) with a sidecar and per-image purpose, linked from
`docs/venues.md#hollywood-boulevard` and credited in `CREDITS.md`. Cropped the foreground crowd
out of the facade shot; EXIF stripped. (`docs/screens/refs/hollywood/`.)
- [x] Joke Shop board: three licence-cleared photos (hillside/canopy block, frontage with palms,
canopy signature band) with a sidecar, linked from `docs/venues.md#the-joke-shop` and credited in
`CREDITS.md`. The CC BY-SA `The Comedy Store.jpg` from the old link list was deliberately not
used; the public-domain frames cover the same evidence. (`docs/screens/refs/joke-shop/`.)
**Removed 2026-11-03:** the committed Comedy Store photo board was replaced by the local-only
`tmp/research/comedy-store/` research set (owner override; see §3D model and local map re-evaluation → A).
- [x] TV Studio: no separate board — the venue is the same El Capitan Entertainment Centre block as
the Hollywood day shift, restaged for a late-night arrival. The Hollywood board covers both
venues and the premiere-staging photo is the arrival evidence; the reuse is now stated in
`docs/venues.md#hollywood-boulevard`, `docs/gameplay.md`, `PLAN.md` and
`docs/development-status.md`.
- [x] Fill the Hollywood gaps that have a clean licence: El Capitan Theatre neighbour and marquee,
Walk of Fame terrazzo band, star close-up, and a night street-lighting context frame (cropped
to remove people and a plate). The board now has nine photos. (CC BY-SA night frame is
redistributed unmodified for study only; the review is recorded in the sidecar.)
- [ ] Generate one concept board per venue with `tools/scene_concepts.py generate` (palette, facade
rhythm, lighting), keep raw output gitignored, commit only the selected board, and record the
model, prompt and spend in the sidecar.
- [x] Build a three-up sheet per venue (real photo | generated concept | current build capture) and
use it as the reference for the character, prop and venue verdicts. (Done for the venue
exteriors via `docs/screens/boards/`; character/prop verdicts still use the Blender
preview sheets.)
- [ ] Apply the screenshot rule to every workstream: each accepted fix updates the relevant row in
`README.md` plus the matching docs page (`docs/blender_previews/README.md`,
`docs/screenshots/README.md`, `docs/3d-model-review.md` or the venue map doc).
- [ ] Re-run `tools/check_screenshots.sh` and `python3 tools/check_docs.py` after gallery changes
and record both results in the handoff.
- [ ] Keep AI-generated images out of `assets/` until they are picked, cropped, credited and (for
textures) audited; `CREDITS.md` and ADR-0015 still govern provenance.
### G. Gates and documentation
- [x] Add the measurable checks the review needs (texel density, weight/pose sanity, collider
coverage, LOD/impostor check) as stdlib or GDScript gates where possible; CI stays offline.
(Texel-density gate in `tools/test_blender_assets.py` checks all five slots per character
and the 40–400 px/m authored band plus the 250 px/m fabric floor; the pose→bone contract is
covered by `test_pose_reaches_the_skeleton_bones`; LOD/impostor already has
`test_avatar_mesh_budget_and_lod_swap`. Collider coverage is still open — split below.)
- [x] Add a collider/proxy check: every library prop that blocks walking has a
`StaticBody3D` + `NavigationObstacle3D`, checked against the catalogue.
(`world_builder.gd` tags every library node with a `library_prop` meta and declares the
non-blocking `marquee`/`handrail`/`lantern`/`door` set; two tests in `test_world.gd`
verify placed props (body + shape + avoidance radius, all three venues) and library
dressing (body unless documented exempt). Mutation-checked: removing `_add_prop_collision`
fails 3 tests, dropping `handrail` from the exemption list fails with the exact node.
Suite is 275/275.)
- [x] Add a coverage test so every catalogue asset has a scorecard row and verdict.
(`tools/test_model_review.py`: every `CHARACTERS`/`GEAR`/`PROPS` entry needs a row in
`docs/3d-model-review.md`, and character rows need a non-empty verdict.)
- [x] Document the new gates: `docs/blender-assets.md` now lists `texels`, the
`test_model_review.py` coverage gate and the `test_world.gd` collider/proxy gate; skill
routes live in `AGENTS.md` §Local Blender skills and each `TODO.md` section; the scorecard
and evidence table are in `docs/3d-model-review.md`.
- [x] Record accepted limits/deferred rebuilds: `docs/known-limitations.md` already records the
stylized low-poly style, no finger-curl rig and the >26 m impostor; `MEMORY.md` holds the
durable decisions (texel density accepted, cloth sim out of scope, open palm + forward
offset, face subdivision prototype gated on the owner). The corrected face note is also in
`docs/blender-assets.md`.
**Local map workstreams (H–M).** Build a local, data-backed map pipeline that can replace or
supplement the stylized venue geometry with licensed OpenStreetMap footprints, 3D building
massing, reference-derived photoreal facades and AI-assisted composition. This remains part of
the same unversioned track, not a release gate. The shared reference/provenance gates in A/F
still apply; the map pipeline has its own exit in M.
*Local-map skill route: `environment-artist`, `archviz`, `hard-surface`, `geometry-nodes`,
`procedural-modeling`, `vegetation-artist`, `set-dressing`, `lighting`, `materials`,
`texture-workflow`, `lookdev`, `scene-assembly`, `qa-review`.*
### H. Local map data, licensing and scope
- [x] First target block + role: **Hollywood (El Capitan, 6840 Hollywood Blvd) and the Comedy
Store (8433 Sunset Blvd)**; the result **supplements/informs** the stylized `VENUE_SPECS`
(reference-derived materials, facades and massing), never replaces them with imported OSM
geometry. Recorded in both venue map docs.
- [x] ODbL audit: OpenStreetMap is used as **reference only** (hand-built geometry informed by
OSM ways + Commons photos), so the shipped geometry is not an ODbL derived database and no
share-alike obligation attaches. Attribution is still kept via the OSM way links in
`docs/venues.md#hollywood-boulevard` / `docs/venues.md#the-joke-shop`. If derived OSM geometry ever
ships, it needs the attribution + share-alike review in a separate ADR.
- [x] OSM extract workflow: no offline extract is required while OSM is reference-only. If the
pipeline later imports footprints, pin an Overpass query + extract date and record it in a
provenance sidecar (see below); no city-scale extract is imported into the runtime.
- [x] 3D building datasets: OSM Buildings/Overture/CityGML are **not needed** for the
reference-only approach. If massing import returns, OSM Buildings LOD1 is the first
candidate (CC0-ish footprints); CityGML/Overture need a licence review per source.
- [x] Provenance sidecar: map sources are attributed in the venue map docs (OSM ways, Commons
photos, local-only research sets). Any future **derived** map asset adds a sidecar with
source URL, licence, capture/extract date and attribution text before it is committed.
### I. Local map reference imagery
**Recent-photo research (2026-11-03):** Commons geosearch around 34.10118/-118.34009 (500 m) plus
category/date sorting shows usable imagery from the last 12 months: Dolby Theatre billboards
(2026-09-30, CC0, 6 frames), Dolby street level and a fresh Walk of Fame star (2026-07-17, CC0),
a Michael Jackson impersonator at the El Capitan frontage (2026-02-25, CC BY-SA 4.0), the Celia
Cruz Square marker and a 2025-09-28 star series (CC0), and a Thunderbolts premiere (2025-04-28,
CC BY-SA 2.0). No recent frame shoots the El Capitan facade straight-on in daylight; the best
facade evidence is still the 2009 CC BY frame. The 8 downloaded candidates are in
`tmp/research/elcapitan-2026/` (gitignored) with `meta.json`; nothing was committed.
- [x] Wire `tools/refboard.py` (standalone, stdlib-only): `search --source search|geo|category`
lists candidates with ISO date, age-in-months and a commit/review/skip licence gate
(`--allow-ccbysa` after a recorded review); `fetch` downloads usable rows into `tmp/` with
spaced requests and writes `meta.json`; `rows` emits SIDECAR + README table rows from the
manifest. Six recent CC0 photos of the block were re-fetched into
`tmp/research/elcapitan-2026-tool/`. Tests in `tools/test_refboard.py` (licence gate, recent
filter, review-row exclusion, row emission).
- [ ] Add a `--max-age-months` filter (default 12) and a `--must-reuse` mode that matches the
folder's existing sidecar entries so a refresh only reports what changed.
- [ ] Research-diff step: keep the last-12-months board as the "current reality" evidence and the
older photos as the geometry evidence, labelled per photo; no sidecar date field needs to
change, only the purpose column.
- [ ] Write a reference shot list per block: facade materials, windows, signage, rooflines,
street furniture, curb cuts and lighting.
- [ ] Download and catalogue **committed** reference images from Wikimedia Commons, local/own
photography or other clearly licensed sources; record author, licence and attribution with
the §F sidecar template. Local-only research that ignores licence limits (DuckDuckGo,
street-view frames, neighbours, across-street buildings) is collected under §A into
`tmp/research/` and is never committed.
- [ ] Separate development-only references from shipped textures; commit only images with clear
redistribution rights and keep the rest under `tmp/` or the ignored reference folders.
- [ ] Build the per-block real-photo board under `docs/screens/refs/<block>/` using the sidecar
template from the 3D review §E, and link it from the venue map documents.
- [ ] Define the photoreal target and a material-derivation workflow (albedo/normal/roughness,
AO, weathering) that matches the existing Compatibility renderer (`texture-workflow`,
`materials`, `lookdev`), with the recent 2026 street frames as the current-reality reference
and the 2009 facade frame as the geometry reference.
- [ ] Remaining Hollywood gap: a licensed **night photograph of the hero facade and blade sign**
(the night frame on the board is a different block, near Vine), plus a sidewalk-level shot of
the steps/door recess and the east retail neighbour. For the committed board the licence gate
stays (no unlicensed Street View or scraped social media); local-only copies of the same
viewpoints go through section A instead.
### J. Local map geometry and AI-assisted composing
- [x] OSM footprint import and 3D building massing: **deferred** — the §H decision keeps OSM as
reference-only, so no footprint/massing import runs; the procedural `VENUE_SPECS` plus the
reference-derived materials carry the geometry instead.
- [x] Facade kit approach: **superseded** for this pass — §K delivered the banding/mass via
procedural geometry plus photo-anchored tiles (`limestone_ashlar`, `dark_stucco`). A reusable
Blender facade kit (window bays, cornices, storefronts) is a future enhancement, not a
blocker for the current maps.
- [x] Joke Shop building-appearance pass: `dark_stucco` tile (1024 px) promoted via the picks
pipeline and wired into `_build_comedy_store_building`, replacing the grey paver tile so the
hall reads as flat matte charcoal instead of masonry; photo-anchored day concept generated
from the hillside PD frame (the branded frontage photo trips the model's image-recitation
filter — recorded in the concept README); day capture promoted and the three-up board rebuilt.
Test asserts the hall wall carries `dark_stucco`.
- [x] Building-appearance realism pass, measured against the reference set: bring the masonic
facade's material response (limestone value/roughness, joint lines, weathering streaks),
the banner/canopy and marquee materials, and the night emissive balance to the level of the
2009 facade photo and the 2026 street frames; capture before/after and record each delta.
(Ashlar field: `limestone_ashlar` tile, reference-anchored on the facade photo, 768 px over
~4.6 m of wall = ~167 px/m, promoted via the picks pipeline, warm-shifted tint behind
`VENUE_SPECS`. Banner cloth: warm self-light registered with `_marquee_emissive` (0.14 base;
0.28 night / 0.028 day). Marquee sign face + bulbs: warm-incandescent already in the prop
PALETTE, now test-verified to brighten at night via `_marquee_emissive`. Day and night
captures promoted, both three-up boards rebuilt.)
- [ ] Upgrade the facade from flat material bands to 3D photoreal detail (real cornices/reveals,
not just the ashlar/stucco tiles). Recorded as a deferred enhancement — the tiles already
give the material read; the remaining gap is geometric reveal depth (texture-projected or
Blender modules). Verify texel density, tiling and night look when it lands.
- [x] Integrate behind `WorldBuilder.VENUE_SPECS`: the photo-anchored tiles are wired into
`_build_masonic_building` / `_build_comedy_store_building` with colliders, navigation
obstacles and the existing day/night materials.
### K. Comedy Store map recreation
*Plan (2026-11-03): rebuild the Joke Shop venue geometry from the new local-only reference set
(`tmp/research/comedy-store/`, gitignored) and the four photo-anchored scene pairs now in
`docs/screens/concepts/joke_shop/`. Evidence wins for shape; the concepts win for material/mood.
Nothing is committed until the licence gate on any promoted reference and the map-budget tests
pass. The current `_build_comedy_store_building()` stays as the fallback.*
- [x] Extract the real proportions and reconcile: vision-read `tmp/research/comedy-store/` frames
via `tools/describe_image.py` and recorded the reconciled table in `docs/venues.md#the-joke-shop`
§Measured proportions. Key finding: the game facade is proportionally **taller** than the
real (3.2:1 vs ~6:1) — the main shape-language deviation; canopy pitch, blade-sign diameter,
entrance and roofline all match closely. Curtain-bay count is crop-dependent (3–7), so 7 is
accepted as a readable stylization.
- [x] Rank the shape-language gaps and write the top-five fix list (measurements from the
proportions pass, §`docs/venues.md#the-joke-shop`):
1. **Facade height** — game 8.4 m vs real ~5.5–6 m (≈3.2:1 vs ~6:1 ratio). **Fixed
2026-11-03:** `facade_height` lowered to 6.0 m; the wall now reads as a low single-storey
hall with the canopy capping the top (vision-verified).
2. **Rounded west corner** — the real building has a ~3–4 m radius corner; the game uses a
hard 90° corner, and the entrance sits at the rounded corner.
3. **Canopy depth/slope** — game 4.9 m at 11° vs real ~4–4.5 m with a shallower pitch.
4. **Neighbouring hotel/parking mass** — absent; the hotel reference frame shows a low
blocker mass east of the club.
5. **Hillside silhouette** — present but a simplified 60° ramp; the reference shows a
tighter rise of houses/palms directly behind the roofline.
- [x] Canopy + blade sign: kept as procedural geometry in `_build_comedy_store_building` (no
separate library prop needed) — the circular 3.9 m white disc on a roof pole and the sloping
black canopy with the white signature fascia both match the reference and were vision-verified
against the entrance concept.
- [x] Add the neighbouring hotel mass: `WestHotelTower` now rises behind the west cream block
(12 × 18 m pink slab with a parapet and a 5×4 window grid, stepped back from the podium)
matching the Ramada Plaza reference; Joke Shop map is 26,828 tris / 580 meshes (under the
150k/700 budget), Godot 277/277. The parking-ramp/drop-off detail stays out (low-detail
blocker mass only).
- [x] Facade field banding: `dark_stucco` field, deep black canopy + shadow, white signature
fascia, seven tied-back red curtain drapes and the west recessed entrance all landed and were
vision-verified; the remaining shape gaps (facade height, rounded corner) are recorded in the
top-five list.
- [x] Gates after the rebuild: Godot 277/277 (map budget 26,828 tris / 580 meshes under 150k/700;
navigation/collider tests pass), `tools/check_screenshots.sh` 21 good / 0 bad; the three-up
board and `docs/venues.md#the-joke-shop` were refreshed.
- [ ] Update `docs/development-status.md`, `docs/asset-tooling.md` and `CHANGELOG.md` when the
rebuild lands; record any accepted deviations from the reference.
### L. Generated location imagery
*Skill route: `lookdev`, `lighting`, `environment-artist`, `texture-workflow`; tooling:
`tools/scene_concepts.py`, `tools/asset_pipeline.py`, `tools/screen_designs.py`.*
**Tool plan for reference-anchored generation (plan first, implement after this section's review):**
- [x] Add `scene_concepts.py generate --reference <photo.png>`: attach a licence-cleared real photo
as a source image to the OpenRouter image request (multimodal chat endpoint with a base64
data URL), with a prompt suffix that preserves the photographed facade geometry. Record the
reference file, its licence and page in the `.refgen.json` provenance sidecar next to the
generated frame.
- [x] Extend `generate_textures.generate_image()` with an optional `reference_path` argument
(base64 data URL of the photo); existing call signatures unchanged, and the path is gated by
`scene_concepts._reference_source` requiring a refs-board `SIDECAR.md` row (or an explicit
`--reference-unchecked`). Verified live on the El Capitan day concept anchored on the CC BY
2.0 2009 facade photo; tests in `ReferenceGateTests`.
- [x] Add a sidecar-driven variant pass: `scene_concepts.py anchor --reference <photo>
--treatments day,night,weathered` generates same-framing variants with treatment clauses,
one `.refgen.json` provenance sidecar per frame. Verified live on the facade photo (night +
overcast frames committed; the weathered treatment reads as mild overcast — noted on the
board). Tests in `AnchorTests`.
- [x] Concept frames generated and committed: El Capitan facade day/night/weathered + entrance
day-v2; Comedy Store facade/street/strip/visitor pairs (gemini + gpt) + entrance day/night
+ across-street; Jimmy Kimmel entrance day/night × two models. Each frame carries a
`.refgen.json` sidecar (model, prompt, source photo).
- [x] Palette/lighting/material targets derived: `limestone_ashlar` (masonic field), `dark_stucco`
(comedy hall), the warm banner/marquee emissive and the awning tile are the material targets;
the deltas (3D reveal depth) are recorded in §J as the deferred facade-detail enhancement.
- [ ] Generate signage, awning and poster texture candidates with `asset_pipeline.py`; run the
role audit and the `CREDITS.md` provenance check before anything is used in the kit.
- [ ] Compare generated concepts against the licensed photos: where they disagree, the photos win
for shape and the generated board only informs colour, mood and detail density. Human review
stays the acceptance gate, and AI output is never treated as licensed map data.
### M. Local map QA and exit
- [x] Compare the block against screenshots/map docs and keep the stylized fallback: the three-up
boards (photo | concept | capture) and `docs/venues.md#the-joke-shop` record the evidence chain;
the procedural `_build_comedy_store_building()` remains the fallback.
- [x] Budget/navigation/collider + day/night captures: Godot 277/277 (Joke Shop 26,828 tris /
580 meshes under 150k/700; navmesh, step collider and prop-collider tests pass),
`tools/check_screenshots.sh` 21 good / 0 bad.
- [x] Gallery + before/after: the Joke Shop day gallery frame and three-up board were recaptured
after each facade change; the photo/concept/capture strip is the before/after record.
- [x] `tools/check_screenshots.sh` re-run (21/0) and the Joke Shop three-up board refreshed.
- [x] Docs: `docs/venues.md#the-joke-shop` (mapping, measured proportions, simplifications),
`docs/asset-tooling.md` (`describe_image.py`) and `docs/development-status.md` updated.
`CHANGELOG.md` is untouched (unversioned track; no release).
- [x] Exit: **one local block (the Comedy Store) is reconstructed with reference-derived facades
and massing** — 6.0 m low hall, white signature fascia, tied-back curtains, hotel tower —
passes performance/navigation/visual QA with complete provenance; the rounded west corner
and hillside detail are recorded as accepted simplifications.
## Maintenance and documentation hygiene
Unversioned housekeeping that keeps the planning sources trustworthy. Do these when they block a
task, not on a schedule.
**Status (2026-11-05):** v0.9.8 published; three venues; 277 headless + 162 Python tests; the
Hollywood and Joke Shop facade-realism passes landed (`limestone_ashlar`, `dark_stucco`, banner
night emissive, marquee emissive test); reference boards (`hollywood`, `joke-shop`) with sidecars;
three-up boards; `tools/refboard.py` + reference-anchored generation (`--reference`, `anchor`) +
`tools/three_up.py`. Gaussian splatting stays deferred at the end of this file.
### Memory and status accuracy
- [x] Trim `MEMORY.md` from ~1088 lines to the durable set: dropped the superseded 2026-11-01
handoff, the v0.2.x host/disk/NSIS/release anecdotes and duplicated release notes; kept the
gotchas (async/event wiring, import order, colliders, RNG, contracts, Godot `--import` cache,
image-recitation filter, capture-harness windowing, bisect zero-area triangles) and the
easter-egg note. Now ~800 lines.
- [x] Re-check the remaining memory claims against code before deleting them; `MAX_LIVE_NPCS` is
still 16 for gameplay while the separate budget smoke spawns 24, so do not "fix" that.
(Verified phase timing is data-driven via `shift_phases`, fixed the stale `REQUIRE_SIGNIN_VIEW`
contradiction and the old 272/108 counts.)
- [x] Fix stale status claims as they are found; add a documentation check or grep note only if
the same class of drift returns. (Counts and the sign-in state corrected on 2026-11-03.)
- [x] Keep the `PLAN.md` gap-analysis baseline and the README status line in sync when a test count,
version or feature milestone changes (baseline bumped to 277/162 on 2026-11-05).
### Imagery and provenance upkeep
- [ ] Keep `docs/screens/refs/` sidecars, concept-board records and `CREDITS.md` in sync whenever
an image is added, replaced or dropped; photo-anchored concept frames must keep their
`.refgen.json` provenance sidecar (source photo, licence, page, model) next to the frame.
- [ ] Run `python3 tools/check_docs.py` after any gallery or reference-board edit so every tracked
image stays linked from its folder README.
- [ ] Use `tools/refboard.py search/fetch/rows` for new Commons research instead of ad-hoc API
scripts; downloads stay under `tmp/` and only licence-cleared rows (commit/review gate)
reach a board after a sidecar entry.
- [ ] Delete or ignore development-only photos and raw generations once a board is committed; the
repository carries the picks and their provenance, not the working set.
### Godot import-cache hygiene
- [ ] When a new committed texture (`assets/textures/...`, `picks/`) fails to import under the
existing `.godot/` cache, use the scratch-copy import (`rsync --exclude .godot ...` then
`godot --headless --import` and copy back the `.import` + `.godot/imported/*.ctex/.md5`);
never hand-edit the `uid=` in a copied `.import`. Record any new instance in MEMORY.
- [ ] Prefer `tools/scene_concepts.py promote` (or a manual move into `game_ready/v2/` plus a
`PICKS` entry) so new picks follow the same path as `limestone_ashlar` and `dark_stucco`.
### Agent skill upkeep
- [x] Add the tracked project skills `gig-line-blender-asset-contract` and
`gig-line-release-qa` under `.agents/skills/`, with references for the
asset contract/verification and release/QA runbooks.
- [x] Keep the third-party `blender-skills` pack local/gitignored while allowing
project-authored skills to be committed; reference the skills from
`tools/README.md`, `AGENTS.md` and `MEMORY.md`.
- [ ] Revisit the skills after the next Blender or release work changes the
contract, commands or gates; stale skills are worse than no skill.
### Blender skills upkeep
- [ ] Re-check the installed Blender pack (`version.json`) when updating it and record the version
in `AGENTS.md` if it changes; keep `.agents/` gitignored and `.agents/.gdignore` in place.
- [ ] Prune the skill routes in `AGENTS.md` if a skill stops matching the project (for example if
a future second-rig path makes `rigging`/`animation` guidance authoritative).
## Gaussian splatting evaluation (deferred)
Postponed for further development on 2026-11-03. The local-map track ships the facade kit and
reference-anchored material passes first; this photo-driven pipeline stays an unversioned,
offline study with no runtime or release dependency until an ADR closes it. All licence and
privacy gates below remain in force whenever the track resumes.
### Photo intake and provenance
Gaussian splatting is evaluated as a photo-driven pipeline: licensed or permissioned photos
become a reference, texture source, mesh proxy or distant backdrop. It is an unversioned local
track with no runtime dependency or release gate until the ADR closes it, and it extends the
reference-image licence and privacy gates above rather than replacing them.
- [ ] Define the accepted photo channels: project/team captures, volunteer or user-supplied
photos with written permission, and downloaded images from clearly licensed archives
(Wikimedia Commons, Openverse, Mapillary and similar). Record which channel produced
each reconstruction.
- [ ] Write a one-page capture brief for user-supplied photos: overlapping arc or orbit around
the facade, 60–80% view overlap, 12 MP+ originals, fixed exposure/white balance, HDR and
filters off, no motion blur, no people or plates in frame where avoidable, overcast or
even light, and the facade height/doors/steps included in several frames.
- [ ] Build an intake checklist and provenance sidecar per photo set: contributor, consent or
property permission, licence and attribution text, source URL, capture date, camera/lens,
location, faces/plates/interiors present, redactions applied, file hashes and whether the
set is shipped or development-only.
- [ ] Set the licence gate: own photos need an explicit contributor licence; CC0/CC BY are
preferred; CC BY-SA triggers a share-alike review for derived textures and meshes; reject
Street View, social-media scrapes, unlicensed commercial imagery and AI-upscaled copies of
unknown originals. Attribution must be feasible in `CREDITS.md` before use.
- [ ] Set the privacy gate: blur or remove faces, minors, licence plates, screens, private
interiors and anything behind a closed door; obtain property permission where required;
keep a redacted copy as the reconstruction input and never commit the originals.
- [ ] Treat downloaded photo sets as sparse and of unknown intrinsics. Use them for reference,
palette and texture study, or as extra training views; do not expect a usable splat from
10–30 casual web photos of a facade.
- [ ] Keep photos, masks, COLMAP databases, checkpoints and splats outside git; commit only
derived, licensed, provenance-stamped assets that pass the review gate.
### Reconstruction and alignment
- [ ] Pin a reproducible offline toolchain (for example COLMAP or GLOMAP for camera poses plus
gsplat, OpenSplat, Nerfstudio or Brush for training), with exact versions, GPU/VRAM/disk
needs and known failure modes recorded in the sidecar.
- [ ] Reconstruct two quality tiers: a dense 100–400-photo arc/orbit capture and a sparse
10–30-photo downloaded set. Record camera-registration rate, training time, splat count,
file size and held-out PSNR/SSIM/LPIPS for both.
- [ ] Mask sky, people, vehicles and moving foliage before training; verify the masks do not
erase facade edges, signage or window detail, and keep the redacted input variant.
- [ ] Align the splat to game coordinates from the OSM footprint plus surveyed points (door
width, column spacing, step rise, storefront height) and record alignment residuals in a
Blender scene containing both the splat and the current venue geometry (`scene-assembly`).
- [ ] Judge quality from held-out views and a render from the game's day/night camera poses
(`camera-cinematography`, `lookdev`, `qa-review`), not from training views alone; reject
splats that only look correct from the capture path.
- [ ] Maintain a coverage map per block: which facades, corners and rooflines have usable views,
and which areas must fall back to the facade kit or stylized geometry.
### Map enhancement paths
- [ ] **Path A — Reference and QA (lowest risk):** render the splat from game camera poses and
compare it with the realism captures; derive dimensions, signage placement, facade rhythm
and material colours. No splat data reaches the runtime.
- [ ] **Path B — Texture derivation:** project splat renders **or reference photos** onto the
facade kit to produce albedo, normal, roughness and AO candidates; de-light and bake them
(`texture-workflow`), then verify UV scale, tiling and readability under the Compatibility
renderer. Deriving directly from a committed reference photo requires its licence to allow
derived works and a `CREDITS.md` entry; CC BY-SA sources stay out unless reviewed.
- [ ] **Path C — Mesh proxy:** extract a mesh from the splat or dense reconstruction, retopo and
dress it in Blender (`retopology`, `prop-artist`), assign LODs (`lod-pipeline`), and keep it
inside the venue triangle/material budgets with mesh-based colliders (`collision-proxy`).
- [ ] **Path D — Distant backdrop:** test a baked panorama, impostor or billboard generated from
the splat for skyline and opposite-street depth where the player cannot walk; measure file
size, loading and visible pop at the venue boundary.
- [ ] **Path E — User-photo supplement:** let a small supplied photo set improve one facade or
landmark (a signage patch, colour reference or hero detail) without rebuilding the venue;
define the minimum evidence and the same licence/privacy/review gate as a full capture.
- [ ] For every path record quality gain versus effort, licence compatibility of the derived
output, and whether the result may ship or stays development-only. Prefer the cheapest
path that closes the visible gap.
### Runtime evaluation and decision
- [ ] Probe Godot 4.7.2 Compatibility and Web support for splat rendering in a throwaway scene:
addon or importer availability, shader/sort cost, memory, load time and fallback behaviour.
Do not add a splat dependency to the release project during evaluation.
- [ ] Set hard limits before any runtime experiment: maximum per-splat file size and total
memory, allowed frame-time delta on the 24-NPC budget scene, acceptable Web load time, and
no compute-shader requirement for the Compatibility/Web path.
- [ ] Keep collision and navigation mesh-based even if a splat is displayed; a splat must never
become the playable floor or the access/occlusion authority.
- [ ] Write the ADR with a per-path go/no-go, the accepted licence/provenance rules, whether each
result ships or stays development-only, rejected alternatives and the evidence bundle.
- [ ] Exit: one licensed photo capture measurably improves a venue through a documented path and
passes visual, performance and provenance review; or the approach is rejected with
reproducible findings and removed from the release path.
## Milestone 0.9.9 — Quaternius NPC base swap (patch)
Goal: make one Quaternius-derived NPC render and animate correctly in Godot and ship it as the
pilot base, with the procedural library still selectable as the fallback. This is the first
versioned character milestone; it contains the rig correction that has been blocking the
ADR-0018 rollout.
**Why this is a patch:** it changes character presentation and animation quality but adds no new
player-facing system. The 0.10.0 minor is reserved for the release gates (see below), so a
blocked rig can no longer block the whole release.
### Rig and export correction
- [x] Reproduce and record the failure: the `armature_apply` + `export_apply=True` bake wrote
wrong inverse bind matrices (Godot rendered A-pose while the rest was arm-down). Recorded
in `MEMORY.md`; `bake_armdown_clean.py` was the failing recipe.
- [x] Fix the arm-down bake so the exported skin matches the rest. `tmp/npc-research/bake_armdown_v2.py`
(pose-rotate → apply modifier → reset pose → edit-mode rotate arms → re-add modifier →
export `export_apply=False`) now yields a correct arm-down base: Blender deformed x ±0.221
and the rest bone tail points down. Still local (`tmp/`), not shipped.
- [x] Re-derive the pose composition for a non-180° rest. Superseded by the rig-agnostic
`_swing_bone()` below: the rest-relative `set_bone_pose_rotation` form was never exact,
because `get_bone_rest()` is parent-relative and already carries the importer's axis
conversion. Kept in `MEMORY.md` as a rejected approach.
- [x] Fix the skinning/rest bake end to end (commit `cd285b3`):
(1) the canonical base is **join-only** (`tmp/npc-research/build_base_minimal.py`: no
`remove_doubles` weld, no scale/ground transform, keep the Armature modifiers) — the weld
and the transform corrupted the Quaternius weights;
(2) the arm-down bake (`tmp/npc-research/bake_armdown_v5.py` — pose, read pose matrices,
apply modifier, **reset pose to identity**, set rest matrices, re-add modifier) bakes the
rest correctly (DEFORMED x −0.228/0.228, Godot bind pose arm-down);
(3) `NpcAvatar._swing_bone()` now writes the **global** bone pose from
`get_bone_global_rest()`, so one formula drives both the 62-joint Quaternius rig and the
13-core fallback (measured: posed bone axis matches the proxy pivot at dot 1.000 on the
Quaternius, and the procedural rigs are unchanged);
(4) `RIG_BONES` + `NpcAvatar.bone_name()` hold the only side mapping (the Quaternius labels
`L`/`R` opposite the project), and the hand proxy is `global_wrist − global_elbow`.
Verified in Godot: idle/sit/walk captures on npc_casual plus the unchanged procedural rigs.
- [x] Add regression tests: `test_shipped_rig_resolves_the_animation_joints` covers the joint map,
the baked arm-down rest, the down-pointing arm axis and the proxy side mapping; the Blender
audit/`inspect` and `npc_proportions` now understand both rigs. Godot 278/278, Python
167/167.
- [x] Give the Quaternius base the five tint slots: `build_base_minimal.py` renames the upstream
materials to `skin`/`cloth`/`pants`/`hair`/`accessory` and keeps `Eye`/`Eyebrows` as
fixed-colour detail. The source UV map is degenerate (every loop at 0,0), so the build now
Smart-UV-unwraps and repacks the layout — texel density went from an unmeasurable 0 to
73–74 px/m on the 256² maps and the 256² AO bake works again.
- [ ] Verify Idle and Walk on the 62-joint rig: loop seam, foot contact, root motion and frame
rate. Record which of the 24 clips are in scope and which stay unused.
- [ ] Decide and record how poses the clips do not cover (seated, raise-hand, head turn) are
driven at runtime.
- [ ] Keep the fallback tested: `npc_base`, the MakeHuman head and `_build_body()` remain
selectable; no MPFB work is reopened and no Stage-1 proportion warp returns. (The procedural
library is still the majority of `CHARACTER_POOLS` and captures clean, but the fallback path
itself has no dedicated test yet.)
- [ ] Re-fit the remaining gear against the Quaternius torso: the `backpack` strap geometry was
authored for the old body width and now hangs in front of the chest. The camera was fixed
with a wrist strap (a neck strap floats when the item is hand-attached).
### Silhouette and face gate (owner)
- [x] Re-run the silhouette gate at gameplay distance and in portrait against the current
`npc_casual`, front / three-quarter / profile. ADR-0018 removed the proportion warp, so
source proportions are accepted by definition (head 13.4 % of height, measured by
`npc_proportions`).
- [x] Owner review page with the visual signoff sheets and checklist:
[`docs/npc-visual-review.md`](docs/npc-visual-review.md) (the procedural-era review text
that used to live there is retired; its baseline sheets stay in `blender_previews/`).
- [x] Face-fidelity comparison sheet: [`docs/screens/npc-face-gate/`](../../docs/screens/npc-face-gate/README.md)
captures the Quaternius base, the procedural + MakeHuman head and a procedural crowd variant
at front / three-quarter / profile / 8 m. The page records the three options (full swap,
crowd only, add head detail) and recommends the full swap.
- [x] **Owner decision:** option (a) full swap (2026-11-05). Applied: all twelve characters are
baked from the three Quaternius sources inside 24,000 tris / 24,000 glTF vertices
(`a6ccd86`), and the gate was re-run.
- [x] Record the decision and its consequence in `MEMORY.md`,
`docs/npc-model-sourcing.md#shipped-roster-099-owner-chose-the-full-swap` and the
follow-up decision section of
[`docs/adr/0018-quaternius-stage2-runtime-rig.md`](docs/adr/0018-quaternius-stage2-runtime-rig.md).
- [ ] Owner visual signoff on the shipped roster: [`docs/npc-visual-review.md`](docs/npc-visual-review.md).
### Pilot rollout
- [x] Map the five tint slots onto the Quaternius materials and unwrap the degenerate source
UV map; bake the 256² AO and check texel density (73–74 px/m).
- [ ] Bake the per-slot albedo/normal/ORM maps and a per-slot atlas (the five slots currently
share one whole-body UV set).
- [-] Re-fit gear attach points against the Quaternius skeleton and test cap, helmet, backpack,
shoulder bag, lanyard, clipboard and firefighter tank. (Camera done: wrist strap instead of
the floating neck strap. Backpack straps still cut across the Quaternius chest.)
- [ ] Wire the approved base into one role (or the crowd roles if option b is chosen) with the
procedural model as an instant fallback; no runtime download or optional dependency.
- [ ] Run `blender_assets.py inspect`/`audit`/`fit`, `npc_proportions.py`, the 24-NPC budget smoke
and the capture gate; record the measurements before/after.
- [ ] Release `0.9.9`: update the version source in `project.godot` (`config/version`), write the
`CHANGELOG.md` entry, run `tools/build_releases.sh`, verify checksums, commit, tag and push.
**Exit:** one committed Quaternius GLB renders arm-down with correct skinning, plays Idle and
Walk on the 62-joint rig, poses correctly under the new composition, passes the owner face and
silhouette gate, fits the 18 gear pieces, stays inside budget, and ships as `0.9.9` with the
procedural library still available as a fallback.
## Milestone 0.9.10 — NPC roster rollout (patch)
Goal: extend the approved base to the full 12-role roster in asset-family batches, without
regressing gear fit, budget or readability.
### Roster build
- [x] Map the 12 roles to the three full Quaternius GLBs (owner chose the full swap): hoodie for
casual/coat/hoodie/crowd_c, casual tee for jacket/dress/crowd_a/crowd_b, worker for
vest/firefighter/guard/crowd_d. `npc_firefighter`, `npc_guard` and `npc_crowd_d` drop the
upstream hivis vest and hard hat. All twelve built, AO-baked and committed (`a6ccd86`).
- [ ] Add the modular part library (hair styles, outfits) so the roster regains the silhouette
variety the twelve procedural meshes had; three shared meshes is the current ceiling.
### Owner review findings (2026-11-06)
Review page: [`docs/npc-visual-review.md`](docs/npc-visual-review.md). The owner signed the roster
off **with follow-ups** and rejected checklist rows 3 (faces at portrait), 4 (hands and hand-held
gear) and 5 (head gear on the skull).
- [x] Worker torso holes on `npc_guard`: the build deleted `Worker_Vest`, which on the modular
Worker *is* the torso. Fixed by mapping it to `cloth` instead of deleting it.
- [x] Worker crown hole on `npc_firefighter`/`npc_crowd_d`: the deleted `Worker_Yellow` hat was
the crown of the skull. Fixed by keeping it and splitting the material at z 1.65 (crown →
`hair`, vest stripes → `cloth`).
- [x] Unibrow on `npc_guard`/`npc_vest`/`npc_crowd_d`: the Worker `Eyebrows` bar *and* its
oversized eye quads are cut out, and the hoodie's two brows are transplanted onto the Worker
head, lifted 18 mm clear of the eye line (`WORKER_HEAD_DONOR`).
- [x] Worker crown reads as a hard hat: the hoodie's hair is transplanted onto the Worker head and
the hard-hat faces above the collar are cut away, so guard/responder/crowd_d have hair.
- [x] Worker roles (vest, firefighter, guard, crowd_d) had **no visible eyes**: the Worker's own
quads sit inside the head. The hoodie's eye pair is now transplanted with a 4 mm forward
offset, so all four roles show two eyes and two brows (owner request).
- [x] Owner: remove **all** hats except the firefighter helmet, and remove the performer cone.
Caps and hoods are gone from `ACCESSORIES`, the per-character gear list and the primitive
fallbacks; the performer keeps only the baton and board in hand. The head-gear lift/scale
compensation those hats needed is deleted with them, so `firefighter_helmet` is the only
head item and no role can hide its face.
- [x] Worker faces: the dark "unibrow" band turned out to be the **cap brim** sitting on the brow,
not head geometry. `HEAD_GEAR_LIFT` went 0.07 → 0.103 (the gear is authored around the head
*centre* at 1.62, the Quaternius head centre is 1.69) and `GEAR_SCALE` grows the cap/hood so
they clear the 0.228 m hair shell. The cap now reads as a cap and both eyes *and* brows show.
- [x] Hair poking through head gear: fixed by the same lift + cap/hood scale; the firefighter
helmet dome covers the hair, and the cap reads as a cap rather than a visor.
- [ ] The eyes are single-colour quads on every base; a light sclera + pupil texture on the `Eye`
slot would finish the face at portrait distance.
- [x] Owner review page re-created for the second pass:
[`docs/npc-visual-review.md`](docs/npc-visual-review.md), with the hat/head-item table and a
refreshed eight-row checklist.
- [-] Arm/torso intersection on `npc_dress`/`npc_jacket`/`npc_crowd_a`/`npc_crowd_d`: the baked
rest now aims the upper arm 7° out from vertical. Verified clear on the `casual` base; a
light clip remains on `worker`, so this needs either a wider angle or a per-base shoulder
offset.
- [ ] Worker crown still reads as a hard hat: the shape is upstream geometry, so it needs the
modular hair/cap library (or a carved brim) instead of a tint.
- [ ] Eyes and brows still read as one heavy dark mass at portrait distance (checklist row 3).
- [ ] Head gear still does not sit on the skull convincingly (row 5): the firefighter helmet brim
is 1.55× the head width and the cap sits high.
- [x] Carried role props floated beside the body: `_add_props()` placed the performer hat/board
and the protester pole/sign at fixed rig offsets. They now ride the hand proxies
(`_hand_for()`), and the performer cone was resized from a 0.44 m base to 0.25 m so it sits
on the 0.20 m head instead of swallowing it.
- [ ] Hand-held gear is still authored for the old hand (row 4): the `npc_vest` clipboard is
oversized on the shorter Quaternius arm.
- [-] Build one asset family per commit, AO-baked and re-imported; never run a no-op full-library
rebuild. Use the concept + vision-judge loop for head/outfit decisions and record model,
prompt and spend. (Roster family landed in one commit because the three sources share one
pipeline; the 12 × 6 baseline sheet is captured but not yet owner-approved.)
- [ ] Keep every variant inside 24,000 tris / 24,000 glTF vertices and the five tint slots; no
new bone beyond the accepted 62-joint set without an ADR.
- [ ] Refresh `CHARACTERS`, the manifest roles/tint data, `CREDITS.md` and the review doc rows as
each family lands.
### Prop and gear fit pass (new NPC attach points)
Goal: every gear piece and role prop is measured against, and re-authored for, the Quaternius
skeleton. The 0.9.9 roster swap moved the hand from y 0.78 to the wrist at y 1.017, the head centre
from 1.62 to 1.69 and the shoulder width from 0.27 to 0.15, so anything authored for the procedural
body is now the wrong size or in the wrong place. Nothing in this workstream changes the runtime
contract.
- [x] Extend `tools/blender_assets.py fit` to measure **hand** gear (it skipped `hand` outright).
It now reports grip drop, the lowest point at rest against the Quaternius wrist (y 1.017), the
hip-line flag (0.95), the authored size and `size_vs_head` — the proportion number that makes
"reads oversized" measurable.
- [x] Record the nine hand-item measurements (table in `docs/blender-assets.md`). Only the
`clipboard` was actually mis-authored: its grip sat in the *middle* of the board, so it hung
through the thigh line. It now grips the top edge like a carried clipboard (0.22 x 0.30 m).
- [x] Re-author the `clipboard` (top-edge grip, 0.22 x 0.30 m) and rebuild + re-bake AO. The
`umbrella` (0.90 m) measures on its real-world size and keeps its grip, so it stays.
- [x] Re-fit the role props in `NpcAvatar._add_props()`: the delivery parcel (0.34 m slab across
the torso) is now a 0.26 m carried box in the hand, and the confused visitor carries a folded
map instead of a tray hovering at the hip.
- [x] The primitive cap/hood fallbacks in `_add_accessory()` are deleted with the hat removal;
the remaining hand-item primitives are unreachable while the gear GLBs exist and stay as the
documented fallback.
- [x] Retire the gear GLBs the roster no longer uses: `cap` and `hood` are gone from `GEAR`, the
manifest and the asset tree (`beanie` was already retired). The builder branches stay for a
future design, guarded by a comment.
- [x] `docs/blender-assets.md` documents the 15-piece matrix and the hand-item fit table;
`architecture.md`, `asset-tooling.md` and the preview index were updated with the new counts.
`CREDITS.md` still cites the upstream pack, which is unchanged.
- [x] Owner feedback: the back gear hung at the waist. The `back` attach moved from y 1.10 to
1.26 (shoulder-blade height on the Quaternius torso); backpack, shoulder bag and SCBA tank
re-captured on the character sheet.
- [x] Re-measure `fit` for the back and chest gear on the slimmer torso: `backpack` inner x 0.150
against a 0.190 clothing radius (straps cross the body, as intended), `shoulder_bag` x
clearance +0.072, `scba_tank` inner x 0.150, `lanyard` radial clearance +0.012 on the chest.
The backpack straps were re-fitted for this torso in `fb3e75a`; nothing further needed.
- [x] Rebuilt `docs/blender_previews/sheet_npc_gear.jpg` for the 15-piece set (all renders
regenerated) and re-ran the 24-NPC budget smoke (175,264 tris, 0.87 ms).
- [x] New [`src/test/capture_npc_gear.gd`](src/test/capture_npc_gear.gd) captures every gear piece
**worn by a real avatar** through the runtime attach path, one frame per item, and
`docs/blender_previews/sheet_npc_gear_on_character.jpg` collects them. It caught two things
the asset renders could not: `sunglasses` were hidden inside the hair shell, and the outfit's
own gear double-attached over the tested piece (the capture now clears it first).
- [x] Owner feedback on grip height applied: every hand item drops into the palm
(`HAND_ITEM_DROP`, camera 0.085 m, flashlight 0.065, cup 0.050, umbrella 0.045, clipboard and
board 0.035, phone/radio/wand 0.030), the carried role props drop 0.06 m, and the lanyard
attach moved from y 1.30 to 1.38 for the taller Quaternius sternum.
- [x] `docs/npc-visual-review.md` row 4 now points at the gear-on-character sheet and the
measurement table. Only the owner verdict is outstanding.
**Exit:** every gear piece and role prop has a measured fit against the Quaternius attach points,
the hand-held items are within +/-10 % of their authored real-world size, and the review page row 4
is signed off.
### Gates and rollout
- [ ] Re-bake AO per rebuilt GLB and refresh the stale `.godot` import cache (scratch-copy
import) before judging any face or body change.
- [ ] Run `inspect`/`audit`/`fit`/`texels` and `npc_proportions.py report`; record the
12-variant measurement table.
- [-] Capture the 12 × 6 baseline and get owner approval before replacing the committed GLBs.
**Owner review page: [`docs/npc-visual-review.md`](docs/npc-visual-review.md)** — the
full-body roster, front/profile per role, the idle/sit/walk pose sheet and the 8 m crowd
strip, with an eight-row signoff checklist and the six issues found while capturing. The
GLBs are already committed (0.9.9); this signoff either accepts them or turns each rejected
row into a fix task.
- [ ] Re-run the 24-NPC budget smoke and the Web/Compatibility capture; record frame time,
mesh/triangle counts and Web load before/after.
- [ ] Retire the procedural variants that are no longer used, keeping one tested fallback; update
`docs/development-status.md`, `docs/3d-model-review.md`, `docs/blender-previews` sheets and
the README character row.
- [ ] Release `0.9.10` with the full procedure (version, changelog, local build, checksums, tag).
- [ ] Shrink the release artifacts: `assets/` alone is ~273 MB in the Linux binary. Excluding
`tmp/`, `build/`, `docs/`, `tools/`, `src/test/` and `.agents/` from the export presets
already cut 426 MB to 273 MB; the remaining bulk is texture resolution.
**Exit:** all 12 roles use the approved base or an explicitly kept fallback, every variant passes
the contract and gear gates, the 24-NPC and Web budgets are measured and documented, and the
baseline sheet is owner-approved.
## Milestone 0.9.11 — Venue, prop and lighting polish (patch)
Goal: close the scheduled subset of the unversioned 3D review backlog. Only ranked P1/P2 items
whose fix is visible at gameplay distance are in scope; the review doc stays the evidence and
scorecard source, this milestone is the actionable list.
### Props, gear and foliage
- [ ] Rebuild the ranked prop candidates from §D: the foliage-language P1
(`planter_box`/`round_planter` foliage, `street_tree`/`palm_tree`), plus the
`newspaper_box` slot and `bollard` collar if the budget allows.
- [ ] Replace the flat `_shrub()`/`_tree()` clumps with carded clusters and keep the 60k
library triangle budget; measure before/after.
- [ ] Review the 18 gear pieces for silhouette and strap/buckle readability and rebuild only what
fails; re-run `fit` after every change.
- [ ] Verify prop colliders and navigation obstacles against the final silhouettes.
### Venue, lighting and set dressing
- [ ] Joke Shop / Hollywood shape language: fix the ranked remaining items (west rounded corner,
canopy pitch, facade reveal depth) only where a capture shows a readable gain.
- [ ] `lighting`/`lookdev` pass: flashlight cone/energy for the `06-night-flashlight` stage,
marquee/event-banner emissive read under the Compatibility renderer, street-lamp pools.
- [ ] Set dressing: fix the Joke Shop queue story (podium/stanchion line at the door), rope
barrier thickness, the west palm overlap and the empty sidewalk light pool; apply the same
review to Hollywood and the TV Studio.
- [ ] Build one real-photo comparison for street furniture and record the accepted mismatches.
### Evidence and release
- [ ] Refresh `sheet_props.jpg`, `sheet_npc_gear.jpg`, the three-up boards, the affected venue
captures and the README asset rows.
- [ ] Re-run `tools/check_screenshots.sh` and `python3 tools/check_docs.py`; record both results.
- [ ] Release `0.9.11` with the full procedure.
**Exit:** every scheduled P1/P2 item is fixed or explicitly accepted with a measurement, the
capture gallery and asset sheets are current, and no unreviewed asset remains in the shipped
catalogue.
## Milestone 0.9.12 — Performance, Web and accessibility (patch)
Goal: prove the new character and venue content still meets the frame, memory, load and
accessibility targets on every target before the minor release.
### Performance and platform
- [ ] Profile the 24-NPC budget scene on Compatibility/Web and Desktop HQ; record frame time,
draw calls, texture memory and pck size against the pre-character baselines.
- [ ] Verify LOD/impostor thresholds, the 60k library and 150k/700 venue budgets, and the Web
load time after the character and texture changes.
- [ ] Confirm the Compatibility/Web build still avoids compute shaders and Vulkan-only features;
spot-check the HQ fallback message.
- [ ] Re-run the save/session regression suite on an isolated user-data directory after the
content changes.
### Accessibility and localization readiness
- [ ] Verify font scaling at maximum, the longest strings, caption coverage, reduced motion,
colour-independent escalation cues and the hold-to-confirm emergency path.
- [ ] Run the string catalogue through a pseudo-locale/length check; move remaining hardcoded UI
strings into `content/strings.en.json`.
- [ ] Fix only the issues the checks expose; new accessibility features are 0.11.0 scope.
### Release
- [ ] Regenerate the full capture gallery and the screen-design judge from the current build;
record model, spend and verdict.
- [ ] Release `0.9.12` with the full procedure.
**Exit:** the character and venue changes do not regress frame time, memory, Web load,
save/session reliability or accessibility behaviour, and both automated gates are green.
## Milestone 0.10.0 — Public-readiness release (minor)
Goal: ship the new characters, the polished venues and the public-readiness gates as the
**0.10.0 minor**, after the patch milestones above have landed. Content is frozen at 0.9.12;
only release-blocking fixes and balance changes may land here.
### External playtest and onboarding
- [ ] Run at least five first-time-player sessions covering the tutorial and one full venue
shift; record where players get stuck, misread evidence or over-escalate.
- [ ] Triage by severity and fix every P0/P1 onboarding, evidence or accessibility issue; record
the rest as known limitations or post-0.10 work.
- [ ] Re-run the tutorial/campaign smokes after each fix and capture a first-hour walkthrough.
### Balance and fairness
- [ ] Run the scripted venue sweeps across easy/normal/hard and all four phases using the
`docs/release-qa.md#balance-acceptance` targets; tune event weights, reaction tables,
compliance and report strictness where targets fail.
- [ ] Review emergency calls against independent behaviour labels, not `_read_npc_truth()` as its
own oracle; no routine behaviour may make emergency the dominant winning move.
- [ ] Verify reputation grades, debrief text and campaign progression still teach
de-escalation-first after tuning; record the evidence and deliberate exceptions.
### Legal, signing and storefront
- [ ] Resolve the macOS signing/notarization decision and record the quarantine instructions.
- [ ] Clear or explicitly block on AI-provider commercial rights, storefront/rating approval and
the privacy/mental-health representation review.
- [ ] Confirm every shipped image, texture, character and audio asset has provenance and credits.
### Platform and release engineering
- [ ] Smoke-test the target matrix: Linux portable/AppImage, Windows portable/installer, macOS
unzip/launch, Web static hosting and the Desktop HQ Vulkan fallback.
- [ ] Run `tools/build_releases.sh` from a clean checkout, verify artifact names, sizes, checksums
and download links, and do a `PUBLISH_DRY_RUN`.
- [ ] Run the complete headless suite, Python tooling tests, tutorial/campaign/session/scenario
smokes and the 24-NPC budget smoke.
- [ ] Confirm no P0/P1 defects remain; document P2 items as known limitations.
### Documentation and visual sign-off
- [ ] Update `README.md`, `PLAN.md`, `docs/architecture.md`, the ADRs, `docs/development-status.md`,
`docs/getting-started.md`, `docs/release-qa.md`, `CHANGELOG.md` and this roadmap.
- [ ] Regenerate the gallery and realism baselines from the release-candidate build, not the dev
tree; verify every README/docs image resolves from the tagged build.
- [ ] Run the manual smoke checklist on every target and link the evidence.
- [ ] Release `0.10.0`: version, changelog, clean build, checksums, tag, push and post-release
record.
**Exit:** 0.10.0 is content-locked, externally played, legally reviewed, documented,
reproducibly built and approved for public download.
## Milestone 0.10.1 — Onboarding and accessibility fixes (patch)
- [ ] Fix the onboarding, tutorial and evidence-reading issues deferred from the 0.10.0 playtests
that are cheap and non-structural.
- [ ] Fix accessibility issues found at maximum font scale, in captions or in the colour-blind
cue set.
- [ ] Re-run the manual smoke checklist on the affected paths and release `0.10.1`.
**Exit:** first-hour comprehension and accessibility defects from the 0.10.0 evidence are closed.
## Milestone 0.10.2 — Balance and fairness follow-up (patch)
- [ ] Re-run the venue sweeps against the 0.10.0 targets and tune the remaining failing cells.
- [ ] Review shift-review explanations, reputation grades and campaign difficulty progression.
- [ ] Record the balance evidence and release `0.10.2`.
**Exit:** every balance-acceptance cell is inside target or explicitly accepted with a reason.
## Milestone 0.10.3 — Stability and quality-of-life (patch)
- [ ] Fix save/checkpoint edge cases, UI focus traps, gamepad remapping and pause/resume defects
reported after 0.10.0.
- [ ] Add the small quality-of-life items that need no new system (default focus, key hints,
settings persistence).
- [ ] Release `0.10.3` with the full procedure.
**Exit:** no known P1 stability defect remains before the 0.11.0 feature work starts.
## Milestone 0.11.0 — Systems and content depth (minor)
Goal: add the first post-1.0-track feature set on top of a stable 0.10.x line: a live incident
replay, a fourth venue and the localization foundation.
### Live body-camera replay
- [ ] Replace the text-only incident timeline with a replay of recorded positions, actions and
timestamps; define the recording format, memory cap and deterministic playback.
- [ ] Keep the replay review-only (no new escalation mechanics) and verify it on Compatibility/Web.
- [ ] Add a skip path and a captioned summary so replay is never a hard gate on the shift review.
### Fourth venue
- [ ] Evaluate and select the fourth real-site venue (candidate: the TCL Chinese Theatre
forecourt at 6925 Hollywood Blvd) in an ADR covering layout, access policy, gameplay
distinctness and asset budget.
- [ ] Build the venue under `content/venues/` with licensed reference, sidecar provenance and the
three-up board; enter it from venue selection and the campaign.
- [ ] Add venue-specific events, access artifacts and escalation cases; extend the balance matrix
to the new cells.
### Localization foundation
- [ ] Extract every remaining hardcoded string into the JSON catalogue and make the pseudo-locale
length check a CI gate.
- [ ] Add one full secondary language (text, layout, font coverage) with the translation workflow
documented; no machine-translated release without review.
- [ ] Verify layout at maximum font scale in both languages.
### Release
- [ ] Re-run the capture gallery, screen-design judge, budget and platform smokes for the new
content; release `0.11.0` with the full procedure.
**Exit:** the replay, the fourth venue and one secondary language ship without regressing the
0.10.x performance, accessibility or balance baselines.
## Milestone 0.11.1 — Replay and venue fixes (patch)
- [ ] Fix the replay, new-venue and localization defects found in wider play.
- [ ] Re-run the new venue's balance cells and the replay capture gate; release `0.11.1`.
**Exit:** no P1 defect remains in the 0.11.0 feature set.
## Milestone 0.11.2 — Content and balance polish (patch)
- [ ] Tune the new venue events and the campaign pacing; fix the remaining content readability
issues.
- [ ] Refresh the affected captures, boards and asset rows; release `0.11.2`.
**Exit:** the 0.11.x line is stable enough to freeze its contracts.
## Milestone 0.12.0 — Platform and distribution (minor)
Goal: make the game distributable on the launch platforms with signed builds, a frozen save
contract and complete localization.
### Signing and storefronts
- [ ] Sign and notarize the macOS build; record the exact commands and certificate handling.
- [ ] Produce the storefront packages and metadata (rating, privacy, credits, screenshots) for the
chosen stores; complete the AI-provenance and commercial-rights review.
- [ ] Add the storefront-facing extras that are in scope (achievements, cloud-save decision,
controller/Steam Deck verification) behind explicit feature flags.
### Contracts and hardware
- [ ] Freeze the save/checkpoint format as version 2 with export/import and forward-migration
tests; document the compatibility promise.
- [ ] Verify the target-hardware matrix (minimum GPU, Steam Deck, Web browsers, integrated
graphics) and publish the measured results.
- [ ] Finish the launch languages and the accessibility statement for the shipped feature set.
### Release
- [ ] Release `0.12.0` with the full procedure and the storefront artifacts.
**Exit:** signed, storefront-ready builds exist for every launch platform with a frozen save
contract, complete localization and measured hardware support.
## Milestone 0.12.1 — Distribution fixes (patch)
- [ ] Fix signing, installer, storefront-metadata and save-migration defects found in the
storefront dry runs; release `0.12.1`.
**Exit:** every storefront build installs, launches and migrates saves cleanly.
## Milestone 0.13.0 — Content complete (minor)
Goal: finish the planned content and lock it for the 1.0.0 release candidate.
### Content and presentation
- [ ] Finalise all planned venues, events, dialogue and access artifacts; close the content
backlog with an explicit accept list.
- [ ] Complete recorded audio (VO and music) if the 0.11.0 audio decision funded it; otherwise
record the accepted procedural-only direction.
- [ ] Finalise the tutorial and first-hour flow against the playtest evidence.
### Release candidate preparation
- [ ] Run a full accessibility audit against the shipped features and fix the remaining issues.
- [ ] Run the balance acceptance protocol one final time and freeze the tuning.
- [ ] Declare content and feature freeze; only release-blocking fixes may land after this point.
- [ ] Release `0.13.0` as the content-complete milestone.
**Exit:** all planned content is implemented and frozen, and no known P0/P1 feature gap remains.
## Milestone 0.13.1 — Release candidate 1 (patch)
- [ ] Build release candidates on every platform from a clean worktree; run the complete smoke
matrix, save migration, performance and accessibility checks.
- [ ] Fix release-blocking defects only; release `0.13.1`.
**Exit:** a release candidate exists that passes every gate except final legal/storefront
approval.
## Milestone 0.13.2 — Release candidate 2 (patch)
- [ ] Verify the 0.13.1 fixes on the target matrix and re-run the full gallery and judge.
- [ ] Freeze the release notes, credits, privacy and known-limitations documents; release
`0.13.2` as the final candidate.
**Exit:** the candidate is build-reproducible, documented and awaiting only the final 1.0.0
release decision.
## Milestone 1.0.0 — Release (major)
Goal: publish the first stable release with a documented compatibility promise.
- [ ] Confirm legal, privacy, rating and storefront approval is complete in writing.
- [ ] Confirm the hardware matrix, accessibility audit and localization sign-off are complete.
- [ ] Confirm no P0/P1 defects remain and every P2 is a documented known limitation.
- [ ] Update `README.md`, `PLAN.md`, `docs/development-status.md`, `CHANGELOG.md`, `CREDITS.md`,
`PRIVACY.md` and the release notes for 1.0.0.
- [ ] Run the full release procedure: version 1.0.0, changelog, clean build, checksum
verification, tag, push and publish.
- [ ] Record the post-release state in `MEMORY.md` and open the 1.0.x maintenance list.
**Exit:** 1.0.0 is published on every launch platform with complete documentation, verified
checksums and no open release blocker.
## Future / deferred (not scheduled)
- [ ] Seasonal events or a fifth venue after the 0.11.0 fourth venue proves the venue pipeline.
- [ ] Real voice/dialogue audio if the 0.13.0 audio decision does not fund it; ambience, cues and
radio crackle stay procedural until then.
- [ ] Swap the primitive prop builder for an image-to-mesh model API behind the same
`assets/models/generated/<prop>.glb` contract.
- [ ] Rare easter eggs/cameos (e.g. Bob Dobbs) only after core content and QA are stable.
- [ ] A second Chinese Theatre/forecourt style venue if the fourth venue is not enough.