Compare commits

...
77 Commits
Author SHA1 Message Date
zwitschi b3acf370fc Mark the roster build done and record the release-size follow-up
Full build / Full desktop build + optional release (push) Successful in 4m58s
Full build / Import + headless tests (push) Successful in 42s
2026-11-06 13:59:28 +01:00
zwitschi 04d211b90b Release 0.9.9: full Quaternius roster, docs, changelog, version, slimmer export filters and checksums
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 13:59:23 +01:00
zwitschi a6ccd86f85 Swap the full 12-character roster onto the CC0 Quaternius bases
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 13:56:30 +01:00
zwitschi 1ce173e9d1 Record the Quaternius pilot bake, measurements and face-gate decision point in the docs
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 13:45:05 +01:00
zwitschi 90f189a269 Add the Quaternius face/silhouette gate sheet and record the geometry measurements
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 13:44:46 +01:00
zwitschi fb3e75aba3 Refit the backpack straps for both torsos (ADR-0018)
Full build / Import + headless tests (push) Successful in 44s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 13:43:52 +01:00
zwitschi cd285b3a39 Ship the Quaternius npc_casual pilot base: rig-agnostic pose sync, five tint slots, unwrapped UVs and wrist-strap camera
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 13:42:37 +01:00
zwitschi 68f49253fe Record the Quaternius pilot fixes and remaining 0.9.9 work in MEMORY.md
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 13:19:42 +01:00
zwitschi b012c6a675 Record the Quaternius bake/pose-sync progress
Full build / Import + headless tests (push) Successful in 50s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 12:17:30 +01:00
zwitschi 2a6f29f5d0 Fix the Quaternius side swap, arm swing axis, and hand-proxy position (backward-compatible)
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 12:17:18 +01:00
zwitschi bb54ef3eec Record the canonical-base and arm-down-bake findings precisely
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 10:33:52 +01:00
zwitschi 11f53f66a3 Record the two skinning root causes: weld corrupts weights, bake needs head/tail plus roll
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 10:26:56 +01:00
zwitschi 9b65a93587 Narrow the Godot skinning blocker to the importer's rotated bind_pose
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 10:13:32 +01:00
zwitschi 56a14747d4 Record the pose-composition fix and the remaining Godot skinning-import blocker
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 10:00:07 +01:00
zwitschi 8b1bf736af Correct the pose composition for a non-180° rest (rest-relative swing axis), backward-compatible with the 13-core fallback
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 09:57:27 +01:00
zwitschi f121167e52 Record the arm-down bake fix and the remaining non-180° rest pose-composition task
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 09:52:13 +01:00
zwitschi 140a7ada0f Plan the 0.9.9-1.0.0 roadmap and re-evaluate the NPC base
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
Move the gap analysis and risks into PLAN.md, version the character track
(0.9.9 rig correction, 0.9.10 roster) and split 0.9.11-0.9.12 polish and
verification out of the old 0.10.0 milestone. Add roadmaps to 0.10.0, 0.11.0
and 1.0.0, record the Quaternius face-fidelity gate and the rest-relative
pose finding in MEMORY.md, and move the completed 0.9.7 and pose-harness
sections to DONE.md.
2026-11-06 09:40:28 +01:00
zwitschi 5508f5510f Record the Quaternius pose-sync blockers (GLB skinning + non-180° rest pose formula)
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 09:25:45 +01:00
zwitschi 7baa2c4f54 Record the Quaternius pose-sync finding (swing axis needs per-bone calibration)
Full build / Import + headless tests (push) Successful in 50s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 08:35:29 +01:00
zwitschi 0cd12af994 Fix proxy pivot derivation to use global rest (local rest was misplacing the Quaternius joints)
Full build / Import + headless tests (push) Successful in 55s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 08:35:14 +01:00
zwitschi 271f10ac4d Map NpcAvatar bone sync and proxy pivots to the Quaternius 62-joint names with old-rig fallback
Full build / Import + headless tests (push) Successful in 2m36s
Full build / Full desktop build + optional release (push) Skipped
2026-11-06 07:38:39 +01:00
zwitschi e2f52fbfd1 Accept ADR-0018 option 2: adopt the Quaternius 62-joint rig at runtime
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
2026-11-05 21:39:38 +01:00
zwitschi f5fa321a8f Draft ADR-0018 (Stage 2 runtime rig) and mark the remaining contract-bake items blocked
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-05 21:38:26 +01:00
zwitschi a2ae9e03c3 Bake the rest pose, warp to the contract, and record the failing silhouette gate
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped
2026-11-05 21:01:26 +01:00
zwitschi cff3937c16 Mark the 62-to-13 joint map complete
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-05 20:49:05 +01:00
zwitschi 1ed9e05d9b Author the 62-to-13 joint map and align the AGENTS.md contract with the Quaternius base
Full build / Import + headless tests (push) Successful in 50s
Full build / Full desktop build + optional release (push) Skipped
2026-11-05 20:48:55 +01:00
zwitschi 8c69f367b0 Record the owner directive: keep Quaternius at source detail during the contract bake
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
2026-11-05 20:47:08 +01:00
zwitschi 2a7921bae6 Build and document the canonical quaternius_base.glb (welded, skinned, 62-joint, 1.81m)
Full build / Import + headless tests (push) Successful in 44s
Full build / Full desktop build + optional release (push) Skipped
2026-11-05 20:43:50 +01:00
zwitschi b419923637 Decide the Quaternius outfit scope: three full GLBs first, modular builder as the role fallback
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
2026-11-05 20:41:23 +01:00
zwitschi c7fd4b59cc Inventory the Quaternius GLBs: meshes, 62 joints, 24 clips and the material-slot map
Full build / Import + headless tests (push) Successful in 55s
Full build / Full desktop build + optional release (push) Skipped
2026-11-05 20:40:59 +01:00
zwitschi 3fa3482608 Complete Quaternius intake record: verify SHA-256 and add the download date
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped
2026-11-05 20:39:39 +01:00
zwitschi 1049d8631e Compact MEMORY.md: drop the redundant session handoff, trim the AI-review entry 2026-11-05 20:38:26 +01:00
zwitschi deb74c53af Record the Quaternius pivot handoff in session memory 2026-11-05 19:44:26 +01:00
zwitschi 9968bc0a4c Rewrite NPC plans around the Quaternius base 2026-11-05 19:43:35 +01:00
zwitschi 2e1bb19e2c Correct the MPFB prototype glTF vertex count 2026-11-05 19:37:37 +01:00
zwitschi 5450dbf88a Correct the MPFB prototype glTF vertex count 2026-11-05 19:37:33 +01:00
zwitschi 79224c5acf Raise the NPC character budget to 24k and refine the prototype neck 2026-11-05 19:37:19 +01:00
zwitschi f0d4b81239 Record the AI-assisted asset review loop and MakeHuman OBJ transform gotchas in MEMORY.md
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-05 19:22:43 +01:00
zwitschi 48a9795248 Record the rejected prototype shirt-shell experiment 2026-11-04 21:31:45 +01:00
zwitschi a5e62534ce Fix the prototype crotch material and loosen the shirt 2026-11-04 21:26:00 +01:00
zwitschi cd7697cba4 Polish the MPFB prototype shoes, cuffs and shirt fit 2026-11-04 19:36:52 +01:00
zwitschi d2f773c7ad Polish the prototype shoes and thigh silhouette 2026-11-04 19:31:58 +01:00
zwitschi a3c3a37270 Smooth the prototype neck and shrink the shoes 2026-11-04 19:27:51 +01:00
zwitschi 8dd4f77901 Fix the MPFB prototype neck and ankle junctions 2026-11-04 19:22:22 +01:00
zwitschi ba444152c9 Record current MPFB body prototype measurement and neck review 2026-11-04 19:16:20 +01:00
zwitschi d56f14c6dd Document MPFB full-body NPC prototype status and seams 2026-11-04 18:53:19 +01:00
zwitschi b297c4543f Add MakeHuman head hair/body prototype and 12-variant rollout plan 2026-11-04 18:08:03 +01:00
zwitschi 958d3ae0ff Add reviewed MakeHuman head prototype (vision gate rejected) 2026-11-04 17:04:20 +01:00
zwitschi d1898b3221 Overhaul the head source: weld + relax the decimated MakeHuman head into one clean manifold shell
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 15:49:44 +01:00
zwitschi 2e1e4d7f70 Refine the MakeHuman head (weld lids/brows, add hair cap) and add the user review page
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 15:36:55 +01:00
zwitschi 0b2f124ffe Record the MakeHuman-head build outcome in the review doc and TODO
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 15:19:10 +01:00
zwitschi cdd2e02564 Add the MakeHuman-head character head source (head=mh) with project eyes/brows/mouth and CC0 attribution
Full build / Import + headless tests (push) Successful in 53s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 15:18:49 +01:00
zwitschi 632ec16aaa AI-driven head rework: judge picks the decimated MakeHuman head over the displacement shell
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 15:02:47 +01:00
zwitschi 30f240b591 Record the hand-gear verification status (open close-up done, gear context deferred to rollout)
Full build / Import + headless tests (push) Successful in 49s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:49:53 +01:00
zwitschi 58d1e79a12 Add the fused hand prototype (hand_l_proto/hand_r_proto) and record the finger-articulation decision
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:49:27 +01:00
zwitschi 44dba010a7 Prepare the head-prototype owner sign-off sheet (before/after at 1.85m)
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:47:25 +01:00
zwitschi 2a53ec9aac Move _head_proto into blender_assets.py and add assemble --head-proto; verify prototype weights
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:45:02 +01:00
zwitschi f0f609fe1b Add head subdivision variants and pick s48 after 1.5m/1.85m comparison
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:42:13 +01:00
zwitschi b386a21173 Finish the head prototype: forehead slope, brow step, jaw step, defined ear helix/lobe
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:39:31 +01:00
zwitschi 00bd197173 Refine the head prototype: flatten face plane, taper jaw, nose/chin relief, fix ear-displacement sign
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 42s
2026-11-04 14:36:53 +01:00
zwitschi 4f1eb1f5bf Fix the part-name test for the new head_proto part
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 46s
2026-11-04 14:23:33 +01:00
zwitschi 50edc47480 Add the connected-shell head prototype (head_proto) with sculpted sockets/nose/lips/ears
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:23:19 +01:00
zwitschi b8d8869e03 Add the local npc_parts.py head/face/hand part pipeline with tests and docs
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 41s
2026-11-04 14:17:20 +01:00
zwitschi 1b554df516 Record the tmp/npc-parts working-set layout decision
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:12:21 +01:00
zwitschi 5fdda88d46 Document the frozen npc_base fallback boundary
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:11:57 +01:00
zwitschi ecd50503e7 Define the measurable face/hands acceptance criteria for the model overhaul
Full build / Import + headless tests (push) Successful in 59s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:11:31 +01:00
zwitschi 2b6e774580 Record the NPC baseline inventory for the model overhaul (npc_base, 12 GLBs, builders, MakeHuman)
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:10:36 +01:00
zwitschi dee939c2ee Consolidate documentation into architecture and merged guides
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 14:06:23 +01:00
zwitschi aec1fb7b69 Raise character limits and document optional Blender MCP
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 13:47:16 +01:00
zwitschi 50a48d2256 Plan the Blender NPC head and hands overhaul
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 13:36:18 +01:00
zwitschi 69f4f11633 Date the 0.9.8 release
Full build / Full desktop build + optional release (push) Successful in 13m27s
Full build / Import + headless tests (push) Successful in 1m33s
2026-11-04 12:28:38 +01:00
zwitschi d1fa25629e Bump to 0.9.8 and stage the weights-fix changelog entry
Full build / Import + headless tests (push) Successful in 8m22s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 12:26:56 +01:00
zwitschi 359bb9a8c7 Fix the hip/thigh collapse and knee pinch: rigid thigh/shin with a short knee blend
Full build / Import + headless tests (push) Successful in 8m52s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 12:26:23 +01:00
zwitschi 36576a023a Fix the wrist/hand detachment: wrist tracks the hand bone, sleeve cuff follows the hand, hand raised to meet the wrist
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 12:11:37 +01:00
zwitschi c1ccfd6e72 Review the pose/weight evidence: wrist detachment + hip/knee flexion are the P1 fixes
Full build / Import + headless tests (push) Successful in 55s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 12:03:42 +01:00
zwitschi 27b8f9a249 Record the MakeHuman final-body swap and its remaining retopology refinement
Full build / Import + headless tests (push) Successful in 54s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 12:02:40 +01:00
zwitschi d5a2c911a6 Record the 0.9.7 release: QA, clean build, v0.9.7 tag
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 12:00:44 +01:00
111 changed files with 4516 additions and 1764 deletions

No files matched your search

@@ -1,6 +1,6 @@
--- ---
name: gig-line-blender-asset-contract name: gig-line-blender-asset-contract
description: Use when adding, rebuilding, auditing or fixing any Gig Line Blender-authored character, NPC base, gear, prop or venue geometry. Covers the 13-bone npc_base contract, five tint slots, triangle/vertex budgets, gear attach space, 256² texture/AO rules, headless flatpak Blender commands, and the inspect/audit/fit/proportion/capture gates. Do not use for pure GDScript/UI work or release publishing. description: Use when adding, rebuilding, auditing or fixing any Gig Line Blender-authored character, NPC base, gear, prop or venue geometry. Covers the required 13 core bones (optional extra bones allowed), five tint slots, 24,000/24,000 character budgets, gear attach space, 256² texture/AO rules, headless flatpak Blender commands, optional MCP guidance, and the inspect/audit/fit/proportion/capture gates. Do not use for pure GDScript/UI work or release publishing.
compatibility: Gig Line repository checkout. Godot 4.7.2 on PATH; Blender 5.2 LTS via the org.blender.Blender flatpak for build/bake/render. Audit, fit, inspect and proportion checks are stdlib-only. compatibility: Gig Line repository checkout. Godot 4.7.2 on PATH; Blender 5.2 LTS via the org.blender.Blender flatpak for build/bake/render. Audit, fit, inspect and proportion checks are stdlib-only.
metadata: metadata:
project: Gig Line project: Gig Line
@@ -21,9 +21,10 @@ Use this skill for any change that touches:
- Blender-authored venue/prop geometry - Blender-authored venue/prop geometry
The project contract and measured gates override any generic Blender advice. The project contract and measured gates override any generic Blender advice.
There is **no Blender MCP** in this project. Blender runs headless through the Blender MCP is optional interactive tooling; the headless flatpak CLI remains
flatpak CLI; the `blender-skills` pack is optional, local guidance only, and authoritative and CI never needs Blender or MCP. The `blender-skills` pack is
gitignored. optional, local guidance only, and gitignored. Setup and safety are in
`docs/asset-tooling.md#optional-interactive-blender-mcp`.
## Read first ## Read first
@@ -47,11 +48,11 @@ and resolve the source of truth before editing assets.
| Area | Rule | | Area | Rule |
| --- | --- | | --- | --- |
| Mesh root | `npc_base` mesh node, skinned under `Rig`; preserve the limb node names. | | Mesh root | `npc_base` mesh node, skinned under `Rig`; preserve the limb node names. |
| Bones | 13 contract bones: `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R`, `Spine`, `Neck` (parent/helper bones must not change the contract). | | Bones | The 13 required core bones: `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R`, `Spine`, `Neck`. Optional extra bones (fingers, face, toes) are allowed; they must not move the core rest positions or break animation proxies. |
| Tint slots | `skin`, `cloth`, `pants`, `accessory`, `hair`. | | Tint slots | `skin`, `cloth`, `pants`, `accessory`, `hair`. |
| Orientation | Front is Blender −Y / Godot +Z; feet grounded near y = 0; metres. | | Orientation | Front is Blender −Y / Godot +Z; feet grounded near y = 0; metres. |
| Library budget | **8,000 triangles / 8,000 glTF vertices** per character (`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS`). | | Character budget | **24,000 triangles / 24,000 glTF vertices** per character (`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS`). |
| Frozen fallback | `npc_base` keeps **6,000 tris / 4,000 verts**. Do not silently raise or lower it. | | Frozen fallback | `npc_base` remains the low-poly fallback; it shares the 24,000/24,000 cap but is not required to use it. Do not silently lower the cap. |
| Gear budget | **1,500 triangles** per gear piece; authored in attach-point space. | | Gear budget | **1,500 triangles** per gear piece; authored in attach-point space. |
| Prop budget | **4,000 triangles** per prop. | | Prop budget | **4,000 triangles** per prop. |
| Gear attach | Use the catalogued attach points; verify with `fit`. Runtime `GEAR_OFFSETS` (lanyard forward, firefighter helmet up) and `HAND_ITEM_FORWARD` are intentional, not asset-contract changes. | | Gear attach | Use the catalogued attach points; verify with `fit`. Runtime `GEAR_OFFSETS` (lanyard forward, firefighter helmet up) and `HAND_ITEM_FORWARD` are intentional, not asset-contract changes. |
@@ -113,8 +114,7 @@ only the specialists the task needs:
- budget/LOD/collision: `asset-optimization`, `lod-pipeline`, `collision-proxy` - budget/LOD/collision: `asset-optimization`, `lod-pipeline`, `collision-proxy`
- final review: `qa-review` - final review: `qa-review`
Translate any MCP instruction into a change to `tools/*.py` or a headless CLI If MCP is available, use it for interactive part exploration only; mirror the result into a scripted change or local `.blend`, then re-export and run the CLI audit/fit gates. Never make the build, CI, tests or runtime depend on MCP. Setup: `docs/asset-tooling.md#optional-interactive-blender-mcp`.
run. Never add an MCP dependency to the build, CI or runtime.
### 4. Build or bake ### 4. Build or bake
@@ -167,7 +167,7 @@ At minimum:
- `npc_proportions.py` passes the stylized guide. - `npc_proportions.py` passes the stylized guide.
- `npc_pipeline.py audit` passes if the base/textures changed. - `npc_pipeline.py audit` passes if the base/textures changed.
- texel density is inside the authored/fabric bands. - texel density is inside the authored/fabric bands.
- No character exceeds 8,000/8,000; gear/prop caps hold. - No character exceeds 24,000/24,000; gear/prop caps hold.
- 24-NPC budget and Web/Compatibility path remain green. - 24-NPC budget and Web/Compatibility path remain green.
- Contract tests, headless suite, capture gates and docs check pass. - Contract tests, headless suite, capture gates and docs check pass.
- The diff contains only the intended assets, tools, tests and docs. - The diff contains only the intended assets, tools, tests and docs.
@@ -197,12 +197,14 @@ gates run, and any accepted limit.
- [`references/asset-contract.md`](references/asset-contract.md) — full contract, - [`references/asset-contract.md`](references/asset-contract.md) — full contract,
inventory, file locations and provenance rules. inventory, file locations and provenance rules.
- `docs/asset-tooling.md#optional-interactive-blender-mcp` — optional MCP installation, connection, safety and
part-workflow instructions.
- [`references/verification.md`](references/verification.md) — command matrix, - [`references/verification.md`](references/verification.md) — command matrix,
capture gates, expected results and acceptance criteria. capture gates, expected results and acceptance criteria.
## Do not ## Do not
- Add an MCP server, Blender dependency or API call to the build/runtime. - Make the build, CI, tests or runtime depend on MCP, Blender or API calls.
- Change the bone/tint/contract to make a generic asset fit. - Change the bone/tint/contract to make a generic asset fit.
- Rewrite committed baselines or overwrite existing images. - Rewrite committed baselines or overwrite existing images.
- Commit `.blend` sources, raw captures or generated AI inputs. - Commit `.blend` sources, raw captures or generated AI inputs.
@@ -17,7 +17,7 @@ This is a working summary. The authoritative sources remain
## Skeleton and geometry ## Skeleton and geometry
The contract skeleton has 13 bones: The required contract skeleton has 13 core bones:
```text ```text
Head, Spine, Neck, Head, Spine, Neck,
@@ -28,6 +28,9 @@ ElbowL, ElbowR,
HandL, HandR HandL, HandR
``` ```
Optional extra bones (fingers, face controls, toes) are allowed if they do not
move these core rest positions and do not break `NpcAvatar`'s animation proxies.
Requirements: Requirements:
- `npc_base` mesh node exists and is skinned under `Rig`. - `npc_base` mesh node exists and is skinned under `Rig`.
@@ -56,8 +59,7 @@ skin, cloth, pants, accessory, hair
| Asset | Limit | | Asset | Limit |
| --- | --- | | --- | --- |
| Library character | 8,000 triangles / 8,000 glTF vertices | | Character (library and base fallback) | 24,000 triangles / 24,000 glTF vertices |
| `npc_base` fallback | 6,000 triangles / 4,000 vertices |
| Gear piece | 1,500 triangles | | Gear piece | 1,500 triangles |
| Prop | 4,000 triangles | | Prop | 4,000 triangles |
| Library prop/venue total | must stay inside the venue tests and library budget | | Library prop/venue total | must stay inside the venue tests and library budget |
@@ -24,6 +24,7 @@ Blender must be absolute.
| NPC base | `python3 tools/npc_pipeline.py audit` | exit 0, contract bones/slots/maps | | NPC base | `python3 tools/npc_pipeline.py audit` | exit 0, contract bones/slots/maps |
| Proportions | `python3 tools/npc_proportions.py report` | all eight inside the stylized guide | | Proportions | `python3 tools/npc_proportions.py report` | all eight inside the stylized guide |
| Single incoming GLB | `flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- inspect --file "$PWD/path.glb" --category <cat> --json` | contract result and measurements | | Single incoming GLB | `flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- inspect --file "$PWD/path.glb" --category <cat> --json` | contract result and measurements |
| Part GLB (local) | `flatpak run org.blender.Blender --background --python "$PWD/tools/npc_parts.py" -- inspect --file "$PWD/tmp/npc-parts/<variant>/<part>.glb"` | measurements; a part GLB is not a shipped asset |
| Prop audit | `python3 tools/model_pipeline.py audit` | exit 0 when generated props changed | | Prop audit | `python3 tools/model_pipeline.py audit` | exit 0 when generated props changed |
| Texture audit | `python3 tools/asset_pipeline.py audit assets/textures/ai/game_ready` | exit 0 when textures changed | | Texture audit | `python3 tools/asset_pipeline.py audit assets/textures/ai/game_ready` | exit 0 when textures changed |
@@ -98,6 +99,12 @@ without measuring.
## Visual review criteria ## Visual review criteria
The part pipeline (`tools/npc_parts.py`) builds/extracts head/face/hand parts under
`tmp/npc-parts/<variant>/`; these are local working files and are **not shipped**. Only the
assembled character GLBs, AO/maps, the manifest and the review sheets are committed. An
assembled GLB still goes through `blender_assets.py inspect`/`audit`/`fit` before any part is
accepted. The `npc_base.blend` fallback is frozen: never save prototype detail into it.
For each changed variant, inspect at least: For each changed variant, inspect at least:
- 2 m portrait: eyes, mouth, ears, neck/shoulder, hair/headgear clearance. - 2 m portrait: eyes, mouth, ears, neck/shoulder, hair/headgear clearance.
+1 -1
View File
@@ -20,7 +20,7 @@ publishing, and the full visual/QA gate. For a Blender/asset rebuild, use
1. `AGENTS.md` — release procedure and verification rules. 1. `AGENTS.md` — release procedure and verification rules.
2. `TODO.md` — current milestone, blockers and release exit criteria. 2. `TODO.md` — current milestone, blockers and release exit criteria.
3. `docs/RELEASING.md` — build/publish commands, artifact list, macOS policy. 3. `docs/RELEASING.md` — build/publish commands, artifact list, macOS policy.
4. `docs/smoke-checklist.md` — manual QA matrix. 4. `docs/release-qa.md` — manual QA matrix and balance protocol.
5. `docs/development-status.md`, `CHANGELOG.md`, `README.md` — current claims. 5. `docs/development-status.md`, `CHANGELOG.md`, `README.md` — current claims.
6. `MEMORY.md` — release gotchas, especially the clean-worktree rule. 6. `MEMORY.md` — release gotchas, especially the clean-worktree rule.
@@ -93,7 +93,7 @@ python3 tools/screen_designs.py judge --model auto
``` ```
Record model, confidence/verdict summary, `diff` count and any manual review. Record model, confidence/verdict summary, `diff` count and any manual review.
`docs/screen-design-report.md` is the durable record. `docs/ui-copy.md#screen-design-judge-report` is the durable record.
## 6. Realism judge (when visual assets/lighting changed) ## 6. Realism judge (when visual assets/lighting changed)
+23 -14
View File
@@ -17,11 +17,13 @@ The project should be developed in small, reviewable steps. Agents must keep pla
- `CHANGELOG.md` — The version history of user-facing changes. Keep an entry for each patch, minor, and major version. - `CHANGELOG.md` — The version history of user-facing changes. Keep an entry for each patch, minor, and major version.
- `docs/development-status.md` — The implemented-features list and what is not yet implemented. Update it whenever a feature lands, is renamed, or is removed. - `docs/development-status.md` — The implemented-features list and what is not yet implemented. Update it whenever a feature lands, is renamed, or is removed.
- `docs/architecture.md` — The arc42-style architecture documentation (goals, constraints, building blocks, runtime views, decisions, risks). - `docs/architecture.md` — The arc42-style architecture documentation (goals, constraints, building blocks, runtime views, decisions, risks).
- `docs/gameplay.md` — Gameplay systems, venues, and the escalation model. - `docs/gameplay.md` — Gameplay systems, tutorial, venues and the escalation model.
- `docs/asset-tooling.md` — Asset pipelines, model tools, and screenshot gates. - `docs/venues.md` — Real-site evidence, game mapping and venue simplifications.
- `docs/asset-tooling.md` — Asset pipelines, model tools, optional Blender MCP and screenshot gates.
- `docs/blender-assets.md` — Blender library operation and asset roles.
- `docs/npc-model-sourcing.md` — Character-source evaluation, licence rules and the character-development plan. - `docs/npc-model-sourcing.md` — Character-source evaluation, licence rules and the character-development plan.
- `docs/getting-started.md` — Setup, run/test commands, and the controls table. - `docs/getting-started.md` — Setup, run/test commands, and the controls table.
- `docs/smoke-checklist.md` — The manual smoke-test checklist. - `docs/release-qa.md` — The manual smoke checklist and balance-acceptance protocol.
- `docs/adr/` — Architecture Decision Records. - `docs/adr/` — Architecture Decision Records.
## Priority and Versioning ## Priority and Versioning
@@ -88,20 +90,27 @@ specialists the task needs:
### Project rules that override a skill ### Project rules that override a skill
1. **The project contract wins.** `npc_base` mesh node, the 13 contract bones at their rest 1. **The project contract wins.** `npc_base` mesh node, five tint slots, front on Blender −Y /
positions, five tint slots, front on Blender −Y / Godot +Z, grounded feet, 8,000 Godot +Z, grounded feet, 24,000 triangles / 24,000 glTF vertices per character, 1,500 per
triangles / 8,000 glTF vertices per character, 1,500 per gear, 4,000 per prop. A skill gear, 4,000 per prop. The shipped procedural library uses the 13 required core bones at their
may recommend another topology, naming or budget scheme; do not adopt it unless the rest positions; optional extra bones (fingers, face, toes) are allowed if they do not move the
change is explicitly planned and the contract, tests and docs are updated together. core rest positions. The one documented exception is the accepted Quaternius runtime path
2. **There is no Blender MCP here.** The skills assume MCP tools; this project runs Blender ([ADR-0018](docs/adr/0018-quaternius-stage2-runtime-rig.md)): it keeps the 62-joint source rig
as headless flatpak scripts (`tools/blender_assets.py`, `tools/npc_pipeline.py`). Translate at runtime while the 0.9.9 face/silhouette gate is open, and it must still supply the five tint
MCP steps into script changes or CLI runs. Never add an MCP dependency to the build, CI or slots, valid gear attach points and the same budgets. Both paths stay tested until that gate
runtime. closes. A skill may recommend another topology, naming or budget scheme; do not adopt it unless
the change is explicitly planned and the contract, tests and docs are updated together.
2. **Blender MCP is optional interactive tooling.** The headless flatpak scripts
(`tools/blender_assets.py`, `tools/npc_pipeline.py`) remain authoritative. MCP may be used
for local part exploration; setup and safety are in [`docs/asset-tooling.md#optional-interactive-blender-mcp`](docs/asset-tooling.md#optional-interactive-blender-mcp).
Never add an MCP dependency to the build, CI, tests or runtime, and always re-run the CLI
audit/fit gates on anything MCP touched.
3. **CI stays offline and skill-free.** Skills are local guidance only; every gate must still 3. **CI stays offline and skill-free.** Skills are local guidance only; every gate must still
work from a fresh checkout with committed `.glb`/`.png`/`.tres` files and Godot. work from a fresh checkout with committed `.glb`/`.png`/`.tres` files and Godot.
4. **No new asset source without the licence gate.** Skills may suggest reference packs or 4. **No new asset source without the licence gate.** Skills may suggest reference packs or
generated meshes; ADR-0015 (CC0, or CC-BY with attribution in `CREDITS.md`) and the generated meshes; ADR-0017 (the CC0 Quaternius NPC base) supersedes the generator-first
provenance rules in `docs/npc-model-sourcing.md` still apply. decision in ADR-0015, and the CC0/CC-BY rule in ADR-0015 (attribution in `CREDITS.md`) plus
the provenance rules in `docs/npc-model-sourcing.md` still apply to any new source.
5. **Evidence, not vibes.** After a skill-informed change, run `blender_assets.py inspect`, 5. **Evidence, not vibes.** After a skill-informed change, run `blender_assets.py inspect`,
`audit` and `fit` plus the relevant capture/smoke gates, and say in the handoff which `audit` and `fit` plus the relevant capture/smoke gates, and say in the handoff which
skills informed the change and what the measurements were. skills informed the change and what the measurements were.
+40
View File
@@ -4,6 +4,46 @@ All notable user-facing changes to Gig Line. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use
Semantic Versioning. Semantic Versioning.
## [Unreleased]
## [0.9.9] — 2026-11-06
### Changed
- Raised the character mesh budget from 16,000 to 24,000 triangles and glTF
vertices so the Quaternius base and the role-dressed variants fit without
heavy decimation. Gear and prop budgets are unchanged.
- Replaced the whole NPC roster with the CC0 Quaternius characters. All twelve
characters (tourist, jacket, coat, vest, dress, hoodie, firefighter, guard and
the four crowd variants) now use one of three source outfits — casual tee,
hoodie or work clothes — dressed by role with the five tint slots and the gear
library. The procedural library and the MakeHuman head remain in the build as
the fallback.
- Characters stand with their arms down instead of a T/A pose, and the seated,
idle and walking poses swing from the shoulder and hip the way the animation
code intends on the new skeleton.
- Head gear (cap, hood, sunglasses, firefighter helmet) and the camera were
resized for the new head and arm. The camera now hangs from a wrist strap
instead of a neck strap that floated next to the head.
- The backpack straps were pulled in so they sit on the chest instead of
hovering in front of it.
### Fixed
- Characters no longer render with black legs or black patches: the new base
carried a collapsed UV map that made the ambient-occlusion bake come out
black, and the roster is now unwrapped and baked correctly.
## [0.9.8] — 2026-11-04
### Fixed
- Characters no longer detach at the wrist: the wrist and hand now track the same
bone, the hand was raised to meet the forearm, and the sleeve cuff follows the
hand instead of the elbow.
- Seated and walking poses no longer collapse the hip/thigh or pinch the knee:
the thigh and shin are rigid to their bones with a short blend at the knee.
## [0.9.7] — 2026-11-04 ## [0.9.7] — 2026-11-04
### Added ### Added
+23
View File
@@ -15,6 +15,10 @@ been cleared for public distribution.
## 3D characters, gear and props ## 3D characters, gear and props
The local MakeHuman head prototype uses the CC0 MakeHuman/MPFB base mesh
(see [`docs/makehuman-head-review.md`](docs/makehuman-head-review.md)); it is a
development prototype, not a shipped character.
The character, gear and prop library is generated by project-owned scripts The character, gear and prop library is generated by project-owned scripts
(`tools/blender_assets.py`, `tools/npc_pipeline.py`, `tools/model_pipeline.py`) (`tools/blender_assets.py`, `tools/npc_pipeline.py`, `tools/model_pipeline.py`)
using Blender. The `.blend` working sources are developer artifacts and are not using Blender. The `.blend` working sources are developer artifacts and are not
@@ -40,6 +44,25 @@ kept outside version control.
The CC-BY preview images are included only as development evidence. Any future The CC-BY preview images are included only as development evidence. Any future
shipped derivative of CesiumMan or RiggedFigure must keep the attribution above. shipped derivative of CesiumMan or RiggedFigure must keep the attribution above.
### MakeHuman head source
The standalone MakeHuman head prototype is derived from the MakeHuman / MPFB base
mesh, **CC0** (`assets/models/blender_src/makehuman_head.glb`; source
<https://static.makehumancommunity.org/makehuman.html>, MakeHuman base mesh,
CC0 1.0). It is cut, retopologised and re-weighted by project scripts; the exported
character GLBs remain project assets. The development source files were removed
from `tmp/` after the 2026-11-05 pivot to Quaternius. The head is retained as a
tested fallback, not the active NPC base. See ADR-0017 and
`docs/npc-model-sourcing.md`.
### Quaternius NPC base source
The active NPC base migration uses **Quaternius Ultimate Modular Characters** and
**Ultimate Modular Women** (CC0 1.0, <https://quaternius.com/>). The three
researched GLBs are development inputs and are not shipped directly; the baked,
retargeted character GLBs are project assets. See ADR-0017 and
`docs/npc-model-sourcing.md` for the decision, source hashes and rollout status.
The venue geometry is authored procedurally in `src/game/world_builder.gd` as a The venue geometry is authored procedurally in `src/game/world_builder.gd` as a
stylized reconstruction. The Joke Shop is a fictional stand-in for the real stylized reconstruction. The Joke Shop is a fictional stand-in for the real
Sunset Strip block; the game does not claim endorsement by, or an exact replica Sunset Strip block; the game does not claim endorsement by, or an exact replica
+56 -7
View File
@@ -1343,7 +1343,7 @@ venues, and see enough authored variety that a second shift does not feel like a
- [x] Write the tutorial beat sheet: menu → movement/look → focus and target cycling → action - [x] Write the tutorial beat sheet: menu → movement/look → focus and target cycling → action
wheel → access inspection → polite direction → formal warning/body camera → radio → wheel → access inspection → polite direction → formal warning/body camera → radio →
shift review. Eight beats and a safe compliant visitor are specified in shift review. Eight beats and a safe compliant visitor are specified in
[`docs/tutorial.md`](docs/tutorial.md). [`docs/gameplay.md#first-shift-tutorial`](docs/gameplay.md#first-shift-tutorial).
- [x] Add a tutorial state machine independent of the normal shift FSM. `Tutorial` in - [x] Add a tutorial state machine independent of the normal shift FSM. `Tutorial` in
`src/game/tutorial.gd` owns the eight beats, accepts only the current beat's action, `src/game/tutorial.gd` owns the eight beats, accepts only the current beat's action,
supports skip/restart and has deterministic headless tests. GameManager/UI wiring starts supports skip/restart and has deterministic headless tests. GameManager/UI wiring starts
@@ -1544,7 +1544,7 @@ and supported by a stable CI/release baseline.
- [x] Existing scenario-content, tutorial and campaign smokes cover the venue/action/review paths; - [x] Existing scenario-content, tutorial and campaign smokes cover the venue/action/review paths;
the 0.9.5 release candidate adds the external first-hour pass. the 0.9.5 release candidate adds the external first-hour pass.
- [x] `docs/getting-started.md` and `docs/smoke-checklist.md` list tutorial, campaign/checkpoint, - [x] `docs/getting-started.md` and `docs/release-qa.md#manual-smoke-checklist` list tutorial, campaign/checkpoint,
session, scenario, audio, crowd, emergency, movement and performance smokes. session, scenario, audio, crowd, emergency, movement and performance smokes.
- [x] Current gallery/judge baselines are green; the 0.9.5 release candidate refreshes the final - [x] Current gallery/judge baselines are green; the 0.9.5 release candidate refreshes the final
gallery after content lock. gallery after content lock.
@@ -1589,7 +1589,7 @@ Goal: streamline the maps, refactor geometry to match the images in `docs/screen
- [x] Remove unnecessary or hidden geometry to improve performance. - [x] Remove unnecessary or hidden geometry to improve performance.
- [x] Ensure texture and material consistency across the map. - [x] Ensure texture and material consistency across the map.
Completed: the Hollywood venue is rebuilt from the real 6840 Hollywood Blvd references (six-column Masonic/El Capitan facade, eight steps, neighbours, Ovation arcade, Walk of Fame, four-lane street). The map-budget test measures 58,884 triangles across 409 meshes; Day/Night menu variants start in `EARLY`/`LATE`; the realism judge gaps are 0.148 (day) and 0.278 (late). See `docs/hollywood-boulevard-map.md`. Completed: the Hollywood venue is rebuilt from the real 6840 Hollywood Blvd references (six-column Masonic/El Capitan facade, eight steps, neighbours, Ovation arcade, Walk of Fame, four-lane street). The map-budget test measures 58,884 triangles across 409 meshes; Day/Night menu variants start in `EARLY`/`LATE`; the realism judge gaps are 0.148 (day) and 0.278 (late). See `docs/venues.md#hollywood-boulevard`.
### Map 2 — Joke Shop ### Map 2 — Joke Shop
@@ -1601,7 +1601,7 @@ Completed: the Hollywood venue is rebuilt from the real 6840 Hollywood Blvd refe
- [x] Remove unnecessary or hidden geometry to improve performance. - [x] Remove unnecessary or hidden geometry to improve performance.
- [x] Ensure texture and material consistency across the map. - [x] Ensure texture and material consistency across the map.
Completed 2026-11-02: the Joke Shop is rebuilt from the real Comedy Store block at 8433 Sunset Blvd (27 m black hall, signature canopy, circular blade sign, red curtains, west entrance, low-rise neighbours, dry hillside and opposite frontage). The map-budget test measures 22,680 triangles across 454 meshes. The day/late captures and real-site notes are in `docs/joke-shop-map.md` and `docs/screens/concepts/joke_shop/`; the final gallery passed 13 good / 0 bad and the screen-design judge (13/13 match, 0 diff), with a 0.454 realism gap on the photoreal night reference. Completed 2026-11-02: the Joke Shop is rebuilt from the real Comedy Store block at 8433 Sunset Blvd (27 m black hall, signature canopy, circular blade sign, red curtains, west entrance, low-rise neighbours, dry hillside and opposite frontage). The map-budget test measures 22,680 triangles across 454 meshes. The day/late captures and real-site notes are in `docs/venues.md#the-joke-shop` and `docs/screens/concepts/joke_shop/`; the final gallery passed 13 good / 0 bad and the screen-design judge (13/13 match, 0 diff), with a 0.454 realism gap on the photoreal night reference.
### Scenery Change (Day/Night) for Hollywood Boulevard ### Scenery Change (Day/Night) for Hollywood Boulevard
@@ -1720,9 +1720,9 @@ screen-design judge before any model/texture changes. No gameplay rules change.
- [x] Ran `tools/check_screenshots.sh` and `screen_designs.py compare`: 21 good, - [x] Ran `tools/check_screenshots.sh` and `screen_designs.py compare`: 21 good,
0 bad. 0 bad.
- [x] Ran the judge with automatic model selection: 21/21 match, 0 review, 0 diff. - [x] Ran the judge with automatic model selection: 21/21 match, 0 review, 0 diff.
- [x] Recorded the judge model and result in `docs/screen-design-report.md`; the - [x] Recorded the judge model and result in `docs/ui-copy.md#screen-design-judge-report`; the
generated captures remain local/gitignored. generated captures remain local/gitignored.
- [x] Add a short UI copy/button checklist to `docs/smoke-checklist.md`. - [x] Add a short UI copy/button checklist to `docs/release-qa.md#manual-smoke-checklist`.
- [x] Exit: all screens have consistent terminology and button behavior, long - [x] Exit: all screens have consistent terminology and button behavior, long
strings/scale pass, and the Python compare/judge gate has no unexplained strings/scale pass, and the Python compare/judge gate has no unexplained
diff. diff.
@@ -1768,7 +1768,7 @@ and equipment fit.
- [x] Audit all variants against a shared human proportion guide (head height, shoulder width, - [x] Audit all variants against a shared human proportion guide (head height, shoulder width,
torso length, hip width, limb length, foot length) and correct the worst outliers. torso length, hip width, limb length, foot length) and correct the worst outliers.
(`tools/npc_proportions.py` + `docs/npc-proportion-audit.md`: all eight pass; no skeleton or (`tools/npc_proportions.py` + `docs/npc-visual-review.md#proportion-audit`: all eight pass; no skeleton or
mesh rescue was needed. Head and broad-build silhouettes are nearest their upper bounds.) mesh rescue was needed. Head and broad-build silhouettes are nearest their upper bounds.)
- [x] Improve the neck and shoulder transition so the silhouette reads as one body rather than a - [x] Improve the neck and shoulder transition so the silhouette reads as one body rather than a
thin post on a hanger. (Fuller neck base/top, lower shoulder root, degenerate-face cleanup; thin post on a hanger. (Fuller neck base/top, lower shoulder root, degenerate-face cleanup;
@@ -2143,3 +2143,52 @@ must inherit the reviewed face/hand/gear baseline instead of multiplying whateve
project-owned clothing; keep out of CI/build.) project-owned clothing; keep out of CI/build.)
- [x] Body-shape diversity decision (2026-11-04, owner): **yes** — use MakeHuman for the next - [x] Body-shape diversity decision (2026-11-04, owner): **yes** — use MakeHuman for the next
character pass. Promoted to the **MakeHuman character pipeline** track. character pass. Promoted to the **MakeHuman character pipeline** track.
## NPC pose harness (v0.9.8)
Completed 2026-11-04. The pose harness bug that made every staged capture save a rest frame
was found and fixed; it unblocked the weights/pose evidence and the seated/walking deformation
fixes. User-facing changes are recorded under 0.9.7 and 0.9.8 in `CHANGELOG.md`.
Goal: `src/test/capture_npc_pose.gd` reports a pose but saves rest frames; the root cause is
narrowed to the skinned mesh ignoring its `Rig` parent transform. Fixing it unblocks the
weights/pose column of the 3D review; the evidence now feeds the Quaternius base bake.
*Skill route: `rigging`; evidence in `docs/3d-model-review.md` §"Pose evidence is blocked".*
- [x] Root cause found (2026-11-04): the harness never disabled the avatar's `_physics_process`,
so `_advance_fsm` + `_move_toward_target` ran every tick and reset `arrived`/velocity, which
made `theater_pose()` return "idle" and reverted the pose to rest. (The earlier "moving the
`Rig` child does not render" probe was a red herring: it set `rig.position.y` inside `_shoot()`
in the same frame as `root.get_texture().get_image()`, so it never got a rendered frame.)
- [x] Harness rebuilt: `capture_npc_pose.gd` now calls `set_physics_process(false)`/
`set_process(false)` after staging the avatar; the pose survives because only the direct
`_animate()` ticks drive it. Verified: sit vs idle diff 5.63, walk vs idle diff 2.29, and the
vision judge confirms the seated frame reads as crouched.
- [x] Captured all poses for all 12 variants (idle / walk_contact / walk_pass / sit × full +
shoulder views = 96 frames) and built `sheet_pose.jpg`/`sheet_shoulder.jpg` under `tmp/`.
(No separate "wave" theater pose exists; the escalation raise-hand gesture is not a distinct
capture in this harness.)
## Milestone 0.9.7 — Character development (patch)
Completed 2026-11-04 and published as v0.9.7. The detailed character-development record already
lives under "Character development" above; this milestone checklist is kept verbatim for the
release record.
Goal: release the finished procedural character development — the realistic face on all eight
variants, the four crowd-variety characters and the per-role pools — as a **patch** on top of
0.9.6. This keeps the 3D model and map improvements out of the patch; they land together in the
0.10.0 minor.
- [x] Rig pose harness fixed and the weights/pose evidence captured (see the top track).
- [x] Character work done: 12-character library, realistic face, crowd variants, per-role pools,
24-NPC budget 6.94 ms (152,464 tris / 168 meshes).
- [x] QA + local build for 0.9.7: Godot 277/277, Python 161/161, audit 49 OK, fit/proportions
12/12, release smoke matrix green, gallery 21/0, clean worktree build (Linux 172 MB,
Windows 206 MB, macOS 159 MB, Web 111 MB, HQ 266 MB), checksums verified, Linux binary
launches headless. Tagged `v0.9.7` (CI build/publish follows).
**Exit:** met — 0.9.7 ships the 12-character library (face realism + crowd variety) with the pose
harness fixed; the runtime contract is unchanged. The Quaternius NPC base and the map rebuild are
not in this patch (they target 0.10.0).
+122 -20
View File
@@ -10,6 +10,11 @@ canonical implemented-features list in `docs/development-status.md`.
the licence gate. Downloads live in `tmp/research/` (gitignored): DDG image search, street-view the licence gate. Downloads live in `tmp/research/` (gitignored): DDG image search, street-view
frames, neighbouring buildings — never committed, never shipped. Promotion into frames, neighbouring buildings — never committed, never shipped. Promotion into
`docs/screens/refs/` always passes the sidecar licence + privacy gate and `CREDITS.md`. `docs/screens/refs/` always passes the sidecar licence + privacy gate and `CREDITS.md`.
- **AI-assisted asset review loop (2026-11-04):** head/face shape decisions use a concept +
judge loop — generate a target concept (`generate_textures.generate_image`, Seedream) and use
`tools/describe_image.py` as the vision judge, iterating until the judge's specific fixes
converge. Review pages: `docs/screens/head-review/README.md`, `docs/3d-model-review.md`
§AI-driven head rework.
- **Project agent skills (2026-11-03):** two tracked skills live in `.agents/skills/`: - **Project agent skills (2026-11-03):** two tracked skills live in `.agents/skills/`:
`gig-line-blender-asset-contract` and `gig-line-release-qa`. They are guidance only, `gig-line-blender-asset-contract` and `gig-line-release-qa`. They are guidance only,
add no runtime dependency and are referenced from `tools/README.md`. The third-party add no runtime dependency and are referenced from `tools/README.md`. The third-party
@@ -19,7 +24,7 @@ canonical implemented-features list in `docs/development-status.md`.
restaged for a late-night arrival. `VENUE_SPECS["tv_studio"]` is a 30.0 m variant of the 29.6 m restaged for a late-night arrival. `VENUE_SPECS["tv_studio"]` is a 30.0 m variant of the 29.6 m
Hollywood facade. One reference board serves both venues; never create a separate TV Studio board. Hollywood facade. One reference board serves both venues; never create a separate TV Studio board.
- **Jimmy Kimmel Live = starting point:** the Hollywood first shift and the TV Studio arrival both - **Jimmy Kimmel Live = starting point:** the Hollywood first shift and the TV Studio arrival both
begin at the El Capitan block. Stated in `docs/hollywood-boulevard-map.md` and `docs/gameplay.md`. begin at the El Capitan block. Stated in `docs/venues.md#hollywood-boulevard` and `docs/gameplay.md`.
- **Street-view limitation:** Google Street View panoramas need a Google API key or manual browser - **Street-view limitation:** Google Street View panoramas need a Google API key or manual browser
capture, so the local reference sets use DDG street-level photos instead. capture, so the local reference sets use DDG street-level photos instead.
- **Image-recitation filter:** Gemini image models reject well-known branded facades (Comedy Store - **Image-recitation filter:** Gemini image models reject well-known branded facades (Comedy Store
@@ -45,22 +50,45 @@ canonical implemented-features list in `docs/development-status.md`.
(94 skills + shared `references/`), gitignored, kept out of Godot's scanner by (94 skills + shared `references/`), gitignored, kept out of Godot's scanner by
`.agents/.gdignore`. Keep the pack flat when updating (`cp -a <pack>/.claude/skills/. ...`). `.agents/.gdignore`. Keep the pack flat when updating (`cp -a <pack>/.claude/skills/. ...`).
Pi gates project `.agents/skills` behind project trust. Pi gates project `.agents/skills` behind project trust.
- **NPC model sourcing (ADR-0015):** CC0 acceptable for shipped assets; CC-BY only with
attribution in `CREDITS.md`; no service assets with redistribution restrictions. The procedural
generator is the primary path. Quaternius and MakeHuman were evaluated as references only, not
shipped bases (Quaternius: 62-bone T-pose rig mismatch; MakeHuman: needs retopology + custom
rig + clothing).
- **Gaussian splatting (unversioned):** offline/development-only until an ADR proves licence, - **Gaussian splatting (unversioned):** offline/development-only until an ADR proves licence,
privacy, Compatibility/Web cost and mesh collision. Accepted photo channels: team captures, privacy, Compatibility/Web cost and mesh collision. Accepted photo channels: team captures,
permissioned user/volunteer photos, clearly licensed downloads; every set needs a permissioned user/volunteer photos, clearly licensed downloads; every set needs a
provenance/consent sidecar. No runtime splat dependency in 0.10.0. provenance/consent sidecar. No runtime splat dependency in 0.10.0.
- **0.10.0 balance protocol:** `docs/balance-acceptance.md` defines targets/denominators for the - **0.10.0 balance protocol:** `docs/release-qa.md#balance-acceptance` defines targets/denominators for the
36 venue/difficulty/phase cells, seeds 1–100, and access/emergency error rates. Gotcha: 36 venue/difficulty/phase cells, seeds 1–100, and access/emergency error rates. Gotcha:
`_read_npc_truth()` is type-derived, not behaviour-derived; the emergency-fairness review must `_read_npc_truth()` is type-derived, not behaviour-derived; the emergency-fairness review must
account for that oracle mismatch. account for that oracle mismatch.
- **Character mesh cap:** eight library variants use 8,000 tris / 8,000 glTF vertices - **Blender MCP is optional (2026-11-04):** setup and safe part workflow are in
(`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in `tools/blender_assets.py`); the frozen `npc_base` fallback `docs/asset-tooling.md#optional-interactive-blender-mcp`. The headless flatpak scripts remain authoritative; MCP is
keeps 6,000/4,000. Measure after any geometry change; never silently lower the cap back to 5,000. never a build/CI/test/runtime dependency, and anything MCP touched must pass the
CLI audit/fit gates.
- **Character mesh cap (2026-11-05):** all characters use 24,000 tris / 24,000 glTF vertices
(`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in `tools/blender_assets.py`; `npc_pipeline.MAX_TRIANGLES`/
`MAX_VERTICES`). The required 13 core bones stay, optional extra bones are allowed, and the
low-poly `npc_base` fallback remains the shipped fallback. The raise funds the Quaternius base bake and
the 12-variant clothing detail. Measure the 24-NPC/Web budget after
any geometry change; never silently lower the cap without a new decision.
- **Quaternius is the NPC base (2026-11-05, owner):** ADR-0017 supersedes the generator-first
sourcing decision. The CC0 Quaternius Ultimate Modular character pack is the primary NPC mesh
and animation source. Stage 1 bakes a proportion-warped, 13-core-contract `npc_base` so
`NpcAvatar`, gear and animations keep working; the 62-joint/24-clip source rig is authoring
input, not a runtime dependency. If the warp fails the silhouette gate, Stage 2 needs a new ADR
for a 62-joint runtime, not a hybrid. The MakeHuman head and procedural `npc_base` remain
tested fallbacks until the Quaternius rollout ships. Quaternius source: CC0, attribution in
`CREDITS.md`; the three researched GLBs and licence notes are kept in
`tmp/npc-research/quaternius/`. MPFB/MakeHuman body work is closed; do not add new work there.
- **Quaternius bake keeps source detail (2026-11-05, owner):** during the contract bake (§2),
keep the Quaternius mesh as close to source as possible — no unnecessary decimation or LOD
reduction. The canonical base is only 3,199 verts / 6,206 tris, far under the 24,000 cap, so
there is no budget reason to decimate before the silhouette gate. Do not reuse the old
7,000-triangle or MPFB decimation targets.
- **Quaternius runtime rig (2026-11-05, owner, ADR-0018 option 2):** the Stage 1 proportion warp
failed the silhouette gate (arm ×1.63 / leg ×0.74 / torso ×1.61; vision judge: paddle hands,
lost forearms, stretched shins). The owner chose to **adopt the Quaternius 62-joint rig at
runtime** instead of warping: `NpcAvatar` maps the Quaternius joint names, gear attach points
are re-derived from the Quaternius skeleton (hip ≈0.87, shoulder ≈0.148/1.39, wrist ≈0.99,
knee ≈0.52), the mesh keeps source LOD (arm-down rest pose baked), and Idle/Walk play directly
on the 62-joint rig (no retarget).
- **Engine baseline:** Godot 4.7.2 stable (official editor `~/.local/bin/godot`). The system's - **Engine baseline:** Godot 4.7.2 stable (official editor `~/.local/bin/godot`). The system's
`godot-runner` cannot load arbitrary project paths (compiled without path overrides); use the `godot-runner` cannot load arbitrary project paths (compiled without path overrides); use the
official editor binary. official editor binary.
@@ -79,6 +107,26 @@ canonical implemented-features list in `docs/development-status.md`.
cleared, storefront/rating approval pending, external playtests and full hardware matrix pending. cleared, storefront/rating approval pending, external playtests and full hardware matrix pending.
Recorded in `CREDITS.md`, `PRIVACY.md`, `docs/accessibility.md`, `docs/known-limitations.md`. Recorded in `CREDITS.md`, `PRIVACY.md`, `docs/accessibility.md`, `docs/known-limitations.md`.
- **Release roadmap (2026-11-05, owner):** `0.9.9` Quaternius pilot base (rig/export fix,
rest-relative pose math, owner face/silhouette gate), `0.9.10` 12-role roster, `0.9.11`
scheduled venue/prop/lighting polish, `0.9.12` performance/Web/accessibility verification,
`0.10.0` public-readiness minor, then `0.10.1`–`0.10.3` fixes, `0.11.0` replay + fourth venue +
localization foundation, `0.12.0` signed/storefront builds + save v2, `0.13.x` content complete
and release candidates, `1.0.0` stable. The full gap analysis and roadmap live in `PLAN.md`; the
character track is versioned now, so a blocked rig cannot block the release. Planning only —
none of these versions is built yet.
- **Quaternius face/readability gate (2026-11-05):** the base is 6.2k tris total and replaces a
library with a sculpted realistic face, so the 0.9.9 gate is a product decision, not just an
art check. Owner picks full swap / crowd-only / extra head detail. Do not lower character
fidelity silently.
- **Pose composition is rest-relative (resolved 2026-11-05):** Godot's `set_bone_pose_rotation`
offsets from the bone rest, so the correct swing around a world axis is
`rest * Quaternion(rest.inverse() * axis, angle)`. For the old 180° rest this reduces to the
legacy `rest * Quaternion(axis, angle)`, so the 13-core fallback is unchanged (commit `8b1bf73`).
- **One character source, one fallback:** no new NPC source without an ADR. MPFB body work stays
closed, the Stage-1 proportion warp is not reopened, and the procedural `npc_base` + MakeHuman
head stay tested until the Quaternius base passes its gate.
## Gotchas / Known Issues ## Gotchas / Known Issues
- **Two character builders — do not confuse them.** `tools/blender_assets.py` is the real builder - **Two character builders — do not confuse them.** `tools/blender_assets.py` is the real builder
@@ -99,6 +147,44 @@ canonical implemented-features list in `docs/development-status.md`.
out of a portrait frame (park the rest of the crowd away instead). out of a portrait frame (park the rest of the crowd away instead).
- Blender: `bisect_plane` and thin bevels leave zero-area triangles (Godot "non-finite normal in - Blender: `bisect_plane` and thin bevels leave zero-area triangles (Godot "non-finite normal in
LOD generation"); `_clean()` + a weld after bisect fixes them and the audit rejects them. LOD generation"); `_clean()` + a weld after bisect fixes them and the audit rejects them.
- Quaternius arm-down bake (working recipe, `tmp/npc-research/bake_armdown_v5.py`): pose-rotate
`UpperArm.L/R` −90° local X → read the pose matrices → apply the Armature modifier → **reset the
pose to identity** → set each arm bone's rest matrix from its pose matrix → re-add the modifier →
export `export_apply=False`. Missing the pose reset double-applies the arm-down (mesh ±0.53
instead of ±0.23). `armature_apply` in Blender 5.2 is broken (deforms the mesh but does not bake
the rest). The canonical base must be **join-only** (`build_base_minimal.py`: no `remove_doubles`
weld, no scale/ground transform, keep the Armature modifiers) — the weld and the scale/ground
transform corrupt the skin weights (arm poses to ±0.51 instead of −0.23).
- Quaternius `NpcAvatar` wiring (commits `2a6f29f`, `cd285b3`): `RIG_BONES` +
`NpcAvatar.bone_name(joint, side)` hold the **only** side mapping — the Quaternius labels `L`/`R`
opposite the project, so proxy side 0 (`-x`) drives `UpperLeg.R`/`UpperArm.R` while side 1 drives
the `.L` bones. The arm/elbow swing axis is `Vector3.LEFT` (legs stay `RIGHT`). The hand proxy is
`global_wrist − global_elbow` (subtracting the parent-relative `elbow.position` misplaces
handheld gear). Old 13-core names resolve through the same table.
- **Bone posing must write the global pose** (`NpcAvatar._swing_bone`): `reset_bone_pose()` then
`set_bone_global_pose(Transform3D(Basis(swing * global_rot), current_origin))`, conjugated with
`get_bone_global_rest()`. Both rejected alternatives fail: the rest-relative
`set_bone_pose_rotation(rest * Quaternion(rest⁻¹·axis, angle))` swings about the parent-relative
rest (the glTF importer already folded the axis conversion into it) and splayed the Quaternius
arms sideways; the plain `rest⁻¹ * swing * rest` conjugation flips the limbs 180°. Verified by
measuring `get_bone_global_pose()` after the call — posed bone axis matches the proxy pivot at
dot 1.000 on the Quaternius and the procedural rigs render unchanged.
Two caveats: children need the parent's swing passed in (the write is absolute), and
`set_bone_global_pose()` is a **no-op outside the scene tree**, so the headless runner (tests run
in `_initialize()`) cannot assert on it.
- **The Quaternius source UV map is degenerate** — every loop at (0,0), flat-colour materials, no
atlas. The AO/texture bake silently produces a 100 % black map, which tints every AO'd slot
(skin, hair, accessory) black in Godot: a black leg is the symptom. `build_base_minimal.py` now
Smart-UV-unwraps and repacks the layout (texel density 0 → 73–74 px/m on 256²), and the base
pipeline is two explicit steps with distinct files: `build_base_minimal.py`
(`quaternius/Casual_Hoodie.glb` → `quaternius_tpose.glb`, joins without welding, renames the
materials to the five tint slots, unwraps) then `bake_armdown_v5.py` (`quaternius_tpose.glb` →
`quaternius_base.glb` + `assets/models/blender_src/npc_casual.blend`). Re-running v5 on its own
output double-bakes the rest.
- Hand gear must be self-contained: the camera carried a 0.42 m neck-strap loop that floats when the
item is parented to the wrist. Hand items get a short wrist strap, not a neck strap. The backpack
straps were authored for the wider procedural chest and floated ~5 cm proud of the Quaternius
torso; the chest points now sit at x 0.12 / Godot z 0.15, which fits both.
- Image models have per-model size floors/caps (Seedream needs 1920+; `GEN_SIZE=2048`, do not - Image models have per-model size floors/caps (Seedream needs 1920+; `GEN_SIZE=2048`, do not
lower; gpt-5-image-mini caps lower). `generate_image` applies model-specific sizing; concept lower; gpt-5-image-mini caps lower). `generate_image` applies model-specific sizing; concept
generation defaults to `--request-size 1536`. generation defaults to `--request-size 1536`.
@@ -163,6 +249,8 @@ canonical implemented-features list in `docs/development-status.md`.
- Character checks: `python3 tools/blender_assets.py inspect|audit|fit`, - Character checks: `python3 tools/blender_assets.py inspect|audit|fit`,
`python3 tools/npc_proportions.py` `python3 tools/npc_proportions.py`
- Docs: `python3 tools/check_docs.py` (every tracked image must be linked from its folder README) - Docs: `python3 tools/check_docs.py` (every tracked image must be linked from its folder README)
- Head/part pipeline: `python3 tools/npc_parts.py build|extract|assemble|inspect|sheet|compare`
(Blender subcommands run under flatpak). Head sources: `build_character(head="face"|"proto"|"mh")`.
- Reference research: `python3 tools/refcollect.py search <q> --slug <block> --limit N` - Reference research: `python3 tools/refcollect.py search <q> --slug <block> --limit N`
(local-only), `tools/refboard.py` (Commons), `tools/three_up.py compose/rows`, (local-only), `tools/refboard.py` (Commons), `tools/three_up.py compose/rows`,
`tools/scene_concepts.py generate/anchor/realism` `tools/scene_concepts.py generate/anchor/realism`
@@ -170,18 +258,32 @@ canonical implemented-features list in `docs/development-status.md`.
## Open Questions ## Open Questions
- Whether the responder avatar should be playable/controllable or purely scripted. - Whether the responder avatar should be playable/controllable or purely scripted.
- Localization target languages beyond English. - Quaternius scope: full 12-role swap, crowd-only, or extra head detail (owner decision at 0.9.9).
- macOS notarization approach for broad distribution. - Localization target languages beyond English (0.11.0/0.12.0).
- macOS notarization approach for broad distribution (0.12.0).
- Fourth-venue choice for 0.11.0 (candidate: TCL Chinese Theatre forecourt).
## Current State ## Current State
The playable build covers the three venues, tutorial/campaign/checkpoints, the 0.9.3–0.9.6 v0.9.8 is published. The build covers the three venues, the shift loop, tutorial/campaign/
polish/texture/integration passes and the Blender + realism asset pipeline (see `DONE.md` and checkpoints, the 0.9.3–0.9.8 polish/texture/integration/character passes and the Blender +
`CHANGELOG.md`). The 12-character library (realistic face + four crowd variants) and the realism asset pipeline (see `DONE.md` and `CHANGELOG.md`). The 12-character procedural library
Comedy Store map rebuild are complete. **Priority (2026-11-04, owner):** 1) release the finished procedural character development as (realistic face + four crowd variants) and the Comedy Store map rebuild are complete.
the **0.9.7 patch**, 2) finish the MakeHuman character + the 3D model and map improvements,
3) re-evaluate the 3D models, 4) ship it all together in the **0.10.0 minor** — `TODO.md` is the **Planning pivot (2026-11-05, owner):** the character and map work was re-sequenced into
priority source. Test counts as of 2026-11-04: Godot 277/277, patches (`0.9.9`–`0.9.12`) with `0.10.0` as the public-readiness minor. The gap analysis and the
Python 161/161, `check_docs` OK. three release roadmaps now live in `PLAN.md`; `TODO.md` is the executable task list. ADR-0017
Stage 1 (proportion warp) is dead; ADR-0018 option 2 (62-joint runtime) is accepted.
**Quaternius pilot (2026-11-05, commits `cd285b3`, `fb3e75a`, `90f189a`):** `npc_casual` ships as
the Quaternius base. It renders arm-down, carries the five tint slots, plays the Idle/Walk/Sit pose
code on the 62-joint rig, and the procedural rigs are visually unchanged. Verified: `inspect`
(no issues), `audit` OK (6,206 tris / 12,988 verts, 62 bones), `fit` head 0.201 m, texel density
73–74 px/m, `npc_proportions` measured, 24-NPC budget smoke OK (168 meshes, 162,304 tris,
0.82 ms avg frame). The face/silhouette gate sheet and the three options are in
`docs/screens/npc-face-gate/`; the **owner decision is the only thing left before the 0.9.9
rollout**.
Test counts as of 2026-11-05: Python 167/167, Godot 278/278, `check_docs` OK.
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu --> <!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
+189 -17
View File
@@ -16,20 +16,91 @@ The game is about **judgment, communication, access verification, and proportion
## Development Priorities ## Development Priorities
The active planning order is set in [`TODO.md`](TODO.md) and in `AGENTS.md`: The active task order is in [`TODO.md`](TODO.md); the gap analysis and the release roadmap are
below. The current order is:
1. **Character development (unversioned).** Expand the owned procedural character library with 1. **0.9.9–0.9.12 patches.** Finish the character base (the accepted Quaternius 62-joint runtime),
crowd, age, build, hair and outfit variety. Keep the frozen bone/tint/gear contract; see roll it out to the 12 roles, close the scheduled venue/prop/lighting polish, and verify
[ADR-0015](docs/adr/0015-npc-model-variety-sourcing.md) and performance, Web and accessibility.
[`docs/npc-model-sourcing.md`](docs/npc-model-sourcing.md). Quaternius is a reference; 2. **0.10.0 minor.** Public readiness: external playtests, balance acceptance, legal/storefront
MakeHuman is deferred to a separate local pipeline. approval, the platform matrix and the documentation/picture lock.
2. **Local map development (unversioned).** Move beyond stylized venue reconstruction with 3. **0.10.x patches → 0.11.0.** Content and systems depth: live body-camera replay, a fourth
licensed OpenStreetMap and 3D building data, downloaded reference imagery for photoreal venue and the localization foundation.
facades, AI-assisted composition, and a Gaussian-splatting evaluation that stays in the 4. **0.12.0 → 1.0.0.** Platform/distribution, content complete, release candidates and the stable
development pipeline unless it proves runtime-safe. release.
3. **Versioned release hardening.** The 0.10.0 release candidate and later semantic versions come
after the unversioned development tracks in planning order. Versions are assigned only when a Versions are assigned only when a track's scope is accepted into a release (see
track's scope is accepted into a release. [Release Roadmap](#release-roadmap)). Unversioned tracks — the 3D review evidence base,
maintenance and the Gaussian-splatting study — do not receive a placeholder version.
## Plan Evaluation (2026-11-05)
The game plan itself is sound and largely built: three venues, the escalation ladder, access
artifacts, the radio/report system, the shift review and the campaign loop all exist and are
tested. What needed fixing was the **delivery plan**, not the design.
- **Character sourcing churned.** The project moved procedural → MakeHuman → Quaternius, and the
Quaternius Stage-1 warp failed its own gate. That leaves a correct decision (ADR-0018) with a
half-finished implementation. The plan now makes the rig correction one small version, gates the
roster behind a face/readability decision, and keeps exactly one fallback.
- **One milestone carried too much.** The old 0.10.0 mixed character work, map work and every
public-release gate, so one blocked export blocked the release. Character and venue polish are
now patches; 0.10.0 is only the release gate.
- **Unversioned work had no deadline.** The 3D review backlog could grow without bound. It is now
capped: the ranked venue/prop/lighting items are scheduled into 0.9.11, the budget checks into
0.9.12, and everything else is an explicit accept or deferred. Gaussian splatting stays
unscheduled because no product need justifies its risk yet.
- **Legal, signing and storefront work is release work, not polish.** It now has named checkboxes
in 0.10.0 and 1.0.0 instead of living in prose.
- **What is deliberately not planned:** new genres or mechanics, a live-service model, or a
runtime splat renderer. The de-escalation-first, rare-emergency design stays frozen.
Feasibility: the remaining character work is small in geometry terms (6.2k triangles against a
24k cap) and larger in rig/export terms; the venue work is polish; most release gates are
evidence gathering rather than engineering. The main schedule risk is external review and
approval, not code.
## Character Creation Re-Evaluation (2026-11-05)
**Where it stands.** The shipped library is 12 procedural Blender variants (4.7k–5.8k triangles
each) with a sculpted face, five tint slots and 18 gear pieces. The owner then chose the CC0
**Quaternius Ultimate Modular Characters** as the primary source. Stage 1 (warp the Quaternius
mesh onto the 13-core contract) failed its silhouette gate — arm ×1.63, leg ×0.74, torso ×1.61,
judged distorted. [ADR-0018](docs/adr/0018-quaternius-stage2-runtime-rig.md) therefore adopts the
source **62-joint rig at runtime**: no warp, source LOD (3,199 verts / 6,206 tris), source
Idle/Walk clips, and gear pivots re-derived from the Quaternius skeleton.
**What is already built.** The canonical `quaternius_base.glb` (welded, grounded, arm-down rest),
the 62→13 joint map, the material→five-slot map, the clip inventory (24 clips; Idle and Walk in
scope) and the `NpcAvatar` joint-name mapping with rest-derived gear pivots.
**What actually blocks it.** Two technical items about the export and the pose math, not the art:
1. the arm-down GLB renders in A-pose in Godot because the bake writes wrong inverse bind
matrices; and
2. `_pose_bone()` composes rotations as `rest * Quaternion(axis, angle)`, which only cancels for
the old 180° rest, not the Quaternius ~76° rest.
**Re-evaluation findings.**
- Triangle budget is not the problem (6.2k against a 24k cap). Do not decimate the source.
- **Face fidelity is the real risk.** The 0.9.7 library has a sculpted realistic face; the
Quaternius head is low-poly. A blanket swap would regress every close-up. The plan therefore
makes the owner choose at 0.9.9 between (a) a full swap, (b) Quaternius for crowd roles with the
procedural library for named roles, or (c) extra head subdivision/hair cards on the Quaternius
head.
- **Animation** becomes clip-driven, which removes the hand-built pose composition as the main
path. Poses the clips do not cover (seated, raise-hand, head turn) must be decided explicitly
rather than patched per scene.
- **One fallback, no third source.** The procedural `npc_base` and the MakeHuman head stay tested
until the new base passes the gate. MPFB body work stays closed and the Stage-1 warp is not
reopened. A new character source needs a new ADR.
- **Sequence matters.** Fix the rig first (0.9.9), then the roster (0.9.10). Rolling a broken rig
across 12 variants would waste the whole pass.
**Reversal condition.** If the skin cannot be exported correctly without a new exporter path, or
if the owner rejects the low-poly face in every option, the plan falls back to the procedural
library and retires Quaternius with a recorded ADR. That decision is made at 0.9.9, not later.
## Core Gameplay Loop ## Core Gameplay Loop
@@ -169,7 +240,7 @@ The action wheel prioritizes de-escalation: Stop, Look, Point, No, Ask, Inspect,
The visual direction is readable stylized realism: The visual direction is readable stylized realism:
- **Character direction:** 8 skinned NPC variants, 18 gear pieces and 20 street/venue props, with four crowd-variety variants prototyped. The generator-first expansion is the active character track ([ADR-0015](docs/adr/0015-npc-model-variety-sourcing.md)); Quaternius is a reference and MakeHuman is deferred to a local pipeline. - **Character direction:** 12 skinned NPC variants, 18 gear pieces and 20 street/venue props. The active character track is the Quaternius base at runtime ([ADR-0018](docs/adr/0018-quaternius-stage2-runtime-rig.md)): the CC0 source mesh keeps its proportions on the 62-joint rig, Idle/Walk run as clips, and the procedural library stays as the tested fallback until the face/silhouette gate passes. See [Character Creation Re-Evaluation](#character-creation-re-evaluation-2026-11-05).
- **Realism pass:** AI-assisted per-phase skyline panoramas, v2 surface tiles with normal maps, wet asphalt at night, a warm day haze, phase-specific sun/exposure/fog, a tight flashlight spot (no beam geometry), and denser foliage. - **Realism pass:** AI-assisted per-phase skyline panoramas, v2 surface tiles with normal maps, wet asphalt at night, a warm day haze, phase-specific sun/exposure/fog, a tight flashlight spot (no beam geometry), and denser foliage.
- **Local map direction:** the venues are currently stylized reconstructions. The local-map track evaluates licensed OpenStreetMap/3D building data, licensed reference photography, AI-assisted composition and Gaussian splatting as a development tool; online data and splat rendering are not runtime dependencies. - **Local map direction:** the venues are currently stylized reconstructions. The local-map track evaluates licensed OpenStreetMap/3D building data, licensed reference photography, AI-assisted composition and Gaussian splatting as a development tool; online data and splat rendering are not runtime dependencies.
- **References and QA:** runtime UI art lives in `assets/images/ui/`; reference/art-direction sets live in `docs/screens/designs/` and `docs/screens/concepts/`; captures live in `docs/screenshots/`; realism runs live in `docs/screens/realism/`. `screen_designs.py compare/judge` and `tools/check_screenshots.sh` guard drift. - **References and QA:** runtime UI art lives in `assets/images/ui/`; reference/art-direction sets live in `docs/screens/designs/` and `docs/screens/concepts/`; captures live in `docs/screenshots/`; realism runs live in `docs/screens/realism/`. `screen_designs.py compare/judge` and `tools/check_screenshots.sh` guard drift.
@@ -177,7 +248,7 @@ The visual direction is readable stylized realism:
The active planning order — unversioned character and local-map development first, then the versioned 0.10.0 release-candidate hardening and later releases — is tracked in [`TODO.md`](TODO.md); this plan stays at the product/design level. The active planning order — unversioned character and local-map development first, then the versioned 0.10.0 release-candidate hardening and later releases — is tracked in [`TODO.md`](TODO.md); this plan stays at the product/design level.
Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [ADR-0008](docs/adr/0008-cone-focus-and-head-ring.md), [ADR-0009](docs/adr/0009-radial-action-menu.md), [ADR-0010](docs/adr/0010-procedural-npc-presentation.md), [ADR-0011](docs/adr/0011-aim-free-selection-and-precision-walk.md), [ADR-0012](docs/adr/0012-surface-materials-and-generated-props.md), [ADR-0013](docs/adr/0013-blender-authored-npc-characters.md), [ADR-0015](docs/adr/0015-npc-model-variety-sourcing.md). Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [ADR-0008](docs/adr/0008-cone-focus-and-head-ring.md), [ADR-0009](docs/adr/0009-radial-action-menu.md), [ADR-0010](docs/adr/0010-procedural-npc-presentation.md), [ADR-0011](docs/adr/0011-aim-free-selection-and-precision-walk.md), [ADR-0012](docs/adr/0012-surface-materials-and-generated-props.md), [ADR-0013](docs/adr/0013-blender-authored-npc-characters.md), [ADR-0015](docs/adr/0015-npc-model-variety-sourcing.md), [ADR-0017](docs/adr/0017-quaternius-npc-base.md).
## Tech Stack ## Tech Stack
@@ -194,7 +265,107 @@ Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [AD
## Versioning and Delivery ## Versioning and Delivery
`config/version` in `project.godot` is the single version source (currently `0.10.0-rc.1`). Releases are tagged `v<version>`; the CI pipeline builds and publishes the standard artifacts plus the optional Desktop HQ zip. Version bumps follow [Semantic Versioning](https://semver.org/): every major, minor and patch increment needs a changelog entry, a local `tools/build_releases.sh` build, and artifact/checksum verification before tagging. Unversioned development tracks do not receive a version until their scope enters a release. See [`docs/RELEASING.md`](docs/RELEASING.md) and [`README.md`](README.md) for commands. `config/version` in `project.godot` is the single version source (currently `0.9.8`). Releases are tagged `v<version>`; the CI pipeline builds and publishes the standard artifacts plus the optional Desktop HQ zip. Version bumps follow [Semantic Versioning](https://semver.org/): every major, minor and patch increment needs a changelog entry, a local `tools/build_releases.sh` build, and artifact/checksum verification before tagging. Unversioned development tracks do not receive a version until their scope enters a release. See [`docs/RELEASING.md`](docs/RELEASING.md) and [`README.md`](README.md) for commands.
## Gap Analysis
**Published baseline:** v0.9.8 (2026-11-04) — three playable venues, tutorial/campaign/checkpoint
resume, the access/radio/escalation loop, shift review, the 12-character Blender library, 277
Godot + 162 Python tests, a 21/21 screen-design judge and Compatibility/Web/Desktop HQ builds.
**Current source:** 0.9.8. The 0.10.0-rc.1 package built on 2026-11-03 is stale and is superseded
by the patch sequence below; it is not a published release.
| Area | Remaining gap | Milestone |
| --- | --- | --- |
| Character base | Quaternius arm-down skin export, rest-relative pose composition, face/silhouette acceptance | 0.9.9 |
| Character roster | 12-role mapping, modular outfits, gear fit, AO/texels, budget evidence | 0.9.10 |
| Venue and asset polish | Ranked prop/foliage/lighting/set-dressing fixes, collider verification | 0.9.11 |
| Performance and Web | Frame/memory/load evidence after the content changes, LOD/impostor verification | 0.9.12 |
| Onboarding and balance | External first-time-player evidence, balance sweeps/tuning, content lock | 0.10.0 |
| Accessibility and localization | Acceptance pass, caption coverage, pseudo-locale/length checks | 0.9.12 / 0.10.0 |
| QA and release engineering | Full smoke matrix, clean build/checksum verification, target-matrix launches | 0.10.0 |
| Legal, signing, storefront | macOS signing/notarization, AI-provider rights, storefront/rating approval, privacy sign-off | 0.10.0 / 0.12.0 |
| Systems and content depth | Live incident replay, fourth venue, localization foundation | 0.11.0 |
| Distribution | Signed storefront builds, frozen save contract, launch languages, hardware matrix | 0.12.0 |
| Stable release | Content complete, accessibility audit, release candidates, 1.0.0 decision | 0.13.0 / 1.0.0 |
| Future | More venues/seasonal content, image-to-mesh props, other deferred ideas | Unscheduled |
The 0.9.3 UI, 0.9.4 animation, 0.9.5 texture, 0.9.6 integration, 0.9.7 character development and
0.9.8 deformation fixes are complete in [`DONE.md`](DONE.md); they are not remaining gaps. The
2026-11-01 documentation audit is also closed.
## Release Roadmap
Every version below follows the release procedure in `AGENTS.md`. Patch versions contain fixes and
polish; minor versions add user-facing features; the major version is the stable release with a
compatibility promise. Each version's executable checklist is in [`TODO.md`](TODO.md).
### Roadmap to 0.10.0
The goal of this line is to finish the character and venue work and clear every public-release
gate. The character track is versioned rather than unversioned so a blocked rig cannot hold the
whole release.
| Version | Type | Scope | Exit criterion |
| --- | --- | --- | --- |
| 0.9.9 | patch | Quaternius pilot: correct skin export, rest-relative pose math, Idle/Walk verification, owner face/silhouette gate, one-role (or crowd-only) rollout | A Quaternius NPC renders and animates correctly and ships behind a tested procedural fallback |
| 0.9.10 | patch | 12-role roster, modular outfits, five slots, AO/texels, gear fit, 12×6 baseline | The full roster passes contract, gear, budget and owner-baseline gates |
| 0.9.11 | patch | Scheduled 3D review fixes: props/foliage, gear silhouette, venue shape language, lighting, set dressing, colliders | Every scheduled P1/P2 item is fixed or accepted with a measurement |
| 0.9.12 | patch | Performance/Web/LOD/memory verification plus accessibility and pseudo-locale checks | No regression against the pre-character baselines |
| 0.10.0 | minor | Public readiness: external playtests, balance acceptance, legal/storefront/signing decisions, platform matrix, docs and picture lock | 0.10.0 is content-locked, externally played, legally reviewed, documented and reproducibly built |
### Roadmap to 0.11.0
This line turns a stable 0.10.x into a deeper game: the incident timeline becomes a live replay, a
fourth venue enters the campaign, and the text catalogue becomes a real localization pipeline.
| Version | Type | Scope | Exit criterion |
| --- | --- | --- | --- |
| 0.10.1 | patch | Onboarding and accessibility issues deferred from the 0.10.0 playtests | First-hour and accessibility defects closed |
| 0.10.2 | patch | Balance/fairness sweeps, review-text and difficulty-progression tuning | Every balance-acceptance cell is in target or explicitly accepted |
| 0.10.3 | patch | Save/checkpoint edge cases, gamepad/remap, pause/resume, small quality-of-life fixes | No known P1 stability defect remains |
| 0.11.0 | minor | Live body-camera replay, fourth venue (candidate: TCL Chinese Theatre forecourt), localization foundation with one secondary language | The new systems ship without regressing performance, accessibility or balance |
### Roadmap to 1.0.0
This line is about distribution, contracts and completeness: signed builds on the launch stores, a
frozen save format, finished content and release candidates.
| Version | Type | Scope | Exit criterion |
| --- | --- | --- | --- |
| 0.11.1 | patch | Replay/venue/localization fixes from wider play | No P1 defect in the 0.11.0 feature set |
| 0.11.2 | patch | New-venue balance, campaign pacing and content readability | The 0.11.x line is stable enough to freeze its contracts |
| 0.12.0 | minor | macOS signing/notarization, storefront packages/metadata/ratings, save-format v2 freeze, hardware matrix, launch languages | Signed, storefront-ready builds exist on every launch platform |
| 0.12.1 | patch | Signing, installer, metadata and save-migration fixes from storefront dry runs | Every storefront build installs, launches and migrates cleanly |
| 0.13.0 | minor | Content complete: all planned venues/events/dialogue, audio decision, final tutorial, accessibility audit, balance freeze | All planned content is implemented and frozen |
| 0.13.1 | patch | Release candidate 1: full smoke/performance/save/accessibility matrix | A candidate passes every gate except final approval |
| 0.13.2 | patch | Release candidate 2: verify fixes, freeze release notes/credits/privacy/known limitations | The final candidate is reproducible and documented |
| 1.0.0 | major | Stable release: legal/storefront/rating approval, hardware/accessibility/localization sign-off, no P0/P1, publish on every platform | 1.0.0 is published with verified checksums and no open blocker |
## Roadmap Dependencies and Risks
- Content volume is the largest schedule risk; tutorial and campaign work must not outrun the
event/dialogue matrix.
- The vision judge is a dev-time aid with an external key and budget; CI must never make it a hard
gate.
- Godot engine changes can alter lighting and imports; do not combine an engine baseline move with
a content or feature milestone.
- Signed macOS builds, AI provenance and storefront approval are release blockers, not polish.
- The character pipeline is the current critical path. The rig/export fix must be proven on one
variant before the roster rollout; otherwise 0.9.10 multiplies a defect.
- Photo-derived map assets add a provenance risk: downloaded or user-supplied photos can carry
unclear licences, identifiable people, plates and property restrictions. No photo- or
splat-derived asset ships without a completed sidecar, consent/licence review and redaction.
- Gaussian-splat training is GPU/disk-heavy and the toolchain churns; keep it offline,
version-pinned and outside CI, and never make a runtime splat renderer a release dependency.
- The Blender skill pack is local, unversioned guidance; it can drift from the project contract or
assume Blender MCP. Re-verify every adopted technique with `inspect`/`audit`/`fit` and never make
a skill, asset pack or tool version a build or CI dependency.
- The 3D re-evaluation can expand without bound. Cap it with the scorecard ranking: P0/P1 only, one
asset family per commit, and an explicit accept for everything else.
- Keep the de-escalation-first rules and rare-emergency design unchanged while adding progression,
difficulty and feedback.
## Design Direction ## Design Direction
@@ -206,4 +377,5 @@ The game should feel like a tense workplace simulation with occasional crisis mo
- A rare Bob Dobbs cameo (Easter-egg appearance; placement note in `MEMORY.md`). - A rare Bob Dobbs cameo (Easter-egg appearance; placement note in `MEMORY.md`).
- Specific unusual NPC behaviors, such as slapping their own face, using hands as scissors, or interacting with props unexpectedly. - Specific unusual NPC behaviors, such as slapping their own face, using hands as scissors, or interacting with props unexpectedly.
- Live 3D body-camera replay; additional venues or seasonal events; real voice/dialogue audio; an image-to-mesh prop pipeline. - A fifth venue or seasonal events 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; an image-to-mesh prop pipeline remains unscheduled.
+33 -31
View File
@@ -8,11 +8,12 @@ The central design goal is simple:
## Development Priorities ## Development Priorities
The active planning order lives in [`TODO.md`](TODO.md) and [`AGENTS.md`](AGENTS.md): The active planning order lives in [`PLAN.md`](PLAN.md) (gap analysis and release roadmap), [`TODO.md`](TODO.md) (executable checklist) and [`AGENTS.md`](AGENTS.md):
1. **3D model and local map re-evaluation** — score every shipped asset, then evaluate licensed OpenStreetMap/3D data, reference-derived photoreal facades, AI-assisted composition and a Gaussian-splatting development evaluation. 1. **`0.9.9`–`0.9.12` patches** — finish the Quaternius NPC base on the accepted 62-joint runtime, roll it out to the 12 roles, close the scheduled venue/prop/lighting polish, then verify performance, Web and accessibility.
2. **Character development** — crowd, age, build, hair and outfit variety on the owned procedural library, keeping the frozen NPC contract. Source evaluation and the licence decision are in [`docs/npc-model-sourcing.md`](docs/npc-model-sourcing.md). 2. **`0.10.0` minor** — public readiness: external playtests, balance acceptance, legal/storefront/signing decisions, the platform matrix and the documentation/picture lock.
3. **Versioned release hardening** — the 0.10.0 release candidate and later semantic versions follow the unversioned development tracks. 3. **`0.10.x` → `0.11.0`** — live incident replay, a fourth venue and the localization foundation.
4. **`0.12.0` → `1.0.0`** — signed storefront builds, a frozen save contract, content complete, release candidates and the stable release.
Versions are assigned only when a track's scope is accepted into a release. Every major, minor and patch version requires a changelog entry, a local release build and artifact verification before tagging. Versions are assigned only when a track's scope is accepted into a release. Every major, minor and patch version requires a changelog entry, a local release build and artifact verification before tagging.
@@ -33,14 +34,17 @@ Versions are assigned only when a track's scope is accepted into a release. Ever
> Downloads are public (no sign-in required) since the host's instance-wide > Downloads are public (no sign-in required) since the host's instance-wide
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31. > `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
> >
> **Latest published: v0.9.6** — the integration checkpoint: the 0.9.5 texture > **Latest published: v0.9.9** — the character-roster patch: all twelve NPCs now
> and material refinement (256² NPC maps, a four-times fabric weave without baked > use the CC0 Quaternius base with the five tint slots, role gear and baked maps.
> AO blotches, repaired Walk of Fame/awning surfaces and per-phase skyline haze) > Superseded: v0.9.8, the character-development patch: the
> is packaged with the v0.9.4 NPC visual optimization, the Hollywood/Joke Shop > 12-character library, realistic face pass, four crowd-variety variants and the
> maps and the v0.9.3 UI/accessibility polish, with the full gallery, smoke matrix > wrist/hip/knee deformation fixes. Previous 0.9.x patches added the
> and Web/Desktop HQ build re-verified. > Hollywood/Joke Shop maps, UI/accessibility polish, NPC visual optimization,
> texture/material refinement and integration checkpoint.
> >
> **Current source: v0.10.0-rc.1** — the release-candidate source; the final tag is pending external playtest and legal review. > **Current source: v0.9.8** — tagged from the same commit. The 0.10.0
> development track remains gated on the Quaternius NPC rollout, local-map work
> and release approvals.
> Releases are built automatically when a `vX.Y.Z` tag is pushed > Releases are built automatically when a `vX.Y.Z` tag is pushed
> (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use > (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use
> `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline > `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline
@@ -94,12 +98,14 @@ Every capture is rendered from the current build by [`tools/check_screenshots.sh
### 3D asset progress ### 3D asset progress
The character, gear and prop libraries are authored procedurally in Blender and audited before use; The character roster is built from the CC0 Quaternius bases (ADR-0017/ADR-0018) and audited
the sheets below track their visual progress. before use; the procedural library and the MakeHuman head stay as the tested fallback. The
sheets below show the
current library and will be replaced after the 12 × 6 baseline review.
| | | | | |
| --- | --- | | --- | --- |
| ![Eight NPC characters](docs/blender_previews/sheet_npc.jpg) | **Character library.** Eight skinned variants (4.5–5.5k triangles each) with five tint slots, attach-point gear and baked maps. | | ![Eight NPC characters](docs/blender_previews/sheet_npc.jpg) | **Character library.** Twelve role-dressed variants (4.5–6.4k triangles each) on three CC0 Quaternius bases, with five tint slots, attach-point gear and baked maps. |
| ![Prop library](docs/blender_previews/sheet_props.jpg) | **Street and venue props.** Twenty library props with colliders, navigation avoidance and colour atlases. | | ![Prop library](docs/blender_previews/sheet_props.jpg) | **Street and venue props.** Twenty library props with colliders, navigation avoidance and colour atlases. |
| ![Neutral NPC baseline](docs/blender_previews/sheet_npc_baseline.jpg) | **Neutral baseline.** 8 characters × 6 views (front, three-quarter, profile, back, full body, crowd) used for the 0.9.4 pass and the current re-evaluation. | | ![Neutral NPC baseline](docs/blender_previews/sheet_npc_baseline.jpg) | **Neutral baseline.** 8 characters × 6 views (front, three-quarter, profile, back, full body, crowd) used for the 0.9.4 pass and the current re-evaluation. |
| ![Texture refinement comparison](docs/blender_previews/sheet_texture_refinement_0.9.5.jpg) | **Texture refinement.** 128 → 256 px NPC maps and a 4× fabric weave, shown before/after on the same camera. | | ![Texture refinement comparison](docs/blender_previews/sheet_texture_refinement_0.9.5.jpg) | **Texture refinement.** 128 → 256 px NPC maps and a 4× fabric weave, shown before/after on the same camera. |
@@ -130,7 +136,7 @@ decisions are recorded as ADRs in [`docs/adr/`](docs/adr/).
## Tech Stack ## Tech Stack
- **Engine:** Godot 4.7.2 · **Language:** GDScript - **Engine:** Godot 4.7.2 · **Language:** GDScript
- **NPCs:** Blender-authored skinned library (`assets/models/blender/`) with procedural fallbacks; lightweight FSMs on `NpcProfile` - **NPCs:** CC0 Quaternius skinned library (`assets/models/blender/`, ADR-0018) with a tested procedural fallback; lightweight FSMs on `NpcProfile`
- **World:** procedural `WorldBuilder` + generated prop models - **World:** procedural `WorldBuilder` + generated prop models
- **Art:** v2 skyline/surface textures in `assets/textures/ai/game_ready/v2/picks/`; UI/scene art in `assets/images/` - **Art:** v2 skyline/surface textures in `assets/textures/ai/game_ready/v2/picks/`; UI/scene art in `assets/images/`
- **Asset tooling:** `asset_pipeline.py`, `model_pipeline.py`, `npc_pipeline.py`, `blender_assets.py`, `scene_concepts.py`, `screen_designs.py`, `check_screenshots.sh` - **Asset tooling:** `asset_pipeline.py`, `model_pipeline.py`, `npc_pipeline.py`, `blender_assets.py`, `scene_concepts.py`, `screen_designs.py`, `check_screenshots.sh`
@@ -159,15 +165,14 @@ Full setup, every run/test/smoke command, and the controls table are in
- [`TODO.md`](TODO.md) — Priority-ordered tasks: character and local-map development, release milestones, and future work. - [`TODO.md`](TODO.md) — Priority-ordered tasks: character and local-map development, release milestones, and future work.
- [`docs/README.md`](docs/README.md) — Documentation index, current priorities and visual previews. - [`docs/README.md`](docs/README.md) — Documentation index, current priorities and visual previews.
- [`docs/npc-model-sourcing.md`](docs/npc-model-sourcing.md) — Character-source evaluation, licence rules and the character-development plan. - [`docs/npc-model-sourcing.md`](docs/npc-model-sourcing.md) — Character-source evaluation, licence rules and the character-development plan.
- [`docs/makehuman-head-review.md`](docs/makehuman-head-review.md) — Local MakeHuman head prototype, provenance and review verdict.
- [`AGENTS.md`](AGENTS.md) — Development, commit, build and collaboration workflow. - [`AGENTS.md`](AGENTS.md) — Development, commit, build and collaboration workflow.
- [`DONE.md`](DONE.md) — Completed task sections. - [`DONE.md`](DONE.md) — Completed task sections.
- [`MEMORY.md`](MEMORY.md) — Persistent session notes. - [`MEMORY.md`](MEMORY.md) — Persistent session notes.
- [`CHANGELOG.md`](CHANGELOG.md) — User-facing changes by version. - [`CHANGELOG.md`](CHANGELOG.md) — User-facing changes by version.
- [`docs/architecture.md`](docs/architecture.md) — arc42-style architecture documentation. - [`docs/architecture.md`](docs/architecture.md) — arc42-style architecture documentation.
- [`docs/gameplay.md`](docs/gameplay.md) — Gameplay systems, venues, and the escalation model. - [`docs/gameplay.md`](docs/gameplay.md) — Gameplay systems, venues, and the escalation model.
- [`docs/hollywood-boulevard-map.md`](docs/hollywood-boulevard-map.md) — Real-site references, game mapping and limitations for the Hollywood venue. - [`docs/venues.md`](docs/venues.md) — Real-site references, game mapping and limitations for Hollywood, the Joke Shop and the TV Studio.
- [`docs/joke-shop-map.md`](docs/joke-shop-map.md) — Real-site references, game mapping and limitations for the Joke Shop / Sunset Strip venue.
- [`docs/tutorial.md`](docs/tutorial.md) — First-shift tutorial beats and rules.
- [`docs/asset-tooling.md`](docs/asset-tooling.md) — Asset pipelines, model tools, and screenshot gates. - [`docs/asset-tooling.md`](docs/asset-tooling.md) — Asset pipelines, model tools, and screenshot gates.
- [`tools/README.md`](tools/README.md) — Tool inventory, categories, test commands and AI-skill evaluation. - [`tools/README.md`](tools/README.md) — Tool inventory, categories, test commands and AI-skill evaluation.
- [`.agents/skills/gig-line-blender-asset-contract`](.agents/skills/gig-line-blender-asset-contract/SKILL.md) — tracked AI skill for Blender/NPC/gear asset work. - [`.agents/skills/gig-line-blender-asset-contract`](.agents/skills/gig-line-blender-asset-contract/SKILL.md) — tracked AI skill for Blender/NPC/gear asset work.
@@ -176,29 +181,26 @@ Full setup, every run/test/smoke command, and the controls table are in
- [`docs/development-status.md`](docs/development-status.md) — Implemented features and what's next. - [`docs/development-status.md`](docs/development-status.md) — Implemented features and what's next.
- [`docs/accessibility.md`](docs/accessibility.md) — Accessibility features, known gaps and statement. - [`docs/accessibility.md`](docs/accessibility.md) — Accessibility features, known gaps and statement.
- [`docs/known-limitations.md`](docs/known-limitations.md) — Release-candidate blockers and accepted limitations. - [`docs/known-limitations.md`](docs/known-limitations.md) — Release-candidate blockers and accepted limitations.
- [`docs/smoke-checklist.md`](docs/smoke-checklist.md) — Manual smoke-test checklist. - [`docs/release-qa.md`](docs/release-qa.md) — Manual smoke checklist and release-candidate balance protocol.
- [`CREDITS.md`](CREDITS.md) — Credits, AI/reference provenance and licensing notes. - [`CREDITS.md`](CREDITS.md) — Credits, AI/reference provenance and licensing notes.
- [`PRIVACY.md`](PRIVACY.md) — Local-only data, save files and development API use. - [`PRIVACY.md`](PRIVACY.md) — Local-only data, save files and development API use.
- [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points. - [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points.
- [`docs/npc-visual-review.md`](docs/npc-visual-review.md) — 0.9.4 NPC baseline, acceptance criteria and ranked visual gaps; 0.9.5 texture comparison is in [`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`](docs/blender_previews/sheet_texture_refinement_0.9.5.jpg). - [`docs/npc-visual-review.md`](docs/npc-visual-review.md) — 0.9.4 NPC baseline, acceptance criteria and ranked visual gaps; 0.9.5 texture comparison is in [`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`](docs/blender_previews/sheet_texture_refinement_0.9.5.jpg).
- [`docs/npc-proportion-audit.md`](docs/npc-proportion-audit.md) — NPC proportion measurements against the shared stylized guide.
- [`docs/adr/`](docs/adr/) — Architecture Decision Records. - [`docs/adr/`](docs/adr/) — Architecture Decision Records.
## Development Status ## Development Status
v0.10.0-rc.1 is the current release-candidate source: the 0.9.6 integration v0.9.8 is the current source version: the character-development patch with the
checkpoint plus save-migration hardening and release tooling, on top of the 0.9.5 12-character library, realistic face and crowd variants, pose/deformation fixes
texture refinement (256² NPC maps, a 4× fabric weave with baked-AO removal, repaired and save-migration/release hardening. It sits on the 0.9.6 integration
Walk of Fame/awning surfaces and per-phase skyline haze), on top of the v0.9.4 checkpoint, 0.9.5 texture refinement, 0.9.4 NPC visual optimization, 0.9.3
NPC visual optimization and the v0.9.3 UI/accessibility polish pass. It builds on UI/accessibility polish, 0.9.2 Hollywood/Joke Shop maps and the 0.9.x engine
v0.9.2's Hollywood/Joke Shop map rebuilds, v0.9.1's Godot 4.7.2 baseline, baseline. The active work is the `0.9.9`–`0.9.12` patch line (Quaternius NPC
v0.9.0's production-quality pass, v0.8's base, roster, venue/asset polish, performance) followed by the `0.10.0`
tutorial/campaign/resume work and the v0.7.x venue/gear/judge work. The active public-readiness minor. See
planning priorities are the unversioned character and local-map development tracks;
the 0.10.0 release candidate is the next versioned release. See
[`docs/development-status.md`](docs/development-status.md) for the implemented list [`docs/development-status.md`](docs/development-status.md) for the implemented list
and [`TODO.md`](TODO.md) for the priority-ordered roadmap. Manual verification uses and [`TODO.md`](TODO.md) for the priority-ordered roadmap. Manual verification uses
the [`docs/smoke-checklist.md`](docs/smoke-checklist.md). [`docs/release-qa.md`](docs/release-qa.md).
## Credits, Privacy and Accessibility ## Credits, Privacy and Accessibility
+524 -337
View File
@@ -1,79 +1,43 @@
# TODO # TODO
Work is grouped by theme. Each item should be small enough to complete and verify on its own; Active task list. Work is grouped by milestone and workstream; every item is small enough to
see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these plans. 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.
**Priority order (2026-11-04, owner):** 1) release the finished procedural character development **Current state (2026-11-05):** v0.9.8 is published. Three venues, the shift loop, the
as the **0.9.7 patch**, 2) finish the MakeHuman character + the 3D model and map improvements, tutorial/campaign, the access/escalation system and the 12-character procedural library are
3) re-evaluate the 3D models, 4) ship all of it together in the **0.10.0 minor**. See `AGENTS.md` shipped. The character plan changed twice and landed on the CC0 Quaternius base; the owner
§Priority and Versioning and §Local Blender skills. 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 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, 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. `blender_assets.py audit`/`fit`/`inspect` and the capture gates stay authoritative.
## Fix the Rig pose harness
Goal: `src/test/capture_npc_pose.gd` reports a pose but saves rest frames; the root cause is
narrowed to the skinned mesh ignoring its `Rig` parent transform. Fixing it unblocks the
weights/pose column of the 3D review and the pose/portrait evidence for the MakeHuman intake.
*Skill route: `rigging`; evidence in `docs/3d-model-review.md` §"Pose evidence is blocked".*
- [x] Root cause found (2026-11-04): the harness never disabled the avatar's `_physics_process`,
so `_advance_fsm` + `_move_toward_target` ran every tick and reset `arrived`/velocity, which
made `theater_pose()` return "idle" and reverted the pose to rest. (The earlier "moving the
`Rig` child does not render" probe was a red herring: it set `rig.position.y` inside `_shoot()`
in the same frame as `root.get_texture().get_image()`, so it never got a rendered frame.)
- [x] Harness rebuilt: `capture_npc_pose.gd` now calls `set_physics_process(false)`/
`set_process(false)` after staging the avatar; the pose survives because only the direct
`_animate()` ticks drive it. Verified: sit vs idle diff 5.63, walk vs idle diff 2.29, and the
vision judge confirms the seated frame reads as crouched.
- [x] Captured all poses for all 12 variants (idle / walk_contact / walk_pass / sit × full +
shoulder views = 96 frames) and built `sheet_pose.jpg`/`sheet_shoulder.jpg` under `tmp/`.
(No separate "wave" theater pose exists; the escalation raise-hand gesture is not a distinct
capture in this harness.)
## MakeHuman character pipeline
Goal: replace the procedural body generator with the CC0 MakeHuman base for the next character
pass — retopologised, rigged and dressed in project-owned clothing — so bodies read more
realistic than the current primitive shapes. This promotes the deferred MakeHuman evaluation to
an active track; ADR-0015 (CC0 / CC-BY with attribution) still governs every intake.
*Skill route: `retopology`, `rigging`, `uv-workflow`, `materials`, `character-artist`,
`asset-optimization`, `godot-export`, `qa-review`.*
### Intake and adaptation
- [x] `adapt` path added to `tools/blender_assets.py` (`adapt --file <in> --out <out>
[--target-tris 8000]`): imports the GLB, applies transforms, grounds the feet, welds seam
splits and decimates to the budget, then exports. The five tint slots + 13-bone rig stay a
manual step (recorded in the command doc).
- [x] `audit` stays the final gate; `test_adapt_requires_blender` covers the non-Blender error
path (161/161 Python).
- [x] Adapter commands + go/no-go documented in `docs/asset-tooling.md` §Intake adaptation.
### MPFB / MakeHuman body pipeline
- [x] Prototype the local pipeline: `tmp/npc-research/makehuman_intake.py` imports the CC0
`base.obj`, scales to 1.82 m, grounds the feet, triangulates, decimates and exports
`tmp/npc-research/makehuman/makehuman_body.glb` — 3,790 verts / 6,444 tris, bounds
x ±0.53 / y 0–1.82 / z −0.12…+0.35, one neutral material, UVs present, unskinned. The
body (no rig) is the input for the rigging step; the script + mesh stay in `tmp/` (CC0, not
committed).
- [x] Rig strategy decided (owner): **retarget the body to the 13-bone contract**.
`tmp/npc-research/makehuman_retarget.py` rotates the A-pose arms down (−38°/+38°), scales the
arms 1.63× to the contract 0.70 m length, welds the split vertices, smooth-shades, adds the
contract armature + `_auto_weights`, paints the five slot regions (hair/cloth/pants/accessory/
skin) and exports `makehuman_retargeted.glb`. **Contract passes:** 6,444 / 8,000 tris,
7,726 / 8,000 verts, 14 bones (13 + Hips), skinned, arm-down, grounded, five slots, no issues.
Vision-reviewed: arms hang down and the silhouette + slot regions read, but the 1.63× arm warp
distorts the deltoid/shoulder (spiky/warped), the decimated hands are blocky, and the slot
boundaries are rough — these are the refinement steps below.
- [ ] Re-run `inspect`/`audit`/`fit`/proportions and the 24-NPC budget after each intake; keep the
procedural generator as the fallback until a MakeHuman variant ships.
## 3D model and local map re-evaluation ## 3D model and local map re-evaluation
Goal: re-judge every shipped 3D asset against the newly available Blender skill guidance, rank Goal: re-judge every shipped 3D asset against the newly available Blender skill guidance, rank
@@ -82,14 +46,15 @@ venues against licensed real-world data — OSM footprints and building massing,
photoreal facades and the photo-driven geometry pipeline (sections H–M). Evidence comes from three 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 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 is an unversioned development track: it must not change the `npc_base` contract, the renderer or
the 0.10.0 release candidate. the public 0.10.0 release.
**Sequencing (2026-11-03, owner):** the asset-review fixes (A–G, notably the character P0/P1 **Sequencing (updated 2026-11-05):** the old block is cleared. The local map rebuild (H–M) is
rebuilds and the face/foliage prototypes) stay **blocked** until the full local map rebuild done, the 96/96 review ran against the new maps, and character work moved to the versioned
(H–M) is done **and** a fresh 3D model review runs against the new maps. The measurements and `0.9.9`/`0.9.10` milestones. This section is now the **evidence, scorecard and measurement
scorecard in `docs/3d-model-review.md` stand as the pre-rebuild baseline, not a go-ahead to start source**, not an open-ended backlog: the venue/prop/lighting fixes are scheduled into
character work now. (Character development has since shipped — see `DONE.md` — and the next `0.9.11`, the budget/LOD checks into `0.9.12`, and the character items (C) are superseded by
character pass is the MakeHuman pipeline above.) `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 **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 (dark stucco, white signature fascia, tied-back curtains, hotel tower) and Hollywood/TV Studio
@@ -146,9 +111,9 @@ licence-free zone only.
(`elcapitan`, `hollywood-block`, `jimmy-kimmel-stage`, `jimmy-kimmel-entrance`, (`elcapitan`, `hollywood-block`, `jimmy-kimmel-stage`, `jimmy-kimmel-entrance`,
`comedy-store`). `comedy-store`).
- [x] Record which local frames changed a decision: added the frame→decision mapping to - [x] Record which local frames changed a decision: added the frame→decision mapping to
`docs/hollywood-boulevard-map.md` (recent `elcapitan-2026` geosearch → 2009 frame stays the `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 geometry reference; `jimmy-kimmel-entrance/07.jpg` → the four entrance concepts) and
`docs/joke-shop-map.md` (`comedy-store/` → the four concept pairs + map plan; `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). `comedy-store-hillside/` → the day-concept anchor that clears the image-recitation filter).
Frames stay gitignored; only the provenance notes are committed. Frames stay gitignored; only the provenance notes are committed.
@@ -176,7 +141,14 @@ licence-free zone only.
per item; record the decision and the reason in the review document. **Deferred until the 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.** full map rebuild (H–M) lands and a fresh 3D review runs against the new maps.**
### C. Character re-evaluation (8 variants + fallback) ### 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`, *Skill route: `character-artist`, `retopology`, `rigging`, `uv-workflow`, `texture-workflow`,
`materials`, `hair-groom`, `asset-optimization`, `lookdev`, `qa-review`.* `materials`, `hair-groom`, `asset-optimization`, `lookdev`, `qa-review`.*
@@ -192,11 +164,16 @@ licence-free zone only.
- [x] Hands/grip review: the hands ARE detailed — `tools/blender_assets.py._hands_and_shoes()` - [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; 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 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** — there are no finger the open palm + forward offset; a finger-curl rig stays out of scope** — the current rig has
bones in the 13-bone contract, so a curl rig is a contract decision, not a modelling tidy-up. no finger bones, so a curl rig is a contract decision, not a modelling tidy-up. Optional
Recorded as ranked gap #6 (accept; revisit only if face work opens the budget). extra finger bones are now allowed by the cap/contract extension, but require an ADR and
- [ ] Weights/pose: captured after the pose harness is fixed (see the top-level weight/budget verification. Recorded as ranked gap #6 (accept; revisit only if face work
**Fix the Rig pose harness** track); shoulder/hip/neck collapse remains unmeasured until then. 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× - [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 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 neutral/runtime-tinted (not painted skin), so density controls only subtle weave/noise, and
@@ -209,8 +186,8 @@ licence-free zone only.
plus stride scaling are the accepted approach; a baked cloth-sim pipeline would only be 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. 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 - [ ] 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. **Blocked until re-run `inspect`, `audit`, `fit` and `npc_proportions.py` after each fix. **Scheduled: the
the map rebuild + second 3D review (see the track's sequencing note).** 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 - [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 `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 crowds and a pedestrian mall — general build/hair/outer-layer variety context, reference
@@ -316,8 +293,8 @@ licence-free zone only.
`tools/three_up.py compose` renders labelled strips into `docs/screens/boards/` (caption `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 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`. night, Joke Shop day. Tests in `tools/test_three_up.py`.
- [x] Link each committed board from `docs/hollywood-boulevard-map.md` and - [x] Link each committed board from `docs/venues.md#hollywood-boulevard` and
`docs/joke-shop-map.md`. `docs/venues.md#the-joke-shop`.
- [ ] Use the generated concept lighting and palette as the target for the `lighting`/`lookdev` - [ ] Use the generated concept lighting and palette as the target for the `lighting`/`lookdev`
pass, and record the deviations that stay accepted. pass, and record the deviations that stay accepted.
- [ ] After the pass, regenerate the venue captures and update README plus both venue documents. - [ ] After the pass, regenerate the venue captures and update README plus both venue documents.
@@ -345,10 +322,10 @@ and never ship a generated or downloaded image without provenance.
log and the generated-board reference table. Linked from `docs/screens/README.md`.) 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, - [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 Ovation across street, premiere staging) with a sidecar and per-image purpose, linked from
`docs/hollywood-boulevard-map.md` and credited in `CREDITS.md`. Cropped the foreground crowd `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/`.) 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, - [x] Joke Shop board: three licence-cleared photos (hillside/canopy block, frontage with palms,
canopy signature band) with a sidecar, linked from `docs/joke-shop-map.md` and credited in 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 `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/`.) 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 **Removed 2026-11-03:** the committed Comedy Store photo board was replaced by the local-only
@@ -356,7 +333,7 @@ and never ship a generated or downloaded image without provenance.
- [x] TV Studio: no separate board — the venue is the same El Capitan Entertainment Centre block as - [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 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 venues and the premiere-staging photo is the arrival evidence; the reuse is now stated in
`docs/hollywood-boulevard-map.md`, `docs/gameplay.md`, `PLAN.md` and `docs/venues.md#hollywood-boulevard`, `docs/gameplay.md`, `PLAN.md` and
`docs/development-status.md`. `docs/development-status.md`.
- [x] Fill the Hollywood gaps that have a clean licence: El Capitan Theatre neighbour and marquee, - [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 Walk of Fame terrazzo band, star close-up, and a night street-lighting context frame (cropped
@@ -425,7 +402,7 @@ still apply; the map pipeline has its own exit in M.
- [x] ODbL audit: OpenStreetMap is used as **reference only** (hand-built geometry informed by - [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 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 share-alike obligation attaches. Attribution is still kept via the OSM way links in
`docs/hollywood-boulevard-map.md` / `docs/joke-shop-map.md`. If derived OSM geometry ever `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. 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 - [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 pipeline later imports footprints, pin an Overpass query + extract date and record it in a
@@ -524,13 +501,13 @@ Nothing is committed until the licence gate on any promoted reference and the ma
pass. The current `_build_comedy_store_building()` stays as the fallback.* 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 - [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/joke-shop-map.md` 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 §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, 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 entrance and roofline all match closely. Curtain-bay count is crop-dependent (3–7), so 7 is
accepted as a readable stylization. accepted as a readable stylization.
- [x] Rank the shape-language gaps and write the top-five fix list (measurements from the - [x] Rank the shape-language gaps and write the top-five fix list (measurements from the
proportions pass, §`docs/joke-shop-map.md`): 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 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 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). hall with the canopy capping the top (vision-verified).
@@ -556,7 +533,7 @@ pass. The current `_build_comedy_store_building()` stays as the fallback.*
top-five list. top-five list.
- [x] Gates after the rebuild: Godot 277/277 (map budget 26,828 tris / 580 meshes under 150k/700; - [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 navigation/collider tests pass), `tools/check_screenshots.sh` 21 good / 0 bad; the three-up
board and `docs/joke-shop-map.md` were refreshed. board and `docs/venues.md#the-joke-shop` were refreshed.
- [ ] Update `docs/development-status.md`, `docs/asset-tooling.md` and `CHANGELOG.md` when the - [ ] Update `docs/development-status.md`, `docs/asset-tooling.md` and `CHANGELOG.md` when the
rebuild lands; record any accepted deviations from the reference. rebuild lands; record any accepted deviations from the reference.
@@ -598,7 +575,7 @@ pass. The current `_build_comedy_store_building()` stays as the fallback.*
### M. Local map QA and exit ### M. Local map QA and exit
- [x] Compare the block against screenshots/map docs and keep the stylized fallback: the three-up - [x] Compare the block against screenshots/map docs and keep the stylized fallback: the three-up
boards (photo | concept | capture) and `docs/joke-shop-map.md` record the evidence chain; boards (photo | concept | capture) and `docs/venues.md#the-joke-shop` record the evidence chain;
the procedural `_build_comedy_store_building()` remains the fallback. the procedural `_build_comedy_store_building()` remains the fallback.
- [x] Budget/navigation/collider + day/night captures: Godot 277/277 (Joke Shop 26,828 tris / - [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), 580 meshes under 150k/700; navmesh, step collider and prop-collider tests pass),
@@ -606,7 +583,7 @@ pass. The current `_build_comedy_store_building()` stays as the fallback.*
- [x] Gallery + before/after: the Joke Shop day gallery frame and three-up board were recaptured - [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. 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] `tools/check_screenshots.sh` re-run (21/0) and the Joke Shop three-up board refreshed.
- [x] Docs: `docs/joke-shop-map.md` (mapping, measured proportions, simplifications), - [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. `docs/asset-tooling.md` (`describe_image.py`) and `docs/development-status.md` updated.
`CHANGELOG.md` is untouched (unversioned track; no release). `CHANGELOG.md` is untouched (unversioned track; no release).
- [x] Exit: **one local block (the Comedy Store) is reconstructed with reference-derived facades - [x] Exit: **one local block (the Comedy Store) is reconstructed with reference-derived facades
@@ -619,11 +596,11 @@ pass. The current `_build_comedy_store_building()` stays as the fallback.*
Unversioned housekeeping that keeps the planning sources trustworthy. Do these when they block a Unversioned housekeeping that keeps the planning sources trustworthy. Do these when they block a
task, not on a schedule. task, not on a schedule.
**Status (2026-11-03):** v0.10.0-rc.1 source; three venues; 277 headless + 161 Python tests; the **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 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; night emissive, marquee emissive test); reference boards (`hollywood`, `joke-shop`) with sidecars;
three-up boards; `tools/refboard.py` + reference-anchored generation (`--reference`, `anchor`) + three-up boards; `tools/refboard.py` + reference-anchored generation (`--reference`, `anchor`) +
`tools/three_up.py`. Gaussian splatting is deferred to the end of this file. `tools/three_up.py`. Gaussian splatting stays deferred at the end of this file.
### Memory and status accuracy ### Memory and status accuracy
@@ -638,8 +615,8 @@ three-up boards; `tools/refboard.py` + reference-anchored generation (`--referen
contradiction and the old 272/108 counts.) 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 - [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.) the same class of drift returns. (Counts and the sign-in state corrected on 2026-11-03.)
- [x] Keep the Gap Analysis baseline and the README status line in sync when a test count, version - [x] Keep the `PLAN.md` gap-analysis baseline and the README status line in sync when a test count,
or feature milestone changes (the baseline was bumped to 277/161 on 2026-11-03). version or feature milestone changes (baseline bumped to 277/162 on 2026-11-05).
### Imagery and provenance upkeep ### Imagery and provenance upkeep
@@ -681,243 +658,6 @@ three-up boards; `tools/refboard.py` + reference-anchored generation (`--referen
- [ ] Prune the skill routes in `AGENTS.md` if a skill stops matching the project (for example if - [ ] 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). a future second-rig path makes `rigging`/`animation` guidance authoritative).
## Gap Analysis and 0.x Roadmap
**Published baseline:** v0.9.6 (2026-11-03) — three playable venues, tutorial/campaign/checkpoint
resume, access/radio/escalation loop, shift review, the Blender asset library, 277 Godot + 161
Python tests, a 21/21 screen-design judge and Compatibility/Web/Desktop HQ builds.
**Current source:** 0.10.0-rc.1. Its clean release-candidate build is verified, but it is not
published while the gates below remain open.
### Open gaps
| Area | Remaining gap | Milestone |
| --- | --- | --- |
| 3D model and local map re-evaluation | Skill-guided asset scorecard/repairs; OSM/3D data, reference-derived facades, AI-assisted composition and map QA | Unversioned |
| Character development | Complete (moved to `DONE.md`); the MakeHuman body pipeline is the next character pass | 0.11.0 |
| Content and balance | External first-time-player evidence, balance sweeps/tuning and content lock | 0.10.0 |
| Audio, accessibility and localization | Accessibility/localization acceptance, caption coverage and pseudo-locale/layout sign-off | 0.10.0 |
| Performance and platform | Target-hardware matrix, Web/Desktop HQ profiling and frame/memory evidence | 0.10.0 |
| QA and release engineering | Final smoke matrix, clean build/checksum verification and target-matrix launch tests | 0.10.0 |
| Legal, signing and storefront | macOS signing/notarization, AI-provider rights, storefront/rating approval and privacy sign-off | 0.10.0 |
| Documentation and sign-off | Final README/status/TODO/manual evidence and known-limitations sign-off | 0.10.0 |
| Future 0.x | Live replay, more venues, VO/music, image-to-mesh props and seasonal content | Unscheduled |
The 0.9.3 UI, 0.9.4 animation, 0.9.5 texture, 0.9.6 integration and save-migration work are
complete in `DONE.md`; they are not remaining gaps. The 2026-11-01 documentation audit
(architecture ownership, stale-claim cleanup and obsolete-plan removal) is also closed.
**Priority (2026-11-04):** fix the `Rig` pose harness, then the MakeHuman character pipeline,
then re-evaluate the 3D models, then ship the **0.11.0** minor release. The 0.10.0 candidate
still accepts only release-blocking fixes until its external-playtest, legal/storefront, signing
and platform gates clear.
## Milestone 0.10.0 — 3D model and map improvements (minor)
Goal: ship all 3D model and map improvements together as the **0.10.0 minor** — the MakeHuman
body retarget (contract armature + project head/hands), the rebuilt Comedy Store/Hollywood maps,
and the re-evaluated 3D scorecard — plus the external playtest, legal/storefront, signing and
platform gates from the old release candidate. The character-development patch (0.9.7) ships
first; this minor contains the model/map work.
### Content lock and external playtest
### Content lock and external playtest
- [x] Declare a content/feature freeze for 0.10.0; only release-blocking fixes and balance changes
may land after the freeze. (Frozen 2026-11-03 after the 0.9.6 integration
checkpoint; this pass is limited to release reliability, accessibility,
balance evidence and packaging.)
- [x] Produce a release-candidate build with the tutorial, campaign, all three venues, Help,
accessibility settings and checkpoint/resume enabled. (Clean worktree
`a9658ec` built `0.10.0-rc.1`: Linux 161.5 MB, Windows 197.2 MB, macOS
148.0 MB, Web 97.8 MB and HQ 241.6 MB. The Web/HQ archives include the
credits, privacy, accessibility and known-limitations pages; SHA-256
manifests were written; the Linux and HQ binaries launched headless and the
Web index/wasm/pck/docs returned HTTP 200. Not published because external
playtest and legal approval remain open.)
- [ ] Run at least five first-time-player sessions covering the tutorial and one full venue shift;
record where players get stuck, misunderstand evidence or choose disproportionate escalation.
- [ ] Triage findings by severity (P0/P1/P2) and fix every P0/P1 onboarding/accessibility issue;
keep the rest as explicit known limitations or post-0.10 items.
- [ ] Re-run the affected tutorial/campaign smokes after each fix and capture a fresh first-hour
walkthrough for the release notes.
### Balance and fairness pass
- [x] Define measurable targets for routine compliance, access-decision accuracy, peaceful
resolution, justified emergency calls and false-positive/false-negative rates.
(`docs/balance-acceptance.md` defines cohorts, denominators, 36 matrix cells,
seeds, thresholds and matched emergency-first comparisons. Documentation verified;
targets are not measured results.)
- [ ] Run scripted venue sweeps across easy/normal/hard and all four phases; compare outcomes and
tune event weights, reaction tables, compliance and report strictness where targets fail.
- [ ] Review emergency calls against actual danger: no routine behavior should make emergency the
dominant winning move, and genuine threats must remain answerable. Include the
type-derived `_read_npc_truth()` mismatch with observed threats/forced entry;
use independently labelled behavior evidence, not the helper as its own oracle.
- [ ] Verify the reputation grades, debrief explanations and campaign progression still communicate
the de-escalation-first design after tuning.
- [ ] Record the balance evidence and any deliberate exceptions in `docs/development-status.md`
or `MEMORY.md`.
### Save migration and session reliability
- [x] Add fixtures for a 0.8-era settings/campaign/history save and a 0.9.0 checkpoint; verify
migration/default-filling and a clean first launch with no files. (Committed
fixtures under `src/test/fixtures/saves/`; 9 migration/failure tests cover
old settings/campaign/history, the 0.9.0 checkpoint, all three venue
checkpoint round-trips, clean install, corrupt JSON and a blocked atomic
replace. Suite is now 272 tests.)
- [x] Test fresh install → tutorial → campaign → pause → quit → resume → review → next venue on
an isolated user-data directory. (Tutorial and campaign smokes shared one
isolated `XDG_DATA_HOME`; tutorial completion/replay/skip then campaign
resume/review-continue/help both passed. Pause/menu behavior is covered by
`smoke_session_shell.gd`.)
- [x] Test corrupt JSON, partial temp files, missing optional fields and a failed atomic rename;
confirm fallback behavior never destroys a valid save. (Corrupt/partial files
fall back to defaults; a blocked temp path reports an error and leaves the
prior valid settings intact. `_load_json` now uses the non-logging JSON
instance parser.)
- [x] Verify checkpoint resume after all three venues, after an incident, with NPCs at the door and
with flashlight/body-camera/door state set. (The migration suite round-trips a
checkpoint for Hollywood, the Joke Shop and the TV Studio with an open
incident, one NPC row, flashlight/body-camera/door flags; the campaign smoke
resumes a live checkpoint through the real `GameManager`.)
- [x] Document save locations, schema versions, reset behavior and rollback/export instructions in
`docs/getting-started.md` and the release notes. (`getting-started.md` now
documents the four files, version-1 checkpoint, atomic write/fallback,
clean install, reset and backup/rollback.)
### Release engineering and distribution
- [x] Run `tools/build_releases.sh` from a clean checkout and verify Linux/Windows/macOS/Web/HQ
artifact names, sizes and checksums. (Clean worktree `a9658ec` built the
five artifacts listed above; `PUBLISH_DRY_RUN=1` produced and printed
`build/SHA256SUMS-0.10.0-rc.1.txt` without touching the API.)
- [ ] Smoke-test fresh Linux portable/AppImage, Windows portable/installer, macOS unzip/launch,
Web static hosting and Desktop HQ Vulkan fallback on the target matrix.
(Linux portable and Desktop HQ launched headless; Web files served over a
local static server with HTTP 200. AppImage/installer were not built locally
and Windows/macOS launch testing still needs the target matrix.)
- [x] Add a disk-space preflight and retention policy to the release/publish scripts so a full
Gitea host cannot leave a partially uploaded release. (`publish_release.sh`
checks artifact size/free space, writes SHA-256 checksums, supports
`PUBLISH_DRY_RUN`, rolls back uploaded assets/new releases on failure, and
offers opt-in `RETAIN_RELEASES` pruning; dry-run verified.)
- [x] Verify release metadata, tag/version consistency, asset counts and download links; retain
at least the latest 0.8/0.9 releases and document older-asset pruning.
(Source/version and tag are both 0.9.6; all seven published releases carry
5–7 assets, including the retained v0.8.0/v0.8.1/v0.9.0/v0.9.1 line.
`publish_release.sh` documents opt-in `RETAIN_RELEASES` pruning and
`RELEASING.md` describes the seven-release policy.)
- [x] Decide and document whether macOS signing/notarization is required for 0.10.0 or explicitly
remains a future release blocker; record the quarantine instructions either way.
(0.10.0 ships unsigned with quarantine instructions; signing/notarization
remains an explicit public-release blocker in `RELEASING.md` and
`known-limitations.md`.)
### Legal, credits and platform policy
- [x] Audit AI-assisted asset provenance and licenses; record source/model usage and any
redistribution constraints for textures, characters, props and UI art.
(`CREDITS.md` records the Blender/OpenRouter toolchain, default model slugs,
missing per-file generation metadata and the commercial-rights review as an
explicit release blocker.)
- [x] Audit audio/SFX/music licensing (or confirm everything is procedural) and add a credits file.
(All runtime audio is procedural; no external music/SFX files are bundled.
`CREDITS.md` records this.)
- [x] Review body-camera/privacy language, mental-health-crisis representation, age/content rating
and storefront requirements; fix any release-blocking copy or content.
(`PRIVACY.md` clarifies the simulated body camera/local-only data; the
accessibility and known-limitations pages record the remaining
mental-health/rating/storefront review as a release blocker rather than
claiming approval.)
- [x] Add the player-facing credits, known limitations, accessibility statement and privacy note to
the release package/README. (`README.md` links `CREDITS.md`, `PRIVACY.md`,
`docs/accessibility.md` and `docs/known-limitations.md`; the Web and Desktop
HQ package builders copy those documents into the shipped archives.)
- [ ] Mark external legal/storefront approval as complete or explicitly block the 0.10.0 release on it.
### Documentation and visual sign-off
- [ ] Update `README.md`, `PLAN.md`, `docs/architecture.md`, ADRs, `docs/development-status.md`,
`docs/getting-started.md`, `docs/smoke-checklist.md`, `CHANGELOG.md` and this roadmap.
- [ ] Regenerate the full screenshot gallery and realism baselines from the release-candidate
build (not the dev tree) after content lock; run `check_screenshots.sh` and the pinned vision
judge, recording model/spend/verdicts, and refresh the README and venue-doc images.
- [ ] Include the reference/concept boards and the updated asset sheets in the release docs, and
verify every README and docs-page image resolves from the tagged build.
- [ ] Run the manual smoke checklist on every target and attach/link the release-candidate evidence.
- [ ] Verify the tutorial/campaign/help text at maximum font scale and with the longest strings.
- [x] Remove or archive stale plans/notes and confirm no contradictory version/status claims remain.
- [x] Document the character development toolchain in `docs/architecture.md`: base vs library
sources, tool/artifact/gate table, runtime attach/animation contracts and rebuild discipline.
(Added §5 "Character development tooling" plus risk and ownership-map updates;
documentation check passed.)
### Release-candidate QA and exit
- [ ] Run the complete headless suite, Python tooling, tutorial/campaign/session/scenario smokes,
24-NPC budget smoke and release build from a clean checkout.
- [ ] Run the manual QA matrix: fresh install, upgrade, offline launch, gamepad, accessibility
settings, pause/resume, all venues and Desktop HQ fallback.
- [ ] Confirm no P0/P1 defects remain; P2 items must be documented as known limitations or
post-0.10 work.
- [ ] Create the `v0.10.0` version bump, changelog entry and tag; build/publish the release and
verify all assets and download links.
- [ ] Exit: the release candidate is content-locked, externally played, legally reviewed,
documented, reproducibly built, and approved as the 0.10.0 release candidate.
## Milestone 0.9.7 — Character development (patch)
Goal: release the finished procedural character development — the realistic face on all eight
variants, the four crowd-variety characters and the per-role pools — as a **patch** on top of
0.9.6. This keeps the 3D model and map improvements out of the patch; they land together in the
0.10.0 minor.
- [x] Rig pose harness fixed and the weights/pose evidence captured (see the top track).
- [x] Character work done: 12-character library, realistic face, crowd variants, per-role pools,
24-NPC budget 6.94 ms (152,464 tris / 168 meshes).
- [ ] Run the full QA matrix and a local release build for 0.9.7; bump `config/version`, write the
`CHANGELOG.md` entry, tag and publish per the Release Procedure.
**Exit:** a 0.9.7 patch ships the 12-character library (face realism + crowd variety) with the
pose harness fixed; the runtime contract is unchanged. The MakeHuman body and the map rebuild are
not in this patch.
## Roadmap dependencies and risks
- Content volume is the largest schedule risk; tutorial and campaign work must not outrun the
event/dialogue matrix.
- The vision judge is a dev-time aid with an external key/budget; CI must never make it a hard gate.
- Godot version changes can alter lighting/imports; do not combine an engine baseline move with a
content/feature milestone.
- Signed macOS builds and legal/AI provenance are release blockers, not polish.
- Photo-derived map assets add a second provenance risk: downloaded or user-supplied photos can
carry unclear licences, identifiable people, plates and property restrictions. No photo or
splat-derived asset ships without a completed sidecar, consent/licence review and redaction.
- Gaussian-splat training is GPU/disk-heavy and the toolchain churns quickly; keep it offline,
version-pinned and outside CI, and never make a runtime splat renderer a release dependency.
- Keep the de-escalation-first rules and rare-emergency design unchanged while adding progression,
difficulty and feedback.
- The Blender skill pack is local, unversioned guidance; it can drift from the project contract or
assume Blender MCP. Re-verify every adopted technique with `inspect`/`audit`/`fit` and never
make a skill, asset pack or tool version a build or CI dependency.
- The 3D re-evaluation can expand without bound (retopo every head, rig every finger). Cap it with
the scorecard ranking: P0/P1 only, one asset family per commit, and an explicit accept for
everything else.
## Future / deferred (not scheduled)
- [ ] Live 3D body-camera replay; the timeline is text-only today.
- [ ] Additional venues or seasonal events authored from the `content/venues/` resources.
- [ ] Real voice/dialogue audio; ambience, cues and radio crackle are procedural.
- [ ] 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.
## Gaussian splatting evaluation (deferred) ## Gaussian splatting evaluation (deferred)
Postponed for further development on 2026-11-03. The local-map track ships the facade kit and Postponed for further development on 2026-11-03. The local-map track ships the facade kit and
@@ -1014,3 +754,450 @@ reference-image licence and privacy gates above rather than replacing them.
- [ ] Exit: one licensed photo capture measurably improves a venue through a documented path and - [ ] 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 passes visual, performance and provenance review; or the approach is rejected with
reproducible findings and removed from the release path. 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] 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.
- [ ] **Owner decision:** pick (a) full swap, (b) crowd only or (c) add head detail from
`docs/screens/npc-face-gate/README.md`, then apply it within 24,000 tris / 24,000 glTF
vertices and re-run the gate.
- [ ] Record the decision and its consequence in `MEMORY.md`, `docs/npc-model-sourcing.md` and
(if it changes the contract) a new ADR. Do not silently lower character fidelity.
### 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.
- [-] 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.
### 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.
- [ ] 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.
+180 -174
View File
@@ -28,12 +28,12 @@
"attach": "back", "attach": "back",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.161, 0.15,
0.296, 0.296,
0.212 0.172
], ],
"min": [ "min": [
-0.161, -0.15,
-0.12, -0.12,
-0.319 -0.319
] ]
@@ -46,7 +46,7 @@
], ],
"path": "assets/models/blender/npc_gear/backpack.glb", "path": "assets/models/blender/npc_gear/backpack.glb",
"triangles": 380, "triangles": 380,
"vertices": 408 "vertices": 410
}, },
"bench": { "bench": {
"bounds_godot": { "bounds_godot": {
@@ -98,14 +98,14 @@
"attach": "hand", "attach": "hand",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.106, 0.065,
0.376, 0.06,
0.143 0.143
], ],
"min": [ "min": [
-0.106, -0.065,
-0.053, -0.053,
-0.055 0.005
] ]
}, },
"category": "npc_gear", "category": "npc_gear",
@@ -114,21 +114,21 @@
"lens" "lens"
], ],
"path": "assets/models/blender/npc_gear/camera.glb", "path": "assets/models/blender/npc_gear/camera.glb",
"triangles": 318, "triangles": 312,
"vertices": 406 "vertices": 422
}, },
"cap": { "cap": {
"attach": "head", "attach": "head",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.168, 0.108,
0.209, 0.204,
0.258 0.165
], ],
"min": [ "min": [
-0.168, -0.108,
0.037, 0.094,
-0.186 -0.119
] ]
}, },
"category": "npc_gear", "category": "npc_gear",
@@ -264,14 +264,14 @@
"attach": "head", "attach": "head",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.26, 0.166,
0.245, 0.227,
0.26 0.166
], ],
"min": [ "min": [
-0.26, -0.166,
0.035, 0.092,
-0.351 -0.225
] ]
}, },
"category": "npc_gear", "category": "npc_gear",
@@ -333,14 +333,14 @@
"attach": "head", "attach": "head",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.184, 0.118,
0.236, 0.221,
0.123 0.079
], ],
"min": [ "min": [
-0.184, -0.118,
-0.146, -0.024,
-0.225 -0.144
] ]
}, },
"category": "npc_gear", "category": "npc_gear",
@@ -428,16 +428,17 @@
"vertices": 648 "vertices": 648
}, },
"npc_casual": { "npc_casual": {
"base": "hoodie",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.321, 0.228,
1.824, 1.866,
0.189 0.287
], ],
"min": [ "min": [
-0.321, -0.228,
0, -0.004,
-0.178 -0.091
] ]
}, },
"category": "npc", "category": "npc",
@@ -445,11 +446,11 @@
"materials": [ "materials": [
"skin", "skin",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white", "Eyebrows",
"Eye",
"hair", "hair",
"rubber" "pants"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_casual.glb", "path": "assets/models/blender/npc/npc_casual.glb",
@@ -466,20 +467,21 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5038, "triangles": 6206,
"vertices": 4756 "vertices": 12988
}, },
"npc_coat": { "npc_coat": {
"base": "hoodie",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.332, 0.228,
1.824, 1.866,
0.189 0.287
], ],
"min": [ "min": [
-0.332, -0.228,
0, -0.004,
-0.18 -0.091
] ]
}, },
"category": "npc", "category": "npc",
@@ -487,11 +489,11 @@
"materials": [ "materials": [
"skin", "skin",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white", "Eyebrows",
"Eye",
"hair", "hair",
"rubber" "pants"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_coat.glb", "path": "assets/models/blender/npc/npc_coat.glb",
@@ -507,20 +509,21 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 6030, "triangles": 6206,
"vertices": 5802 "vertices": 12988
}, },
"npc_crowd_a": { "npc_crowd_a": {
"base": "casual",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.288, 0.18,
1.825, 1.843,
0.189 0.271
], ],
"min": [ "min": [
-0.288, -0.18,
0, -0.009,
-0.195 -0.099
] ]
}, },
"category": "npc", "category": "npc",
@@ -528,11 +531,11 @@
"materials": [ "materials": [
"skin", "skin",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white", "Hair_Brown",
"Brown",
"hair", "hair",
"rubber" "pants"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_crowd_a.glb", "path": "assets/models/blender/npc/npc_crowd_a.glb",
@@ -547,20 +550,21 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5200, "triangles": 6424,
"vertices": 4734 "vertices": 13440
}, },
"npc_crowd_b": { "npc_crowd_b": {
"base": "casual",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.358, 0.18,
1.809, 1.843,
0.189 0.271
], ],
"min": [ "min": [
-0.358, -0.18,
0, -0.009,
-0.155 -0.099
] ]
}, },
"category": "npc", "category": "npc",
@@ -568,11 +572,11 @@
"materials": [ "materials": [
"skin", "skin",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white", "Hair_Brown",
"Brown",
"hair", "hair",
"rubber" "pants"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_crowd_b.glb", "path": "assets/models/blender/npc/npc_crowd_b.glb",
@@ -587,20 +591,21 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 4734, "triangles": 6424,
"vertices": 4353 "vertices": 13440
}, },
"npc_crowd_c": { "npc_crowd_c": {
"base": "hoodie",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.332, 0.228,
1.824, 1.866,
0.189 0.287
], ],
"min": [ "min": [
-0.332, -0.228,
0, -0.004,
-0.18 -0.091
] ]
}, },
"category": "npc", "category": "npc",
@@ -608,11 +613,11 @@
"materials": [ "materials": [
"skin", "skin",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white", "Eyebrows",
"Eye",
"hair", "hair",
"rubber" "pants"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_crowd_c.glb", "path": "assets/models/blender/npc/npc_crowd_c.glb",
@@ -627,20 +632,21 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5674, "triangles": 6206,
"vertices": 5440 "vertices": 12988
}, },
"npc_crowd_d": { "npc_crowd_d": {
"base": "worker",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.293, 0.217,
1.856, 1.826,
0.189 0.287
], ],
"min": [ "min": [
-0.293, -0.217,
0, -0.004,
-0.225 -0.065
] ]
}, },
"category": "npc", "category": "npc",
@@ -648,11 +654,11 @@
"materials": [ "materials": [
"skin", "skin",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white",
"hair", "hair",
"rubber" "Moustache",
"Eye",
"pants"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_crowd_d.glb", "path": "assets/models/blender/npc/npc_crowd_d.glb",
@@ -667,20 +673,21 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5814, "triangles": 4458,
"vertices": 5300 "vertices": 9370
}, },
"npc_dress": { "npc_dress": {
"base": "casual",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.288, 0.18,
1.825, 1.843,
0.208 0.271
], ],
"min": [ "min": [
-0.288, -0.18,
0, -0.009,
-0.208 -0.099
] ]
}, },
"category": "npc", "category": "npc",
@@ -688,11 +695,11 @@
"materials": [ "materials": [
"skin", "skin",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white", "Hair_Brown",
"Brown",
"hair", "hair",
"rubber" "pants"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_dress.glb", "path": "assets/models/blender/npc/npc_dress.glb",
@@ -706,20 +713,21 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5456, "triangles": 6424,
"vertices": 5128 "vertices": 13440
}, },
"npc_firefighter": { "npc_firefighter": {
"base": "worker",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.37, 0.217,
1.819, 1.826,
0.213 0.287
], ],
"min": [ "min": [
-0.37, -0.217,
0, -0.004,
-0.213 -0.065
] ]
}, },
"category": "npc", "category": "npc",
@@ -727,13 +735,11 @@
"materials": [ "materials": [
"skin", "skin",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white",
"hair", "hair",
"hivis", "Moustache",
"reflective", "Eye",
"rubber" "pants"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_firefighter.glb", "path": "assets/models/blender/npc/npc_firefighter.glb",
@@ -747,20 +753,21 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5900, "triangles": 4458,
"vertices": 5902 "vertices": 9370
}, },
"npc_guard": { "npc_guard": {
"base": "worker",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.361, 0.217,
1.819, 1.826,
0.189 0.287
], ],
"min": [ "min": [
-0.361, -0.217,
0, -0.004,
-0.172 -0.065
] ]
}, },
"category": "npc", "category": "npc",
@@ -768,15 +775,11 @@
"materials": [ "materials": [
"skin", "skin",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white",
"hair", "hair",
"rubber", "Moustache",
"leather", "Eye",
"steel", "pants"
"patch",
"plastic_black"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_guard.glb", "path": "assets/models/blender/npc/npc_guard.glb",
@@ -791,20 +794,21 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5602, "triangles": 4458,
"vertices": 5684 "vertices": 9370
}, },
"npc_hoodie": { "npc_hoodie": {
"base": "hoodie",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.326, 0.228,
1.856, 1.866,
0.189 0.287
], ],
"min": [ "min": [
-0.326, -0.228,
0, -0.004,
-0.225 -0.091
] ]
}, },
"category": "npc", "category": "npc",
@@ -812,11 +816,11 @@
"materials": [ "materials": [
"skin", "skin",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white", "Eyebrows",
"Eye",
"hair", "hair",
"rubber" "pants"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_hoodie.glb", "path": "assets/models/blender/npc/npc_hoodie.glb",
@@ -832,20 +836,21 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5860, "triangles": 6206,
"vertices": 5492 "vertices": 12988
}, },
"npc_jacket": { "npc_jacket": {
"base": "casual",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.332, 0.18,
1.824, 1.843,
0.189 0.271
], ],
"min": [ "min": [
-0.332, -0.18,
0, -0.009,
-0.178 -0.099
] ]
}, },
"category": "npc", "category": "npc",
@@ -853,11 +858,11 @@
"materials": [ "materials": [
"skin", "skin",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white", "Hair_Brown",
"Brown",
"hair", "hair",
"rubber" "pants"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_jacket.glb", "path": "assets/models/blender/npc/npc_jacket.glb",
@@ -873,34 +878,35 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5428, "triangles": 6424,
"vertices": 5071 "vertices": 13440
}, },
"npc_vest": { "npc_vest": {
"base": "worker",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.321, 0.217,
1.819, 1.863,
0.189 0.287
], ],
"min": [ "min": [
-0.321, -0.217,
0, -0.004,
-0.172 -0.103
] ]
}, },
"category": "npc", "category": "npc",
"layer": "vest", "layer": "vest",
"materials": [ "materials": [
"skin", "skin",
"Worker_Yellow",
"Worker_Vest",
"cloth", "cloth",
"pants",
"accessory", "accessory",
"eye_white",
"hair", "hair",
"rubber", "Moustache",
"hivis", "Eye",
"reflective" "pants"
], ],
"mesh_node": "npc_base", "mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_vest.glb", "path": "assets/models/blender/npc/npc_vest.glb",
@@ -914,8 +920,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5536, "triangles": 5240,
"vertices": 5375 "vertices": 10936
}, },
"palm_tree": { "palm_tree": {
"bounds_godot": { "bounds_godot": {
@@ -1239,14 +1245,14 @@
"attach": "head", "attach": "head",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.101, 0.065,
0.059, 0.108,
0.158 0.101
], ],
"min": [ "min": [
-0.101, -0.065,
0.011, 0.077,
0.003 0.002
] ]
}, },
"category": "npc_gear", "category": "npc_gear",
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.
Binary file not shown.
@@ -0,0 +1,33 @@
# Quaternius build sources (CC0 1.0)
These are the **build inputs** for the shipped NPC roster, not runtime assets.
`blender_src/.gdignore` keeps Godot from importing this directory.
Source: Quaternius *Ultimate Modular Characters* and *Ultimate Modular Women*
(<https://quaternius.com/packs/ultimatemodularcharacters.html>), licence CC0 1.0,
attribution recorded in the repository `CREDITS.md`.
| File | SHA-256 | Bones | Meshes |
| --- | --- | --- | --- |
| `Casual.glb` | `bc2d84fba249ebaa0629cb9d133ad37234196e4b8004aa205a6397abed04e05c` | 62 | 4 skinned + Icosphere helper |
| `Casual_Hoodie.glb` | `f5b722199f7628b24550989af4230f65d5230727df9c326fd2b3e360effe5249` | 62 | 4 skinned + Icosphere helper |
| `Worker.glb` | `8d0f454336c2effc5c08f63a177af3a82d1a7af025ca9588b40e4c90c4703534` | 62 | 4 skinned + Icosphere helper |
Each file also carries 24 animation clips; the build exports **no** animation data, so
the shipped GLBs contain the mesh, the 62-joint skin and the material slots only.
Reproducing the shipped characters:
flatpak run org.blender.Blender --background \
--python "$PWD/tools/blender_assets.py" -- build --only npc
Upstream quirks the build compensates for (see `docs/npc-model-sourcing.md`):
1. every mesh ships a **degenerate UV map** (all loops at `0,0`) with flat-colour
materials, so the build Smart-UV-unwraps and repacks before export;
2. the meshes must be joined **without welding** — `remove_doubles` corrupts the
skin weights;
3. the rest pose is a T-pose, so the arm-down rest is baked by
`_bake_quaternius_rest()`;
4. the upstream material names differ per file, so each source carries a slot map
down to the project's five tint slots.
Binary file not shown.
+225 -19
View File
@@ -6,7 +6,7 @@ Blender skill guidance says the targets are, the ranked candidate gaps, and the
still need the project owner. Nothing here re-opens the accepted 0.9.4 limits by itself. still need the project owner. Nothing here re-opens the accepted 0.9.4 limits by itself.
Related documents: [`npc-visual-review.md`](npc-visual-review.md) (accepted 0.9.4 limits and Related documents: [`npc-visual-review.md`](npc-visual-review.md) (accepted 0.9.4 limits and
before/after sheets), [`npc-proportion-audit.md`](npc-proportion-audit.md) (proportion guide), before/after sheets), [`npc-visual-review.md`](npc-visual-review.md#proportion-audit) (proportion guide),
[`blender-assets.md`](blender-assets.md) (asset contract), [`asset-tooling.md`](asset-tooling.md) [`blender-assets.md`](blender-assets.md) (asset contract), [`asset-tooling.md`](asset-tooling.md)
(commands). (commands).
@@ -24,9 +24,10 @@ severity, explicit verdict) rather than an opinion pass:
| Props, gear, foliage | `prop-artist`, `hard-surface`, `vegetation-artist` | Silhouette read, bevel/chamfer language, collision proxies, carded foliage | | Props, gear, foliage | `prop-artist`, `hard-surface`, `vegetation-artist` | Silhouette read, bevel/chamfer language, collision proxies, carded foliage |
| Look and verdict | `lookdev`, `qa-review` | Day/night beauty captures, ship / ship-with-notes / no-ship verdict | | Look and verdict | `lookdev`, `qa-review` | Day/night beauty captures, ship / ship-with-notes / no-ship verdict |
Project contract and gates stay authoritative; see `AGENTS.md` §Local Blender skills. The skills Project contract and gates stay authoritative; see `AGENTS.md` §Local Blender skills. Blender MCP
assume Blender MCP, which this project does not use: everything below came from the headless is optional interactive tooling (see [`asset-tooling.md#optional-interactive-blender-mcp`](asset-tooling.md#optional-interactive-blender-mcp)); this review's evidence
`tools/blender_assets.py` / `tools/npc_pipeline.py` pipelines plus the Godot capture harnesses. came from the headless `tools/blender_assets.py` / `tools/npc_pipeline.py` pipelines plus the
Godot capture harnesses, so it remains reproducible without MCP.
## What was captured and measured (2026-11-03) ## What was captured and measured (2026-11-03)
@@ -63,14 +64,14 @@ with the runtime 4× fabric repeat).
| `npc_crowd_b` | 4,734 / 4,353 | 1.809 / +0.000 | 7 | 107–118 | 434–439 | Contract pass; crowd prototype | | `npc_crowd_b` | 4,734 / 4,353 | 1.809 / +0.000 | 7 | 107–118 | 434–439 | Contract pass; crowd prototype |
| `npc_crowd_c` | 5,674 / 5,440 | 1.824 / +0.000 | 7 | 76–83 | 317 | Contract pass; crowd prototype | | `npc_crowd_c` | 5,674 / 5,440 | 1.824 / +0.000 | 7 | 76–83 | 317 | Contract pass; crowd prototype |
| `npc_crowd_d` | 5,814 / 5,300 | 1.856 / +0.000 | 7 | 92–106 | 377–381 | Contract pass; crowd prototype | | `npc_crowd_d` | 5,814 / 5,300 | 1.856 / +0.000 | 7 | 92–106 | 377–381 | Contract pass; crowd prototype |
| `npc_base` (fallback) | 1,204 / 2,488 | 1.836 / +0.000 | 5 | 70.1–72.9 | 4× on fabric | Contract pass; far below its 6,000/4,000 cap | | `npc_base` (fallback) | 1,204 / 2,488 | 1.836 / +0.000 | 5 | 70.1–72.9 | 4× on fabric | Contract pass; compact fallback under the 16,000/16,000 cap |
Blender-side `inspect` counts can differ by ±1 vertex from the glTF accessor count in `audit` Blender-side `inspect` counts can differ by ±1 vertex from the glTF accessor count in `audit`
(normal-split rounding); both are recorded and neither changes a verdict. (normal-split rounding); both are recorded and neither changes a verdict.
Readings: Readings:
- Every character is inside 8,000/8,000 with the farthest at 5,474 tris (68% of budget) and 5,494 - Every character is inside 16,000/16,000 with the farthest well below the new cap and 5,494
verts (69%), so there is headroom for targeted topology detail without a cap change. verts (69%), so there is headroom for targeted topology detail without a cap change.
- Surface area tracks clothing volume: the firefighter coat is the largest body (4.28 m²) and has - Surface area tracks clothing volume: the firefighter coat is the largest body (4.28 m²) and has
the lowest authored density; `npc_casual` is the smallest (2.44 m²) and the highest. the lowest authored density; `npc_casual` is the smallest (2.44 m²) and the highest.
@@ -82,6 +83,208 @@ Readings:
- Material counts 7–11 per character are fixed detail slots (eyes, rubber, hivis, reflective, - Material counts 7–11 per character are fixed detail slots (eyes, rubber, hivis, reflective,
leather, metal), not extra tint slots. leather, metal), not extra tint slots.
## Baseline inventory (2026-11-04)
Recorded for the character baseline before the Quaternius migration. `npc_base.blend` is the
frozen fallback; the 12 characters are currently assembled procedurally by
`tools/blender_assets.py`. The active plan is now the CC0 Quaternius base bake
([ADR-0017](adr/0017-quaternius-npc-base.md)); this section and the MakeHuman notes below are the
pre-migration record. The MPFB/MakeHuman development files were removed from `tmp/` after the
2026-11-05 pivot.
### `assets/models/npc/npc_base.blend` (frozen fallback)
- Mesh `npc_base`: **652 verts / 642 tris**, five material slots in order
`pants, cloth, skin, hair, accessory`.
- Armature: **14 bones** — `Hips, Spine, Neck, Head, ShoulderL/R, ElbowL/R, HandL/R, HipL/R, KneeL/R`
(the required 13 core bones plus the `Hips` root).
- It is the compact fallback, not the prototype host; the overhaul must not absorb prototype
detail into it unless a separate contract decision approves it.
### 12 generated character GLBs
`audit` counts (glTF accessor; Blender `inspect` can differ ±1 on normal-split rounding):
| Character | Tris | Verts | | Character | Tris | Verts |
| --- | ---: | ---: | --- | --- | ---: | ---: |
| `npc_casual` | 5,034 | 4,607 | | `npc_firefighter` | 5,888 | 5,785 |
| `npc_jacket` | 5,432 | 5,225 | | `npc_guard` | 5,670 | 5,671 |
| `npc_coat` | 6,046 | 5,645 | | `npc_crowd_a` | 5,284 | 4,908 |
| `npc_vest` | 5,532 | 5,195 | | `npc_crowd_b` | 4,812 | 4,550 |
| `npc_dress` | 5,540 | 5,073 | | `npc_crowd_c` | 5,690 | 5,510 |
| `npc_hoodie` | 5,864 | 5,325 | | `npc_crowd_d` | 5,810 | 5,444 |
All well under the 16,000/16,000 cap; the largest is `npc_coat` at 38% of the tri budget, so
head/face/hand detail has ample headroom.
### Procedural builders (current geometry locations)
Coordinates are Blender metres, Z-up; face front is −Y / Godot +Z; `s` = ±1 side, `w` = build
width scale.
- **Head** — `_face` + `_shape_head_mesh`: `sphere(Head, 0.155, (0, 0, 1.635), seg=24, rings=16,
scale=(0.92, 1.0, 1.12))`. Forward-only sculpt (brow at z 1.695, cheek at z 1.612, jaw/chin at
z 1.565); recessed creases rejected because they bake as dark AO patches.
- **Eyes** — sclera `sphere(0.019, (0.052s, −0.134, 1.664), scale=(1.25, 0.62, 0.85))`;
iris `sphere(0.0105, (0.052s, −0.141, 1.664), scale=(1.0, 0.55, 1.0))`; upper lid at z +0.014,
lower lid at z −0.012; brow box at (0.052s, −0.143, 1.699).
- **Nose** — bridge `cyl(0.016→0.022, (0, −0.145, 1.652))`, tip `sphere(0.022, (0, −0.162, 1.615))`,
wings `sphere(0.012, (0.016s, −0.160, 1.606))`.
- **Mouth** — slot `sphere(0.028, (0, −0.158, 1.583), scale=(1.0, 0.28, 0.12))`, lower lip
`sphere(0.012, (0, −0.156, 1.578))`.
- **Ears** — `sphere(0.036, (0.143s, 0.008, 1.63), scale=(0.42, 1.0, 1.35))` + lobe + inner concha.
- **Hands** — `_hands_and_shoes`: palm `sphere(0.046, (0.275·s·w, −0.012, 0.762),
scale=(0.80, 0.62, 0.88))`; four fingers base (…, 0.778) → tips z 0.701–0.714; thumb
(…, 0.778 → 0.760 → 0.738). No finger bones; open palm + `HAND_ITEM_FORWARD` only.
### UV layout
- Five tint slots (`skin, hair, accessory, cloth, pants`) at 256² each; fixed detail slots
(`eye_white`, rubber, hivis, reflective, leather, metal) are the extra material slots.
- Authored texel density 64–122 px/m (below the background tier 128–256), fabric effective
287–440 px/m via the 4× repeat; runtime tint carries the look. This is a recorded note until a
portrait shows texture starvation.
### MakeHuman prototypes (historical, not shipped; research files removed from `tmp/`)
| File | Verts / tris | Bones | Slots | Note |
| --- | ---: | ---: | --- | --- |
| `makehuman_body.glb` | 19,320 / 6,444 | 0 | `skin_proto` | CC0 intake, unskinned, 1.82 m grounded |
| `makehuman_retargeted.glb` | 7,062 / 6,444 | 14 | 5 tint | arm-down + contract rig, deltoid warp remains |
| `makehuman_final.glb` | 6,651 / 6,407 | 14 | 8 (incl. `skin.001`, `eye_white`, `rubber`) | project head/hands/shoes swapped; duplicate `skin.001` |
The "Icosphere" the inspector lists alongside these GLBs is the glTF importer's
bone-display helper (a `pose_bone.custom_shape`), not part of the asset; `inspect_glb` already
excludes it from measurements. The real cleanup for `makehuman_final.glb` is the duplicate
`skin.001` material and the manual deltoid retopology.
## Overhaul acceptance criteria (measurable)
Defined 2026-11-04 for the local part pipeline. A prototype must pass every applicable item
before it is rolled out to the other variants; the bar is the stylized low-poly target, not
photorealism.
### Face (one-variant prototype first)
Capture `front` (yaw 0°), `three_quarter` (yaw 35°) and `profile` (yaw 90°) at **1.5 m** and
**1.85 m** (32° FOV), matching `capture_npc_baseline.gd` (a new 1.5 m portrait capture is added
for this). Pass conditions:
- **Eyes** — sclera + iris + both lids distinct at 1.85 m; at 1.5 m the iris is a readable dark
dot against the white sclera, not a floating oval; no socket ring or lid seam.
- **Mouth** — the mouth line and lower-lip volume read at both distances; no moustache-like
shadow from a floating dark slot.
- **Nose** — bridge, tip and ala planes read in three-quarter and profile; no nose-wing seam or
inverted normal at the base.
- **Ears** — concha depth + lobe read in profile; not a flat disc.
- **Jaw/cheek/chin** — front and profile show a jaw transition and cheek/chin planes; no faceted
plane seams, inverted normals or z-fighting.
- **Silhouette** — front/three-quarter/profile agree; face direction is unambiguous.
- **AO** — the 256² bake stays clean: no recessed crease baking into a dark patch.
### Hands (one-variant prototype first)
Capture an **open palm** and a **relaxed grip** at **0.8 m** (`--view=hand` plus a new grip
capture). Pass conditions:
- **Open palm** — four fingers and a thumb read as a hand, not a mitten; at least two of the four
finger gaps are readable.
- **Grip** — the palm plus curled fingers silhouette reads; no finger-through-palm clipping.
- **Gear contact** — phone, cup, clipboard, camera, flashlight, wand and radio contact the palm
at 0.8 m with `HAND_ITEM_FORWARD` unchanged; no floating or hand-through-item clipping.
### Contract (both, every prototype)
- 24,000 triangles / 24,000 glTF vertices per character; gear stays at 1,500.
- Optional extra bones are allowed; the required 13 core bones and their rest positions must not
move; the `npc_base` mesh node name and the five tint slots stay unchanged.
### Measurement gates
`blender_assets.py inspect`/`audit`/`fit`, `npc_proportions.py`, the 24-NPC budget, Godot + Python
tests, the 48-frame baseline and the 96-frame pose/context review all stay authoritative; the new
face/hands captures are the visual bar for this track.
### Working-set layout
Part sources and preview GLBs live under `tmp/npc-parts/<variant>/` (gitignored by the root
`tmp/` rule); only the assembled character GLBs, AO/maps, the manifest and the review sheets are
committed. No new committed `.blend` source tree is added without a docs/ADR decision — the
frozen `npc_base.blend` stays the only tracked `.blend`.
### Head prototype (in progress 2026-11-04)
`tools/npc_parts.py build --part head_proto` is the first connected-shell attempt: a seg-48
head sphere is displaced (not floating spheres) into eye sockets with an orbital rim, a nose
bridge/tip/ala, upper/lower lip volumes, and extruded ears with a concha recess; the eyeballs
stay separate. Local renders live under `tmp/npc-parts/npc_casual/head_proto_*.png`.
Iteration log (vision-judged, profile view):
1. Initial displacement too subtle — read as a "ball with props".
2. Nose + ear visible in profile but the cranium still read as a "ball with a nose".
3. Added face-plane flatten + jaw/chin taper; taper read, but "~90% sphere".
4. Stronger flatten + nose + chin; nose now reads, jawline still dissolves into the skull.
5. Fixed an ear-displacement sign bug (the ± side loop cancelled the helix); the ear now
protrudes (x ±0.155 vs the ±0.139 shell) but reads as a soft blob.
Current state (prototype built, ~3/5 vision read): the nose, mouth notch, chin bump and ear bulge
all read; the centre-profile measurement confirms the relief is objective — nose tip −0.192,
brow −0.150, chin −0.149, jaw step −0.11, forehead −0.126→−0.078. The remaining polish (a flatter
forehead, a defined ear helix/lobe, a jawline that steps to the neck) is folded into the §3.2
variant comparison; the neck is part of `_skin_body`, so the standalone head always lacks it.
### AI-driven head rework (2026-11-04)
The head was reworked against an AI-generated target concept (front + profile, saved under
`tmp/npc-parts/head_concept_*.png`) and judged with `describe_image.py` across several iterations.
The displacement-on-sphere approach oscillates between "decorated egg" (too subtle) and
"spiky wedges" (too aggressive) and settles at ~3/5 — the coarse s48 sphere cannot resolve the
nose/lip/chin relief cleanly.
The judge then compared the displacement head against the **decimated MakeHuman head**
(extracted from `makehuman_retargeted.glb`, 2,009 verts / 1,573 tris, saved as
the local `makehuman_head.glb`) and returned a decisive result: **ship the MakeHuman head** —
it keeps the nose/chin/jaw landmarks at a fraction of the cost, while the procedural head's
"clean ellipse outline instantly screams procedural sphere".
**Conclusion:** the next head asset is the MakeHuman head shape (1,573 tris) plus the project's
separate eye/mouth/brow pieces for readability, not the displacement shell. The displacement
`_head_proto` stays local as a rejected variant.
**Built (2026-11-04):** `_makehuman_head` in `blender_assets.py` imports the committed
`assets/models/blender_src/makehuman_head.glb` (CC0, attributed in `CREDITS.md`), collapses it to
one skin slot, bisects + caps the ragged neck, and adds the project sclera/iris/lids/brows/mouth
at the MakeHuman landmarks. `build_character(name, head="mh")` assembles it; the result passes
`inspect` (14 bones, skinned, no issues). The judge reads the face ~3.5/5; remaining polish is
welding the added eye/brow pieces into the head shell and a skull-conforming hair cap.
**Standalone head re-review (2026-11-04):** the separate
`assets/models/blender_src/makehuman_head.glb` prototype (12,641 tris / 7,690 split verts,
with a skin eyelid mask, recessed globes, flat iris/pupil discs and surface-anchored brows)
was **accepted by the owner** as a basic human head. The automated vision gate still returns
REJECT on the eye/lid region (globe depth, lid overlap, iris at three-quarter) as deferred
polish. The assembled `npc_casual --head mh` now adds a skull-conforming hair cap, a hidden
scalp trim and a narrowed neck/shoulder body: 14,753 tris / 12,823 seam-split verts /
14 bones / 9 materials, `inspect` clean. All 12 variants count 14,693–15,987 tris. See
[`makehuman-head-review.md`](makehuman-head-review.md).
### Head subdivision comparison (2026-11-04)
Three `head_proto` variants at UV-sphere segments 24/48/64 were rendered at 1.85 m and 1.5 m in
front/three-quarter/profile and judged side by side:
| Variant | Verts / tris | 1.85 m | 1.5 m | Verdict |
| --- | ---: | --- | --- | --- |
| s24 | 670 / 1,168 | lumpy, nose/lips melt into the chin | jagged silhouette, no mouth | reject |
| s48 | 1,558 / 2,944 | eyes/brows/nose/lips read cleanly | features resolve cleanly | **pick** |
| s64 | 2,470 / 4,768 | visually identical to s48 | indistinguishable from s48 | reject (wasted verts) |
The chosen s48 is the `head_proto` default; the rejected s24/s64 stay local under
`tmp/npc-parts/`. A before/after sign-off sheet (current `_face` vs s48, 1.85 m
front/three-quarter/profile) is
[`blender_previews/sheet_head_proto_before_after.jpg`](blender_previews/sheet_head_proto_before_after.jpg).
## Scorecard skeleton ## Scorecard skeleton
Verdicts use `qa-review` semantics: **P0** (blocker: wrong scale, broken normals, budget fail), Verdicts use `qa-review` semantics: **P0** (blocker: wrong scale, broken normals, budget fail),
@@ -93,18 +296,18 @@ yet reviewed; this document is the place to fill it.
| Character | Silhouette | Face @1.85 m | Hands/grip | Weights/pose | Cloth/hair | LOD | Verdict | | Character | Silhouette | Face @1.85 m | Hands/grip | Weights/pose | Cloth/hair | LOD | Verdict |
| --- | --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- | --- |
| *(pose captured 2026-11-04: see "Pose evidence" below)* | | | | | | | | | *(pose captured 2026-11-04: see "Pose evidence" below)* | | | | | | | |
| `npc_casual` | accept (0.9.4) | accept (face prototype) | accept (open palm) | captured (review pending) | accept | accept (two-quad impostor) | pending owner | | `npc_casual` | accept (0.9.4) | accept (face prototype) | accept (open palm) | fixed (wrist/hand + hip/knee) | accept | accept (two-quad impostor) | pending owner |
| `npc_jacket` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner | | `npc_jacket` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
| `npc_coat` | accept | accept (face prototype) | accept | captured (review pending) | accept (hem 0.76 stride) | accept | pending owner | | `npc_coat` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept (hem 0.76 stride) | accept | pending owner |
| `npc_vest` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner | | `npc_vest` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
| `npc_dress` | accept; performer props dominate | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner | | `npc_dress` | accept; performer props dominate | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
| `npc_hoodie` | accept (hooded criterion) | accept (lower face only) | accept | captured (review pending) | accept | accept | pending owner | | `npc_hoodie` | accept (hooded criterion) | accept (lower face only) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
| `npc_firefighter` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner | | `npc_firefighter` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
| `npc_guard` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner | | `npc_guard` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
| `npc_crowd_a` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | crowd prototype | | `npc_crowd_a` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | crowd prototype |
| `npc_crowd_b` | accept | accept (face prototype) | accept | captured (review pending) | accept (beard) | accept | crowd prototype | | `npc_crowd_b` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept (beard) | accept | crowd prototype |
| `npc_crowd_c` | accept | accept (face prototype) | accept | captured (review pending) | accept (coat hem) | accept | crowd prototype | | `npc_crowd_c` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept (coat hem) | accept | crowd prototype |
| `npc_crowd_d` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | crowd prototype | | `npc_crowd_d` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | crowd prototype |
### Gear (18) ### Gear (18)
@@ -254,7 +457,7 @@ owner review decides.
brow ridge, a fuller lower lip and both eyelids (upper + lower); recessed creases (nasolabial, brow ridge, a fuller lower lip and both eyelids (upper + lower); recessed creases (nasolabial,
temple, chin cleft) were rejected because they bake as dark AO patches. Vision-reviewed: the new temple, chin cleft) were rejected because they bake as dark AO patches. Vision-reviewed: the new
face reads cleaner and more realistic than the old (set-in eyes, brow break, lip volume, no face reads cleaner and more realistic than the old (set-in eyes, brow break, lip volume, no
faceting); tris 4,482 → 5,038 (still under 8,000). The other seven variants keep the old face faceting); tris 4,482 → 5,038 (still under 16,000). The other seven variants keep the old face
until this prototype is approved. until this prototype is approved.
2. **No pose/weight evidence (unknown).** Idle, walk, sit, block and turn deformation has never been 2. **No pose/weight evidence (unknown).** Idle, walk, sit, block and turn deformation has never been
captured as a review set, so shoulder/hip/neck collapse is unmeasured. Cheap to add and it captured as a review set, so shoulder/hip/neck collapse is unmeasured. Cheap to add and it
@@ -294,6 +497,9 @@ and the full carded-cluster foliage pass is the one remaining owner-gated P1. No
## Next actions ## Next actions
- Review the assembled [`makehuman-head-review.md`](makehuman-head-review.md) prototype (hair cap and full-body silhouette) before rebuilding the 12 shipped character GLBs.
Tracked in [`TODO.md`](../TODO.md) `3D model re-evaluation`: the remaining `A. Baseline…` item is Tracked in [`TODO.md`](../TODO.md) `3D model re-evaluation`: the remaining `A. Baseline…` item is
this owner review; `B`/`C`/`D` carry the per-family work, and `E` adds the pose/weight, this owner review; `B`/`C`/`D` carry the per-family work, and `E` adds the pose/weight,
collider-coverage and scorecard-coverage gates this document found missing. collider-coverage and scorecard-coverage gates this document found missing.
+7 -9
View File
@@ -4,30 +4,28 @@ This folder holds the project documentation, visual references and generated QA
## Current priorities ## Current priorities
1. [3D model and local map re-evaluation](../TODO.md#3d-model-and-local-map-re-evaluation) — asset scorecard and fixes, plus OpenStreetMap/3D data, licensed reference imagery, photoreal facades, AI-assisted composition and map/Gaussian-splatting evaluation. 1. [Quaternius NPC base migration](../TODO.md#quaternius-npc-base-migration-top-priority) — CC0 Quaternius mesh/animation source baked to the 24,000-budget runtime contract.
2. [Character development](../TODO.md#character-development) — crowd, age, build, hair and outfit variety; generator-first, with the frozen NPC contract. 2. [3D model and local map improvements](../TODO.md#3d-model-and-local-map-re-evaluation) — asset re-evaluation and local map reconstruction.
3. [0.10.0 release candidate](../TODO.md#milestone-0100--release-candidate) — versioned release hardening after the unversioned development tracks. 3. [0.10.0 release candidate](../TODO.md#milestone-0100--3d-model-and-map-improvements-minor) — versioned release hardening after the unversioned development tracks.
## Start here ## Start here
- [`../PLAN.md`](../PLAN.md) — product, design intent and development priorities - [`../PLAN.md`](../PLAN.md) — product, design intent and development priorities
- [`../TODO.md`](../TODO.md) — priority-ordered active tasks and roadmap - [`../TODO.md`](../TODO.md) — priority-ordered active tasks and roadmap
- [`npc-model-sourcing.md`](npc-model-sourcing.md) — character-source evaluation, licence rules and the character-development plan - [`npc-model-sourcing.md`](npc-model-sourcing.md) — character-source evaluation, licence rules and the character-development plan
- [`makehuman-head-review.md`](makehuman-head-review.md) — retained MakeHuman fallback head, provenance and review verdict
- [`architecture.md`](architecture.md) — arc42 system architecture - [`architecture.md`](architecture.md) — arc42 system architecture
- [`getting-started.md`](getting-started.md) — setup, run/test commands and controls - [`getting-started.md`](getting-started.md) — setup, run/test commands and controls
- [`asset-tooling.md`](asset-tooling.md) — image/model pipelines and screenshot gates - [`asset-tooling.md`](asset-tooling.md) — image/model pipelines and screenshot gates
- [`../tools/README.md`](../tools/README.md) — tool inventory, categories and test runner - [`../tools/README.md`](../tools/README.md) — tool inventory, categories and test runner
- [`gameplay.md`](gameplay.md) — controls, venues and escalation - [`gameplay.md`](gameplay.md) — controls, venues and escalation
- [`joke-shop-map.md`](joke-shop-map.md) — Sunset Strip real-site mapping - [`venues.md`](venues.md) — Hollywood, Joke Shop and TV Studio real-site mapping
- [`tutorial.md`](tutorial.md) — first-shift tutorial beats
- [`npc-visual-review.md`](npc-visual-review.md) — NPC visual baseline, acceptance criteria and ranked gaps - [`npc-visual-review.md`](npc-visual-review.md) — NPC visual baseline, acceptance criteria and ranked gaps
- [`npc-proportion-audit.md`](npc-proportion-audit.md) — NPC proportion ratios and stylized guide results
- [`3d-model-review.md`](3d-model-review.md) — 3D model re-evaluation: measurements, scorecard and ranked gaps - [`3d-model-review.md`](3d-model-review.md) — 3D model re-evaluation: measurements, scorecard and ranked gaps
- [`development-status.md`](development-status.md) — implemented features, current gaps and active planning - [`development-status.md`](development-status.md) — implemented features, current gaps and active planning
- [`accessibility.md`](accessibility.md) — accessibility statement and known gaps - [`accessibility.md`](accessibility.md) — accessibility statement and known gaps
- [`known-limitations.md`](known-limitations.md) — release-candidate blockers and accepted limits - [`known-limitations.md`](known-limitations.md) — release-candidate blockers and accepted limits
- [`smoke-checklist.md`](smoke-checklist.md) — manual verification - [`release-qa.md`](release-qa.md) — manual smoke checklist and balance-acceptance protocol
- [`balance-acceptance.md`](balance-acceptance.md) — release-candidate balance targets and evidence protocol
- [`../CREDITS.md`](../CREDITS.md) — credits and asset provenance - [`../CREDITS.md`](../CREDITS.md) — credits and asset provenance
- [`../PRIVACY.md`](../PRIVACY.md) — privacy note - [`../PRIVACY.md`](../PRIVACY.md) — privacy note
- [`ui-copy.md`](ui-copy.md) — screen, copy and button inventory - [`ui-copy.md`](ui-copy.md) — screen, copy and button inventory
@@ -39,7 +37,7 @@ This folder holds the project documentation, visual references and generated QA
- [`screens/README.md`](screens/README.md) — concepts, designs, screenshots and realism runs - [`screens/README.md`](screens/README.md) — concepts, designs, screenshots and realism runs
- [`blender_previews/README.md`](blender_previews/README.md) — character/gear/prop previews - [`blender_previews/README.md`](blender_previews/README.md) — character/gear/prop previews
- [`screenshots/README.md`](screenshots/README.md) — committed game gallery - [`screenshots/README.md`](screenshots/README.md) — committed game gallery
- [`screen-design-report.md`](screen-design-report.md) — latest screen-design judge result - [`ui-copy.md`](ui-copy.md#screen-design-judge-report) — screen/button inventory and latest judge result
## Quick preview ## Quick preview
+1 -1
View File
@@ -28,5 +28,5 @@ Known gaps:
review is still a 0.10.0 release-review item. review is still a 0.10.0 release-review item.
Test evidence and the manual checklist live in Test evidence and the manual checklist live in
[`smoke-checklist.md`](smoke-checklist.md); settings and save behaviour are [`release-qa.md`](release-qa.md#manual-smoke-checklist); settings and save behaviour are
documented in [`getting-started.md`](getting-started.md). documented in [`getting-started.md`](getting-started.md).
+18 -1
View File
@@ -1,8 +1,14 @@
# ADR-0015: NPC model variety sourcing # ADR-0015: NPC model variety sourcing
- **Status:** Accepted (planning) - **Status:** Superseded by [ADR-0017](0017-quaternius-npc-base.md) (2026-11-05)
- **Date:** 2026-11-03 - **Date:** 2026-11-03
## Superseded
ADR-0017 replaces the generator-first decision below: Quaternius is now the
primary NPC base. The evaluation, licence rules and consequences remain as the
record of the 2026-11-03 decision.
## Context ## Context
The library has eight skinned characters, and role/seed selection reuses them The library has eight skinned characters, and role/seed selection reuses them
@@ -70,3 +76,14 @@ measured. None was a drop-in; the full results are in
polygon count, no rig in the base mesh and a separate authoring dependency. polygon count, no rig in the base mesh and a separate authoring dependency.
- **Use Mixamo or Ready Player Me.** Rejected: service terms are not a simple - **Use Mixamo or Ready Player Me.** Rejected: service terms are not a simple
free/open-source redistribution basis for shipped meshes. free/open-source redistribution basis for shipped meshes.
## Amendment 2026-11-04
- The character budget is raised to **16,000 triangles / 16,000 glTF vertices** per character.
The required 13 core bones remain; optional extra bones (fingers, face, toes) are allowed if
they do not move the core rest positions. The low-poly fallback remains the fallback, not a
second contract.
- MakeHuman is now an active local development pipeline (body retarget plus project-owned
head/hands), while the procedural library remains the shipped path and CI fallback.
- Blender MCP is optional interactive tooling only; it is not a build, CI, test or runtime
dependency. Setup is in [`docs/asset-tooling.md#optional-interactive-blender-mcp`](../asset-tooling.md#optional-interactive-blender-mcp).
+42
View File
@@ -0,0 +1,42 @@
# ADR-0016: 24,000-triangle character budget
- **Status:** Accepted
- **Date:** 2026-11-05
## Context
ADR-0015 raised the character contract to 16,000 triangles / 16,000 glTF
vertices for the MakeHuman head, hands and crowd variety. The local MPFB
full-body prototype now uses that head, project hands and shoes, and a fused
neck: it measures 19,747 triangles before any clothing variation. The 16,000 cap
leaves no room for the firefighter and hoodie gear, and it forces the body and
neck into a low decimation that produces visible facets.
## Decision
Raise the character contract to **24,000 triangles / 24,000 glTF vertices** per
character (`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS`, `npc_pipeline.MAX_TRIANGLES`/
`MAX_VERTICES`, and the `adapt` defaults). The 13 core bones, five tint slots,
front orientation, grounding and single `npc_base` skinned mesh are unchanged.
Gear stays at 1,500 triangles and props at 4,000.
## Consequences
- The MPFB body, accepted head and neck fit with headroom for the 12 clothing
variants and future jacket/hoodie detail.
- The 24-NPC and Web frame-time budgets must be re-measured before shipping any
variant built at the new cap; the extra geometry is not free.
- The procedural generator and the low-poly `npc_base` fallback remain valid;
they are not required to use the new headroom.
- MPFB stays a local development tool. Production must bake a committed source
GLB; CI and the build must not depend on MPFB.
- Historical reviews and measurements that state 16,000 remain accurate for the
date they were recorded; current contract documents state 24,000.
## Alternatives
- **Keep 16,000 and decimate the MPFB body further:** rejected because the neck
and shoulder already show facets and the clothing variants would exceed the
cap.
- **Separate caps for the MPFB prototype and the shipped characters:** rejected
because it creates a second contract and makes the rollout ambiguous.
+69
View File
@@ -0,0 +1,69 @@
# ADR-0017: Quaternius NPC base
- **Status:** Accepted
- **Date:** 2026-11-05
- **Supersedes:** ADR-0015 (generator-first character sourcing)
## Context
The current NPC library is built by the project-owned procedural generator. It
keeps the contract and ships, but the body, neck, hands and shoes need a
bespoke part pipeline to read as human. The MakeHuman head and MPFB body
prototypes improved detail but added a second authoring path, a 19,747-triangle
prototype and unresolved neck/shoulder seams.
The CC0 **Quaternius Ultimate Modular Characters** pack already provides a
coherent low-poly human silhouette, 21 modular outfits and 24 animation clips.
The researched GLBs (`Casual`, `Casual_Hoodie`, `Worker`) measure 4 meshes, 62
joints and 24 clips each. The owner directed that Quaternius become the NPC
base. The full evaluation is in [`docs/npc-model-sourcing.md`](../npc-model-sourcing.md).
## Decision
1. **Quaternius is the primary NPC mesh and animation source.** Keep the
licence and SHA-256 provenance for the three researched GLBs.
2. **Stage 1 keeps the runtime contract.** Bake a canonical Quaternius base onto
the existing 13-core contract, five tint slots, 24,000/24,000 budget,
grounded −Y front and one `npc_base` skinned mesh. Retarget Idle and Walk;
discard the combat/gun/sword clips. `NpcAvatar`, gear attach points and the
pose composition remain the runtime interface.
3. **The 62-joint rig is an authoring source, not a runtime dependency.** It is
not shipped and Quaternius is not loaded by CI, build or runtime.
4. **Stage 2 needs its own ADR.** If the proportion warp fails the silhouette,
deformation or gear gate, stop and decide whether to adopt the 62-joint rig
at runtime. Do not ship a hybrid rig or a second gear offset system.
5. **Close the MakeHuman/MPFB body work.** The accepted MakeHuman head and the
procedural `npc_base` stay as tested fallbacks until the Quaternius rollout
ships. No new MPFB development.
6. **Commit baked assets only.** The release consumes committed GLBs, maps and
the manifest; Quaternius and Blender remain development tools.
## Consequences
### Positive
- A human base, modular outfits and a coherent animation set are already
authored and CC0.
- The bespoke neck, hand and shoe pipelines are no longer on the critical path.
- The runtime contract and gear stay stable during the mesh migration.
### Negative
- A 62-joint T-pose does not share the arm-down 13-core rest pose or
proportions; the proportion warp is the main risk.
- Quaternius' animation quality is only available after retargeting, unless
Stage 2 is accepted.
- The migration touches all 12 variants, their AO maps, manifest entries and
baseline captures at once.
## Alternatives
- **Keep the procedural generator as the primary path.** Rejected by the owner:
the human silhouette remains the weak point and the bespoke part pipeline is
larger than the Quaternius bake.
- **Adopt the 62-joint rig immediately.** Deferred to Stage 2 because it changes
`NpcAvatar`, pose composition and gear offsets at the same time as the mesh.
- **Continue the MPFB body.** Rejected: it needs a separate head/neck/hand
pipeline and still has unresolved surface seams.
- **Use a service character generator (Mixamo, Ready Player Me).** Rejected:
redistribution and runtime terms are not a CC0 basis.
@@ -0,0 +1,96 @@
# ADR-0018: Quaternius Stage 2 — adopt source proportions at runtime
- **Status:** Accepted (2026-11-05, owner chose option 2)
- **Date:** 2026-11-05
- **Supersedes:** ADR-0017 decision 2 (Stage 1 keeps the 13-core contract)
## Context
ADR-0017 made Quaternius the NPC base and staged the migration: Stage 1 bakes a
proportion-warped mesh onto the existing 13-core contract; Stage 2 adopts the
62-joint rig at runtime only if the warp fails.
The Stage 1 proportion warp was executed and measured (`docs/npc-model-sourcing.md`
§Rest-pose and proportion-warp measurement). The arm-down rest pose bakes cleanly, but the
warp to the contract proportions does not:
| Joint | Quaternius | Contract | Delta |
| --- | --- | --- | --- |
| Hips height | 0.869 | 0.62 | −0.249 |
| Shoulder x | 0.148 | 0.27 | +0.122 |
| Arm (shoulder→hand tip) | 0.429 | 0.70 | ×1.63 |
| Leg (hip→ankle) | 0.835 | 0.62 | ×0.74 |
| Torso (hip→head) | 0.745 | 1.20 | ×1.61 |
The vision judge reads the warped mesh as distorted: paddle/claw hands, forearms lost into the
torso, stretched shins, "long legs + short torso". The arm either stays short (a shortened arm
against the contract hand) or stretches 1.63× (a distorted deltoid + hand). Both are the gate's
rejection conditions.
## Decision
Stop the Stage 1 rollout and **adopt the Quaternius 62-joint rig at runtime** (owner chose
option 2). The mesh is not proportion-warped: it keeps its source LOD (3,199 welded verts /
6,206 tris) and only the arm-down rest pose is baked.
- `NpcAvatar` maps the Quaternius joint names instead of the 13-core names.
- Gear attach points are re-derived from the Quaternius skeleton (hip ≈0.87, shoulder ≈0.148/1.39,
wrist ≈0.99, knee ≈0.52).
- The Quaternius Idle and Walk clips are used directly — they are authored for the 62-joint rig,
so no retargeting pass is needed. Combat/gun/sword clips stay unused.
Option 1 (keep the 13-core names with Quaternius rest positions) is rejected in favour of the
full source rig.
## Consequences
### Positive
- The Quaternius silhouette, deltoid, hand and leg proportions are preserved — no paddle hands,
no shortened arm, no torso/leg distortion.
- The mesh stays at source LOD (far under the 24,000 cap), so no decimation is needed.
### Negative
- The runtime contract numbers (hip 0.62, shoulder 0.27, hand 0.78) change; every gear attach
point, `npc_proportions` guide, fit measurement and pose-composition constant must be
re-derived and re-tested.
- `NpcAvatar`'s bone mapping and pose composition are rewritten for the 62-joint rig, a larger
change than the mesh swap.
- The 12-variant rollout and the 12×6 baseline are still blocked until the new rest positions
and gear fit are owner-approved.
## Alternatives
- **Ship the warped mesh.** Rejected by the gate: the 1.63× arm / 0.74× leg warp visibly
distorts and the owner directed keeping the Quaternius as close to source as possible.
- **Keep the old contract and accept a short arm.** Rejected for the same reason: the hand ends
at ≈0.99 instead of 0.78, so handheld gear floats unless attach points move — which is option 1.
- **Abandon Quaternius and keep the procedural generator.** Rejected in ADR-0017.
## Open question
Resolved: option 2 (62-joint runtime) is the owner's choice. The first implementation step is
the `NpcAvatar` bone-map + gear-attach re-derivation, then the slot/AO bake and the 12-variant
rollout.
## Follow-up decision: full roster swap
- **Date:** 2026-11-05 (owner, at the `0.9.9` face/silhouette gate)
The owner chose **option (a) full swap** from
[`docs/screens/npc-face-gate/`](../../screens/npc-face-gate/README.md): every roster character
moves onto the Quaternius base, and the procedural generator plus the MakeHuman head retire to
the tested fallback rather than staying in the shipped roster.
Scope consequences:
- The shipped roster keeps its twelve public character names and `CHARACTER_POOLS` mapping; the
geometry behind them comes from the three CC0 full-body sources already researched
(`Casual`, `Casual_Hoodie`, `Worker`), aliased per role.
- Variety now comes from the five tint slots, the gear library and the per-role outfit rules
instead of twelve distinct meshes. Roster silhouettes are therefore *less* varied than the
procedural library until the modular part library (hair, outfits) is added — tracked as a
`0.9.10` task, not hidden.
- The procedural library, `_build_body()` and the MakeHuman head stay in the tree and tested as
the instant fallback; no MPFB or Stage-1 proportion-warp work reopens.
+3
View File
@@ -19,3 +19,6 @@ Each ADR records the context, decision, consequences and alternatives for a dura
- [0013-blender-authored-npc-characters](0013-blender-authored-npc-characters.md) - [0013-blender-authored-npc-characters](0013-blender-authored-npc-characters.md)
- [0014-runtime-image-root](0014-runtime-image-root.md) - [0014-runtime-image-root](0014-runtime-image-root.md)
- [0015-npc-model-variety-sourcing](0015-npc-model-variety-sourcing.md) - [0015-npc-model-variety-sourcing](0015-npc-model-variety-sourcing.md)
- [0016-npc-character-budget-24k](0016-npc-character-budget-24k.md)
- [0017-quaternius-npc-base](0017-quaternius-npc-base.md)
- [0018-quaternius-stage2-runtime-rig](0018-quaternius-stage2-runtime-rig.md)
+103 -22
View File
@@ -37,7 +37,7 @@ This document follows the structure of the arc42 architecture documentation temp
- **MINOR** — new venues, NPC archetypes, systems, or features that are backward compatible. - **MINOR** — new venues, NPC archetypes, systems, or features that are backward compatible.
- **PATCH** — bug fixes and small polish that do not change behavior. - **PATCH** — bug fixes and small polish that do not change behavior.
The single source of truth for the version is `config/version` in `project.godot` (currently `0.10.0-rc.1`). Each release is tagged in git as `v<version>` (e.g. `v0.6.0`). The `0.x` series is pre-stable, so MINOR bumps may introduce breaking changes while the prototype is still stabilizing. `tools/build_releases.sh` and `tools/publish_release.sh` read the version from `project.godot`, so bumping it there is the only required step before a release. The full bump checklist lives in [`RELEASING.md`](RELEASING.md). The single source of truth for the version is `config/version` in `project.godot` (currently `0.9.8`). Each release is tagged in git as `v<version>` (e.g. `v0.6.0`). The `0.x` series is pre-stable, so MINOR bumps may introduce breaking changes while the prototype is still stabilizing. `tools/build_releases.sh` and `tools/publish_release.sh` read the version from `project.godot`, so bumping it there is the only required step before a release. The full bump checklist lives in [`RELEASING.md`](RELEASING.md).
## 3. Context and Scope ## 3. Context and Scope
@@ -81,7 +81,7 @@ The player interacts with NPCs, venue entrances, access artifacts, equipment, st
- `WorldBuilder` assembles the procedural venue from a per-venue `VENUE_SPECS` table (facade, windows, stairs, street, nav bounds, guard spawn, feature flags and prop placements), prefers the Blender prop library in `assets/models/blender/props/`, and falls back to `assets/models/generated/` or primitives. `GameManager` rebuilds only the `World` container when the venue changes, so the guard/UI and their state survive. It also wires the phase-driven library emissives and street dressing. - `WorldBuilder` assembles the procedural venue from a per-venue `VENUE_SPECS` table (facade, windows, stairs, street, nav bounds, guard spawn, feature flags and prop placements), prefers the Blender prop library in `assets/models/blender/props/`, and falls back to `assets/models/generated/` or primitives. `GameManager` rebuilds only the `World` container when the venue changes, so the guard/UI and their state survive. It also wires the phase-driven library emissives and street dressing.
- `src/game/ui/art.gd` loads runtime UI/scene art from `assets/images/ui/` and `assets/images/scenes/`, with null fallbacks so panels keep their procedural layouts. - `src/game/ui/art.gd` loads runtime UI/scene art from `assets/images/ui/` and `assets/images/scenes/`, with null fallbacks so panels keep their procedural layouts.
- `tools/model_pipeline.py` builds and audits the street-prop `.glb` models and packs their colour atlases. - `tools/model_pipeline.py` builds and audits the street-prop `.glb` models and packs their colour atlases.
- `tools/npc_pipeline.py` builds and audits the skinned NPC base model (`assets/models/generated/npc_base.glb`) and its per-slot material maps, with an optional AI fabric override (ADR-0013). - `tools/npc_pipeline.py` builds and audits the skinned NPC base model (`assets/models/generated/npc_base.glb`) and its per-slot material maps, with an optional AI fabric override (ADR-0013). The active character migration bakes the CC0 Quaternius base onto the same contract (ADR-0017); the procedural base remains the fallback.
- `tools/blender_assets.py` builds, renders and audits the Blender asset library (`assets/models/blender/{npc,npc_gear,props}/` + `manifest.json`): NPC character variants that are drop-in for `npc_base`, attach-point gear, and street/venue props. `NpcAvatar` selects variants/gear by role/seed and `WorldBuilder` loads the prop replacements and new street dressing, with procedural fallbacks (see `docs/blender-assets.md`). - `tools/blender_assets.py` builds, renders and audits the Blender asset library (`assets/models/blender/{npc,npc_gear,props}/` + `manifest.json`): NPC character variants that are drop-in for `npc_base`, attach-point gear, and street/venue props. `NpcAvatar` selects variants/gear by role/seed and `WorldBuilder` loads the prop replacements and new street dressing, with procedural fallbacks (see `docs/blender-assets.md`).
- `tools/scene_concepts.py` generates realism-pass references (`docs/screens/concepts/`), skyline backdrops and surface tiles (raw `assets/textures/ai/v2/raw/` → candidates `assets/textures/ai/game_ready/v2/`), promotes the validated ones to the tracked `v2/picks/` that `SurfaceMaterials`/`WorldBuilder` preload, audits them, and scores `src/test/capture_realism.gd` A/B captures (six stages, runtime texture swaps) against the concepts (`docs/screens/realism/<run>/`). - `tools/scene_concepts.py` generates realism-pass references (`docs/screens/concepts/`), skyline backdrops and surface tiles (raw `assets/textures/ai/v2/raw/` → candidates `assets/textures/ai/game_ready/v2/`), promotes the validated ones to the tracked `v2/picks/` that `SurfaceMaterials`/`WorldBuilder` preload, audits them, and scores `src/test/capture_realism.gd` A/B captures (six stages, runtime texture swaps) against the concepts (`docs/screens/realism/<run>/`).
- `tools/asset_pipeline.py` generates and audits role-aware textures; `tools/screen_designs.py` generates screen references and judges captures; `tools/check_screenshots.sh` renders the staged gallery and runs the compare gate. - `tools/asset_pipeline.py` generates and audits role-aware textures; `tools/screen_designs.py` generates screen references and judges captures; `tools/check_screenshots.sh` renders the staged gallery and runs the compare gate.
@@ -128,23 +128,99 @@ Contracts that keep the pipeline replaceable:
- UI/scene art is loaded through `UiArt` with a null/neutral fallback; composed images live in `assets/images/`, not in the docs reference tree (ADR-0014). - UI/scene art is loaded through `UiArt` with a null/neutral fallback; composed images live in `assets/images/`, not in the docs reference tree (ADR-0014).
- CI stays offline: only committed `.glb`/`.png`/`.tres` and Godot are needed; optional AI/Blender steps are reproducible development tools. - CI stays offline: only committed `.glb`/`.png`/`.tres` and Godot are needed; optional AI/Blender steps are reproducible development tools.
### Character and gear asset contract
The runtime NPC contract is intentionally narrow. It is enforced by
`tools/blender_assets.py audit`, the GLB `inspect` path and the GDScript tests:
| Contract | Rule |
| --- | --- |
| Mesh node | `npc_base`, skinned under an armature imported as `Skeleton3D`. |
| Core bones | `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R`, `Spine`, `Neck` at the frozen rest positions. Optional extra bones (fingers, face, toes) are allowed if they do not move the core rest positions or break `NpcAvatar` proxies. |
| Tint slots | `skin`, `cloth`, `pants`, `accessory`, `hair`; fixed detail materials keep their authored colours. |
| Orientation | Blender −Y / Godot +Z front, feet grounded near `y = 0`, metre scale. |
| Budget | 24,000 triangles / 24,000 glTF vertices per character; 1,500 per gear piece; 4,000 per prop. The low-poly `npc_base` remains a fallback, not a second contract. |
| Textures | 256² albedo + normal + packed ORM per slot; cloth/pants use the 4× `NPC_FABRIC_UV_SCALE` and skip per-character baked AO, while skin/hair/gear keep AO. |
| Gear attach | Authored in attach-point space: head `Rig/Head` (0, 1.62, 0), back (0, 1.10, 0), chest (0, 1.30, 0), hand (±0.27, 0.78, 0.02); grip 5 cm below the origin. `GEAR_OFFSETS` and `HAND_ITEM_FORWARD` are runtime fit corrections, not authored-geometry changes. |
| Fallback chain | Library variant → `npc_base.glb` → procedural `NpcAvatar` mesh. Committed GLBs and maps must keep CI offline and Blender-free. |
Asset types and their consumers:
- Library characters, gear and props: `assets/models/blender/**`, generated by
`tools/blender_assets.py`; loaded by `NpcAvatar`/`WorldBuilder` with
generated/procedural fallbacks.
- Frozen base: `assets/models/generated/npc_base.glb`, generated by
`tools/npc_pipeline.py`; the procedural avatar remains the last-resort path.
- Maps: `assets/textures/npc/*_{albedo,normal,orm}.png` and baked AO maps; the
mesh budgets and material wiring are checked by `audit`/`fit`/`texels` and the
headless suite.
- Editable `.blend` sources are development-only; only exported GLBs, AO/maps,
manifests and committed review sheets are runtime/repository assets.
### Venue implementation contract
The three venues are authored by `WorldBuilder.VENUE_SPECS` and rebuilt through
the `World` container when the venue changes; the guard, UI and checkpoint state
survive. Venue data is content, while the following runtime rules are fixed:
| Venue | Real reference | Game implementation | Budget |
| --- | --- | --- | --- |
| Hollywood Boulevard | 6840 Hollywood Blvd / 1921 Masonic Temple | Six-column Masonic facade, eight steps, Walk of Fame, boulevard, Ovation arcade and neighbours | 58,884 tris / 409 meshes |
| The Joke Shop | Fictional stand-in for 8433 Sunset Blvd | 27 m black hall, signature canopy, circular blade sign, red curtain bays, west entrance, hillside and neighbours | 25,828 tris / 518 meshes |
| Late Night TV Studio | Same El Capitan block as Hollywood | Reuses the Masonic facade and steps with bulb canopy, car, barriers and studio-arrival staging | shares the Hollywood block; TV spec is a 30.0 m variant |
Venue rules:
- Coordinates are Godot metres; the guard's front is +Z. `VENUE_SPECS` owns
facade dimensions, street/nav bounds, guard spawn, feature flags and prop
placements; tests enforce the map budgets.
- Collision and navigation remain mesh-based. Props that block walking need a
`StaticBody3D` plus `NavigationObstacle3D`; the marquee, handrail, lantern and
door are documented non-blocking exceptions.
- Day/night is a phase offset on one geometry/scenario, not a second baked map.
The venue menu's Night row starts in `LATE` while the checkpoint stores the
offset.
- Real-site references are evidence for shape/materials, not shipped assets.
Licensed boards live under `docs/screens/refs/` with sidecars; local-only
research stays in `tmp/research/` and is never committed.
- The stylized geometry remains the runtime fallback if a future data-backed
block or facade kit fails budget, navigation, visual or provenance gates.
### Persistence and save contract
Persistence is local-only under Godot's `user://` directory. The architecture
owns the schema; operational locations and reset/rollback steps live in
[`getting-started.md`](getting-started.md#save-data-and-migration).
| File | Contents | Compatibility |
| --- | --- | --- |
| `settings.json` | Camera/HUD/bindings/difficulty/audio/accessibility/tutorial flags | Schema-less compatible growth; missing keys merge from `Save.DEFAULT_SETTINGS`. |
| `campaign.json` | Campaign index, completed venues, difficulty, shift number, last grade | Missing fields fall back to safe campaign defaults. |
| `shift_results.json` | Completed-shift review history (array) | Append-only; unknown fields are ignored by readers. |
| `shift_state.json` | Resumable checkpoint: version `1`, venue, guard/door/flashlight/body-camera state, coordinator snapshot, NPC rows | Versioned; unknown/missing state falls back rather than blocking boot. |
Writes go to a `.tmp` file and are renamed after the complete JSON is written.
Corrupt or partial files are ignored on load; a failed replace leaves the prior
valid file intact. The checkpoint deliberately excludes the tutorial overlay and
transient input state.
### Character development tooling ### Character development tooling
Characters have two authored sources and one procedural fallback. The eight-variant Characters have one active migration, two committed fallbacks and one procedural last
Blender library (`assets/models/blender/npc/`) is the normal presentation path; the resort. The Quaternius base migration (ADR-0017) bakes the CC0 Quaternius mesh and
older skinned `npc_base` (`assets/models/generated/npc_base.glb`) remains a frozen Idle/Walk clips onto the existing 13-core contract and produces the next 12-variant
contract and fallback; `NpcAvatar`'s procedural rig is the offline last resort. library. Until that ships, the 12-variant Blender library
Four crowd-variety prototypes have been built and audited locally but are not yet in (`assets/models/blender/npc/`) is the normal presentation path, the older skinned
the shipped catalogue; generator-first expansion is the active character track `npc_base` (`assets/models/generated/npc_base.glb`) is the frozen contract fallback,
(ADR-0015). Blender is a developer tool only — runtime, tests and CI consume committed and `NpcAvatar`'s procedural rig is the offline last resort. Blender and Quaternius are
GLBs and maps. developer tools only — runtime, tests and CI consume committed GLBs and maps.
| Tool | Role | Committed artifact | Verification gate | | Tool | Role | Committed artifact | Verification gate |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `tools/npc_pipeline.py` | Builds the skinned base avatar, five-slot material maps, palette and AI-fabric preparation (`prepare`/`palette`) | `npc_base.glb`, `assets/textures/npc/*_{albedo,normal,orm}.png`, `palette.json` | `audit_glb` against the frozen 6,000-triangle / 4,000-vertex base contract | | `tools/npc_pipeline.py` | Builds the skinned base avatar, five-slot material maps, palette and AI-fabric preparation (`prepare`/`palette`) | `npc_base.glb`, `assets/textures/npc/*_{albedo,normal,orm}.png`, `palette.json` | `audit_glb` against the 24,000-triangle / 24,000-vertex base contract (13 core bones required; extras allowed) |
| `tools/blender_assets.py` | Builds/renders the library: 8 characters, 18 gear pieces, 20 props, plus `manifest.json`; stdlib `audit`/`fit`/`sheet`/`list` | `assets/models/blender/{npc,npc_gear,props}/*.glb` | Library cap 8,000 tris / 8,000 verts per character, 1,500 per gear, 4,000 per prop; grounding, UV, degenerate-triangle, attach-fit and manifest coverage checks | | `tools/blender_assets.py` | Builds/renders the library: 8 characters, 18 gear pieces, 20 props, plus `manifest.json`; stdlib `audit`/`fit`/`sheet`/`list` | `assets/models/blender/{npc,npc_gear,props}/*.glb` | Character cap 24,000 tris / 24,000 verts, 1,500 per gear, 4,000 per prop; grounding, UV, degenerate-triangle, attach-fit and manifest coverage checks |
| `tools/bake_ao.py` | Bakes per-asset AO maps | `<asset>_ao.png` (256²) | Wired through `Materials.apply_ao()`/`SurfaceMaterials`; cloth/pants opt out (see below) | | `tools/bake_ao.py` | Bakes per-asset AO maps | `<asset>_ao.png` (256²) | Wired through `Materials.apply_ao()`/`SurfaceMaterials`; cloth/pants opt out (see below) |
| `tools/npc_proportions.py` | Measures exported GLB rest pose (height, head, shoulders, hips, torso, limbs, feet) against the stylized guide | report / JSON in `docs/npc-proportion-audit.md` | All eight variants must stay inside the guide after a body change | | `tools/npc_proportions.py` | Measures exported GLB rest pose (height, head, shoulders, hips, torso, limbs, feet) against the stylized guide | report / JSON in `docs/npc-visual-review.md#proportion-audit` | All eight variants must stay inside the guide after a body change |
| `tools/npc_baseline.py` | Assembles neutral-baseline sheets and before/after comparisons | `docs/blender_previews/sheet_npc_baseline*.jpg` | Human review against `docs/npc-visual-review.md` | | `tools/npc_baseline.py` | Assembles neutral-baseline sheets and before/after comparisons | `docs/blender_previews/sheet_npc_baseline*.jpg` | Human review against `docs/npc-visual-review.md` |
| `src/test/capture_npc_baseline.gd` | Fixed-camera studio baseline, 8 variants × 6 views | local `tmp/` frames | Neutral silhouette/face/gear review | | `src/test/capture_npc_baseline.gd` | Fixed-camera studio baseline, 8 variants × 6 views | local `tmp/` frames | Neutral silhouette/face/gear review |
| `src/test/capture_npc_review.gd` | Final-context gate: 8 variants × 2 venues × day/night × 3 distances = 96 frames, crowd hidden, review-only `character_override` for the guard | local `tmp/` frames | Pre-build acceptance gate for gear fit and venue lighting | | `src/test/capture_npc_review.gd` | Final-context gate: 8 variants × 2 venues × day/night × 3 distances = 96 frames, crowd hidden, review-only `character_override` for the guard | local `tmp/` frames | Pre-build acceptance gate for gear fit and venue lighting |
@@ -162,14 +238,14 @@ Runtime contracts that must survive any character change:
- Cloth/pants use `Materials.NPC_FABRIC_UV_SCALE` (4×) and skip the baked per-character AO; - Cloth/pants use `Materials.NPC_FABRIC_UV_SCALE` (4×) and skip the baked per-character AO;
skin/hair/gear keep AO. Do not reintroduce the blotchy cloth AO. skin/hair/gear keep AO. Do not reintroduce the blotchy cloth AO.
- `apply_lod()` swaps to an impostor at 26 m; the 24-NPC budget smoke (6.94 ms, 168 meshes, - `apply_lod()` swaps to an impostor at 26 m; the 24-NPC budget smoke (6.94 ms, 168 meshes,
131,616 triangles) is the performance gate. Changing either triangle cap requires 152,464 triangles) is the performance gate. Changing either triangle cap requires
re-measuring that smoke and the Web/Compatibility path. re-measuring that smoke and the Web/Compatibility path.
- Rebuild only the assets whose geometry changed (Blender GLB export is byte-nondeterministic), - Rebuild only the assets whose geometry changed (Blender GLB export is byte-nondeterministic),
then run `godot --headless --path . --import`; never commit a no-op full rebuild. then run `godot --headless --path . --import`; never commit a no-op full rebuild.
Evidence and detailed contracts live in [`blender-assets.md`](blender-assets.md), Evidence and detailed contracts live in [`blender-assets.md`](blender-assets.md),
[`npc-visual-review.md`](npc-visual-review.md) and [`npc-visual-review.md`](npc-visual-review.md) and
[`npc-proportion-audit.md`](npc-proportion-audit.md); command-level usage is in [`npc-visual-review.md#proportion-audit`](npc-visual-review.md#proportion-audit); command-level usage is in
[`asset-tooling.md`](asset-tooling.md). [`asset-tooling.md`](asset-tooling.md).
### Local map development tooling (planned) ### Local map development tooling (planned)
@@ -312,6 +388,7 @@ Architecture decisions are recorded as ADRs:
- [ADR-0008: Cone focus and focus circle](adr/0008-cone-focus-and-head-ring.md) - [ADR-0008: Cone focus and focus circle](adr/0008-cone-focus-and-head-ring.md)
- [ADR-0009: Radial action menu](adr/0009-radial-action-menu.md) - [ADR-0009: Radial action menu](adr/0009-radial-action-menu.md)
- [ADR-0010: Procedural NPC presentation](adr/0010-procedural-npc-presentation.md) - [ADR-0010: Procedural NPC presentation](adr/0010-procedural-npc-presentation.md)
- [ADR-0017: Quaternius NPC base](adr/0017-quaternius-npc-base.md)
- [ADR-0011: Aim-free selection and precision walk](adr/0011-aim-free-selection-and-precision-walk.md) - [ADR-0011: Aim-free selection and precision walk](adr/0011-aim-free-selection-and-precision-walk.md)
- [ADR-0012: Surface materials and generated props](adr/0012-surface-materials-and-generated-props.md) - [ADR-0012: Surface materials and generated props](adr/0012-surface-materials-and-generated-props.md)
- [ADR-0013: Blender-authored NPC characters and baked materials](adr/0013-blender-authored-npc-characters.md) - [ADR-0013: Blender-authored NPC characters and baked materials](adr/0013-blender-authored-npc-characters.md)
@@ -359,17 +436,21 @@ architecture document.
| Document | Canonical role | Relationship to this architecture | | Document | Canonical role | Relationship to this architecture |
| --- | --- | --- | | --- | --- | --- |
| [`PLAN.md`](../PLAN.md) | Product/design intent, pillars, venues and success criteria | Design source; architecture realizes it | | [`PLAN.md`](../PLAN.md) | Product/design intent, pillars, venues and success criteria | Design source; architecture realizes it |
| [`docs/gameplay.md`](gameplay.md) | Playable loop, escalation, venue mechanics | Runtime/behavioral view | | [`docs/gameplay.md`](gameplay.md) | Playable loop, escalation, tutorial, venue mechanics | Runtime/behavioral view |
| [`docs/venues.md`](venues.md) | Real-site evidence, mapping decisions and deliberate simplifications | Runtime venues' evidence companion to §5 |
| [`docs/architecture.md`](architecture.md) | System boundaries, data flow, constraints, deployment, cross-cutting decisions, quality risks | Single architecture source | | [`docs/architecture.md`](architecture.md) | System boundaries, data flow, constraints, deployment, cross-cutting decisions, quality risks | Single architecture source |
| [`docs/adr/`](adr/) | Immutable decision records with context and consequences | Decision history linked from §9 | | [`docs/adr/`](adr/) | Immutable decision records with context and consequences | Decision history linked from §9 |
| [`docs/asset-tooling.md`](asset-tooling.md) | Build/audit/capture commands and pipeline usage | Operational appendix to the pipeline section | | [`docs/asset-tooling.md`](asset-tooling.md) | Build/audit/capture commands, optional Blender MCP and pipeline usage | Operational appendix to the pipeline section |
| [`docs/blender-assets.md`](blender-assets.md) | Blender character/gear/prop contracts and attach points | Detailed asset contract appendix | | [`docs/blender-assets.md`](blender-assets.md) | Blender library art direction, roles, layout and checks | Operational asset appendix; contract lives here in §5 |
| [`docs/npc-visual-review.md`](npc-visual-review.md) | NPC baseline views, acceptance criteria and ranked visual gaps | Visual-QA appendix to the character tooling section | | [`docs/npc-visual-review.md`](npc-visual-review.md) | NPC baseline views, acceptance criteria, visual gaps and proportion audit | Visual-QA appendix to the character tooling section |
| [`docs/npc-proportion-audit.md`](npc-proportion-audit.md) | Measured proportions against the stylized guide | Measurement appendix to the character tooling section | | [`docs/3d-model-review.md`](3d-model-review.md) | Scorecard, measurements and ranked asset gaps | Working review companion to §5 |
| [`docs/npc-model-sourcing.md`](npc-model-sourcing.md) | Character-source evaluation, licence rules and the character-development plan | Supports the character tooling section and ADR-0015 | | [`docs/npc-model-sourcing.md`](npc-model-sourcing.md) | Character-source evaluation and licence/prototype outcome | Supports the character tooling section and ADR-0015 |
| [`docs/getting-started.md`](getting-started.md) | Setup, commands, controls | Developer onboarding | | [`docs/getting-started.md`](getting-started.md) | Setup, commands, controls | Developer onboarding |
| [`docs/RELEASING.md`](RELEASING.md) | Release checklist and CI/publishing steps | Deployment appendix to §7 | | [`docs/RELEASING.md`](RELEASING.md) | Release checklist and CI/publishing steps | Deployment appendix to §7 |
| [`docs/smoke-checklist.md`](smoke-checklist.md) | Manual verification script | QA appendix to §10 | | [`docs/release-qa.md`](release-qa.md) | Manual smoke checklist and balance protocol | QA appendix to §10 |
| [`docs/accessibility.md`](accessibility.md) | Accessibility features, statement and known gaps | Player-facing quality statement |
| [`docs/known-limitations.md`](known-limitations.md) | Release blockers and accepted limitations | Player/release-facing risk statement |
| [`docs/ui-copy.md`](ui-copy.md) | Screen/button inventory, copy audit and screen-judge report | Presentation appendix |
| [`docs/development-status.md`](development-status.md) | Implemented features and current gaps | Status companion to the roadmap | | [`docs/development-status.md`](development-status.md) | Implemented features and current gaps | Status companion to the roadmap |
| [`TODO.md`](../TODO.md) | Priority-ordered active tasks, unversioned development tracks and versioned roadmap | Planning source; no architecture duplication | | [`TODO.md`](../TODO.md) | Priority-ordered active tasks, unversioned development tracks and versioned roadmap | Planning source; no architecture duplication |
| [`CHANGELOG.md`](../CHANGELOG.md) | User-facing release history | Release/version companion | | [`CHANGELOG.md`](../CHANGELOG.md) | User-facing release history | Release/version companion |
+179 -32
View File
@@ -82,6 +82,8 @@ tonemapping, glow, fog, colour grading) that the phase lighting applies.
NPCs are moving from the procedural capsule rig to a skinned, UV-mapped base NPCs are moving from the procedural capsule rig to a skinned, UV-mapped base
character authored in Blender ([ADR-0013](adr/0013-blender-authored-npc-characters.md)). character authored in Blender ([ADR-0013](adr/0013-blender-authored-npc-characters.md)).
The active base is the CC0 Quaternius modular character, baked to the runtime contract
([ADR-0017](adr/0017-quaternius-npc-base.md)).
`tools/npc_pipeline.py` builds `assets/models/generated/npc_base.glb` plus the `tools/npc_pipeline.py` builds `assets/models/generated/npc_base.glb` plus the
per-slot material maps. `NpcAvatar` instantiates that skinned model, tints its per-slot material maps. `NpcAvatar` instantiates that skinned model, tints its
five material slots from the seed-driven outfit, and drives the bones from the five material slots from the seed-driven outfit, and drives the bones from the
@@ -129,10 +131,10 @@ with colliders/avoidance and drives their emissive parts from the day/night
cycle. The single `npc_base.glb` and primitive props remain offline fallbacks. cycle. The single `npc_base.glb` and primitive props remain offline fallbacks.
Contract and integration notes: [`blender-assets.md`](blender-assets.md). Contract and integration notes: [`blender-assets.md`](blender-assets.md).
Sourcing and expansion research (no external model is shipped yet): Sourcing and migration plan: [`npc-model-sourcing.md`](npc-model-sourcing.md) records the
[`npc-model-sourcing.md`](npc-model-sourcing.md) compares Quaternius, Kenney, Khronos and 2026-11-05 direction (ADR-0017). Quaternius Ultimate Modular (CC0) is the primary NPC mesh
MakeHuman sources against the character contract and records the 2026-11-03 direction and animation source, baked to the existing 13-core contract; the procedural generator is
(procedural generator first, Quaternius reference, MakeHuman deferred). the fallback. Kenney/Khronos/MakeHuman remain evaluated references, not active bases.
| Characters | Props | | Characters | Props |
| --- | --- | | --- | --- |
@@ -140,11 +142,13 @@ MakeHuman sources against the character contract and records the 2026-11-03 dire
```bash ```bash
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- build flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- build
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- build --head mh
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- render flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- render
python3 tools/blender_assets.py sheet # docs/blender_previews/sheet_*.jpg python3 tools/blender_assets.py sheet # docs/blender_previews/sheet_*.jpg
python3 tools/blender_assets.py audit # no Blender needed python3 tools/blender_assets.py audit # no Blender needed
python3 tools/blender_assets.py fit # gear-vs-body fit report python3 tools/blender_assets.py fit # gear-vs-body fit report
python3 tools/blender_assets.py texels # per-slot texel density (px/m) of the characters python3 tools/blender_assets.py texels # per-slot texel density (px/m) of the characters
# `build --head mh` selects the retained MakeHuman fallback head; the default is still `face`.
godot --headless --path . --import godot --headless --path . --import
``` ```
@@ -186,24 +190,24 @@ Caveats:
### Intake adaptation (`adapt`) ### Intake adaptation (`adapt`)
`adapt` is the mechanical first half of an external-mesh intake (MakeHuman, `adapt` is the mechanical first half of an external-mesh intake (Quaternius,
MPFB, downloaded GLB): import, apply transforms, ground the feet, weld seam MakeHuman fallback, downloaded GLB): import, apply transforms, ground the feet,
splits, decimate to the character budget, and export a GLB. It does **not** map weld seam splits, decimate to the character budget, and export a GLB. It does **not** map
the five tint slots or rebuild the 13-bone `npc_base` armature — those stay a the five tint slots or rebuild the 13-core-bone `npc_base` armature (optional extra bones are allowed) — those stay a
manual `retopology`/`rigging` step with a recorded second-rig contract if the manual `retopology`/`rigging` step with a recorded second-rig contract if the
source rig cannot be retargeted: source rig cannot be retargeted:
```bash ```bash
flatpak run org.blender.Blender --background \ flatpak run org.blender.Blender --background \
--python "$PWD/tools/blender_assets.py" -- adapt \ --python "$PWD/tools/blender_assets.py" -- adapt \
--file "$PWD/tmp/npc-research/makehuman_base.glb" \ --file "$PWD/tmp/npc-research/quaternius/Casual.glb" \
--out "$PWD/tmp/npc-research/makehuman_adapted.glb" \ --out "$PWD/tmp/npc-research/quaternius_adapted.glb" \
--target-tris 8000 --target-tris 24000
# prints: adapted makehuman_adapted.glb: 36972 -> 8000 tris (bones=N, meshes=1) # prints: adapted quaternius_adapted.glb: <source> -> 24000 tris (bones=N, meshes=1)
``` ```
Run `inspect` on the adapted GLB before and after, and keep the procedural Run `inspect` on the adapted GLB before and after, and keep the procedural
generator as the fallback until a MakeHuman variant passes `audit`/`fit` and the generator as the fallback until a Quaternius variant passes `audit`/`fit` and the
24-NPC budget. 24-NPC budget.
- Numbers are Blender-side measurements. Triangles match `audit` on project assets; the seam-split - Numbers are Blender-side measurements. Triangles match `audit` on project assets; the seam-split
vertex count can differ by ±1 because the importer rounds normals. `audit` stays the single final vertex count can differ by ±1 because the importer rounds normals. `audit` stays the single final
@@ -300,24 +304,11 @@ python3 tools/blender_assets.py texels --only npc_coat
## Local map development (planned) ## Local map development (planned)
The local map workstreams (**H–M**) in [`TODO.md`](../TODO.md#3d-model-re-evaluation) The local map workstreams (H–M) in [`TODO.md`](../TODO.md#3d-model-re-evaluation) are
are not implemented yet; there are no map-generation commands to run. The planned planned, not implemented; there are no map-generation commands yet. The runtime
pipeline will evaluate: contract, provenance rules, budget/navigation gates and fallback are in
[`architecture.md#venue-implementation-contract`](architecture.md#venue-implementation-contract).
- offline OpenStreetMap extracts for footprints and licence-compatible 3D building The detailed pipeline/task list lives in `TODO.md`.
massing (OSM Buildings, Overture, CityGML/LOD1–2);
- downloaded reference imagery from Wikimedia Commons, own/team photography or
other clearly licensed sources, with author/licence/attribution and privacy
redaction recorded per set;
- AI-assisted composition boards for palette, facade rhythm and material study,
with model/prompt/output provenance and human acceptance;
- Gaussian splatting as a development-only capture/reference/texture/proxy tool,
version-pinned offline and outside CI, until an ADR proves runtime safety.
No online map/photo/splat service is a build or play dependency. Committed map
assets will need provenance sidecars, ODbL/photo-licence review and the same
`WorldBuilder` venue-budget/navigation/collider gates as the current stylized
geometry. The detailed task plan and exit criteria live in `TODO.md`.
## Confirmed game assets ## Confirmed game assets
@@ -343,6 +334,162 @@ python3 tools/model_pipeline.py audit
python3 tools/npc_pipeline.py audit python3 tools/npc_pipeline.py audit
``` ```
## Optional interactive Blender MCP
Status: **optional interactive tooling, not a build dependency.** Blender MCP is
useful for exploring a local part scene, moving the viewport and iterating on a
head/hand mesh without a manual UI walkthrough. The authoritative asset
pipeline remains the headless CLI plus `audit`/`fit`/`inspect`; CI, tests and
release builds must continue to work with no Blender process and no MCP server.
This project does not vend the third-party addon or pin a runtime MCP
dependency. Install it outside the repository and keep generated scenes under
`tmp/`.
### What it adds
With Blender MCP connected, an agent can:
- query the current scene and object data;
- run small `bpy` snippets through the MCP bridge;
- capture a viewport screenshot for visual checks;
- inspect or modify a local part scene before exporting a GLB.
It does **not** replace:
- `python3 tools/blender_assets.py audit/fit/inspect`;
- `python3 tools/npc_proportions.py`;
- the Godot capture and smoke gates;
- the project's licence/provenance review.
### Prerequisites
- Blender 5.2 LTS, installed as the `org.blender.Blender` flatpak (the project
standard).
- An MCP-capable client.
- The upstream [BlenderMCP addon](https://github.com/ahujasid/blender-mcp)
(`addon.py`) and its MCP server.
- `uv`/`uvx` if you use the server's `uvx blender-mcp` launcher; a local install
of the server package is equally valid.
Check the upstream README for the current package/addon version and any command
changes before installing. Do not copy the addon into this repository.
### Setup
#### 1. Install the Blender addon
1. Download or clone the upstream BlenderMCP project.
2. In Blender: **Edit → Preferences → Add-ons → Install from Disk**, select
`addon.py` from that checkout.
3. Enable **Interface: Blender MCP**.
4. Open the 3D View sidebar (N panel), find the **BlenderMCP** tab, and click
**Connect to MCP server**. The default server listens on
`localhost:9876`.
The addon must be running inside the Blender process that has the scene open.
Starting a second Blender instance does not connect the first one.
#### 2. Configure the MCP client
For Pi, add the server at user scope so it is not committed into the project:
```bash
pi mcp add blender -- uvx blender-mcp
pi mcp list
```
Then start/reload Pi and use `/mcp` to inspect the connection. Other clients
use their normal MCP server configuration. A generic JSON example:
```json
{
"mcpServers": {
"blender": {
"command": "uvx",
"args": ["blender-mcp"]
}
}
}
```
For a client with a different config schema, use the equivalent server command
and keep the server name `blender`. Restart/reload the client after adding it,
then request the tool list and run a harmless `get_scene_info` before editing.
#### 3. Connect through the flatpak
Start Blender through the project's normal flatpak command, open the local part
scene, and connect from the sidebar. If the client cannot reach the addon:
- confirm the addon panel says connected;
- confirm the client is launching the MCP server, not just a shell;
- check port `9876` is not occupied;
- if the flatpak sandbox is blocking localhost networking, grant network
permission only for this interactive use, for example:
`flatpak override --user --share=network org.blender.Blender`
(review the permission change afterwards; do not add it to CI or build
scripts).
- restart both the MCP client and Blender after changing the addon or config.
### Recommended part-workflow
The headless part pipeline is `tools/npc_parts.py` (see
[`blender-assets.md#local-part-pipeline`](blender-assets.md#local-part-pipeline)). It builds,
extracts, assembles, inspects and sheets the reusable head/face and hand parts and reuses the
`blender_assets.py` builders, so there is no second face/hand implementation. The MCP steps below
remain optional interactive exploration; the headless commands stay the build and audit path.
1. Create a local scene, for example
`tmp/npc-parts/npc_casual/head.blend`, or open a copy of an exported part GLB.
Never save over the tracked `assets/models/npc/npc_base.blend`.
2. Ask the MCP client for `get_scene_info`, then inspect the object/bone/mesh
names it reports.
3. Use `execute_blender_code` for small, reversible edits. Prefer adding a new
object or modifier over deleting existing geometry; keep a backup copy.
4. Use `get_viewport_screenshot` after camera/material changes to check the
head/face/hand before exporting.
5. Export a **local** GLB under `tmp/npc-parts/`, not directly into
`assets/models/`.
6. Run the normal gates on the exported file:
```bash
flatpak run org.blender.Blender --background \
--python "$PWD/tools/blender_assets.py" -- \
inspect --file "$PWD/tmp/npc-parts/head.glb" --category npc --json
python3 tools/blender_assets.py audit
python3 tools/blender_assets.py fit
python3 tools/npc_proportions.py report
```
7. Only after the part is reviewed and the contract passes should it be
assembled into a character and rebuilt through the headless pipeline.
### Safety and project rules
- MCP is **not** part of `project.godot`, the build, CI, tests, release packages
or runtime.
- Do not commit `.blend` part scenes, MCP configuration, API keys or generated
previews. The working set stays in `tmp/`; a part GLB is not a shipped asset.
- Do not use MCP to bypass the contract, budgets, AO/UV rules or licence gate.
- The 13 core bones must remain at their rest positions; extra optional bones
are allowed only if `NpcAvatar` and the audit/tests still pass.
- Prefer scripted headless changes for reproducible work; MCP is for the
interactive exploration and review that is awkward in a one-shot script.
- If MCP is unavailable, use the headless CLI and document the manual step; never
make the project depend on MCP being present.
### Troubleshooting
| Symptom | Check |
| --- | --- |
| Client cannot find `blender` | MCP client config path/schema; restart client; confirm server command exists |
| `Connection refused` | Addon **Connect to MCP server** pressed; Blender still running; port 9876 |
| Tools return stale scene data | Refresh scene/object query after edits; restart the connection if needed |
| Flatpak cannot reach localhost | Check flatpak network permission; grant only for interactive use |
| Changes look wrong in export | Re-import the GLB with `inspect`; check transforms, scale, UVs and materials |
| Contract audit fails | Fix the mesh/bones first; do not edit the audit expectations around a broken asset |
## Realism pass: scene concepts, skylines and textures ## Realism pass: scene concepts, skylines and textures
The skyline, surface tiles and phase lighting were rebuilt against AI concept frames. The The skyline, surface tiles and phase lighting were rebuilt against AI concept frames. The
@@ -406,7 +553,7 @@ contract.
`tools/npc_proportions.py` checks the eight character GLBs against the shared `tools/npc_proportions.py` checks the eight character GLBs against the shared
height/head/shoulder/hip/torso/leg/arm/foot guide; the current results are in height/head/shoulder/hip/torso/leg/arm/foot guide; the current results are in
[`npc-proportion-audit.md`](npc-proportion-audit.md). It reads the exported GLBs [`npc-visual-review.md`](npc-visual-review.md#proportion-audit). It reads the exported GLBs
and does not require Blender. and does not require Blender.
`capture_npc_pose.gd` is the pose/deformation harness for the 3D re-evaluation: idle, `capture_npc_pose.gd` is the pose/deformation harness for the 3D re-evaluation: idle,
+80 -45
View File
@@ -3,7 +3,10 @@
Architecture context: the asset contracts and fallback path are summarized in Architecture context: the asset contracts and fallback path are summarized in
[`architecture.md`](architecture.md#content-and-asset-pipeline-architecture). [`architecture.md`](architecture.md#content-and-asset-pipeline-architecture).
Status: built, audited, and integrated (2026-11-01). `NpcAvatar` selects the Status: built, audited, and integrated (2026-11-01). The active character migration
replaces the procedural body with a baked CC0 Quaternius base
([ADR-0017](adr/0017-quaternius-npc-base.md)); the current library remains the shipped
fallback until the Quaternius 12-variant baseline passes review. `NpcAvatar` selects the
character variants and attaches the gear; `WorldBuilder` loads the prop library character variants and attaches the gear; `WorldBuilder` loads the prop library
with colliders and phase-driven emissives. The 0.9.4 neutral baseline and its with colliders and phase-driven emissives. The 0.9.4 neutral baseline and its
acceptance criteria are in [`npc-visual-review.md`](npc-visual-review.md); acceptance criteria are in [`npc-visual-review.md`](npc-visual-review.md);
@@ -35,6 +38,60 @@ captures in `docs/screenshots/`.
doors, stair handrails and a dark sedan. Today's in-game props are primitive doors, stair handrails and a dark sedan. Today's in-game props are primitive
boxes, and the trees are cones. boxes, and the trees are cones.
## Frozen fallback boundary
`assets/models/npc/npc_base.blend` is the tracked, frozen plain-primitive
fallback. Prototype head/face/hand detail from the local part pipeline must not
be saved into it; a separate contract decision (ADR or owner sign-off) is
required before any prototype detail is absorbed. Working scenes live under
`tmp/npc-parts/` (gitignored) and are exported as local GLBs for review, never
directly over the tracked `.blend`. The same rule already appears in
[`asset-tooling.md#recommended-part-workflow`](asset-tooling.md#recommended-part-workflow).
## Local part pipeline
The overhaul uses `tools/npc_parts.py` to build, extract, assemble, inspect and sheet the
reusable head/face and hand parts before they are rolled into the 12 characters. It reuses the
`blender_assets.py` builders (no second face/hand implementation) and stays local-only/headless.
Part sources and preview GLBs live under `tmp/npc-parts/<variant>/`; they are **not shipped
assets** — only the assembled character GLBs, AO/maps, the manifest and the review sheets are
committed.
```bash
# Build one part fresh from the current builders (head|face|hand_l|hand_r)
flatpak run org.blender.Blender --background \
--python "$PWD/tools/npc_parts.py" -- build --part face --variant npc_casual \
--out "$PWD/tmp/npc-parts/npc_casual/face.glb"
# Pull the current shipped head/hands out of the built character GLB (comparison)
flatpak run org.blender.Blender --background \
--python "$PWD/tools/npc_parts.py" -- extract --part hand_l --variant npc_casual \
--out "$PWD/tmp/npc-parts/npc_casual/hand_l.glb"
# Assemble a variant (current fallback path; Quaternius migration in progress)
flatpak run org.blender.Blender --background \
--python "$PWD/tools/npc_parts.py" -- assemble --variant npc_casual \
--out "$PWD/tmp/npc-parts/npc_casual/assembled.glb"
# Inspect a part or assembled GLB (Blender import view)
flatpak run org.blender.Blender --background \
--python "$PWD/tools/npc_parts.py" -- inspect --file "$PWD/tmp/npc-parts/npc_casual/face.glb"
# Contact sheet / before-after comparison (outside Blender; stdlib + Pillow)
python3 tools/npc_parts.py sheet --dir tmp/npc-parts/npc_casual \
--out docs/blender_previews/sheet_parts.jpg
python3 tools/npc_parts.py compare --before DIR --after DIR --out sheet.jpg
```
`build --part head` produces the bare head shell; `build --part face` is the shell plus
nose/eyes/mouth/ears; `build --part hand_l|hand_r` are the left/right hand geometry. `extract`
pulls the current shipped geometry out of the built character GLB by vertex group (`Head` for
`head`/`face`, `HandL`/`HandR` for the hands), so it shows what is actually shipped rather than
what the builders would emit fresh. The assembled GLB must still pass
`blender_assets.py inspect`/`audit`/`fit` before any part is accepted.
## Optional MCP
Blender MCP can be used for interactive head/face/hand part exploration, but it
is not required. The headless commands below remain the build and audit path;
see [`asset-tooling.md#optional-interactive-blender-mcp`](asset-tooling.md#optional-interactive-blender-mcp) for installation, connection and safety.
## Running it ## Running it
```bash ```bash
@@ -85,35 +142,22 @@ render about 2.5 min.
## Characters ## Characters
All eight use the `npc_base` contract, so each one can replace All library variants share the
`npc_base.glb` without code changes: [character and gear asset contract](architecture.md#character-and-gear-asset-contract):
`npc_base` mesh node, five tint slots, +Z visual front, 24,000/24,000 character budget and the
same 256² slot-map/AO rules. The shipped roster is on the 62-joint Quaternius skeleton
(ADR-0018); the procedural fallback built by `build --source procedural` keeps the 13 core bones.
Both are accepted by `audit`, `inspect`, the runtime bone map and the tests.
- mesh node `npc_base` under an armature that imports as `Skeleton3D` The three Quaternius sources (`casual`, `hoodie`, `worker`) live in
- bones `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R` `assets/models/blender_src/quaternius/` as build inputs. `build` (default
(plus `Hips`, `Spine`, `Neck`) at the **same rest positions as `npc_base`**. `--source quaternius`) does a two-pass bake per character: a cached T-pose round trip that joins
`test_character_joints_line_up_with_npc_base` checks this within 1 cm, so the without welding, maps the upstream materials onto the five slots and unwraps the degenerate
animation proxies in `_build_skinned_body()` stay valid. source UVs, then an arm-down rest bake exported with `export_apply=False`. The per-character slot
- material slots `skin`, `cloth`, `pants`, `accessory`, `hair` carry the same map, triangle counts and measurements are in
neutral `assets/textures/npc/<slot>_{albedo,normal}.png` maps at 256², so [`npc-model-sourcing.md#shipped-roster-099-owner-chose-the-full-swap`](npc-model-sourcing.md#shipped-roster-099-owner-chose-the-full-swap).
`Materials.npc_slot()` tints them the same way it tints `npc_base`. Cloth and
pants repeat the weave 4× (`NPC_FABRIC_UV_SCALE`) and intentionally skip the
per-character baked AO; skin, hair and fixed gear keep it.
- budget ≤ **8,000 triangles and ≤ 8,000 glTF vertices** for the eight library
variants (vertex count after seam splits; `CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in
`tools/blender_assets.py`, raised from 6,000/5,000 on 2026-11-02). The frozen
`npc_base` fallback keeps its original 6,000/4,000 contract. The current range
is 4,482–5,474 triangles and 4,260–5,494 vertices, leaving room for the
remaining grip and clothing detail.
Construction: the torso and limbs are one continuous Skin-modifier mesh, Role selection and look notes (tint + gear now carry the role, not the mesh):
subdivided twice, decimated to 26 % with X symmetry, and cut at the waist, cuffs,
sleeves and neck so the material borders are clean edge loops. The head is a
separate part carrying the face detail: brow ridge, sclera/iris/upper-lid per eye,
cheekbones, a nose with a bridge and tip, ears with lobes, and upper and lower
lips. Hands have four fingers plus a thumb; sleeves end in a cuff ring.
Hair, shoes and clothing layers are separate parts. Weights come from distance to
the bone segments, limited by body region. Parts that should stay rigid (head,
hands, shoes) get a single bone.
| Character | `layer_for()` / look | Suggested roles | Preview gear | | Character | `layer_for()` / look | Suggested roles | Preview gear |
| --- | --- | --- | --- | | --- | --- | --- | --- |
@@ -132,6 +176,11 @@ colour texture. The current `_apply_outfit_materials()` sets
`albedo_color = WHITE` on any slot it does not recognise, and these keep their `albedo_color = WHITE` on any slot it does not recognise, and these keep their
colour through that. When integrating, tint only the five contract slots. colour through that. When integrating, tint only the five contract slots.
**Head gear fit.** Head gear is authored for the shipped Quaternius head:
`HEAD_GEAR_SCALE = 0.64` (0.201 m base head against the 0.315 m procedural one) and
`HEAD_GEAR_LIFT = 0.07` (its centre sits higher). The procedural fallback therefore wears slightly
undersized caps — cosmetic and fallback-only.
**Runtime tint notes (implemented).** Only the five contract slots are tinted; **Runtime tint notes (implemented).** Only the five contract slots are tinted;
fixed detail materials keep their authored colour. On `npc_vest` the vest is the fixed detail materials keep their authored colour. On `npc_vest` the vest is the
fixed `hivis` material and `cloth` is the undershirt, so the yellow override is fixed `hivis` material and `cloth` is the undershirt, so the yellow override is
@@ -140,22 +189,8 @@ the role's `TYPE_COLORS`.
## Gear attach points ## Gear attach points
Gear is authored in attach-point space, so the origin of each GLB is its Gear is authored in attach-point space; the exact contract and runtime fit offsets are in
attach point: [`architecture.md#character-and-gear-asset-contract`](architecture.md#character-and-gear-asset-contract).
| Attach | Godot parent / position | Gear |
| --- | --- | --- |
| `head` | `Rig/Head` marker (0, 1.62, 0) | cap, hood, sunglasses, firefighter_helmet |
| `back` | `Rig` at (0, 1.10, 0), spine centre; items sit behind the body (Blender +Y = Godot −Z) | backpack, shoulder_bag, scba_tank |
| `chest` | `Rig` at (0, 1.30, 0), sternum | lanyard |
| `hand` | Hand proxy (±0.27, 0.78, 0.02); grip 5 cm below the origin | phone, cup, clipboard, umbrella, camera, protest_sign, flashlight, wand, radio |
The character previews in `sheet_npc.jpg` are built by importing the gear at
exactly these points, which gives a quick visual check of the convention.
`NpcAvatar.GEAR_OFFSETS` applies two final-context nudges after attachment:
the lanyard moves 4 cm forward so its badge clears the jacket, and the
firefighter helmet moves 3 cm up so its brim sits above the eyes. The
`fit` report remains an asset-space measurement of the untouched GLBs.
## Props ## Props
@@ -185,7 +220,7 @@ carry baked AO and the shared neutral material maps described above.
- `python3 tools/blender_assets.py audit`: container, `POSITION`/`NORMAL`/ - `python3 tools/blender_assets.py audit`: container, `POSITION`/`NORMAL`/
`TEXCOORD_0`, UVs in [0, 1], no zero-area triangles (Godot warns about them `TEXCOORD_0`, UVs in [0, 1], no zero-area triangles (Godot warns about them
during LOD generation), budgets (library characters 8000/8000, base fallback 6000/4000, gear 1500, props 4000 during LOD generation), budgets (characters 24000/24000, gear 1500, props 4000
triangles), grounding, and an editable `.blend`. For characters it also triangles), grounding, and an editable `.blend`. For characters it also
checks skinning, bones, slots, the `npc_base` mesh node and the height. checks skinning, bones, slots, the `npc_base` mesh node and the height.
- `tools/tests/test_blender_assets.py`: catalogue and contract consistency with - `tools/tests/test_blender_assets.py`: catalogue and contract consistency with
+3 -1
View File
@@ -9,6 +9,7 @@ capture harnesses.
| --- | --- | | --- | --- |
| [![sheet_npc.jpg](sheet_npc.jpg)](sheet_npc.jpg) <br> `sheet_npc.jpg` | Eight skinned character variants (casual, jacket, coat, hi-vis vest, dress, hoodie, firefighter, guard) with their gear attach points. | | [![sheet_npc.jpg](sheet_npc.jpg)](sheet_npc.jpg) <br> `sheet_npc.jpg` | Eight skinned character variants (casual, jacket, coat, hi-vis vest, dress, hoodie, firefighter, guard) with their gear attach points. |
| [![sheet_npc_baseline.jpg](sheet_npc_baseline.jpg)](sheet_npc_baseline.jpg) <br> `sheet_npc_baseline.jpg` | Neutral studio baseline: 8 characters x front, three-quarter, profile, back, full body and crowd views. | | [![sheet_npc_baseline.jpg](sheet_npc_baseline.jpg)](sheet_npc_baseline.jpg) <br> `sheet_npc_baseline.jpg` | Neutral studio baseline: 8 characters x front, three-quarter, profile, back, full body and crowd views. |
| [![sheet_makehuman_head.jpg](sheet_makehuman_head.jpg)](sheet_makehuman_head.jpg) <br> `sheet_makehuman_head.jpg` | MakeHuman head: front, three-quarter and profile review views. The owner accepted the basic head; the automated vision gate still rejects the eye/lid region as deferred polish. See [`../makehuman-head-review.md`](../makehuman-head-review.md). |
| [![sheet_npc_baseline_compare-body.jpg](sheet_npc_baseline_compare-body.jpg)](sheet_npc_baseline_compare-body.jpg) <br> `sheet_npc_baseline_compare-body.jpg` | Neck/shoulder transition: fuller neck base and a lower shoulder root. | | [![sheet_npc_baseline_compare-body.jpg](sheet_npc_baseline_compare-body.jpg)](sheet_npc_baseline_compare-body.jpg) <br> `sheet_npc_baseline_compare-body.jpg` | Neck/shoulder transition: fuller neck base and a lower shoulder root. |
| [![sheet_npc_baseline_compare-face-detail.jpg](sheet_npc_baseline_compare-face-detail.jpg)](sheet_npc_baseline_compare-face-detail.jpg) <br> `sheet_npc_baseline_compare-face-detail.jpg` | Face detail: nose ala wings, a single rounded mouth and a darker ear concha. | | [![sheet_npc_baseline_compare-face-detail.jpg](sheet_npc_baseline_compare-face-detail.jpg)](sheet_npc_baseline_compare-face-detail.jpg) <br> `sheet_npc_baseline_compare-face-detail.jpg` | Face detail: nose ala wings, a single rounded mouth and a darker ear concha. |
| [![sheet_npc_baseline_compare-face.jpg](sheet_npc_baseline_compare-face.jpg)](sheet_npc_baseline_compare-face.jpg) <br> `sheet_npc_baseline_compare-face.jpg` | Face-depth pass: recessed sclera, upper lid and sculpted chin/cheek planes. | | [![sheet_npc_baseline_compare-face.jpg](sheet_npc_baseline_compare-face.jpg)](sheet_npc_baseline_compare-face.jpg) <br> `sheet_npc_baseline_compare-face.jpg` | Face-depth pass: recessed sclera, upper lid and sculpted chin/cheek planes. |
@@ -21,11 +22,12 @@ capture harnesses.
| [![sheet_npc_gear.jpg](sheet_npc_gear.jpg)](sheet_npc_gear.jpg) <br> `sheet_npc_gear.jpg` | Eighteen gear pieces authored in attach space: caps, hood, helmet, bags, tanks, lanyards and handheld items. | | [![sheet_npc_gear.jpg](sheet_npc_gear.jpg)](sheet_npc_gear.jpg) <br> `sheet_npc_gear.jpg` | Eighteen gear pieces authored in attach space: caps, hood, helmet, bags, tanks, lanyards and handheld items. |
| [![sheet_npc_hands_compare.jpg](sheet_npc_hands_compare.jpg)](sheet_npc_hands_compare.jpg) <br> `sheet_npc_hands_compare.jpg` | Hand pass: wider palms, segmented fingers and a separate thumb. | | [![sheet_npc_hands_compare.jpg](sheet_npc_hands_compare.jpg)](sheet_npc_hands_compare.jpg) <br> `sheet_npc_hands_compare.jpg` | Hand pass: wider palms, segmented fingers and a separate thumb. |
| [![sheet_npc_hands_grip_compare.jpg](sheet_npc_hands_grip_compare.jpg)](sheet_npc_hands_grip_compare.jpg) <br> `sheet_npc_hands_grip_compare.jpg` | Grip pass: the forward hand-item offset that puts props in the palm. | | [![sheet_npc_hands_grip_compare.jpg](sheet_npc_hands_grip_compare.jpg)](sheet_npc_hands_grip_compare.jpg) <br> `sheet_npc_hands_grip_compare.jpg` | Grip pass: the forward hand-item offset that puts props in the palm. |
| [![sheet_head_proto_before_after.jpg](sheet_head_proto_before_after.jpg)](sheet_head_proto_before_after.jpg) <br> `sheet_head_proto_before_after.jpg` | Head overhaul sign-off: current `_face` vs the s48 connected-shell prototype at 1.85 m front/three-quarter/profile. |
| [![sheet_npc_sources_research.jpg](sheet_npc_sources_research.jpg)](sheet_npc_sources_research.jpg) <br> `sheet_npc_sources_research.jpg` | External character-source evaluation (CC0/CC-BY) for the roster research. | | [![sheet_npc_sources_research.jpg](sheet_npc_sources_research.jpg)](sheet_npc_sources_research.jpg) <br> `sheet_npc_sources_research.jpg` | External character-source evaluation (CC0/CC-BY) for the roster research. |
| [![sheet_props.jpg](sheet_props.jpg)](sheet_props.jpg) <br> `sheet_props.jpg` | Twenty street and venue props with baked maps, ground contact and collider footprints. | | [![sheet_props.jpg](sheet_props.jpg)](sheet_props.jpg) <br> `sheet_props.jpg` | Twenty street and venue props with baked maps, ground contact and collider footprints. |
| [![sheet_texture_refinement_0.9.5.jpg](sheet_texture_refinement_0.9.5.jpg)](sheet_texture_refinement_0.9.5.jpg) <br> `sheet_texture_refinement_0.9.5.jpg` | 0.9.5 texture refinement before/after: pavers, awning stripes and NPC fabric. | | [![sheet_texture_refinement_0.9.5.jpg](sheet_texture_refinement_0.9.5.jpg)](sheet_texture_refinement_0.9.5.jpg) <br> `sheet_texture_refinement_0.9.5.jpg` | 0.9.5 texture refinement before/after: pavers, awning stripes and NPC fabric. |
The acceptance criteria for these sheets are in [`../npc-visual-review.md`](../npc-visual-review.md), The acceptance criteria for these sheets are in [`../npc-visual-review.md`](../npc-visual-review.md),
the measurements in [`../npc-proportion-audit.md`](../npc-proportion-audit.md) and the measurements in [`../npc-visual-review.md#proportion-audit`](../npc-visual-review.md#proportion-audit) and
[`../3d-model-review.md`](../3d-model-review.md), and the asset contract in [`../3d-model-review.md`](../3d-model-review.md), and the asset contract in
[`../blender-assets.md`](../blender-assets.md). [`../blender-assets.md`](../blender-assets.md).
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

+46 -91
View File
@@ -1,97 +1,52 @@
# Development Status # Development Status
Current planning priorities are the unversioned **3D model and local map re-evaluation** and Baseline: **v0.9.8** (2026-11-04). This page lists what the build currently
**Character development** tracks in [`TODO.md`](../TODO.md); none is part of the implemented list supports and what remains; it is not a release history. Completed work lives in
below yet. The re-evaluation scores the existing characters, gear, props and [`DONE.md`](../DONE.md), user-facing changes in [`CHANGELOG.md`](../CHANGELOG.md),
venues against the locally installed Blender skill guidance before the roster expands. The 0.10.0 and the active order/gaps in [`TODO.md`](../TODO.md).
release candidate remains the next versioned release.
Implemented so far: ## Implemented
- 3D re-evaluation — facade and prop pass (unversioned, 2026-11-03): the Masonic/El Capitan facade uses the photo-anchored `limestone_ashlar` field and warm-tinted stone; the TV Studio reuses `masonic_facade` (same Jimmy Kimmel starting point); the Joke Shop canopy signature band is white over black, the red curtain bays are seven tied-back hourglass drapes, and the hall wall uses `dark_stucco`. The `fire_hydrant` was rebuilt with a bolted flange, bonnet cap nut and larger outlets; `bollard` gained a flared base collar and `newspaper_box` a recessed window slot and door seam. A venue light-falloff gate asserts physical attenuation, and `tools/describe_image.py` adds free-form vision review alongside the structured judges. The scorecard, measurements and accepted limits are in [`3d-model-review.md`](3d-model-review.md). | Area | Current capability |
- Godot 4.7.2 project scaffold, input map, procedural world builder (CI/local/release unified) | --- | --- |
- Venue-driven simulation core: `ShiftCoordinator`, `VenueScenario`, `EventScheduler`, `NPCController` + FSM, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem` | Core shift | Venue menu → 15-minute shift → events, incidents, review and campaign continuation. Pause/restart/settings/quit, checkpoint resume and campaign persistence are implemented. |
- Three authored scenarios (El Capitan Entertainment Centre / Hollywood Boulevard, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools | Venues | Hollywood Boulevard (Masonic Temple / El Capitan block), The Joke Shop (Sunset Strip stand-in), and the Late Night TV Studio (same El Capitan block, arrival staging). Day/Night is a phase offset on one map, not a second bake. |
- Headless unit tests: **277 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings, venue light-falloff gate, prop collider coverage) | NPCs | 12-character Blender library (8 variants + 4 crowd variants), five tint slots, 13 required core bones with optional extras, gear attach points, procedural fallback, LOD/impostor tier and a 24-NPC budget smoke. |
- Simulation extras: protester archetype, CALL_SOMEONE reaction, door lock/secure mechanic (visual indicator), flashlight with night perception bonus, LATE shift phase reaching the violent-threat event | Interaction | Focus cone/aim-free cycling, action wheel, gestural deterrence, reason/recognize, inspection, wand screening, blocking, body camera, assistance/emergency radio, responder arrival and text timeline. |
- First-person prototype with interaction HUD, access inspection, wand screening, radio report form (incl. emergency hold-to-confirm), dispatched responder avatars, aim-free targeting, venue cycling between all three scenarios, color-coded escalation feedback, procedural audio cues, navigation-mesh NPC paths, JSON-backed localized strings, persisted shift history, debug overlay, and shift review | Access and escalation | Six escalation levels, venue access policies, artifact validity/recognition, door lock, crowd reactions, investigation/false-report handling and shift review. |
- Control pass: focus cone on NPC heads with a billboarded blue circle in front of the face and name, action wheel (LMB/Space) with de-escalation-first slices, mouse-look freeze while a panel is open, acceleration-based walking, and named input actions (`action_menu`, `confirm`, `cancel`, `inspect`, `wand`) | UI/input | Keyboard/mouse and gamepad bindings, focus rings, default modal focus, single top-modal close path, shared button states, font scaling, long-string handling, searchable Help and 21 committed UI/game captures. |
- Session shell: venue start menu (keyboard + mouse), Esc pause overlay (resume / restart / settings / quit to menu), settings panel with mouse-look sensitivity and HUD font scale persisted to `user://settings.json`, and "Run another shift" returning to the venue menu | Audio/accessibility | Procedural crowd bed and radio cues, master/SFX/ambience volumes, reduced motion, captions, colour-independent escalation cue, hold-to-confirm emergency and guarded destructive actions. |
- World presentation: skyline backdrop rendered with an alpha material, walkable step and landing colliders, larger marquee text, and larger focus circle / name tag | Persistence | Separate settings, campaign, shift-history and versioned checkpoint files; default-filling migration, atomic writes and corrupt/partial-file fallback. Save locations and rollback are documented in [`getting-started.md`](getting-started.md#save-data-and-migration). |
- Routine traffic despawns after a while at its waypoint so the crowd cap no longer fills with idle standers | Assets | Blender character/gear/prop library with AO and manifests; 256² NPC maps with 4× fabric weave; image/texture pipelines, reference boards and three-up boards; environment/realism capture tools. |
- PLAN verbs: eye contact (distinct Level 1 cue), recognizing a person as a player decision recorded as access evidence, a body-camera flag that a formal warning arms (and `B` toggles), blocking that physically stations the guard between the person and the door, and a Level 3 request-reason ask answered from the NPC's `dialogue_lines` | Build/release | Godot 4.7.2 Compatibility/Web default plus optional Forward+ Desktop HQ. Gitea checks/build/publish, seven release assets, checksums, preflight/rollback and clean-worktree release discipline. |
- Incidents are now actually opened on focus and closed at shift end, so player actions, access evidence, escalation levels, and unresolved interactions reach the review (previously every recorded event was dropped)
- Crowd presentation: aggressive behavior and responder arrivals make idle bystanders stop and face the incident; panic sends people away from the door (and they leave the scene). A busy 16-NPC entrance measured ~1.2 ms/frame, so no LOD/culling was needed at the current cap
- Distant skyline ring: four alpha-scissored planes at 200 m read as a hazy city on the horizon (the baked backdrop used to show as a black slab), with a larger ground plane so the buildings meet the street
- Flashlight aims straight ahead and lights the person/door being checked instead of the ground in front of the guard
- Emergency aftermath: the `evacuation` outcome locks the door and clears the entrance, an emergency call that the situation did not justify opens an investigation and applies the false-report hit, and the shift review names the responder outcome and any investigations
- Body-camera timeline: the shift review opens a text timeline of every recorded incident event (time, person, action/outcome) that you step through with the arrow keys or buttons. No live 3D replay yet
- NPC theater: seated guests sit on the steps with bent legs, influencers and tourists raise a camera prop while photographing, performers sway their sign, and filming visitors pan slowly — all once they reach their intent's waypoint
- Audio: a looping procedural crowd bed (filtered murmur) whose volume follows `CrowdSystem.mood`, plus radio open/close crackle. All generated at runtime, no audio assets, and safe with a dummy audio server
- Accessibility: the escalation cue shows a shape meter plus the numeric level (`Escalation: Blocking [###--] L3/5`) so it reads in grayscale, and the HUD font scale from settings is applied at boot by resizing fonts (not Control.scale, which broke the right-aligned layout)
- Editor-ready venues: `tools/export_venues.gd` writes `content/venues/<id>.tres` from `ScenarioFactory`, and the `.tres` files load back with events, profiles, policy, phases, door lines, and locations intact (the factory stays the source of truth)
- Localization safety: `Strings.fmt` validates the placeholder count before formatting and fails safe (warns, returns the template) instead of raising an engine error, `Strings.placeholder_count` ignores `%%`, and `Strings.validate()` lints the table for trailing `%`, unsupported conversions, and mixed positional/sequential placeholders
- Authored scenario content: Joke Shop reservation-without-ID, borrowed/mismatched ID, a guest who refuses the wand screen, and a late arrival during a performance; Studio filming-boundary repeats and a celebrity arrival that draws a small crowd; Building events that point the player at the body camera and request-reason verbs
- Visual polish pass (v0.2.0): phase-based lighting (sun/sky/ambient per shift phase), street lamps with night-mode bulbs and omni lights, glowing marquee signage, crosswalk, planters and low-poly street trees, NPC height/brightness variety, denser routine traffic — day and late-night screenshot sets
- Assets pass: authored textures in `assets/textures/` (brick facade, concrete, asphalt, awning, skyline, vignette, HUD icons, access-card art) generated by `tools/generate_textures.py`, soft sun shadows, humanoid NPC rigs with archetype props + procedural walk bob and attention head-turn, inspection artifact cards, escalation vignette, mode icons, and a body-camera overlay (REC + shift clock)
- Rendering pass (v0.3.0 work, [ADR-0012](adr/0012-surface-materials-and-generated-props.md)): one material module with roughness/metallic profiles per surface, ACES tonemapping, glow, aerial fog and colour grading driven by the shift phase, correctly scaled brick, framed facade windows that light up at night, door trim and handles, a curb and lane dashes, and the glowing marquee/lamp bulbs. NPCs gained per-role clothing palettes, caps/hoods/backpacks/sunglasses/lanyards, hands and shoes, and an arm/leg walk swing
- NPC characters (v0.5.0): articulated limb rigs, layered outfits, hand items, face/hair variety, idle behaviour (phone, clipboard, crossed arms, look-around), escalation gestures, readable reaction symbols, door-queue offsets, and three LOD tiers with a billboard impostor
- Player movement (v0.5.0): separate acceleration/braking, step-up assist, head bob and landing dip (toggleable), precision-walk FOV/chip/tick, surface-aware footsteps, gamepad support, and persistent key/button remapping in settings
- Prop collision and placement (v0.4.0): every prop fits a static collider from its mesh bounds and publishes a navigation obstacle, so the guard and guests cannot walk through benches, bins, or bollards; placements are grouped into zones with a validator, and deterministic variants add rotation, scale, and small clutter
- Prop atlas (v0.3.1): `tools/model_pipeline.py` packs each prop's palette into one power-of-two colour atlas plus a metallic/roughness atlas and emits a single material with per-vertex UVs, so a prop is one draw material rather than one per part; `audit` enforces the contract
- Generated prop models (v0.3.0 work): `tools/model_pipeline.py` builds bollards, benches, bins, planters, newspaper boxes and a hydrant as `.glb` props with per-part materials, loaded by `WorldBuilder` with a procedural fallback; the optional AI concept step (`model_pipeline.py concept`) supplies per-prop colour palettes
- Environment pass (v0.6.0): 1024² brick/concrete/asphalt with derived tangent normal maps, 256² baked AO maps for every Blender character/gear/prop, and library props whose bulbs/LEDs/sign faces follow the day/night phase
- NPC characters + library (v0.6.0): `NpcAvatar` loads one of 8 Blender-authored skinned variants (role/seed selected) with five tintable material slots, `Rig/Head` focus anchor, and attach-point gear; `npc_base.glb` and the procedural rig remain fallbacks. `tools/blender_assets.py` builds/audits the character, gear, and 20-prop library; `WorldBuilder` loads the replacements and new street dressing (lamp, trees, palms, planters, barriers, podium, doors, marquee, sedan, handrail) with colliders, navigation obstacles, and phase-driven emissives
- Web build: `build/gig-line-web-<version>.zip` (Godot's non-threaded web template, no COOP/COEP headers needed) built by `tools/build_releases.sh` and published with the desktop artifacts; serve the unzipped folder from any static host
- Aim-free selection pass (v0.3.0 work, [ADR-0011](adr/0011-aim-free-selection-and-precision-walk.md)): the focus cone ranks candidates by angle then distance, `Tab` and the mouse wheel cycle nearby people nearest-first and pin the selection, right click clears focus with a short re-acquire grace, the HUD reports how many people are in the ring, and holding `Shift` walks at half speed for close work
- Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing. The wand panel lists the guest's carried items, and multiline HUD/wand strings render real line breaks. ## Current verification baseline
- NPC neck/shoulder transition (0.9.4 work): the body graph now uses a fuller neck base, a slightly tapered neck top and a lower shoulder root, giving the head/neck/shoulder line a trapezius slope instead of a thin post. The full character library was rebuilt with a final degenerate-face cleanup; the baseline comparison is `sheet_npc_baseline_compare-body.jpg`.
- NPC face-depth pass (0.9.4 work): sclera are recessed and capped by a thin upper lid, and the separate jaw/cheek spheres are replaced by a chin plate and cheekbones sculpted into the head mesh. The full library was rebuilt; the comparison is `sheet_npc_baseline_compare-face.jpg`.
- NPC torso/hip transition (0.9.4 work): the pelvis and chest are slightly wider than the waist, giving shirts and jackets a readable taper into the hips without changing the build multipliers or the skeleton. The comparison is `sheet_npc_baseline_compare-torso.jpg`.
- Release v0.9.6 (2026-11-03): the tagged commit `4bb2896` passed Gitea Actions run 1296 (checks + full build) and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.6>.
- Release candidate (0.10.0-rc.1, 2026-11-03): clean worktree `a9658ec` built Linux 161.5 MB, Windows 197.2 MB, macOS 148.0 MB, Web 97.8 MB and Desktop HQ 241.6 MB. The Web/HQ archives contain `CREDITS.md`, `PRIVACY.md`, `docs/accessibility.md` and `docs/known-limitations.md`; SHA-256 checksums were generated, the Linux and HQ binaries launched headless, and the Web index/wasm/pck/docs served with HTTP 200. The candidate is not published: external playtest, AI-provider/legal approval, macOS signing and the Windows/macOS target matrix remain open blockers.
- Save migration and reliability (0.10.0 work): committed 0.8-era settings/campaign/history fixtures and a 0.9.0 checkpoint. The loader default-fills old settings, keeps campaign progress and validates checkpoints for all three venues with an open incident, NPC rows, door, flashlight and body-camera state. Corrupt and partial files fall back to defaults; a blocked atomic replace leaves the prior valid file intact. `_load_json` uses the non-logging JSON parser; save locations and rollback are documented in `getting-started.md`. Suite is 277/277;
Python tooling is 161/161 after the GLB `inspect` intake coverage.
- Integration checkpoint (0.9.6 work): the clean worktree at `f5b8ef2` rebuilt all five release packages and the Desktop HQ zip without ignored `tmp/`/`build/` content (Linux 161.5 MB, Windows 197.1 MB, macOS 148.0 MB, Web 97.8 MB, HQ 241.5 MB). The Web zip contains `index.html`, `index.wasm` and `index.pck`; the HQ zip contains `override.cfg` and launches headless with the Forward+ override. Current verification: Godot 263/263, Python 101/101, 14 smokes green, 24-NPC budget 6.90 ms / 168 meshes / 131,616 triangles, gallery 21 good / 0 bad, screen judge 21/21 match with 0 review/diff, and a pinned realism judge (`z-ai/glm-5.3-flash`, one pass per shot) at mean overall 2.1 and Pillow gap 0.291. No new gameplay or content changes.
- Release v0.9.5 (2026-11-02): the tagged commit `4bc6390` passed Gitea Actions run 922 (checks + full build) and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.5>.
- Release v0.9.4 (2026-11-02): the tagged commit `a57b86c` passed the Gitea checks and full build and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.4>.
- Environment texture pass (0.9.5 work): `capture_realism.gd` now has hidden-HUD `surfaces`/`surfaces_joke` close-up stages. The Walk of Fame band uses the paver texture as dark terrazzo, the neighbour awning keeps its cream stripe, and the skyline shader blends its lower 42 m toward a per-phase haze colour. Brick, asphalt, limestone and paver UV scales/profiles passed without replacement.
- Texture/prop audit (0.9.5 work): all 21 screen-art textures are 1280×720 and referenced; access cards are 256×128 and HUD icons 64×64. No oversized or unused runtime texture was found; the legacy root textures remain only as the texture-generator/asset-test fallback. The 24-NPC smoke now reports 168 meshes, 131,616 triangles and 6.94 ms (50 ms budget); compressed payload is 24.55 MB of surface picks, 22.67 MB of screen art and 0.48 MB of NPC maps.
- NPC texture pass (0.9.5 work): the NPC base and all eight library characters now use 256² albedo/normal/ORM maps instead of 128². Cloth and pants use `NPC_FABRIC_UV_SCALE` (4× weave) and skip the baked per-character AO, removing the large blotchy patches in portraits; skin, hair and gear keep baked AO. The base and eight GLBs plus AO maps were rebuilt; the 24-NPC smoke remains green.
- Texture visual verification (0.9.5 work): the nine-stage realism run used `z-ai/glm-5.3-flash` once per shot; mean `after` scores were overall 1.8, lighting 2.3, materials 1.8, texture detail 1.2, backdrop 2.0 and characters 1.2, with a Pillow metric gap of 0.306. The judge remains an optional review aid, not a CI gate. The committed before/after close-up sheet is [`blender_previews/sheet_texture_refinement_0.9.5.jpg`](blender_previews/sheet_texture_refinement_0.9.5.jpg); it records that the paver/Walk of Fame and NPC fabric repairs are visible, while asphalt, limestone and skyline picks held without a replacement. `tools/check_screenshots.sh` passed 21 good / 0 bad and the screen-design judge passed 21/21 match with 0 review.
- NPC visual optimization (0.9.4, complete 2026-11-02): all eight variants passed the portrait/readability, proportion, gear, foot/shoe, headgear and final-context gates. The library uses an 8,000-triangle / 8,000-glTF-vertex cap (up from 6,000/5,000; `npc_base` fallback remains 6,000/4,000); current characters measure 4,482–5,474 triangles and 4,260–5,494 vertices. The 48-frame baseline and 96-frame final-context review pass, the hand/gear offset and shoes are in, and the accepted stylized limits are documented in `npc-visual-review.md`.
- NPC face-detail pass (0.9.4 work): ala wings and a smaller nose bridge/tip, one rounded mouth slot, and a darker ear concha; all eight rebuilt. The comparison is `sheet_npc_baseline_compare-face-detail.jpg`.
- NPC face realism pass (2026-11-03/04): all eight variants now use the `seg=24/rings=16` head with a subtle brow ridge, a fuller lower lip and both eyelids (recessed creases were rejected because they bake as dark AO patches). Vision-reviewed as cleaner/more realistic than the 0.9.4 face; characters measure 5,038–5,900 tris / 4,756–5,902 verts (still under 8,000). `sheet_npc_baseline.jpg` was refreshed.
- Crowd-variety roster (character development, 2026-11-04): four new variants — `npc_crowd_a` (slim/bun/sunglasses), `npc_crowd_b` (broad/bald/beard/shoulder bag), `npc_crowd_c` (coat/umbrella), `npc_crowd_d` (slim/hood/cup) — built with the realistic face and wired into `CHARACTER_POOLS` so the routine crowd roles (tourist, confused visitor, intoxicated) pick them by seed instead of cloning `npc_casual`. The 24-NPC smoke is now 152,464 tris / 168 meshes / 6.94 ms (under the 50 ms budget).
- NPC headgear clearance (0.9.4 work): the 96-frame final-context review after the face-detail pass confirms the cap, firefighter helmet, hood and sunglasses clear the refined brow and eyes; no gear rebuild was needed.
- NPC footwear pass (0.9.4 work): casual shoes gained rounded toe and heel caps; responder/firefighter boots gained matching toe/heel volumes while retaining the black rubber boot language. All eight rebuilt; comparison `sheet_npc_baseline_compare-shoes.jpg`.
- NPC hand-item grip (0.9.4 work): runtime `HAND_ITEM_FORWARD` moves every hand gear 4 cm forward from the wrist proxy, so phone, cup, clipboard, camera, flashlight, wand and radio sit in the open palm instead of partly inside it. The open-hand/no-finger-rig limit is accepted for this stylized release; the comparison is `sheet_npc_hands_grip_compare.jpg`.
- NPC hand pass (0.9.4 work): wider, shallower palms; four two-segment bent fingers with rounded tips and a separate bent thumb; optional `--view=hand` capture at 0.80 m. The hand comparison is `sheet_npc_hands_compare.jpg`; the forward offset is in and the open-palm limit is accepted.
- NPC proportion audit (0.9.4 work): `tools/npc_proportions.py` measures all eight GLBs against a stylized height/head/shoulder/hip/torso/leg/arm/foot guide. All variants pass; the head and broad-build silhouettes are the nearest upper bounds. Results are in [`npc-proportion-audit.md`](npc-proportion-audit.md).
- NPC P1 portrait fixes (0.9.4 work): tourists now hide their photo-pose camera bag outside `photo`/`film` poses, removing the duplicate-backpack silhouette; the backpack gear was rebuilt with flattened, forward chest straps and a fresh AO map. The before/after sheet is [`blender_previews/sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg); the remaining face/hand work was closed under the 0.9.4 acceptance limits.
- NPC visual baseline (0.9.4 work): `src/test/capture_npc_baseline.gd` captures all eight library characters from front, three-quarter, profile, back, full-body and crowd views in a neutral studio (48 frames). The criteria, measurements and ranked gaps are in [`npc-visual-review.md`](npc-visual-review.md); the committed sheet is [`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg). No model changes were made in this baseline step.
- UI-state capture pass (0.9.3 work): `src/test/capture_ui_states.gd` adds committed captures 14–21 for the start menu, pause, settings, shift review, emergency radio, action wheel, crowd panic and responder arrival. Every one of the 21 catalogue entries now has a current-build capture; `tools/check_screenshots.sh` reports 21 good / 0 bad and the automatic judge reports 21/21 match, 0 diff. Crowd panic and responder arrival are staged presentation frames, not gameplay changes.
- Realism adoption (post-v0.6.0): v2 per-phase skylines and surface tiles with normal maps, retuned sun/exposure/fog, wet asphalt, denser foliage, a tight additive flashlight spot with no beam geometry, and refreshed
staging captures. `tools/check_screenshots.sh` is the repeatable capture/compare gate; judge is clean of `diff` verdicts.
- Input/reliability fixes: selecting a venue releases the start-menu input lock and recaptures the mouse; the body-camera overlay no longer covers the shift status. Mouse look now reads at `_input`, restores capture on window focus/click-back, and the HUD is mouse-transparent. Normal radio reports submit on click; emergency calls still require the hold-to-confirm path.
- Production-quality pass (v0.9.0): mixer/accessibility settings, audio volume clamping, 24-NPC budget smoke, shortened coat/dress stride, and a unified Godot 4.7.2 CI/local support baseline with documented upgrade/rollback requirements.
- Campaign and first-hour pass (v0.8.0): campaign progression, checkpoint resume, difficulty presets, a searchable Help overlay, recovery events, contextual dialogue, policy/debrief lines and tutorial/replay/skip. Scripted QA bypasses the tutorial unless `--tutorial` is passed.
- Tutorial pass (0.8.0 work): the eight-beat first shift is wired through a non-blocking prompt overlay, a safe compliant visitor, real wheel/inspection/warning/radio panels, persisted completion/skip and a Settings replay action. Scripted tests/captures bypass it unless `--tutorial` is passed; `smoke_tutorial.gd` covers the full flow.
- Vision judge (v0.7.2): discovers the cheapest current OpenRouter image+JSON model, reports the model/fallbacks used, and clears the full screen-design judge (13/13 match, 0 diff).
- Desktop HQ (v0.7.1): optional Forward+ Windows/Linux zip with volumetric fog, SDFGI and a volumetric flashlight; the default Compatibility/Web builds and headless tests gate the effect off. A/B captures are generated locally under `docs/screens/realism/` (gitignored).
- Hollywood landmark map (map optimization): the default venue is now a stylized reconstruction of 6840 Hollywood Blvd (the 1921 Masonic Temple / El Capitan Entertainment Centre) with a six-column Ionic portico, eight stone steps, Masonic parapet motto and medallions, three red event banners, central and side entrances, the El Capitan Theatre and east retail neighbours, the Ovation Hollywood arcade opposite, the Walk of Fame terrazzo and a four-lane boulevard with red kerb paint. The facade uses the shared limestone/stone material; the whole venue measures **58,884 triangles across 409 meshes** in the map-budget test. The venue menu exposes separate **Day** and **Night** Hollywood rows; the night row resolves to the same scenario with a late-phase offset, so its whole shift stays after dark. Real-site references and the geometry mapping are in [`hollywood-boulevard-map.md`](hollywood-boulevard-map.md).
- Day/night scenery variants (map optimization): the Hollywood row in the venue menu is split into Day and Night entries, with localized names/blurbs. The night entry uses `ShiftCoordinator.phase_offset_s` to start a full-length shift in `LATE` while the clock still starts at 00:00; the scheduler reads the offset time so late-night routine traffic and the late violent-threat event still fire, and the offset round-trips through checkpoints. Three `LATE` routine events keep the night street populated. Local lights were retuned from constant falloff to linear attenuation so sconces pool instead of flooding the stone.
- Joke Shop / Sunset Strip map (map optimization): the second venue is now a stylized reconstruction of The Comedy Store block at 8433 Sunset Blvd, with a 27 m black hall, signature-covered sloping canopy, circular **THE JOKE SHOP** blade sign, seven tied-back red velvet curtain drapes, a west-end entrance, cream and white low-rise neighbours, a dry hillside with stucco houses and palms behind the roofline, and a low retail/billboard frontage across Sunset. The same geometry serves day and late lighting through the phase system. Real-site references and limits are in [`joke-shop-map.md`](joke-shop-map.md); the map measures **25,828 triangles across 518 meshes** in the budget test.
- Venue geometry (v0.7.0, Joke Shop frontage superseded by the map-optimization pass): the TV Studio is the same El Capitan block restaged for a late-night arrival — a stone facade, bulb canopy, glossy black car and barrier line. `WorldBuilder.VENUE_SPECS` drives facade/street/nav dimensions, and `GameManager` rebuilds only the world container when the venue changes.
- Gear fit (post-v0.6.0): Blender gear is measured against the avatar head/skirt silhouette and the checks run in both the Python tooling and the headless suite. Sunglasses, the shoulder bag and the firefighter helmet were fitted to the new faces; the Influencer portrait and gallery were recaptured with a before/after report.
- End-to-end interaction pass (0.9.3 work): `smoke_ui_flows.gd` drives inspection allow/deny, normal vs emergency radio, Help filtering, wand allow/deny and review return-to-menu in the real scene; campaign and timeline smokes cover review continue and timeline round-trips.
- Destructive-action pass (0.9.3 work): reset campaign and tutorial replay share a guarded confirmation dialog (one open instance, one emission, Cancel is inert); emergency send remains hold-to-confirm with no post-submit double-fire. Covered by session/radio tests and recorded in `docs/ui-copy.md`.
- Font/layout pass (0.9.3 work): HUD font scale was exercised at 0.75× and 1.75×; the target chip measures and caps scaled names, top labels clip, and shared action buttons ellipsize long 1.5–2× labels instead of overflowing. Covered by accessibility/button tests and recorded in `docs/ui-copy.md`.
- Modal-close pass (0.9.3 work): one top-modal close path handles Esc/right-click for every overlay, Settings opened from the menu restores the menu, Help no longer opens Pause behind it, and pointer/guard freeze state is recaptured after each close. The session smoke covers these paths.
- Keyboard/gamepad focus pass (0.9.3 work): `UiFocus` rings wrap through visible/enabled controls, hidden Continue and disabled timeline actions are skipped, each modal has a default focus, and A/B are bound to `ui_accept`/`ui_cancel` for pad-only navigation. Recorded in `docs/ui-copy.md`.
- Button-state pass (0.9.3 work): a shared `UiStyles` helper now gives every action button explicit normal, hover, pressed, focus and disabled styles; the timeline disables Previous/Next at its boundaries, while hidden continue states and the keyboard-only tutorial skip remain intentional. Recorded in `docs/ui-copy.md`.
- Dialogue pass (0.9.3 work): NPC reason answers now use the authored `reason` context, with an in-character answer for every shared archetype; Hollywood and Studio gained door-staff lines; radio cancellation, tutorial Ask terminology, help, start hints and campaign reset copy were clarified. Audit recorded in `docs/ui-copy.md`.
- Pre-build NPC review (2026-11-02): all eight library characters were staged at portrait, conversation and crowd distance on Hollywood and the Joke Shop, in day and night lighting, with the normal crowd hidden (`src/test/capture_npc_review.gd`). No P0 issues were found. Two P1 gear-fit issues were fixed with runtime attach offsets: the authorized-guest lanyard badge now clears the jacket and the firefighter helmet sits above the eyes. Backpack straps, caps, hands and the intentionally low-poly faces were accepted for the stylized target; close-range flashlight wash remains an optional-lighting follow-up. The 24-NPC budget stayed at 6.94 ms, the gallery passed 13 good / 0 bad and the screen-design judge stayed 13/13, and a clean portrait scored a 0.732 vision gap against the photoreal concept.
Current gaps for the 0.x release line: deeper authored content/dialogue, animation and audio production, localization/accessibility expansion, 24-NPC/platform profiling, external playtesting, and signed/notarized release packaging. The full gap analysis and milestone plan live in [`TODO.md`](../TODO.md); the current architecture and documentation ownership map are in [`docs/architecture.md`](architecture.md). - Godot headless suite: **277 passing**
- Python tooling tests: **162 passing**
- Asset audits: `blender_assets.py audit` / `fit`, `npc_pipeline.py audit` and
`npc_proportions.py` pass for the committed library.
- 24-NPC smoke: 168 meshes, 152,464 triangles, 6.94 ms (50 ms budget).
- Screenshot gate: 21 good / 0 bad; screen-design judge 21/21 match.
- Release baseline: v0.9.8 is published; the 0.10.0 release candidate remains
gated on external playtest, legal/storefront, signing and platform-matrix work.
## Known gaps
- External first-time-player sessions and balance sweeps are not completed.
- The Quaternius roster is shipped (`0.9.9`): all twelve characters are baked from the three CC0
sources (casual tee, hoodie, work clothes), 4,458–6,424 triangles each, 62-joint rig at source
proportions, arm-down rest, five tint slots, unwrapped UVs and baked 256² AO. The procedural
library and the MakeHuman head remain the tested fallback.
- Roster silhouette variety is narrower than the procedural library (three meshes for twelve
roles, differentiated by tint and gear) until the modular part library is added.
- Head gear is authored for the shipped Quaternius head; the procedural fallback therefore wears
slightly undersized caps. Cosmetic, fallback-only.
- Accessibility/localization acceptance, target-hardware profiling, macOS
signing/notarization and AI/storefront legal approval remain open.
- Live 3D replay, recorded VO/music, more venues and seasonal content are future
work.
See [`TODO.md`](../TODO.md) for the ordered task list and
[`known-limitations.md`](known-limitations.md) for the current release blockers.
Architecture and ownership are in [`architecture.md`](architecture.md).
+51 -3
View File
@@ -16,7 +16,7 @@ A typical shift lasts 10–15 minutes. The first-person camera stays at the game
The game rewards calm resolutions rather than aggressive behavior. Emergency assistance and armed responders are rare last-resort systems with meaningful consequences. The game rewards calm resolutions rather than aggressive behavior. Emergency assistance and armed responders are rare last-resort systems with meaningful consequences.
The first shift starts with an in-game tutorial covering movement/look, focus cycling, the action wheel, access inspection, polite direction, formal warning/body camera, radio and shift review. Its beat sheet is in [`tutorial.md`](tutorial.md). After that a short campaign runs Hollywood → Joke Shop → TV Studio; progress, difficulty and a pause-safe checkpoint are persisted locally. Help (from the venue menu) explains the controls and the current venue policy. The first shift starts with an in-game tutorial covering movement/look, focus cycling, the action wheel, access inspection, polite direction, formal warning/body camera, radio and shift review. See the tutorial section below. After that a short campaign runs Hollywood → Joke Shop → TV Studio; progress, difficulty and a pause-safe checkpoint are persisted locally. Help (from the venue menu) explains the controls and the current venue policy.
## Venues ## Venues
@@ -24,7 +24,7 @@ The first shift starts with an in-game tutorial covering movement/look, focus cy
The map is a stylized reconstruction of the real building and block; the The map is a stylized reconstruction of the real building and block; the
reference notes and geometry mapping are in reference notes and geometry mapping are in
[`hollywood-boulevard-map.md`](hollywood-boulevard-map.md). The venue menu [`venues.md`](venues.md#hollywood-boulevard). The venue menu
offers the same map as **Hollywood Boulevard — Day** and **Hollywood Boulevard offers the same map as **Hollywood Boulevard — Day** and **Hollywood Boulevard
— Night**; the night variant starts directly in the late phase instead of — Night**; the night variant starts directly in the late phase instead of
waiting for the shift clock. waiting for the shift clock.
@@ -35,7 +35,7 @@ Authorized people must either be recognized by the guard or present a valid wris
### The Joke Shop ### The Joke Shop
The Joke Shop is a Sunset Strip comedy club inspired by the real Comedy Store at 8433 Sunset Blvd: a black hall under a deep signature-covered canopy, a circular blade sign, red velvet curtains, a west-end entrance, neighbouring low-rise frontage and a dry hillside of houses behind. The real-site mapping is in [`joke-shop-map.md`](joke-shop-map.md). The door person checks IDs, tickets, reservations, and guest-list status, then uses a handheld security wand according to venue policy. The player must interpret wand alerts rather than treating every alert as proof of a weapon; keys, belts, phones, and other harmless objects can trigger false alarms. The Joke Shop is a Sunset Strip comedy club inspired by the real Comedy Store at 8433 Sunset Blvd: a black hall under a deep signature-covered canopy, a circular blade sign, red velvet curtains, a west-end entrance, neighbouring low-rise frontage and a dry hillside of houses behind. The real-site mapping is in [`venues.md`](venues.md#the-joke-shop). The door person checks IDs, tickets, reservations, and guest-list status, then uses a handheld security wand according to venue policy. The player must interpret wand alerts rather than treating every alert as proof of a weapon; keys, belts, phones, and other harmless objects can trigger false alarms.
### Hollywood Boulevard Late Night TV Studio ### Hollywood Boulevard Late Night TV Studio
@@ -68,3 +68,51 @@ An off-duty law-enforcement officer or armed emergency responder may arrive duri
- **Crowd reaction:** Aggressive behavior, visible weapons, and emergency response can create panic or attract a crowd. - **Crowd reaction:** Aggressive behavior, visible weapons, and emergency response can create panic or attract a crowd.
- **Reputation:** Peaceful resolutions, accurate reports, low property damage, and justified calls improve performance; unnecessary confrontations and false reports reduce it. - **Reputation:** Peaceful resolutions, accurate reports, low property damage, and justified calls improve performance; unnecessary confrontations and false reports reduce it.
- **Shift review:** The player receives feedback on access decisions, escalation choices, public complaints, injuries, and security outcomes. - **Shift review:** The player receives feedback on access decisions, escalation choices, public complaints, injuries, and security outcomes.
## First-shift tutorial
Status: implemented for v0.8.0. The tutorial teaches the real controls and the real
inspection/wheel/radio panels; it is not a fake UI or a separate combat mode.
Scripted test/capture runs bypass it unless `--tutorial` is passed; a windowed
smoke covers completion, replay and skip.
### Rules
- One short, safe, compliant visitor. No emergency, threat, crowd panic or random
event can interrupt the tutorial.
- Prompts advance on the performed action, not on a timer. The player can keep
moving/looking while a prompt is visible.
- The tutorial is skippable at any point (F1). Completion and skip are both
persisted separately; a skipped tutorial can be replayed from Settings.
- The tutorial state machine is independent of the shift FSM. It observes the
same actions as normal play and must be deterministic for tests.
### Beat sheet
| # | Beat | Prompt | Advances when | Notes |
| --- | --- | --- | --- | --- |
| 1 | Arrive | "Walk forward and look around. The entrance is behind you." | The guard has moved at least 1 m and looked away/returned | Start at the normal guard post, facing the street. |
| 2 | Focus | "Look at the visitor. Press Tab or the mouse wheel to cycle targets." | A target is acquired and explicitly cycled | The visitor waits at the foot of the steps; no other guest is required. |
| 3 | Wheel | "Open the action wheel with Space or left click." | The radial wheel opens on the visitor | The wheel is the real de-escalation-first wheel. |
| 4 | Inspect | "Choose Inspect and read the access evidence." | The inspection panel is opened and the policy result is visible | Use a valid wristband so the tutorial can teach both evidence and a safe admit/deny decision. |
| 5 | Direction | "Close the panel and give a polite direction." | A Level 1 polite-direction action is applied | The visitor complies; there is no escalation requirement. |
| 6 | Warning | "Give a formal warning if the visitor persists. It arms the body camera." | A Level 2 warning is applied or the body camera is switched on | Show the REC overlay and the escalation cue. |
| 7 | Radio | "Open the assistance radio and submit a short report." | The assistance report is submitted successfully | A non-emergency assistance call; no responder is required to resolve the tutorial. |
| 8 | Review | "Finish the shift and review what happened." | The shift review opens | The tutorial ends; the real review remains available for inspection. |
### Visitor script
- Display name: `Visitor`.
- Intent: `enter` (approaches the door, then waits for interaction).
- Access: valid wristband evidence; the policy result should be unambiguous.
- Behavior: compliant at every escalation level; never becomes a threat, never
joins a crowd and never triggers an emergency.
- Failure handling: if the player denies admission incorrectly, the tutorial
explains the evidence but does not fail the run; the visitor remains safe and
the review records the decision.
### Persistence and replay
Settings gain `tutorial_completed` and `tutorial_skipped`. A first launch starts
the tutorial after venue selection. Skipping sets `tutorial_skipped`; Settings/
Help offers "Replay tutorial", which clears both flags for the next shift.
+12 -18
View File
@@ -74,31 +74,25 @@ XDG_DATA_HOME=/tmp/gigline-campaign godot --path . --fixed-fps 60 --script res:/
The unit test runner is the project's own lightweight harness (no editor plugin required) so CI can run it without the editor. The unit test runner is the project's own lightweight harness (no editor plugin required) so CI can run it without the editor.
## Save Data and Migration ## Save Data
The game keeps four small JSON files in Godot's `user://` directory, which is The game writes four JSON files in Godot's `user://` directory:
`~/.local/share/godot/app_userdata/Gig Line/` on Linux,
`%APPDATA%\Godot\app_userdata\Gig Line\` on Windows and
`~/Library/Application Support/Godot/app_userdata/Gig Line/` on macOS:
| File | Contents | | Platform | Location |
| --- | --- | | --- | --- |
| `settings.json` | Camera, HUD, binding, difficulty, audio, accessibility and tutorial flags. Missing keys are filled from the current defaults; there is no schema version because the field set grew compatibly. | | Linux | `~/.local/share/godot/app_userdata/Gig Line/` |
| `campaign.json` | Campaign index, completed venues, difficulty, shift number and last grade. Older files with missing fields open at the safe defaults. | | Windows | `%APPDATA%\Godot\app_userdata\Gig Line\` |
| `shift_results.json` | Completed-shift review history, appended as an array. | | macOS | `~/Library/Application Support/Godot/app_userdata/Gig Line/` |
| `shift_state.json` | Resumable mid-shift checkpoint, version `1`. It stores the venue, guard/door/flashlight/body-camera state, coordinator snapshot and NPC rows. |
Writes go to a sibling `.tmp` file and are renamed only after the complete JSON Files: `settings.json`, `campaign.json`, `shift_results.json` and the versioned
is written. A corrupt or partial file is ignored on load, so the player gets `shift_state.json` checkpoint. The schema, migration/default-filling rules,
defaults rather than a crash and a valid old file is not overwritten by a failed atomic-write behavior and checkpoint contents are in
replace. The migration fixtures used by the tests live in [`architecture.md#persistence-and-save-contract`](architecture.md#persistence-and-save-contract).
`src/test/fixtures/saves/`.
For a clean first launch, delete the four files (or the whole `Gig Line` user For a clean first launch, delete the four files (or the whole `Gig Line` user
directory). Settings' **Reset campaign** clears campaign progress separately. directory). Settings' **Reset campaign** clears campaign progress separately.
To back up or move a profile, copy those files while the game is closed; to roll To back up or move a profile, copy the files while the game is closed; restore
back, restore the previous copies. Shift history is diagnostic and is not them to roll back. Shift history is diagnostic and not required to resume.
required to resume a checkpoint.
## Controls ## Controls
-122
View File
@@ -1,122 +0,0 @@
# Hollywood Boulevard map reference
The Hollywood venue is a stylized reconstruction of the real block around the
El Capitan Entertainment Centre. It is a game space, not a survey-accurate
model: the landmark silhouette, street wall and approach read from the player's
eye height, while side streets and interiors are simplified.
## Real site
- **Address:** 6840 Hollywood Boulevard, Hollywood, Los Angeles, CA 90028.
- **Coordinates:** 34.10118 N, 118.34009 W (OSM way `424024147`).
- **Building:** the 1921 Hollywood Masonic Temple, designed by John C. Austin in
the Classical Revival style; now the El Capitan Entertainment Centre and the
home of *Jimmy Kimmel Live!*. This is the game's **starting point**: the first
shift (Hollywood) and the late-night TV Studio arrival both begin at this
building, where the Kimmel stage and studio occupy the interior behind the
six-column facade.
- **Facade:** a light stone/render Neoclassical front with six oversized Ionic
columns, a recessed main entrance reached by eight stone steps, ornamental
iron door grilles, a continuous crosscut-grille transom, two street-level side
entrances, and a parapet with the Masonic motto and circular medallions.
- **Neighbours:** the El Capitan Theatre (6838) immediately west, a modern
retail block east, and the Ovation Hollywood frontage (6801) across the
four-lane boulevard.
- **Sidewalk:** the Hollywood Walk of Fame terrazzo strip with brass stars runs
along the kerb; the kerb is painted red in the reference photographs.
### Late Night TV Studio venue
The game's third venue is **the same block**, not a separate real site. The building houses
*Jimmy Kimmel Live!*, so the "Late Night TV Studio" scenario restages 6840 Hollywood Blvd as a
studio arrival: the same stone facade and steps, plus a bulb-lit canopy, a glossy black car at the
kerb, a barrier/stanchion line and an audience queue. The Kimmel stage/audience and the entrance
marquee are researched as local-only reference sets under
`tmp/research/jimmy-kimmel-stage/` and `tmp/research/jimmy-kimmel-entrance/` (gitignored); the
photo-anchored entrance concepts live in
[`screens/concepts/hollywood_boulevard/README.md`](screens/concepts/hollywood_boulevard/README.md). `VENUE_SPECS["tv_studio"]` is a variant of the
Hollywood spec (30.0 m facade instead of 29.6 m, arrival staging instead of the day-time banners),
and it shares this block's reference board rather than owning one.
## Game mapping
| Real feature | In-game representation |
| --- | --- |
| 29.6 m wide, 14.5 m high facade | `VENUE_SPECS["hollywood_building"]`: `facade_width` 29.6, `facade_height` 14.5, front at `z = -4` |
| Eight stone steps | `stair_count` 8, `stair_height` 0.10, `stair_tread` 0.38, 12 m wide |
| Six Ionic columns | `IonicColonnade` at x = -12.4 … 12.4, 4.96 m bay spacing |
| Recessed main entrance + two side doors | central 4.2 m door and 2.5 m side doors with transoms |
| Red event banners | three stylized `EL / CAPITAN / LIVE` banners in the central bays |
| Masonic motto + medallions | `FriezeInscription` label and two stone discs |
| El Capitan Theatre | `NeighbourBlocks/ElCapitanTheatre` with marquee, blade sign and practical light |
| Retail block east | `NeighbourBlocks/EastRetailBlock` with glazed shopfront |
| Ovation Hollywood | `OvationHollywood` arcade with seven arched openings, sign and south sidewalk |
| Walk of Fame | `WalkOfFame` dark terrazzo band with ten stars |
| Four-lane boulevard | double-yellow centre, broken white lane lines, red kerb paint and crosswalk |
| TV Studio arrival staging | `VENUE_SPECS["tv_studio"]`: same six-column Masonic facade and eight steps (reuses `masonic_facade`), plus `bulb_canopy`, `sedan`, `handrail` and barrier placements instead of the day-time marquee |
The generated AI references live in
[`docs/screens/concepts/hollywood_boulevard/`](screens/concepts/hollywood_boulevard/README.md).
They are art direction for lighting, material scale and composition; they are
not texture or geometry sources.
## Deliberate simplifications
- Show branding is replaced by the venue-neutral `EL / CAPITAN / LIVE` banner
text. The building name and address are retained.
- The opposite Ovation frontage is represented as a single arcade block rather
than its full mall plan. Interiors, the Metro entrance and side streets are
out of playable bounds.
- The AI concept frames are 1280×720 references. The in-game geometry is built
from boxes, cylinders and a small number of custom arch/star meshes so it
stays inside the Compatibility/Web budget.
- The venue menu offers Day and Night Hollywood rows. Night is the same
geometry and scenario with a late-phase offset, not a second baked map; the
phase still drives sun, sky, fog, windows, signage and practical lights.
- The landmark facade uses the shared smooth limestone/stone texture rather
than the noisier granite panel tile. The whole venue measures **58,884
triangles across 409 meshes** in `test_hollywood_geometry_stays_inside_the_map_budget`,
well inside the 150k/600 budget.
- Local lights use linear attenuation (`omni_attenuation = 1.0`). Godot treats
`0.0` as constant falloff, which previously turned the night facade into a
flat yellow wall; the sconces now pool light instead.
- The masonic facade field uses the `limestone_ashlar` pick: a seamless ashlar
tile generated reference-anchored on the 2009 facade photo (CC BY 2.0),
768 px over ~4.6 m of wall (~167 px/m), so the wall carries course joints and
per-block value variation instead of flat plaster.
- Night balance: the event banners carry a warm self-light and the marquee sign
face + bulbs brighten after dark (registered with `_marquee_emissive`), so the
facade reads as practical-lights-only rather than a flat lit wall. The marquee
canopy stays black metal by design — the fictional "GIG LINE" marquee, not the
red El Capitan Theatre marquee next door.
## References
The committed, license-cleared photo board for this block — with the provenance sidecar and the
decisions each photo informed — is in
[`screens/refs/hollywood/README.md`](screens/refs/hollywood/README.md). The three-up boards
(real photo | generated concept | current capture) live in
[`screens/boards/README.md`](screens/boards/README.md) and make the remaining facade-realism gap
visible: the capture panel still shows flat material bands where the photo has limestone value
variation, joint lines and reveal depth.
- [Hollywood Masonic Temple — Wikipedia](https://en.wikipedia.org/wiki/Hollywood_Masonic_Temple)
- [El Capitan Entertainment Centre — OpenStreetMap way 424024147](https://www.openstreetmap.org/way/424024147)
- [El Capitan Theatre — OpenStreetMap way 424024148](https://www.openstreetmap.org/way/424024148)
- [Ovation Hollywood — OpenStreetMap way 151485232](https://www.openstreetmap.org/way/151485232)
- [Hollywood Masonic Temple facade — Wikimedia Commons](https://commons.wikimedia.org/wiki/File:El_Capitan_Theater_frontal_fachade-The_Masonic_Temple.jpg)
- Local frames that changed a decision (gitignored, never committed):
- `tmp/research/elcapitan-2026/` (recent Commons geosearch) → established that no recent photo
frames the El Capitan facade straight-on in daylight, so the 2009 CC BY frame stays the
geometry reference.
- `tmp/research/jimmy-kimmel-entrance/07.jpg` → the photo anchor for the four Jimmy Kimmel
entrance concepts (day/night × gemini/gpt), confirming the starting-point building.
- [Hollywood Masonic Temple and El Capitan Theatre, 2013 — Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Hollywood_Masonic_Temple_2013-10-13_10-02-25.jpg)
## Verification
```bash
godot --headless --path . --script res://src/test/test_runner.gd
godot --path . --script res://src/test/capture_realism.gd -- --out=/tmp/gig-line-hollywood --stages=day,evening,late
python3 tools/check_docs.py
```
-122
View File
@@ -1,122 +0,0 @@
# The Joke Shop — Sunset Strip map notes
The game's second venue is a fictional comedy club called **The Joke Shop**. Its
block, canopy, blade sign, entrance and hillside are based on the real Comedy
Store at **8433 Sunset Blvd, West Hollywood, CA 90069**.
The club name and the game's guest stories are invented. The building is a
stylized reconstruction, not a survey-accurate model: the playable frontage is
27 m, while the real parcel is much deeper and contains several rooms that the
game does not enter.
## Real site
| Item | Reference |
| --- | --- |
| Address | 8433 Sunset Blvd, West Hollywood, CA 90069, United States |
| Real venue | The Comedy Store (comedy club and theatre) |
| OpenStreetMap | Way [`428854155`](https://www.openstreetmap.org/way/428854155), centroid 34.095265, -118.373965 |
| OSM tags | `amenity=theatre`, `building=commercial`, `height=12.2`, `start_date=1940`, `wikidata=Q3520350` |
| Footprint | OSM polygon bounding box about **47.1 m × 41.2 m** across the club and rear parcels |
| Street | Sunset Boulevard, a major east–west arterial on the Sunset Strip |
| Setting | Dry Hollywood Hills directly behind the club, with houses and palms rising above the roofline |
The building began as **Ciro's**, a 1940 nightclub; it became The Comedy Store
in the early 1970s. Its street front is a low black hall under a deep,
signature-covered canopy, with the circular blade sign above the roof, red
curtains along the frontage and the main entrance set toward the west end. The
hillside houses behind it are part of the silhouette from the sidewalk.
## Measured proportions (vision-extracted from the local frames, 2026-11-03)
`tools/describe_image.py` was used to read the gitignored `tmp/research/comedy-store/` frames
(the harness cannot display images directly). Two frames disagreed on crop-dependent details, so
the ranges below are the reconciled reading; the game spec is a deliberate compression.
| Feature | Reference reading | Game spec | Reconciliation |
| --- | --- | --- | --- |
| Street frontage | ~35–40 m wide, parapet ~5.5–6 m (≈6:1 width:height), rounded corner | 27 m × 6.0 m | Facade height lowered 8.4 → 6.0 m (2026-11-03); width still compressed for the playable block |
| Canopy | ~4–4.5 m projection, tilts forward noticeably | 4.9 m at 11° | Close; the forward pitch is confirmed |
| Fascia band | black, 30–40 cm deep, marquee-bulb string | black `SignatureCanopy` fascia + `CanopySignatures` + bulbs | Matches; signatures now white |
| Circular blade sign | ~3–4 m diameter white disc, red serif wordmark, pole-mounted | 3.9 m white disc, black wordmark, pole | Diameter matches; the game wordmark is black (fictional `THE JOKE SHOP`) |
| Red curtain bays | 3–5 visible depending on frame, floor-length, tied-back mid-bay | 7 tied-back drapes | Count is crop-dependent; 7 is a readable stylization, not a defect |
| Entrance | west end, recessed at the rounded corner | west end at x = -8.8, recessed | Matches |
| Roofline | taller dark mass rises behind the right half | `RoofPlant` box at x = 5.5 | Matches |
## How the map maps to the real site
| Real element | Game implementation |
| --- | --- |
| Low black comedy hall | 27 m × 12 m footprint, 6.0 m street wall; matte black concrete surface with a low plinth and parapet |
| Deep overhanging canopy | 4.9 m sloped slab dropping about 1 m to the outer fascia; the fascia carries white signature lettering and the club name |
| Signature-covered front | Two `Label3D` signature bands on the fascia plus a smaller wall panel, so the handwriting reads at gameplay distance without dozens of decals |
| Circular blade sign | 3.9 m white disc with a black rim on a roof pole; `THE JOKE SHOP` lettering faces the street |
| Red curtains | Seven tied-back velvet drapes between the entrance and the east end — each cinched into an hourglass with a scalloped hem and dark recess between bays; a red velvet entrance curtain |
| West-end entrance | Recessed opening at x = -8.8 m with a low step, brass pulls and the door-lock indicator |
| Warm practicals | Sconces along the wall, canopy downlights, entrance wash and three ground uplights; all off in daylight and switched by `set_night_mode()` |
| Neighbouring low-rise frontage | Cream Mediterranean block west (balconies, red awning) with a stepped-back 5–6 storey pink hotel tower behind it (`WestHotelTower`, the Ramada Plaza mass); white commercial block east (ribbon windows, lobby) |
| Hillside and houses | Fog-free unshaded 60° ramp behind the rear wall with stucco houses, shrubs and palms; its tint drops to a dark silhouette at night |
| Opposite side of Sunset | Low retail row, rooftop billboard, neon and palms as background context beyond the far kerb |
| Sidewalk furniture | Palms, queue barriers, stanchions, bins, planters, a-frame sign, hydrant and a parked sedan; prop placements pass the shared validator |
## Scene composition
- **Frontage:** 27 m wide, façade centre at z = -8.0, front face at z = -2.0.
- **Sidewalk:** 38 m wide × 14 m deep, meeting the kerb at z = 11.9.
- **Street:** 90 m × 24 m, with the four-lane markings reused from the
Hollywood block and a crosswalk in front of the entrance.
- **Playable area:** x = -15…15, z = -0.6…14.0. The guard starts on the west
side of the entrance at (-4.8, 0.1, 1.4), facing the approach.
- **Map budget:** the headless audit measures **25,828 triangles across 518
meshes**, well inside the 150k/700 test budget.
The same geometry serves the day and late-night looks. There is no second baked
map: the phase lighting, skyline/fog tint, lit windows and practicals change,
just as with the Hollywood day/night rows.
## Simplifications and limits
- The real club's rear rooms, basement and roof plant are not modelled; the
playable area is the street frontage only.
- The 47 m OSM footprint includes rear parcels and does not match the 27 m
street frontage; the game keeps the frontage proportions rather than the full
lot.
- Neighbouring businesses are represented by generic low-rise blocks (plus the
stepped-back `WestHotelTower` mass behind the west block, standing in for the
Ramada Plaza). Their exact names and current tenants were not verified, so the
docs do not claim them.
- The real club's west street corner is rounded (radius ~3–4 m); the game keeps
a hard 90° corner as a stylized-reconstruction simplification.
- The hillside is a background ramp with simplified houses, not a terrain
heightfield; it exists to restore the real silhouette above the roofline.
- Signage uses the fictional **The Joke Shop** name, not the real Comedy Store
wordmark.
- The concept frames are photoreal; the game keeps its stylized, low-poly
material treatment. The vision judge therefore still scores material and
foliage detail below the reference. The remaining texture/realism work is
tracked with the postponed 0.9.5 pass, not treated as a map-geometry defect.
## References
The old committed Comedy Store photo board was removed on 2026-11-03 in favour of a local-only
research set under `tmp/research/comedy-store/` (owner override: development reference, never
committed, no licence gate). The three-up board (real photo | generated concept | current capture)
is in [`screens/boards/README.md`](screens/boards/README.md); its photo panel is a historical
public-domain frame, and the current block reference now lives in the gitignored research folder.
- OpenStreetMap way 428854155: <https://www.openstreetmap.org/way/428854155>
- AI art-direction frames: [`screens/concepts/joke_shop/`](screens/concepts/joke_shop/README.md)
- Local frames that changed a decision (gitignored, never committed):
- `tmp/research/comedy-store/` facade/street/strip/visitor frames → the four
`comedy_store_*` photo-anchored concept pairs and the map-recreation plan.
- `tmp/research/comedy-store-hillside/` (public-domain hillside frame) → the photo anchor for
`joke_shop_entrance_day-v2.png` (a less-branded frame that clears the image-recitation filter).
- The hall wall uses the `dark_stucco` pick (1024 px, matte charcoal grain) instead of the grey
paver tile, so the black stucco reads as the real club's flat plaster rather than masonry.
- Verification: `godot --headless --path . --script res://src/test/test_runner.gd`
(`test_joke_shop_uses_the_real_sunset_strip_layout`,
`test_joke_shop_geometry_stays_inside_the_map_budget`), and
`godot --path . --script res://src/test/capture_realism.gd -- --stages=joke_shop_day,joke_shop`.
The committed gallery passed `tools/check_screenshots.sh` (13 good / 0 bad)
and the screen-design judge (13/13 match, 0 diff). The latest local realism
run reports a reference gap of 0.454 for the night entrance.
+140
View File
@@ -0,0 +1,140 @@
# MakeHuman head prototype
Status: **retained fallback; superseded as the active NPC head by the Quaternius
base (ADR-0017).** The owner accepted the basic human head on 2026-11-04. The automated vision gate
still records REJECT on the eye/lid region as deferred polish. The asset is a
standalone head/neck extracted from the CC0 MakeHuman base. The assembled path
adds hair, a hidden scalp trim and the procedural body. The 12 shipped GLBs have
not been rebuilt with it, and the Quaternius migration replaces it as the active
base. The development source files were removed from `tmp/` on 2026-11-05.
## Asset
- GLB: [`assets/models/blender_src/makehuman_head.glb`](../assets/models/blender_src/makehuman_head.glb)
- Review sheet: [`blender_previews/sheet_makehuman_head.jpg`](blender_previews/sheet_makehuman_head.jpg)
- Source workflow (development-only, files removed after the Quaternius pivot;
retained here as the historical method) — extracts the MakeHuman head, cuts it at the neck, keeps the head/eye/mouth
components, cuts an almond aperture through a skin eyelid mask, adds recessed
eyeballs with flat iris/pupil discs, anchors brows to the face surface, renders
front/three-quarter/profile and exports the standalone GLB.
- Measurements (final standalone build, `inspect`): **12,641 triangles / 7,690
split vertices**; bounds approximately `x ±0.094 m`, `y -0.180…0.042 m`,
`z 1.530…1.803 m` in project Z-up space.
- Assembled `npc_casual --head mh`: **14,753 triangles / 12,823 seam-split
vertices / 14 bones / 9 materials**, `inspect` clean. All 12 variants count
14,693–15,987 triangles with the same head.
- Materials used: `skin`, `eye_white`, `iris`, `pupil`, `hair`. No textures are
baked into this prototype.
![MakeHuman head review sheet](blender_previews/sheet_makehuman_head.jpg)
## What was done
1. Imported the CC0 MakeHuman base OBJ and cleared the importer axis rotation.
2. Applied the project scale/axis transform: 1.82 m body, Z-up, metres,
front −Y.
3. Cut the head at the neck (`z ≈ 1.53`) rather than keeping the cut shoulders;
capped the neck opening.
4. Built an eyelid mask by subtracting an almond aperture from a skin sphere
centred on each eye (`solver = "EXACT"`), then recessed the globes so the
cornea sits behind the lid plane.
5. Added flat iris/pupil discs on the globe axis; disabled backface culling so
the discs stay visible at three-quarter angles.
6. Anchored brow strips to the skin with object-space ray casts and added a
shallow supraorbital ridge so they do not float, then reduced the crown
height to improve the profile silhouette.
7. Fixed a material-remap bug introduced by joining the extra eye objects: the
joined head faces were pointing at the `eye_white` slot, making the whole
head render as white clay. The largest connected component is now forced
back onto `skin` after the join.
8. Rendered front/three-quarter/profile views in a neutral review studio and
exported the standalone GLB.
## Provenance
- Base mesh: MakeHuman / MPFB base, CC0, from the local research set
(development source files removed after the 2026-11-05 Quaternius pivot); see
[`CREDITS.md`](../CREDITS.md).
- The head is a **derived prototype**, not a shipped NPC. It does not enter
`assets/models/blender/npc/`, `npc_base`, the runtime `NpcAvatar` path, CI, or
a release package. It is retained as a fallback, not the active NPC base.
- Source photographs/proportions are not used as textures here; this is a
geometry-only head built from the researched model data.
## Review criteria
Accept only if:
- the head reads as human at front/three-quarter/profile and the neck is a
clean head/bust silhouette, not a shoulder cut;
- eyes, brow, nose, mouth and ears are readable without spikes, holes or
floating shells;
- the asset is inside the character budget and has no script/contract violation;
- the prototype remains clearly local and un-integrated until the part pipeline
is approved.
## Judge result
**Owner verdict: ACCEPT (basic human head, 2026-11-04).**
**Automated vision gate: REJECT** (vision judge `z-ai/glm-5.3-flash`, final
standalone build render set above). The eye/lid findings below remain deferred
polish; they are no longer a rollout blocker because the owner accepted the head.
Blocking findings:
- **P0 — globe depth is inconsistent across views.** The front/three-quarter
reads still show a bead-like iris with a pale sclera halo, while the profile
reduces the eye to a brown sliver. The judge reads this as a faked recess
rather than a socketed globe under a lid.
- **P0 — no convincing lid overlap.** The iris remains nearly circular in front
and the upper lid does not visibly clip the globe; the skin lid mask reads as
a crater around a bead.
- **P0 — iris orientation reads decal-locked** at three-quarter, so the gaze
appears to drift off the head axis.
- **P0 — the aperture/lid mask is too circular** and lacks medial/lateral
canthi, so the eye still reads as a doll eye at portrait distance.
Should-fix findings:
- the brow tubes are better anchored but the medial ends still detach in
three-quarter/profile;
- nose lacks a defined tip/wing and nostrils;
- mouth is a faint geometric seam with no lip volume;
- crown/back-of-skull is still faceted and egg-like in profile;
- the catchlight is baked geometry and is not consistent between eyes.
What passed: the skin/eye material remap is correct, the eye openings are
closed, the standalone GLB reports **12,641 triangles / 7,690 split vertices**,
and an assembled `npc_casual` with this head passes `inspect` at **14,753 /
16,000 triangles, 12,823 seam-split vertices, 14 bones, no issues**.
Known limits: bald mannequin-like presentation, stylized geometry-only
iris/pupil, no rig, no UV texture pass, and the neck cap is a review cut rather
than a body-welded seam.
## Assembled prototype (2026-11-04)
- `_makehuman_head` imports the GLB and adds a **skull-conforming hair cap**
derived from the closed head shell (scaled shell + boolean front-sphere
cut + decimate). Hair skips the old `hair_normal.png` because the skull UVs
produced large dark tangent patches.
- Head faces fully covered by the cap are deleted to recover ~950 triangles;
boundary faces stay as a safety band, so the accepted face geometry is
untouched. The hidden neck plug uses 12 segments.
- The procedural body was narrowed at the neck and shoulders (chest 0.158,
neck base 0.060, neck top 0.054, shoulder root z 1.345) and its skin decimate
dropped to 0.21 so all 12 variants fit the 16,000 cap.
- Local review renders were removed with the 2026-11-05 `tmp/` cleanup; the
committed review sheet above is the retained evidence.
## Deferred polish
- Replace the radial lid mask with real upper/lower lid masses that fold over
the globe, with pointed canthi, and pass the profile view.
- Move the iris onto a curved cornea cap so its orientation follows the globe at
three-quarter angles, and use a lighting-driven catchlight instead of baked
white geometry.
- Refine the hair cap silhouette (current short cap reads as a helmet) and
improve the full-body shoulder/arm shape before the 12-variant rollout.
- Add a clean UV/material pass and a contract armature/weights when the head
becomes part of the shipped library characters.
+308 -320
View File
@@ -1,336 +1,324 @@
# NPC model sourcing and expansion plan # NPC Model Sourcing
Status: **direction chosen 2026-11-03.** The procedural generator is the primary This page records the source evaluation and the active character plan. The
path, the Quaternius CC0 pack is a reviewed reference/prototype, and MakeHuman is decision is [ADR-0017](adr/0017-quaternius-npc-base.md), which supersedes the
deferred to a separate local development pipeline. No downloaded source model is generator-first sourcing decision in [ADR-0015](adr/0015-npc-model-variety-sourcing.md).
committed to the game or included in a release; the committed research preview
sheets carry the required attribution. Local evaluation files live under
`tmp/npc-research/` (gitignored). The decision is recorded in
[ADR-0015](adr/0015-npc-model-variety-sourcing.md).
This page records the free/open-source character sources that were reviewed, how ## Active direction
they behave when imported into the existing Blender tooling, and the options for
creating a larger NPC model library. The runtime contract and current library are
documented in [`blender-assets.md`](blender-assets.md) and
[`npc-visual-review.md`](npc-visual-review.md).
## Why this review exists **Quaternius Ultimate Modular Characters (CC0) is the primary NPC mesh and
animation source.** The project-owned procedural library remains the shipped
fallback until the Quaternius migration passes the silhouette and gear gates.
The library has eight skinned characters, and `NpcAvatar.character_model_for()` The migration keeps the runtime contract that Godot, gear and tests already
reuses them across role families. That is enough for a release candidate, but consume:
crowds repeat the same faces, builds and outfits. Three venues, a 24-NPC budget
and close-up portraits make that repetition visible.
Any expansion must preserve the existing integration contract: - one `npc_base` skinned mesh under the 13 required core bones (optional extras
allowed);
- five tint slots: `skin`, `cloth`, `pants`, `accessory`, `hair`;
- front on Blender −Y / Godot +Z, feet grounded, height about 1.81 m;
- 24,000 triangles / 24,000 glTF vertices per character;
- gear stays at 1,500 triangles and props at 4,000;
- committed GLBs and maps only; no Quaternius, Blender or network dependency in
CI, build or runtime.
- mesh node `npc_base` under a `Skeleton3D`; ### Stage 1 (default)
- bones `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R` plus
`Hips`, `Spine`, `Neck` at the contract rest positions;
- five tint slots (`skin`, `cloth`, `pants`, `accessory`, `hair`);
- front on Blender −Y / Godot +Z, feet near the floor, 1.6–2.1 m height;
- at most 8,000 triangles and 8,000 glTF vertices per character;
- gear attach points at head, back, chest and hand.
The existing `audit` command is the gate. A source that looks good in a preview 1. Import the researched Quaternius GLBs and choose a canonical base.
but fails `audit` is not usable without an adaptation step. 2. Map the 62 source joints to the 13-core contract and bake a proportion warp.
3. Skin the mesh, retarget Idle and Walk, and verify seated/arm-down poses.
4. Map the five slots, unwrap and bake 256² maps.
5. Fit the existing gear and render the silhouette gate.
6. Build the 12 variants, audit, and capture the 12 × 6 baseline.
## What was evaluated The Quaternius 62-joint rig and its 24 animation clips are an **authoring
source**. They are not loaded at runtime.
Blender 5.2.0 LTS (Flatpak) imported each file in background mode. Measurements ### Stage 2 gate
below come from the imported scene, because external GLBs often carry node
transforms that the project's audit (which reads accessor-local bounds) does not
apply. "Vertices" are the imported, seam-split counts that matter for the glTF
budget, not the source file's vertex count.
| Source | Licence | Files checked | Geometry / rig | Height | Verdict | The 62-joint T-pose source does not share the project's arm-down rest pose or
| --- | --- | --- | --- | --- | --- | proportions. If the Stage 1 proportion warp cannot preserve the Quaternius
| **Quaternius Ultimate Modular Men / Women** (mirrored GLB pack) | CC0 1.0 | `Casual_Hoodie.glb`, `Worker.glb`, `Casual.glb` | 5,240–6,424 tris; 10,534–12,968 verts; 62 bones; 24 animations; 4 skinned meshes on one armature | 1.80–1.87 m | **Best silhouette/outfit match.** Fails the contract: wrong bone names/rest positions, no tint slots, no `npc_base` node, vertex budget exceeded. | silhouette and deformation, stop the rollout and write a new ADR for adopting
| **Kenney Blocky Characters 2.0** | CC0 1.0 | full pack (18 characters), `character-a.glb`, `character-f.glb` | 72 tris; 143 verts; static, no armature; one texture atlas per character | 2.7 m imported (needs 0.67× scale) | **Not a close-up character.** Useful as a crowd/impostor LOD or placeholder, but cannot be driven by the skeleton and has no separate face/clothing slots. | the 62-joint rig at runtime. Do not ship a hybrid rig, a second gear offset
| **Khronos CesiumMan** | CC BY 4.0 (© Cesium) | `CesiumMan.glb` | 4,672 tris; 3,273 verts; 19-bone walk rig; 1 material/texture; 1 animation | 1.45 m | **Technical retarget test only.** One outfit and one look, wrong rig, short; works as a skinning/pose reference. Attribution is required. | system or a silent `NpcAvatar` change.
| **Khronos RiggedFigure** | CC BY 4.0 (© Cesium) | `RiggedFigure.glb` | 256 tris; 370 verts; 19-bone walk rig; no UVs; 1 animation | 1.48 m | **Rig smoke test only.** A mannequin with no usable surface detail. |
| **MakeHuman core base mesh / MPFB** | Core assets CC0 1.0; application AGPL/GPL | `makehuman/data/3dobjs/base.obj` | 19,158 verts; 18,486 polygons; 36,972 tris; no rig in the base mesh | ~16.9 model units before scaling | **Best body-shape diversity, highest pipeline cost.** Needs scaling, decimation/retopology and a generated rig. The MakeHuman/MPFB tool itself is not bundled. |
Local review sheet (front views, not committed): ## Research findings
`tmp/npc-research/previews/contact_sheet.jpg`.
Rejected without downloading: Mixamo and Ready Player Me. Both are useful The researched files are the three converted Quaternius outfits used by the
services, but their terms are not a simple free/open-source redistribution basis Ducz Character Creator: `Casual`, `Casual_Hoodie` and `Worker`. They are kept
for shipped character meshes. Sketchfab, Poly Pizza and OpenGameArt can contain locally in `tmp/npc-research/quaternius/` with the licence and mirror notes.
excellent CC0 work, but each asset needs a case-by-case licence and provenance
check; they are not a consistent library source.
### Measured contract failures | File | SHA-256 | Structure |
Every example fails the unmodified `audit` contract:
| Check | Quaternius | Kenney | CesiumMan | RiggedFigure | MakeHuman base |
| --- | --- | --- | --- | --- | --- |
| `npc_base` mesh node | no | no | no | no | no |
| Required 13 bones | no (62 different names) | no rig | no (19 different names) | no (19 different names) | no rig |
| Five tint slots | no | no | no | no | no |
| ≤ 8,000 tris | yes | yes | yes | yes | no |
| ≤ 8,000 verts | no (10.5–13k) | yes | yes | yes | no (19k) |
| Feet on floor | yes | yes (2.7 m scale) | yes (−11 mm) | yes | no (needs scaling) |
| Height 1.6–2.1 m | yes | no (2.7 m) | no (1.45 m) | no (1.48 m) | needs scale |
| Skinned / UV mapped | yes / yes | no / yes | yes / yes | yes / no | no / no |
The practical conclusion is that **there is no drop-in download**. External
sources need a deliberate intake step, and the expensive part is not decimation:
it is rebuilding or retargeting the skeleton and material slots without breaking
the animation proxies and gear placement.
The table uses Blender-imported world bounds. The stdlib `audit` reads accessor-local
bounds and ignores node transforms, so an unapplied external GLB can fail floor/height
checks even when the imported scene looks correct. The intake step must apply
transforms before it measures and exports.
## Options
### Option A — extend the procedural generator
Add more variation to `tools/blender_assets.py` rather than importing models:
body builds, height ranges, head/face shapes, hair and facial hair, outfit
layers, and more `CHARACTERS` specs. This keeps a single owned style, needs no
third-party licence review, and stays inside the existing builder/audit flow.
- **Strengths:** lowest integration risk, deterministic, no new dependencies.
- **Limits:** the current face and clothing vocabulary is intentionally simple;
more variants may still read as the same model with a different coat.
- **Best for:** a quick 4–8 variant expansion and filling obvious role gaps.
### Option B — Quaternius CC0 intake with a contract adapter
Use Quaternius meshes as silhouette/outfit donors and adapt them to the project
contract. The visible result is a different body/clothing language from the
current library, so the adapter must also normalise head/hand style or rebuild
those parts.
- **Strengths:** best free outfit/hair variety, animations included, CC0,
already close to game proportions, front axis and floor contact.
- **Work:** weld/decimate to the vertex budget, remap materials to the five
slots, rebuild the armature at the contract rest positions, transfer weights,
repose/normalise proportions, rename the mesh node, export, audit and compare
in the acceptance harness.
- **Main risk:** retargeting a 62-bone T-pose rig to the 13-bone arm-down
contract. Hip/knee rest heights differ substantially (Quaternius upper leg
starts near 0.96 m; the contract hip starts near 0.62 m), so a naive
nearest-bone weight transfer can produce a good still image and bad walk
poses. Prototype one character before committing to a roster.
- **Best for:** a larger, visibly different library if the prototype passes the
portrait and walk gates.
### Option C — MakeHuman/MPFB body-shape pipeline
Generate a range of ages, builds and body shapes from the CC0 MakeHuman base
mesh, then replace the head/hands and dress them with project-owned clothing.
This is the strongest answer to "everyone has the same body", but requires an
offline authoring step, decimation/retopology and a custom rig/weight step. It
should be treated as a later pipeline project, not a quick content drop.
## Decision and prototype outcome
The project owner reviewed this page on 2026-11-03. The decision is:
1. **CC0 is acceptable** for shipped character assets. CC-BY is acceptable only
with attribution in `CREDITS.md`; service assets whose terms restrict
redistribution remain excluded.
2. **Extend the procedural generator** as the primary path, starting with
crowd-variety looks.
3. **Prototype Quaternius** as a secondary source. It is not approved for
shipping yet; the contract-retarget result below is a no-go for the current
skeleton.
4. **Use crowd variety** as the first visual goal, not role-specific costumes.
5. **Evaluate MakeHuman for a later, separate local development pipeline.** It
is not part of the normal build, CI or release flow.
6. **Commit the research previews with attribution.** The CC-BY entries are
listed in `CREDITS.md` and remain development-only.
### Quaternius prototype
The prototype used `Casual_Hoodie.glb`:
- **Geometry budget is not the blocker.** Imported: 12,518 vertices / 6,206
triangles. After welding seam splits: 3,199 vertices / 6,206 triangles. After
a 0.75 decimate: 2,423 vertices / 4,654 triangles, comfortably inside the
library budget.
- **Animation quality is attractive.** The imported Idle and Walk clips read
well at game distance, and the model brings 24 animations.
- **The contract rest pose is the blocker.** The source is a T-pose 62-bone rig
with realistic proportions: upper leg starts near 0.96 m, arm span is about
1.68 m, and the shoulder joint sits near x = 0.153 m. The project contract is
an arm-down 13-bone rig with a hip near 0.62 m, a shoulder near x = 0.27 m and
a hand near z = 0.78 m. Rebuilding the contract armature around this mesh would
either distort the body substantially or require a full proportion warp.
- **Decision:** do not retarget Quaternius to the contract now. Keep it as a
silhouette/style reference and, if a second rig path is ever wanted, prototype
it as its own animation path (its own skeleton, AnimationPlayer clips and
per-model gear offsets) rather than forcing it into `npc_base`. That is a
separate architecture decision, not an asset conversion.
Preview: [`blender_previews/sheet_npc_sources_research.jpg`](blender_previews/sheet_npc_sources_research.jpg).
### Generator crowd prototype
Four proposed crowd specs were built with the existing generator in a temporary
output directory. All four audit clean and stay inside the character budget:
| Prototype | Look | Tris / verts | Gear |
| --- | --- | --- | --- |
| `npc_crowd_a` | slim, bun, sunglasses | 4,644 / 4,356 | sunglasses |
| `npc_crowd_b` | broad, bald, beard | 4,550 / 4,453 | shoulder bag |
| `npc_crowd_c` | average, short hair, long coat | 5,118 / 5,086 | umbrella |
| `npc_crowd_d` | slim, short hair, hood up | 5,258 / 4,907 | cup |
They vary build, head silhouette, hair/beard and outer layer while keeping the
existing face language. They are **not in the shipped catalogue**; production
integration belongs to the roster/runtime work under the unversioned `Character
development` section, after the current release candidate.
Preview: [`blender_previews/sheet_npc_crowd_prototypes.jpg`](blender_previews/sheet_npc_crowd_prototypes.jpg).
### MakeHuman local-pipeline evaluation
The CC0 base mesh is 19,158 vertices / 18,486 polygons (36,972 triangles after
triangulation), with 21,334 UV coordinates but no normals, material groups or
rig. Scaling it to 1.82 m and decimating to 0.35 produced 3,780 vertices / 6,468
triangles, which fits the geometry budget but is a collapse-decimation result,
not production topology.
MakeHuman is therefore **feasible only as a separate local body-shape pipeline**.
The boundary for that future work is:
Intake prototype (2026-11-04): `tmp/npc-research/makehuman_intake.py` scales the
CC0 base to 1.82 m, grounds it, triangulates and decimates to **3,790 verts /
6,444 tris**, then exports `tmp/npc-research/makehuman/makehuman_body.glb` (body
only, one neutral material, UVs present, no rig). The body is the input for the
contract-rig/retopology step; the script and mesh stay under `tmp/` and are never
committed.
Retarget prototype (2026-11-04): `tmp/npc-research/makehuman_retarget.py` rotates
the A-pose arms down (−38°/+38°) and scales them **1.63×** to the contract 0.70 m
arm, welds the split vertices, smooth-shades, adds the 13-bone contract armature
with the project's distance-to-bone `_auto_weights`, paints the five slot regions,
renames the mesh to `npc_base`, and exports `makehuman_retargeted.glb` — **6,444 /
8,000 tris, 7,726 / 8,000 verts**, 14 bones, skinned, arm-down, grounded, five
slots, no contract issues. The proportion warp (1.63× arms) is the same class of
issue that blocked Quaternius; here it is accepted, but the vision review shows
it distorts the deltoid/shoulder, the decimated hands are blocky, and the slot
boundaries are rough — refinement work (arm-scale falloff, project head/hands,
weight tuning) is the next step.
The arm-scale falloff (1.0 at the shoulder tapering to 1.63 at the fingertip) was
added, then the arm threshold was raised to x > 0.22 and a rotation falloff was
added — the deltoid/shoulder junction is now smooth (no spiky triangles or
arm/torso gaps). Remaining visual defects: the decimated MakeHuman head reads
dark/featureless, the hands are flat claw-like, and the feet are detached/spiked
— these are fixed by swapping in the project's `_face`/`_hands_and_shoes`/shoe
parts, the next step.
- MPFB/MakeHuman is installed and run by a contributor, never by CI or the game
build.
- Generated bodies and intermediate exports stay in `tmp/` (gitignored); the
CC0 base mesh is not committed.
- The contributor scales to 1.8–1.9 m, decimates or retopologises to roughly
6–7k triangles, and checks the silhouette against `npc-visual-review.md`.
- The body gets the contract armature, weights, project-owned head/hands,
clothing and gear; it must pass `blender_assets.py audit` and `fit`.
- Only the final CC0-derived GLB and its provenance note are committed. The
application code and any non-CC0 assets stay outside the repository.
This is deferred; it is not part of the current crowd-variety work.
## Provisional NPC roster
The design rule stays: clothes, props and behaviour carry the read; a model must
not label a person as "the threat". The roster therefore adds ordinary adults
across age, build and style, not costumes that telegraph the outcome.
| Look family | Current | Proposed additions | Variation axes |
| --- | --- | --- | --- |
| Everyday | `npc_casual`, `npc_jacket`, `npc_coat`, `npc_hoodie`, `npc_dress` | `npc_crowd_a` (bun/slim), `npc_crowd_b` (bald/broad), `npc_crowd_c` (coat/umbrella), `npc_crowd_d` (hood/cup) | build, height, hair, facial hair, outer layer, accessory |
| Workwear | `npc_vest`, `npc_guard` | later: second hi-vis build, venue-security build | build, height, headwear, badge/radio gear |
| Responder | `npc_firefighter` | later: paramedic/medical responder only if the design supports it | turnout vs medical kit, build, helmet |
| Crowd/LOD | none | optional Kenney blocky or a decimated library variant for distant crowds | silhouette and palette only |
A first target is **12 library characters** (the current eight plus the four
crowd prototypes), not twenty. That is enough to break the clone effect without
doubling the portrait QA matrix. `character_model_for()` becomes a pool per role
family with a deterministic seed choice; the current two-way alternation for
guests, trespassers and threats is the template.
## Implementation plan
The actionable tasks live in `TODO.md` under the `Character development` section. The workstreams
are:
1. **Decision and licence gate.** Complete: ADR-0015, the CC0/CC-BY rule and the
committed preview sheets.
2. **Generator crowd variants.** Complete as a prototype (four specs, clean
audit, review sheet). Production integration is pending the current release candidate.
3. **Quaternius intake.** The contract-retarget prototype is a no-go. Keep the
source as a reference; a second-rig/animation path would need its own ADR.
4. **Roster build and runtime.** Add the four approved specs to `CHARACTERS`,
build and commit their GLBs, update the manifest, and extend the role pools.
5. **QA and integration.** Run the 24-NPC budget, portrait baseline, gear fit,
headless suite and release build. Record provenance and any accepted limits.
Only then update `CHANGELOG.md` and `docs/development-status.md`.
### Adapter notes
- Apply node transforms before measuring or exporting. CesiumMan imports with a
Z-up root and an `Icosphere` helper object; exclude helpers, not just the mesh
names, when measuring.
- Weld duplicate seam vertices before decimating. Quaternius' 12k imported
vertices are mostly UV/normal splits; the underlying surface is much smaller,
but the glTF budget counts the splits after export.
- Decimate to a target below 8,000 tris/verts, then re-unwrap only if the
existing UVs cannot survive the material remap. The current library uses
smart projection at 80° to keep islands large.
- Map materials by semantic role, not by source name. A source "Purple" or
"Worker_Vest" needs an explicit mapping to `cloth`, `pants`, `skin`, `hair` or
`accessory`; do not rely on colour heuristics.
- Keep the face direction unambiguous. The front is Blender −Y; verify it with
a capture, not by guessing from the source file.
- For any future external-rig path, do not silently add a second skeleton format
to `NpcAvatar`. Record the per-model skeleton, animation source and gear
offsets as an explicit contract.
## Review outcome
The project owner answered the review questions on 2026-11-03:
| Question | Answer |
| --- | --- |
| Are third-party CC0 characters acceptable? | Yes. |
| Which direction? | Prototype Quaternius and extend the procedural generator. |
| Which looks first? | Crowd variety. |
| Should MakeHuman be pursued? | Evaluate it for a later, separate local development pipeline. |
| Commit the previews? | Yes, with CC-BY attribution. |
## Downloaded example provenance
Source models were downloaded on 2026-11-03 and kept out of version control. The two
research preview sheets are committed under `docs/blender_previews/`; the CC-BY
attribution is recorded in `CREDITS.md`.
| File | Source | SHA-256 |
| --- | --- | --- | | --- | --- | --- |
| `quaternius/Casual_Hoodie.glb` | `raw.githubusercontent.com/LuanDucate/Ducz.CharacterCreator/HEAD/public/models/Male/Casual_Hoodie.glb` (Quaternius CC0) | `f5b722199f7628b24550989af4230f65d5230727df9c326fd2b3e360effe5249` | | `Casual.glb` | `bc2d84fba249ebaa0629cb9d133ad37234196e4b8004aa205a6397abed04e05c` | 4 meshes, 62 joints, 24 clips, 7 materials |
| `quaternius/Worker.glb` | same mirror, `Male/Worker.glb` | `8d0f454336c2effc5c08f63a177af3a82d1a7af025ca9588b40e4c90c4703534` | | `Casual_Hoodie.glb` | `f5b722199f7628b24550989af4230f65d5230727df9c326fd2b3e360effe5249` | 4 meshes, 62 joints, 24 clips, 7 materials |
| `quaternius/Casual.glb` | same mirror, `Female/Casual.glb` | `bc2d84fba249ebaa0629cb9d133ad37234196e4b8004aa205a6397abed04e05c` | | `Worker.glb` | `8d0f454336c2effc5c08f63a177af3a82d1a7af025ca9588b40e4c90c4703534` | 4 meshes, 62 joints, 24 clips, 11 materials |
| `kenney_blocky_characters.zip` | `kenney.nl/assets/blocky-characters` | `5e123859aa0c1598342b600c6db197024a1d63eb9ec531398b310725f589887e` |
| `khronos/CesiumMan.glb` | `github.com/KhronosGroup/glTF-Sample-Assets` | `b7001eaeea8254bd44773bcd247e78696d94169388fbb2a1800fc69434e777d9` |
| `khronos/RiggedFigure.glb` | same repository | `d6be85417d3e256861ee733eea6916093a7af7c79c16366181fd8abcaeb38cf5` |
| `makehuman/base.obj` | `github.com/makehumancommunity/makehuman/blob/master/makehuman/data/3dobjs/base.obj` | `8e761e6624b8f54536409135d1636da63b32486a90d4897f84e121d144f6fb4c` |
Source terms: Source: [Quaternius Ultimate Modular Characters](https://quaternius.com/packs/ultimatemodularcharacters.html)
and [Ultimate Modular Women](https://quaternius.com/packs/ultimatemodularwomen.html),
CC0 1.0. Download date: 2026-11-03 (via the Ducz Character Creator browser export).
The Ducz mirror is a browser builder over the same packs; it adds 21
outfits and a shared skeleton. The three full GLBs are the initial scope; the
21-part builder is a fallback for role coverage, not the default.
- Quaternius packs are CC0 1.0 according to the pack page and the mirror's **Initial outfit scope (2026-11-05):** the three full GLBs (`Casual`,
credits. The mirror is a convenience download; the original pack page is `Casual_Hoodie`, `Worker`) are the canonical-base and silhouette-gate scope.
<https://quaternius.com/packs/ultimatemodularcharacters.html>. They cover only ~3 of the 12 roles (casual, hood, vest); the jacket, coat,
- Kenney Blocky Characters 2.0 is CC0 1.0; the pack's `License.txt` is included dress, responder (firefighter) and guard looks are not in the three GLBs, so
in the download. §4 roster coverage must pull those from the 21-part modular builder or author
- CesiumMan and RiggedFigure are CC BY 4.0, © 2017 Cesium. If any derivative is new clothing. Do not start the modular builder until the canonical base passes
committed or shipped, the attribution must be added to `CREDITS.md`. §3.
- MakeHuman core assets are CC0 per `LICENSE.ASSETS.md`; the application source
is AGPL and is not bundled. The first prototype measured 12,518 imported vertices, 3,199 after welding and
2,423 vertices / 4,654 triangles after a 0.75 decimate. Idle and Walk read well.
The source is a T-pose with a 62-joint realistic rig, roughly 0.96 m hip,
1.68 m arm span and 0.153 m shoulder offset, so a contract bake is required.
The 24,000 cap leaves room to stay much closer to the source LOD than the
prototype's 4,654 triangles. Do not decimate to the old 7,000-triangle target
before the silhouette gate.
## Quaternius GLB inventory (2026-11-05)
All three GLBs share one 62-joint rig and the same 24 animation clips; only the meshes and
materials differ. Each import also adds a 42-vert/80-tri `Icosphere` bone-display helper — ignore it.
### Meshes (verts / tris)
| GLB | Body | Feet | Head | Legs | Total |
| --- | --- | --- | --- | --- | --- |
| `Casual` | 5,338 / 2,654 | 1,082 / 538 | 5,122 / 2,526 | 1,426 / 706 | 12,968 / 6,424 |
| `Casual_Hoodie` | 6,132 / 3,048 | 1,024 / 512 | 3,836 / 1,888 | 1,526 / 758 | 12,518 / 6,206 |
| `Worker` | 6,074 / 3,018 | 928 / 464 | 1,862 / 936 | 1,670 / 822 | 10,534 / 5,240 |
### Joints (62)
- Spine: `Root, Hips, Torso, Body, Abdomen, Chest, Neck, Head` (8).
- Arms: `Shoulder.L/R, UpperArm.L/R, LowerArm.L/R, Wrist.L/R` (8) plus `PT.L/R` (2 pole targets).
- Fingers: `Thumb×3, Index×4, Middle×4, Ring×4, Pinky×4` × L/R (30).
- Legs: `UpperLeg.L/R, LowerLeg.L/R, Foot.L/R` (6).
### Animation clips (24)
`Idle, Idle_Neutral, Walk, Run, Run_Back, Run_Left, Run_Right, Wave, Interact, Death,
Gun_Shoot, Idle_Gun, Idle_Gun_Pointing, Idle_Gun_Shoot, Idle_Sword, Run_Shoot, Sword_Slash,
HitRecieve, HitRecieve_2, Kick_Left, Kick_Right, Punch_Left, Punch_Right, Roll`.
The retarget scope is Idle and Walk; combat/gun/sword clips are discarded (see §2).
### Material → slot map
The five project slots (`skin`, `cloth`, `pants`, `accessory`, `hair`) are assigned by mesh
region first, then material — the same material name can mean `cloth` on the body and
`accessory` on the feet.
| Material | Where | Project slot |
| --- | --- | --- |
| `Skin` | all meshes | `skin` |
| `Hair_Blond`, `Hair_Brown`, `Hair`, `Brown` (head), `Eyebrows`, `Moustache` | Head | `hair` |
| `Eye` | Head | `accessory` |
| `Worker_Yellow` | Worker body (vest stripe) + head (helmet) | `cloth` / `accessory` |
| `White`, `Grey`, `Purple`, `Black` | Body / Feet | `cloth` (body) / `accessory` (feet) |
| `Orange`, `LightBlue`, `LightBrown`, `Brown`, `Brown2` | Legs | `pants` |
| `Worker_Vest` | Worker body | `cloth` |
### Canonical base (built 2026-11-05)
`tmp/npc-research/quaternius_base.glb` is the welded, triangulated, grounded, animation-free
canonical base built from `Casual_Hoodie.glb` (the most detailed body) by
`tmp/npc-research/build_quaternius_base.py`:
- one `npc_base` mesh — 3,199 welded verts / 6,206 tris (Body + Feet + Head + Legs joined);
- 62 joints, skinned (no unskinned faces);
- height 1.81 m, feet at y 0, front on Godot +Z / Blender −Y (T-pose rest);
- no animation clips; `inspect` clean (no issues).
This is the §2 contract-bake input: the 62-joint rig is authoring-only and is mapped to the
13-core contract next, not shipped as-is.
## 62-joint → 13-core joint map (2026-11-05)
The Quaternius hierarchy (measured from `quaternius_base.glb`):
- `Root → Body → Hips → Abdomen → Torso → Chest → Neck → Head` (spine);
- `Chest → Shoulder.L/R → UpperArm.L/R → LowerArm.L/R → Wrist.L/R → fingers` (arms);
- `Body → UpperLeg.L/R → LowerLeg.L/R` and `Root → Foot.L/R` (legs — the foot is a separate
child of Root, not of LowerLeg);
- `Root → PT.L/R` (IK pole targets, no deforming weights).
The contract is 14 bones (13 core + `Hips` root): `Hips, Spine, Neck, Head, Shoulder.L/R,
Elbow.L/R, Hand.L/R, Hip.L/R, Knee.L/R`. The map:
| Contract bone | Quaternius source | Notes |
| --- | --- | --- |
| `Hips` | `Hips` | `Root` + `Body` collapse into `Hips` |
| `Spine` | `Abdomen`, `Torso`, `Chest` | three collapse into one |
| `Neck` | `Neck` | |
| `Head` | `Head` | |
| `Shoulder.L/R` | `UpperArm.L/R` | the collar `Shoulder.L/R` collapses into `Spine` |
| `Elbow.L/R` | `LowerArm.L/R` | |
| `Hand.L/R` | `Wrist.L/R` | |
| `Hip.L/R` | `UpperLeg.L/R` | |
| `Knee.L/R` | `LowerLeg.L/R` | |
Optional extras (allowed by the contract; kept if they survive within 24,000 vertices):
- the 30 finger bones (`Thumb×3, Index×4, Middle×4, Ring×4, Pinky×4` × L/R) → optional finger
bones;
- `Foot.L/R` → optional toe bones.
Dropped (never shipped): `PT.L/R` (pole targets carry no deforming weights), and the
`Root`/`Body`/collar-`Shoulder` collapse bones above.
## Rest-pose and proportion-warp measurement (2026-11-05)
The rest-pose bake (T-pose → arm-down) works: rotate the `UpperArm.L/R` pose bones −90° around
their local X, apply the Armature modifier to bake the deformation, then `armature_apply` to bake
it as the new rest pose (see `tmp/npc-research/warp_full.py`). The mesh deforms cleanly to the
arm-down pose (deformed bounds x ±0.221 vs the T-pose ±0.811).
The proportion warp to the contract positions is measured and it **distorts the silhouette**:
| Joint | Quaternius | Contract | Delta |
| --- | --- | --- | --- |
| Hips height | 0.869 | 0.62 | −0.249 (lower hip) |
| Head top | 1.614 | 1.82 | +0.206 (raise head) |
| Shoulder x | 0.148 | 0.27 | +0.122 (widen) |
| Arm (shoulder→wrist) | 0.402 | 0.60 | ×1.49 |
| Arm (shoulder→hand tip) | 0.429 | 0.70 | ×1.63 |
| Leg (hip→ankle) | 0.835 | 0.62 | ×0.74 |
| Torso (hip→head) | 0.745 | 1.20 | ×1.61 |
The vision judge reads the warped mesh as: paddle/claw hands (the 1.63× arm stretch blows the
hand out), forearms lost into the torso, stretched shins, and "long legs + short torso". The arm
must either stay short (a "shortened arm" vs the contract hand) or stretch 1.63× (a distorted
deltoid + hand) — both are the gate's rejection conditions.
**Finding:** the Stage 1 proportion warp cannot preserve the Quaternius silhouette at the
contract proportions; the gate fails and Stage 2 (a 62-joint runtime ADR) is indicated. Do not
start the 12-variant rebuild on the warped base.
[ADR-0018](adr/0018-quaternius-stage2-runtime-rig.md) records the Stage 2 decision (accepted
2026-11-05, owner chose option 2): adopt the **Quaternius 62-joint rig at runtime**, keep the
mesh at source LOD, re-derive gear attach points from the Quaternius skeleton, and use the
Quaternius Idle/Walk clips directly (no retarget).
## Pilot bake and shipped state (2026-11-05)
`npc_casual` is the first Quaternius-derived character. The reproducible pipeline lives in
`tmp/npc-research/` (local tooling, not shipped) and has two steps:
1. `build_base_minimal.py` — import `quaternius/Casual_Hoodie.glb`, join the four skinned meshes
**without welding**, rename the upstream materials to the project tint slots, Smart-UV-unwrap
and repack, export `quaternius_tpose.glb`.
2. `bake_armdown_v5.py` — pose `UpperArm.L/R` −90° local X, read the posed matrices, apply the
Armature modifier, **reset the pose to identity**, set the rest matrices, re-add the modifier,
export `quaternius_base.glb` and save `assets/models/blender_src/npc_casual.blend` for the AO
bake.
Then `tools/bake_ao.py --only npc_casual` and a copy of the GLB to
`assets/models/blender/npc/npc_casual.glb`.
| Measurement | npc_casual (Quaternius) | procedural library |
| --- | --- | --- |
| Triangles | 6,206 | 4,812–6,046 |
| glTF vertices | 12,988 | 4,908–5,785 |
| Bones | 62 | 13 |
| Head height / body height | 0.134 | 0.16–0.22 (stylized band) |
| Shoulder width | 0.201 m head width, `fit` OK | `fit` OK |
| Texel density (256² maps) | 73–74 px/m | 75–96 px/m |
| Materials | `skin`, `cloth`, `pants`, `hair`, `accessory` + fixed `Eye`, `Eyebrows` | five slots + fixed detail |
Two source quirks shaped the bake:
- the upstream UV map is degenerate (every loop at 0,0) and its materials are flat colours, so the
AO/texture bake must follow a real unwrap or it silently writes a black map;
- the upstream materials are named `Skin`/`Purple`/`LightBlue`/`White`/`Hair`, so the five-slot
contract is applied as a rename, not a re-author.
The face and silhouette gate sheet is in [`screens/npc-face-gate/`](screens/npc-face-gate/README.md).
## Shipped roster (0.9.9, owner chose the full swap)
Every public character name is baked from one of the three CC0 sources. The build is
`tools/blender_assets.py build` (default `--source quaternius`); the sources live in
`assets/models/blender_src/quaternius/` and the geometry pipeline is two passes:
1. `_quaternius_tpose_glb()` — import, drop the Icosphere helper, join **without welding**,
repoint the upstream materials onto the five tint slots, Smart-UV-unwrap + repack, export an
intermediate T-pose GLB. The round trip also normalises the mirrored upstream bone rolls,
which is what makes the next pass symmetric.
2. `_bake_quaternius_rest()` — pose `UpperArm.L/R` -90° local X, read the posed matrices, apply
the Armature modifier, reset the pose, write the rest matrices, re-add the modifier, export
with `export_apply=False`.
Then `tools/bake_ao.py --only <name>`.
| Character | Source | Tris | Material slots |
| --- | --- | --- | --- |
| `npc_casual` | hoodie | 6,206 | skin, cloth, pants, hair, accessory + Eye, Eyebrows |
| `npc_jacket` | casual | 6,424 | five slots + eyes/brows |
| `npc_coat` | hoodie | 6,206 | five slots + eyes/brows |
| `npc_vest` | worker | 5,240 | five slots + hivis vest/hat, eyes, moustache |
| `npc_dress` | casual | 6,424 | five slots + eyes/brows |
| `npc_hoodie` | hoodie | 6,206 | five slots + eyes/brows |
| `npc_firefighter` | worker (vest + hat dropped) | 4,458 | five slots + eyes, moustache |
| `npc_guard` | worker (vest + hat dropped) | 4,458 | five slots + eyes, moustache |
| `npc_crowd_a` | casual | 6,424 | five slots + eyes/brows |
| `npc_crowd_b` | casual | 6,424 | five slots + eyes/brows |
| `npc_crowd_c` | hoodie | 6,206 | five slots + eyes/brows |
| `npc_crowd_d` | worker (vest + hat dropped) | 4,458 | five slots + eyes, moustache |
Source slot maps (`QUATERNIUS_SOURCES`): the `casual` tee is `Skin`/`White`/`Orange`/`Grey`/
`Hair_Blond`; the hoodie is `Skin`/`Purple`/`LightBlue`/`Hair`/`White`; the worker is `Skin`/
`LightBrown`/`Brown`+`Brown2`/`Grey`+`Black`/`Eyebrows`. Everything not in the map keeps its
authored colour (eyes, brows, moustache, hivis vest and hard hat), so the contract still holds if
upstream adds a material — it shows up as an extra fixed-colour slot instead of vanishing.
Head gear is authored for this head: `HEAD_GEAR_SCALE = 0.64` and `HEAD_GEAR_LIFT = 0.07` map the
0.315 m procedural head onto the 0.201–0.214 m Quaternius head. The procedural fallback therefore
wears slightly small hats.
## Contract mismatch
The expensive part is the rig and proportions, not the mesh. The source has a
T-pose and a realistic skeleton; the runtime expects an arm-down 13-core rig,
fixed gear attach points and the existing pose composition. The Stage 1 bake
must therefore:
- rotate the arms down and match the contract wrist positions;
- warp the hips, knees, shoulders and head without collapsing the deltoid,
shortening the arms or changing the silhouette;
- keep one skinned `npc_base` mesh and the five slots;
- verify gear against the new shoulder, hip and head positions.
If those constraints distort the model, Stage 2 is the correct next decision,
not a local workaround.
## Licence and provenance
- CC0 preferred. CC-BY is allowed only with attribution in
[`CREDITS.md`](../CREDITS.md).
- No service asset whose terms restrict redistribution may be shipped.
- Keep the source URL, author, licence, download date and SHA-256 in this page
and in `CREDITS.md`.
- Research files and generated previews stay in `tmp/` (gitignored). Only final
audited assets and committed review sheets enter the repository.
- Quaternius models and animations are CC0; the existing credits entry already
names Quaternius. Keep it accurate if the shipped source set changes.
## Closed tracks
The MakeHuman/MPFB body pipeline is closed for the NPC base. The accepted
MakeHuman head and the procedural `npc_base` remain tested fallbacks until the
Quaternius rollout is complete. Their research scripts and renders were removed
from `tmp/` after the pivot; the measurements and decisions remain in
[`DONE.md`](../DONE.md), [`MEMORY.md`](../MEMORY.md) and the review docs.
## Related documents
- [ADR-0017: Quaternius NPC base](adr/0017-quaternius-npc-base.md)
- [ADR-0016: 24,000-triangle character budget](adr/0016-npc-character-budget-24k.md)
- [Blender asset contract](architecture.md#character-and-gear-asset-contract)
- [`3d-model-review.md`](3d-model-review.md), [`npc-visual-review.md`](npc-visual-review.md)
- [`asset-tooling.md`](asset-tooling.md)
-79
View File
@@ -1,79 +0,0 @@
# NPC proportion audit
Status: audited 2026-11-02. All eight library variants are inside the stylized
proportion guide; no geometry correction was required in this pass. The report
is repeatable from the exported GLBs:
```bash
python3 tools/npc_proportions.py report
python3 tools/npc_proportions.py report --json
```
## Method
`tools/npc_proportions.py` reads the GLB meshes and rest-pose joint hierarchy:
- height from the full mesh bounds;
- head height, width and aspect from `Head`/`skin` vertices (hair excluded so
long hairstyles do not change the head measurement);
- shoulder silhouette from vertices in the upper-chest band around the
shoulder joint;
- hip width from `HipL`/`HipR` pants vertices;
- torso length from the Spine joint to the shoulder joint;
- leg length from the Spine joint to the lowest vertex;
- arm length from the shoulder joint to the Hand joint;
- foot length from the forward extent of the KneeL/KneeR shoe vertices.
All ratios are divided by total standing height except `head_aspect`
(width / head height).
## Guide
These are stylized-but-credible bands for the game's low-poly adults, not
photoreal anatomy. The broad build is allowed a larger shoulder/hip silhouette
than the slim build.
| Ratio | Target | Notes |
| --- | ---: | --- |
| `head_height` | 0.16–0.22 | deliberately larger than a realistic 0.13–0.15 |
| `head_aspect` | 0.70–1.00 | width / height; rounded heads are acceptable |
| `shoulder_width` | 0.28–0.38 | silhouette includes sleeves and build scaling |
| `hip_width` | 0.17–0.23 | pants-only width, not skirt/coat flare |
| `torso_length` | 0.28–0.38 | Spine joint to shoulder |
| `leg_length` | 0.40–0.48 | Spine joint to ground |
| `arm_length` | 0.28–0.38 | shoulder to hand |
| `foot_length` | 0.12–0.18 | shoe forward extent |
## Result
| Character | Height | Head | Aspect | Shoulder | Hip | Torso | Leg | Arm | Foot | Result |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |
| `npc_casual` | 1.824 m | 0.190 | 0.907 | 0.321 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
| `npc_jacket` | 1.824 m | 0.190 | 0.907 | 0.333 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
| `npc_coat` | 1.824 m | 0.190 | 0.907 | 0.328 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
| `npc_vest` | 1.819 m | 0.191 | 0.907 | 0.322 | 0.209 | 0.308 | 0.451 | 0.330 | 0.144 | PASS |
| `npc_dress` | 1.825 m | 0.190 | 0.907 | 0.289 | 0.191 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
| `npc_hoodie` | 1.856 m | 0.187 | 0.907 | 0.328 | 0.206 | 0.302 | 0.442 | 0.323 | 0.141 | PASS |
| `npc_firefighter` | 1.819 m | 0.191 | 0.907 | 0.358 | 0.223 | 0.308 | 0.451 | 0.330 | 0.148 | PASS |
| `npc_guard` | 1.819 m | 0.191 | 0.907 | 0.369 | 0.219 | 0.308 | 0.451 | 0.330 | 0.144 | PASS |
No variant is outside a band, so the body pass does not need a global skeleton
or mesh rescale. The values closest to a boundary are:
- **head height 0.190** against the 0.22 upper bound, and **head aspect 0.907**
against 1.00 — the head is deliberately large. Any head-size change must be
coordinated with the face/headgear pass and its gear-fit measurements.
- **guard shoulder 0.369** and **firefighter hip 0.223** against the broad-build
upper bounds — these are the intended uniform/turnout silhouettes, not
outliers.
- **leg length 0.449** and **torso 0.307** sit inside the guide for all variants;
the earlier short-leg impression came from the hip joint position, not the
visible pelvis line.
## Follow-up
The proportion audit does not replace the visual criteria in
[`npc-visual-review.md`](npc-visual-review.md): neck/shoulder/hip transitions,
face depth, hand grips, clothing hems and shoes still need the appearance pass.
Re-run the audit after any body mesh or skeleton change and record the new
ratios here.
+82 -10
View File
@@ -71,9 +71,9 @@ The criteria below are for the stylized target, not photorealism. A change must
pass every applicable item before a library rebuild is accepted. pass every applicable item before a library rebuild is accepted.
1. **Asset contract:** `npc_base` mesh node, the required bones, five tint slots, 1. **Asset contract:** `npc_base` mesh node, the required bones, five tint slots,
front +Z, feet within 3 cm of y=0, 1.6–2.1 m height, at most 8,000 triangles front +Z, feet within 3 cm of y=0, 1.6–2.1 m height, at most 24,000 triangles
and 8,000 glTF vertices per library character. The frozen `npc_base` fallback and 24,000 glTF vertices per character. The required 13 core bones stay fixed;
keeps 6,000 triangles / 4,000 vertices. Gear remains at most 1,500 triangles. optional extra bones are allowed. Gear remains at most 1,500 triangles.
2. **Proportion and silhouette:** head, neck, shoulders, torso, hips, limbs and 2. **Proportion and silhouette:** head, neck, shoulders, torso, hips, limbs and
feet read as one body; front/three-quarter/profile silhouettes agree with the feet read as one body; front/three-quarter/profile silhouettes agree with the
rest of the eight-character library; broad/slim/average build variation is rest of the eight-character library; broad/slim/average build variation is
@@ -113,11 +113,83 @@ measurement.
## Proportion audit ## Proportion audit
The separate [`npc-proportion-audit.md`](npc-proportion-audit.md) measures Status: audited 2026-11-02. All eight library variants are inside the stylized
height, head size, shoulder/hip width, torso, leg, arm and foot ratios for every proportion guide; no geometry correction was required in this pass. The report
variant with `tools/npc_proportions.py`. All eight pass the stylized guide; the is repeatable from the exported GLBs:
head and broad-build shoulders sit nearest their upper bounds. No skeleton or
mesh proportion change was needed in this pass. ```bash
python3 tools/npc_proportions.py report
python3 tools/npc_proportions.py report --json
```
### Method
`tools/npc_proportions.py` reads the GLB meshes and rest-pose joint hierarchy:
- height from the full mesh bounds;
- head height, width and aspect from `Head`/`skin` vertices (hair excluded so
long hairstyles do not change the head measurement);
- shoulder silhouette from vertices in the upper-chest band around the
shoulder joint;
- hip width from `HipL`/`HipR` pants vertices;
- torso length from the Spine joint to the shoulder joint;
- leg length from the Spine joint to the lowest vertex;
- arm length from the shoulder joint to the Hand joint;
- foot length from the forward extent of the KneeL/KneeR shoe vertices.
All ratios are divided by total standing height except `head_aspect`
(width / head height).
### Guide
These are stylized-but-credible bands for the game's low-poly adults, not
photoreal anatomy. The broad build is allowed a larger shoulder/hip silhouette
than the slim build.
| Ratio | Target | Notes |
| --- | ---: | --- |
| `head_height` | 0.16–0.22 | deliberately larger than a realistic 0.13–0.15 |
| `head_aspect` | 0.70–1.00 | width / height; rounded heads are acceptable |
| `shoulder_width` | 0.28–0.38 | silhouette includes sleeves and build scaling |
| `hip_width` | 0.17–0.23 | pants-only width, not skirt/coat flare |
| `torso_length` | 0.28–0.38 | Spine joint to shoulder |
| `leg_length` | 0.40–0.48 | Spine joint to ground |
| `arm_length` | 0.28–0.38 | shoulder to hand |
| `foot_length` | 0.12–0.18 | shoe forward extent |
### Result
| Character | Height | Head | Aspect | Shoulder | Hip | Torso | Leg | Arm | Foot | Result |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |
| `npc_casual` | 1.824 m | 0.190 | 0.907 | 0.321 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
| `npc_jacket` | 1.824 m | 0.190 | 0.907 | 0.333 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
| `npc_coat` | 1.824 m | 0.190 | 0.907 | 0.328 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
| `npc_vest` | 1.819 m | 0.191 | 0.907 | 0.322 | 0.209 | 0.308 | 0.451 | 0.330 | 0.144 | PASS |
| `npc_dress` | 1.825 m | 0.190 | 0.907 | 0.289 | 0.191 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
| `npc_hoodie` | 1.856 m | 0.187 | 0.907 | 0.328 | 0.206 | 0.302 | 0.442 | 0.323 | 0.141 | PASS |
| `npc_firefighter` | 1.819 m | 0.191 | 0.907 | 0.358 | 0.223 | 0.308 | 0.451 | 0.330 | 0.148 | PASS |
| `npc_guard` | 1.819 m | 0.191 | 0.907 | 0.369 | 0.219 | 0.308 | 0.451 | 0.330 | 0.144 | PASS |
No variant is outside a band, so the body pass does not need a global skeleton
or mesh rescale. The values closest to a boundary are:
- **head height 0.190** against the 0.22 upper bound, and **head aspect 0.907**
against 1.00 — the head is deliberately large. Any head-size change must be
coordinated with the face/headgear pass and its gear-fit measurements.
- **guard shoulder 0.369** and **firefighter hip 0.223** against the broad-build
upper bounds — these are the intended uniform/turnout silhouettes, not
outliers.
- **leg length 0.449** and **torso 0.307** sit inside the guide for all variants;
the earlier short-leg impression came from the hip joint position, not the
visible pelvis line.
### Follow-up
The proportion audit does not replace the visual criteria in
[`npc-visual-review.md`](npc-visual-review.md): neck/shoulder/hip transitions,
face depth, hand grips, clothing hems and shoes still need the appearance pass.
Re-run the audit after any body mesh or skeleton change and record the new
ratios here.
## Baseline measurements ## Baseline measurements
@@ -134,8 +206,8 @@ From `python3 tools/blender_assets.py fit` and the manifest:
| `npc_firefighter` | 5,344 | 5,494 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 | | `npc_firefighter` | 5,344 | 5,494 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
| `npc_guard` | 5,046 | 5,096 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 | | `npc_guard` | 5,046 | 5,096 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
`npc_coat` is widest at 5,474 triangles (2,526 below the 8,000 cap); `npc_coat` is widest at 5,474 triangles (well below the 16,000 cap);
`npc_firefighter` is closest on vertices at 5,494 (2,506 below the 8,000 cap). `npc_firefighter` is closest on vertices at 5,494 (well below the 16,000 cap).
The refit backpack measures 380 triangles / 406 vertices. The refit backpack measures 380 triangles / 406 vertices.
## Baseline findings, ranked ## Baseline findings, ranked
@@ -1,11 +1,69 @@
# Release-candidate balance acceptance # Release QA and Balance
Manual verification and the 0.10.0 balance protocol. The executable command
matrix is in the release runbook/skill; this page owns the human smoke checks and
the measurable balance targets. Architecture owns the gates themselves; gameplay
owns the mechanics.
## Manual smoke checklist
1. `godot --path .` starts the game on the venue menu without script errors.
2. Choosing a venue (click or ↑/↓ + Enter) starts that venue's shift and hides the menu.
3. The guard moves and looks around; the building, steps, and sidewalk are visible.
4. The guard's camera sits at eye height and the guard stands on the landing, not inside the building.
5. Walking into the steps carries the guard up onto the landing (steps have colliders).
6. The skyline backdrop shows silhouettes against the sky, not an opaque black slab.
7. The `GIG LINE` marquee is readable from the sidewalk.
8. NPCs spawn and approach the entrance during the first minutes.
9. Looking at an NPC inside the focus cone shows a blue focus circle in front of the face, the person's name, and the HUD target line.
10. `Space` or left click opens the action wheel; `A`/`D` highlight a slice, `Space` confirms it, `Esc`/right click cancels without a log entry.
11. `E`/`C` open the inspection/wand panels from the wheel and freeze movement until they close; walking never fires the wand.
12. Pressing `Q` produces a reaction log entry and reputation feedback.
13. `E` opens access inspection showing venue-correct artifacts and policy result; `A`/`D` record a decision.
14. `6` opens the assistance form; submitting shows dispatch feedback.
15. `R` opens the emergency form; the button must be held ~1.6s or the call cancels.
16. `G` toggles the flashlight (indicator appears in the HUD); `L` toggles the door lock (indicator turns red when locked).
17. `F` toggles the debug overlay with NPC states and shift stats.
18. The wheel's `Look` slice is eye contact (Level 1) and logs as its own action.
19. `Reason` asks why the person is there and logs their `dialogue_lines` reply.
20. `Recognize` records the guard's recognition as access evidence; pressing it twice says so.
21. `Warn` turns the body camera on (REC overlay appears); `B` toggles it manually.
22. `Block` moves the guard between the person and the door.
23. The shift review reports the incident count, including interactions left unresolved.
24. Escalating on someone (or a responder arriving) makes idle bystanders stop and face the incident.
25. Repeated alarms panic the crowd and nearby people clear away from the door instead of standing around.
26. `Esc` with nothing open pauses the shift; resume / restart / settings / quit-to-menu all work, and settings changes persist across a restart.
27. NPCs route around the building using the navigation mesh (no stuck-at-wall behavior during a smoke run).
28. An unjustified emergency call (calm person, no weapon) logs an investigation and costs reputation.
29. After an evacuation outcome the door is locked and NPCs are leaving the entrance.
30. The shift review's "Body camera timeline" button steps through the recorded events (Left/Right or the buttons) and Esc returns to the review.
31. The escalation label shows a `[###--] L3/5` shape meter as well as the level name, and stays readable in grayscale.
32. Changing "HUD font scale" in Settings resizes the HUD text and persists across a restart.
33. When the shift timer reaches 0, the shift review screen shows grade, policy, incident summary, responder outcomes, investigations, and persisted shift history; campaign Continue starts the next venue.
34. On a fresh profile the tutorial starts on Hollywood, advances through its eight beats, and F1 skips it; Settings Replay tutorial restarts it.
35. Pausing saves a checkpoint: returning to the menu and choosing Continue resumes elapsed time, NPCs, incidents, door/flashlight state and campaign position.
36. Settings difficulty changes spawn pacing/compliance/report strictness while escalation rules stay the same; Help filters controls and shows the venue policy.
37. The F debug overlay lists the next eligible events; recovery events and recently played scripted events behave as authored.
38. Master/SFX/ambience volume settings clamp at 0/1 and persist; Reduce motion disables head bob; the captions toggle persists.
39. The 24-NPC budget smoke stays inside its frame budget; coats/dresses do not show obvious leg clipping in a normal walk.
40. Help remains readable with the largest HUD font scale, and CI/local verification use the same Godot 4.7.2 baseline.
41. UI copy: inspection/wand use "A = admit · D = refuse entry", review returns with "Return to menu", and application exit reads "Quit game".
42. Button states remain readable on hover/focus/pressed; settings Apply/Cancel and radio normal-send/emergency-hold behave as labelled.
43. The venue menu offers Hollywood Boulevard — Day and — Night; either starts the same map, and Night starts in the late phase with a full shift clock.
44. The Joke Shop opens on the Sunset Strip club: the black hall, signature canopy, circular **THE JOKE SHOP** sign, red curtains, west entrance, palms, dry hillside with houses, neighbouring frontage and the opposite side of Sunset all read from the door camera. Day lighting and late lighting both look correct; the guard can walk the sidewalk without leaving the nav bounds.
45. Esc and right-click close only the top panel (Help, Settings, Pause, Review, Radio, timeline, inspection/wand, action wheel); Settings opened from the venue menu returns to the menu, Help no longer opens Pause behind it, and closing a modal recaptures the mouse without leaving the guard frozen.
46. At 0.75× and 1.75× HUD font scale, long target names stay inside the target chip, the chip grows/caps correctly, and long button labels ellipsize instead of pushing panels off-screen.
47. Reset campaign and Replay tutorial both ask for confirmation; a second click reuses the open dialog, Cancel does nothing, and OK fires once. Emergency send requires the full hold, sends exactly once, and does not send again on release.
48. UI copy/button sweep: every panel uses the canonical admit/refuse, warning/blocking, assistance/emergency and body-camera terms; the start menu, pause, settings, Help, inspection, wand, radio, wheel, timeline and shift review all show readable hover/focus/pressed/disabled states; Tab/arrow focus wraps without landing on hidden or disabled actions; Esc/right-click closes only the top panel; and destructive reset/replay plus emergency send still require their confirmation or hold.
## Balance acceptance
These are the acceptance targets for the 0.10.0 balance pass, not measured results. These are the acceptance targets for the 0.10.0 balance pass, not measured results.
No reaction tables, event weights or scoring rules were changed to set them. The No reaction tables, event weights or scoring rules were changed to set them. The
[roadmap](../TODO.md) tracks the sweeps and external playtests still needed. [roadmap](../TODO.md) tracks the sweeps and external playtests still needed.
[Gameplay](gameplay.md) owns the mechanics; this page owns their balance-test protocol. [Gameplay](gameplay.md) owns the mechanics; this page owns their balance-test protocol.
## Test matrix and evidence ### Test matrix and evidence
Run Hollywood, Joke Shop and TV Studio on easy, normal and hard, in Early, Run Hollywood, Joke Shop and TV Studio on easy, normal and hard, in Early,
Midday, Evening and Late: **36 cells**. Use seeds 1–100 for each cell and retain Midday, Evening and Late: **36 cells**. Use seeds 1–100 for each cell and retain
@@ -37,7 +95,7 @@ cell. Use at least **100 eligible cases per metric per cell**; if the schedule
provides fewer, add a separately labelled forced-case sample. Report zero eligible provides fewer, add a separately labelled forced-case sample. Report zero eligible
cases as **N/A**, never as 100% success. Keep scheduled and forced results separate. cases as **N/A**, never as 100% success. Keep scheduled and forced results separate.
## Targets ### Targets
Percentages below apply to each matrix cell, not only a pooled average. Scripted Percentages below apply to each matrix cell, not only a pooled average. Scripted
access/report cases use complete evidence and are held to exact correctness; access/report cases use complete evidence and are held to exact correctness;
@@ -71,7 +129,7 @@ after an accepted emergency call). Separately test depleted battery and active
cooldown cases for safe containment/assistance; do not silently drop them from the cooldown cases for safe containment/assistance; do not silently drop them from the
evidence or claim the initially-available-radio target covers them. evidence or claim the initially-available-radio target covers them.
## Scoring and emergency dominance ### Scoring and emergency dominance
Compare the calm policy with an emergency-first policy on the same routine events Compare the calm policy with an emergency-first policy on the same routine events
and seeds. Each comparison starts with fresh radio, crowd and reputation state. and seeds. Each comparison starts with fresh radio, crowd and reputation state.
@@ -87,7 +145,7 @@ the incident remains answerable and the debrief explains the outcome without an
unjustified-call investigation. Also compare assistance/containment routes where unjustified-call investigation. Also compare assistance/containment routes where
appropriate; emergency need not be mandatory for every difficult interaction. appropriate; emergency need not be mandatory for every difficult interaction.
## Implementation cautions and sign-off ### Implementation cautions and sign-off
- `NPCController.roll_reaction()` samples per-level weights. Difficulty adds - `NPCController.roll_reaction()` samples per-level weights. Difficulty adds
+12/0/−8 to the `COMPLY` weight before normalization; `base_compliance` alone +12/0/−8 to the `COMPLY` weight before normalization; `base_compliance` alone
-14
View File
@@ -1,14 +0,0 @@
# Screen-design judge report
Date: 2026-11-02
Model: automatic cheapest image+JSON selection (`z-ai/glm-5.3-flash` at run time)
Command: `python3 tools/screen_designs.py judge`
Result: **21/21 match, 0 review, 0 diff**
The screenshot gallery was regenerated on the current build before judging, including the eight UI-state captures 14–21 added in `src/test/capture_ui_states.gd`.
Accepted captures are in [`docs/screens/`](screens/README.md); the committed
gallery is in [`docs/screenshots/`](screenshots/README.md).
The automatic model selection and retry/spend behavior is documented in
[`docs/asset-tooling.md`](asset-tooling.md). This report intentionally records
the outcome rather than generated captures, which remain local/gitignored.
+2 -2
View File
@@ -15,13 +15,13 @@ The map-optimization references for the El Capitan Entertainment Centre block
(6840 Hollywood Blvd) are in (6840 Hollywood Blvd) are in
[`hollywood_boulevard/README.md`](hollywood_boulevard/README.md). The real-site [`hollywood_boulevard/README.md`](hollywood_boulevard/README.md). The real-site
facts and game mapping are documented in facts and game mapping are documented in
[`../../hollywood-boulevard-map.md`](../../hollywood-boulevard-map.md). [`../../venues.md`](../../venues.md#hollywood-boulevard).
## The Joke Shop — Sunset Strip ## The Joke Shop — Sunset Strip
The current Joke Shop map is based on The Comedy Store at 8433 Sunset Blvd. The current Joke Shop map is based on The Comedy Store at 8433 Sunset Blvd.
The real-site mapping and simplifications are in The real-site mapping and simplifications are in
[`../../joke-shop-map.md`](../../joke-shop-map.md); the frames are in [`../../venues.md`](../../venues.md#the-joke-shop); the frames are in
[`joke_shop/README.md`](joke_shop/README.md). [`joke_shop/README.md`](joke_shop/README.md).
![Joke Shop entrance in daylight](joke_shop/joke_shop_entrance_day.png) ![Joke Shop entrance in daylight](joke_shop/joke_shop_entrance_day.png)
@@ -4,7 +4,7 @@ AI-generated art-direction frames for the real El Capitan Entertainment Centre
block at 6840 Hollywood Boulevard. They are generated with block at 6840 Hollywood Boulevard. They are generated with
`tools/scene_concepts.py generate --group scene --only ...` and are not shipped `tools/scene_concepts.py generate --group scene --only ...` and are not shipped
game textures. The geometry mapping and source links are in game textures. The geometry mapping and source links are in
[`../../../hollywood-boulevard-map.md`](../../../hollywood-boulevard-map.md). [`../../../venues.md`](../../../venues.md#hollywood-boulevard).
## Entrance — day ## Entrance — day
+1 -1
View File
@@ -4,7 +4,7 @@ AI-generated art-direction frames for the fictional Joke Shop, based on the
real Comedy Store block at 8433 Sunset Boulevard. They are generated with real Comedy Store block at 8433 Sunset Boulevard. They are generated with
`tools/scene_concepts.py generate --group scene --only ...` and are not shipped `tools/scene_concepts.py generate --group scene --only ...` and are not shipped
game textures. The geometry mapping and source links are in game textures. The geometry mapping and source links are in
[`../../../joke-shop-map.md`](../../../joke-shop-map.md). [`../../../venues.md`](../../../venues.md#the-joke-shop).
## Entrance — day, photo-anchored ## Entrance — day, photo-anchored
+68
View File
@@ -0,0 +1,68 @@
# Head rework — review page (2026-11-04)
> **Historical page / changed source (2026-11-04).** The numbers below describe the
> earlier decimated MakeHuman head (1,548-tri skin). The shared source
> `assets/models/blender_src/makehuman_head.glb` now holds the higher-resolution
> standalone prototype. The owner accepted the basic head; the automated vision gate
> still records REJECT on the eye/lid region as deferred polish. See
> [`../../makehuman-head-review.md`](../../makehuman-head-review.md). Re-run the
> library head build before relying on the comparison counts below.
This page is the review checkpoint for the NPC head rework. The goal was to replace the
sphere-and-floating-primitive face with a head that has real nose / chin / jaw landmarks at
portrait distance.
## What changed
The original procedural face is a head sphere plus separate nose, mouth, ear and eye primitives.
The new head uses the **decimated MakeHuman head** (CC0, 1,573 tris) as the base shape and keeps
the project's separate eyeballs, brow and mouth pieces on top. An AI judge was used to drive the
decision: it scored the procedural rework at ~3/5 and then picked the MakeHuman head as the
clearer silhouette.
| | Source | Tris (head) | Read |
| --- | --- | --- | --- |
| **Before** | procedural `_face` (sphere + primitives) | ~1,900 | flat face, slit eyes |
| **After** | overhauled MakeHuman head + project eyes/brows/mouth | ~1,880 | smooth nose/chin/jaw landmarks |
The head source was overhauled 2026-11-04: the blocky decimated MakeHuman head was welded
(merge-by-distance) and Laplacian-relaxed into one clean manifold shell (`makehuman_head.glb`,
1,548 tris skin), then the project eyes/brows/mouth were added.
## AI-generated target concept
The rework was guided by two generated concept frames (front + profile, `bytedance-seed/seedream-4.5`).
![concept](sheet_concept.jpg)
## 1.85 m comparison (top = current, bottom = new)
Left to right: front, three-quarter, profile.
![1.85 m comparison](sheet_compare_1.85.jpg)
## New head at 1.5 m (portrait)
![1.5 m portrait](sheet_head_1.5.jpg)
## Contract and checks
- `blender_assets.py inspect` on the assembled `npc_casual` with the new head: **14 bones,
skinned, no issues**.
- Source: `assets/models/blender_src/makehuman_head.glb` (CC0, attributed in `CREDITS.md`).
- Python 167/167, Godot 277/277, `check_docs` OK.
## Remaining polish (known, not blockers)
1. The hair cap still reads as a stepped block instead of hugging the skull.
2. The brows weld to the face but can read as a slightly separate bar.
3. The neck underside cap is plain; it is hidden by the body neck in the assembled character.
4. The head skin is 1.5k tris (smooth but low-poly); a retopology pass could add resolution if a
portrait shows faceting.
## Decision (2026-11-04, owner)
The MakeHuman-head direction is approved for the basic human head. The remaining review is the
assembled prototype's hair cap and full-body silhouette. After that, roll the head into the other
11 variants (one asset family at a time) with a before/after sheet per batch, then re-run the
24-NPC budget + AO bake.
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

+66
View File
@@ -0,0 +1,66 @@
# NPC face and silhouette gate — Quaternius pilot (2026-11-05)
This page is the owner decision point for
[ADR-0017](../../adr/0017-quaternius-npc-base.md) + [ADR-0018](../../adr/0018-quaternius-stage2-runtime-rig.md)
stage 2. It answers one question: **which head ships?**
`npc_casual` is now the CC0 Quaternius `Casual_Hoodie` base baked to the project contract
(arm-down rest, five tint slots, 62-joint rig at source proportions). The procedural library with
the MakeHuman head is unchanged and still ships for the other eleven characters.
## The sheet
![face gate sheet](sheet_face_gate.jpg)
Rows: the Quaternius base, the procedural library with the MakeHuman head, and a procedural crowd
variant. Columns: front, three-quarter, profile, and the crowd-distance silhouette the player
actually sees from 8 m.
Source frames: `godot --path . --script res://src/test/capture_npc_baseline.gd -- --out=res://tmp/npc-gate`.
## What the sheet shows
| | Head height / body | Face read at portrait | Crowd-distance read |
| --- | --- | --- | --- |
| Quaternius base | 0.134 | clean low-poly brows, nose, jaw, layered hair | slim, upright, clearly a person |
| procedural + MakeHuman head | ~0.20 | oversized head, soft nose, beard slab reads as a bar | bobblehead proportions |
| procedural crowd variant | ~0.20 | oversized head, flat cheeks | bobblehead proportions |
Measured (`python3 -m pytest tools/tests/test_npc_proportions.py -q`,
`python3 tools/npc_proportions.py`):
- `npc_casual` (Quaternius): head 13.4 % of height, arm-down rest, 6,206 tris, 12,988 glTF
vertices, texel density 73–74 px/m on the 256² maps.
- The procedural library: head 16–22 % of height (the stylized band in
`tools/npc_proportions.GUIDE`).
The Quaternius head is *smaller and more realistic* than the stylized library head, which is
exactly the trade the owner has to accept or reject. The vision judge that reviewed the earlier
head rework reads the Quaternius face as the cleaner silhouette of the three at both distances.
## The three options
1. **Full swap.** Every character rebaked onto the Quaternius base in `0.9.10`; the procedural
library and the MakeHuman head retire to the tested fallback. Consistent look, real anatomy,
no further head work.
2. **Crowd only.** Quaternius for the anonymous crowd roles, procedural + MakeHuman for the named
roles the player talks to. Keeps two art styles in one frame.
3. **Add head detail.** Keep the Quaternius body and author a higher-detail head (subdivision and
hair cards) inside the 24,000-triangle / 24,000-vertex budget. Most work; the current head
already reads well at gameplay distance.
## Recommendation
Option 1. The Quaternius head beats the MakeHuman head at portrait distance in this sheet, and it
costs no further authoring; the budget headroom (6.2k of 24k triangles) stays available for the
hands and gear that the review still flags as weak.
## Recorded decision
Status: **option 1 (full swap) accepted by the owner, 2026-11-05.** Every roster character moves
onto the Quaternius base; the procedural library and the MakeHuman head become the tested
fallback. See the follow-up decision section in
[ADR-0018](../../adr/0018-quaternius-stage2-runtime-rig.md#follow-up-decision-full-roster-swap).
Roster silhouettes are less varied than the procedural library until the modular part library
(hair, outfits) is added in the `0.9.10` roster workstream — recorded rather than hidden.
Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

+2 -2
View File
@@ -76,8 +76,8 @@ committed here is the redacted copy, not the original.
## How the folder is used ## How the folder is used
- Build the venue **three-up boards** (photo | concept | current capture) for the review verdicts. - Build the venue **three-up boards** (photo | concept | current capture) for the review verdicts.
- Link the board from the venue map document (`docs/hollywood-boulevard-map.md`, - Link the board from the venue map document (`docs/venues.md#hollywood-boulevard`,
`docs/joke-shop-map.md`) and from the local-map block notes. `docs/venues.md#the-joke-shop`) and from the local-map block notes.
- Cite the sidecar when a texture, proportion or signage decision is derived from a photo. - Cite the sidecar when a texture, proportion or signage decision is derived from a photo.
- Refresh the board when a venue change makes an old reference misleading, and note the change in - Refresh the board when a venue change makes an old reference misleading, and note the change in
the sidecar. the sidecar.
+1 -1
View File
@@ -34,7 +34,7 @@ attribution string for CC BY / CC BY-SA in the `Author` or a note below.
| Decision | Evidence | Where it landed | | Decision | Evidence | Where it landed |
| --- | --- | --- | | --- | --- | --- |
| e.g. six-column spacing kept at 2.4 m | photo 1, measured against door width | `docs/hollywood-boulevard-map.md` | | e.g. six-column spacing kept at 2.4 m | photo 1, measured against door width | `docs/venues.md#hollywood-boulevard` |
| e.g. banner red sampled to `#8a1f24` | photo 1 + photo 2 | `docs/screens/refs/hollywood/` notes, facade kit | | e.g. banner red sampled to `#8a1f24` | photo 1 + photo 2 | `docs/screens/refs/hollywood/` notes, facade kit |
## Generated boards used with this set ## Generated boards used with this set
+2 -2
View File
@@ -51,10 +51,10 @@ before use.
| Decision | Evidence | Where it landed | | Decision | Evidence | Where it landed |
| --- | --- | --- | | --- | --- | --- |
| Six Ionic columns, ~2.4 m spacing, 8 steps | facade photo measured against the door width | `docs/hollywood-boulevard-map.md` geometry notes | | Six Ionic columns, ~2.4 m spacing, 8 steps | facade photo measured against the door width | `docs/venues.md#hollywood-boulevard` geometry notes |
| Masonic parapet motto and medallions | 1922 elevation + facade photo | `WorldBuilder` facade, `docs/screenshots/01-venue-overview.png` | | Masonic parapet motto and medallions | 1922 elevation + facade photo | `WorldBuilder` facade, `docs/screenshots/01-venue-overview.png` |
| Three vertical event banners | facade photo | facade banners and the day/night capture set | | Three vertical event banners | facade photo | facade banners and the day/night capture set |
| Red carpet + barrier staging narrows the sidewalk | premiere photo | `docs/hollywood-boulevard-map.md` event staging notes | | Red carpet + barrier staging narrows the sidewalk | premiere photo | `docs/venues.md#hollywood-boulevard` event staging notes |
| Across-street height and plaza opening | Ovation photo | opposite frontage massing | | Across-street height and plaza opening | Ovation photo | opposite frontage massing |
| TV Studio arrival staging (bulb canopy, car, barriers) | premiere staging photo | `VENUE_SPECS["tv_studio"]` and the Late Night TV Studio venue docs | | TV Studio arrival staging (bulb canopy, car, barriers) | premiere staging photo | `VENUE_SPECS["tv_studio"]` and the Late Night TV Studio venue docs |
| Terrazzo band width, star size/spacing | terrazzo + star close-up photos | `WalkOfFame` band geometry and the paver material | | Terrazzo band width, star size/spacing | terrazzo + star close-up photos | `WalkOfFame` band geometry and the paver material |
+1 -1
View File
@@ -29,6 +29,6 @@ README, the gameplay docs and the screenshot gate; do not edit them by hand.
| 21 | [Responder arrival](21-responder-arrival.png) | A responder at the locked door | | 21 | [Responder arrival](21-responder-arrival.png) | A responder at the locked door |
The captures are 1280×720 PNGs. Reference designs and the judge report live in The captures are 1280×720 PNGs. Reference designs and the judge report live in
[`../screens/`](../screens/README.md) and [`../screen-design-report.md`](../screen-design-report.md); [`../screens/`](../screens/README.md) and [`../ui-copy.md#screen-design-judge-report`](../ui-copy.md#screen-design-judge-report);
the re-evaluation's pose and review sheets are under the re-evaluation's pose and review sheets are under
[`../blender_previews/`](../blender_previews/README.md). [`../blender_previews/`](../blender_previews/README.md).
-50
View File
@@ -1,50 +0,0 @@
# Smoke Test Checklist
1. `godot --path .` starts the game on the venue menu without script errors.
2. Choosing a venue (click or ↑/↓ + Enter) starts that venue's shift and hides the menu.
3. The guard moves and looks around; the building, steps, and sidewalk are visible.
4. The guard's camera sits at eye height and the guard stands on the landing, not inside the building.
5. Walking into the steps carries the guard up onto the landing (steps have colliders).
6. The skyline backdrop shows silhouettes against the sky, not an opaque black slab.
7. The `GIG LINE` marquee is readable from the sidewalk.
8. NPCs spawn and approach the entrance during the first minutes.
9. Looking at an NPC inside the focus cone shows a blue focus circle in front of the face, the person's name, and the HUD target line.
10. `Space` or left click opens the action wheel; `A`/`D` highlight a slice, `Space` confirms it, `Esc`/right click cancels without a log entry.
11. `E`/`C` open the inspection/wand panels from the wheel and freeze movement until they close; walking never fires the wand.
12. Pressing `Q` produces a reaction log entry and reputation feedback.
13. `E` opens access inspection showing venue-correct artifacts and policy result; `A`/`D` record a decision.
14. `6` opens the assistance form; submitting shows dispatch feedback.
15. `R` opens the emergency form; the button must be held ~1.6s or the call cancels.
16. `G` toggles the flashlight (indicator appears in the HUD); `L` toggles the door lock (indicator turns red when locked).
17. `F` toggles the debug overlay with NPC states and shift stats.
18. The wheel's `Look` slice is eye contact (Level 1) and logs as its own action.
19. `Reason` asks why the person is there and logs their `dialogue_lines` reply.
20. `Recognize` records the guard's recognition as access evidence; pressing it twice says so.
21. `Warn` turns the body camera on (REC overlay appears); `B` toggles it manually.
22. `Block` moves the guard between the person and the door.
23. The shift review reports the incident count, including interactions left unresolved.
24. Escalating on someone (or a responder arriving) makes idle bystanders stop and face the incident.
25. Repeated alarms panic the crowd and nearby people clear away from the door instead of standing around.
26. `Esc` with nothing open pauses the shift; resume / restart / settings / quit-to-menu all work, and settings changes persist across a restart.
27. NPCs route around the building using the navigation mesh (no stuck-at-wall behavior during a smoke run).
28. An unjustified emergency call (calm person, no weapon) logs an investigation and costs reputation.
29. After an evacuation outcome the door is locked and NPCs are leaving the entrance.
30. The shift review's "Body camera timeline" button steps through the recorded events (Left/Right or the buttons) and Esc returns to the review.
31. The escalation label shows a `[###--] L3/5` shape meter as well as the level name, and stays readable in grayscale.
32. Changing "HUD font scale" in Settings resizes the HUD text and persists across a restart.
33. When the shift timer reaches 0, the shift review screen shows grade, policy, incident summary, responder outcomes, investigations, and persisted shift history; campaign Continue starts the next venue.
34. On a fresh profile the tutorial starts on Hollywood, advances through its eight beats, and F1 skips it; Settings Replay tutorial restarts it.
35. Pausing saves a checkpoint: returning to the menu and choosing Continue resumes elapsed time, NPCs, incidents, door/flashlight state and campaign position.
36. Settings difficulty changes spawn pacing/compliance/report strictness while escalation rules stay the same; Help filters controls and shows the venue policy.
37. The F debug overlay lists the next eligible events; recovery events and recently played scripted events behave as authored.
38. Master/SFX/ambience volume settings clamp at 0/1 and persist; Reduce motion disables head bob; the captions toggle persists.
39. The 24-NPC budget smoke stays inside its frame budget; coats/dresses do not show obvious leg clipping in a normal walk.
40. Help remains readable with the largest HUD font scale, and CI/local verification use the same Godot 4.7.2 baseline.
41. UI copy: inspection/wand use "A = admit · D = refuse entry", review returns with "Return to menu", and application exit reads "Quit game".
42. Button states remain readable on hover/focus/pressed; settings Apply/Cancel and radio normal-send/emergency-hold behave as labelled.
43. The venue menu offers Hollywood Boulevard — Day and — Night; either starts the same map, and Night starts in the late phase with a full shift clock.
44. The Joke Shop opens on the Sunset Strip club: the black hall, signature canopy, circular **THE JOKE SHOP** sign, red curtains, west entrance, palms, dry hillside with houses, neighbouring frontage and the opposite side of Sunset all read from the door camera. Day lighting and late lighting both look correct; the guard can walk the sidewalk without leaving the nav bounds.
45. Esc and right-click close only the top panel (Help, Settings, Pause, Review, Radio, timeline, inspection/wand, action wheel); Settings opened from the venue menu returns to the menu, Help no longer opens Pause behind it, and closing a modal recaptures the mouse without leaving the guard frozen.
46. At 0.75× and 1.75× HUD font scale, long target names stay inside the target chip, the chip grows/caps correctly, and long button labels ellipsize instead of pushing panels off-screen.
47. Reset campaign and Replay tutorial both ask for confirmation; a second click reuses the open dialog, Cancel does nothing, and OK fires once. Emergency send requires the full hold, sends exactly once, and does not send again on release.
48. UI copy/button sweep: every panel uses the canonical admit/refuse, warning/blocking, assistance/emergency and body-camera terms; the start menu, pause, settings, Help, inspection, wand, radio, wheel, timeline and shift review all show readable hover/focus/pressed/disabled states; Tab/arrow focus wraps without landing on hidden or disabled actions; Esc/right-click closes only the top panel; and destructive reset/replay plus emergency send still require their confirmation or hold.
-47
View File
@@ -1,47 +0,0 @@
# First-shift tutorial
Status: implemented for v0.8.0. The tutorial teaches the real controls and the real
inspection/wheel/radio panels; it is not a fake UI or a separate combat mode.
Scripted test/capture runs bypass it unless `--tutorial` is passed; a windowed
smoke covers completion, replay and skip.
## Rules
- One short, safe, compliant visitor. No emergency, threat, crowd panic or random
event can interrupt the tutorial.
- Prompts advance on the performed action, not on a timer. The player can keep
moving/looking while a prompt is visible.
- The tutorial is skippable at any point (F1). Completion and skip are both
persisted separately; a skipped tutorial can be replayed from Settings.
- The tutorial state machine is independent of the shift FSM. It observes the
same actions as normal play and must be deterministic for tests.
## Beat sheet
| # | Beat | Prompt | Advances when | Notes |
| --- | --- | --- | --- | --- |
| 1 | Arrive | "Walk forward and look around. The entrance is behind you." | The guard has moved at least 1 m and looked away/returned | Start at the normal guard post, facing the street. |
| 2 | Focus | "Look at the visitor. Press Tab or the mouse wheel to cycle targets." | A target is acquired and explicitly cycled | The visitor waits at the foot of the steps; no other guest is required. |
| 3 | Wheel | "Open the action wheel with Space or left click." | The radial wheel opens on the visitor | The wheel is the real de-escalation-first wheel. |
| 4 | Inspect | "Choose Inspect and read the access evidence." | The inspection panel is opened and the policy result is visible | Use a valid wristband so the tutorial can teach both evidence and a safe admit/deny decision. |
| 5 | Direction | "Close the panel and give a polite direction." | A Level 1 polite-direction action is applied | The visitor complies; there is no escalation requirement. |
| 6 | Warning | "Give a formal warning if the visitor persists. It arms the body camera." | A Level 2 warning is applied or the body camera is switched on | Show the REC overlay and the escalation cue. |
| 7 | Radio | "Open the assistance radio and submit a short report." | The assistance report is submitted successfully | A non-emergency assistance call; no responder is required to resolve the tutorial. |
| 8 | Review | "Finish the shift and review what happened." | The shift review opens | The tutorial ends; the real review remains available for inspection. |
## Visitor script
- Display name: `Visitor`.
- Intent: `enter` (approaches the door, then waits for interaction).
- Access: valid wristband evidence; the policy result should be unambiguous.
- Behavior: compliant at every escalation level; never becomes a threat, never
joins a crowd and never triggers an emergency.
- Failure handling: if the player denies admission incorrectly, the tutorial
explains the evidence but does not fail the run; the visitor remains safe and
the review records the decision.
## Persistence and replay
Settings gain `tutorial_completed` and `tutorial_skipped`. A first launch starts
the tutorial after venue selection. Skipping sets `tutorial_skipped`; Settings/
Help offers "Replay tutorial", which clears both flags for the next shift.
+15
View File
@@ -227,3 +227,18 @@ The gallery is regenerated by `tools/check_screenshots.sh`.
- Focus/hover/pressed/disabled states and keyboard/gamepad rings are recorded above. - Focus/hover/pressed/disabled states and keyboard/gamepad rings are recorded above.
- Pseudo-locale layout and a translator workflow remain scheduled for the - Pseudo-locale layout and a translator workflow remain scheduled for the
localization milestone, not this UI pass. localization milestone, not this UI pass.
## Screen-design judge report
Date: 2026-11-02
Model: automatic cheapest image+JSON selection (`z-ai/glm-5.3-flash` at run time)
Command: `python3 tools/screen_designs.py judge`
Result: **21/21 match, 0 review, 0 diff**
The screenshot gallery was regenerated on the current build before judging, including the eight UI-state captures 14–21 added in `src/test/capture_ui_states.gd`.
Accepted captures are in [`docs/screens/`](screens/README.md); the committed
gallery is in [`docs/screenshots/`](screenshots/README.md).
The automatic model selection and retry/spend behavior is documented in
[`docs/asset-tooling.md`](asset-tooling.md). This report intentionally records
the outcome rather than generated captures, which remain local/gitignored.
+148
View File
@@ -0,0 +1,148 @@
# Venues
The game uses two physical Hollywood blocks across three playable venue rows:
- **Hollywood Boulevard** — the 6840 Hollywood Blvd / El Capitan Entertainment
Centre block, in day and night rows.
- **The Joke Shop** — a fictional comedy club based on the Sunset Strip block
around 8433 Sunset Blvd.
- **Late Night TV Studio** — the same El Capitan block restaged as a studio
arrival.
The runtime implementation contract (facade specs, map budgets, navigation,
colliders, phase behavior and fallback) lives in
[`architecture.md`](architecture.md#venue-implementation-contract). This page
keeps the real-site evidence, mapping decisions and deliberate simplifications.
## Hollywood Boulevard
### Real site
- **Address:** 6840 Hollywood Boulevard, Hollywood, Los Angeles, CA 90028.
- **Coordinates:** 34.10118 N, 118.34009 W; OpenStreetMap way `424024147`.
- **Building:** the 1921 Hollywood Masonic Temple by John C. Austin, now the El
Capitan Entertainment Centre and the home of *Jimmy Kimmel Live!*. It is the
game's starting point for both the Hollywood shift and the TV Studio arrival.
- **Facade:** light stone/render Neoclassical front, six oversized Ionic
columns, a recessed entrance up eight stone steps, ornamental door grilles,
a crosscut transom, two side entrances, and the Masonic parapet motto and
medallions.
- **Neighbours:** the El Capitan Theatre immediately west, a modern retail block
east, and the Ovation Hollywood frontage across the four-lane boulevard.
- **Sidewalk:** the Walk of Fame terrazzo strip with brass stars; red-painted
kerb in the reference photographs.
### Game mapping
| Real feature | In-game representation |
| --- | --- |
| 29.6 m wide, 14.5 m high facade | `VENUE_SPECS["hollywood_building"]`, front at `z = -4` |
| Eight stone steps | 8 steps, 0.10 m rise, 0.38 m tread, 12 m wide |
| Six Ionic columns | `IonicColonnade`, 4.96 m bay spacing |
| Central + two side doors | 4.2 m central door, 2.5 m side doors with transoms |
| Red event banners | three stylized `EL / CAPITAN / LIVE` banners |
| Masonic motto + medallions | `FriezeInscription` and two stone discs |
| El Capitan Theatre / east retail | library-neighbour blocks with marquee/blade or glazed shopfront |
| Ovation Hollywood | arcade with seven arched openings, sign and south sidewalk |
| Walk of Fame | dark terrazzo band with ten stars |
| Four-lane boulevard | double-yellow centre, broken white lanes, red kerb, crosswalk |
| TV Studio arrival | same six-column facade/eight steps plus bulb canopy, sedan, handrail and barriers |
The generated references in
[`screens/concepts/hollywood_boulevard/`](screens/concepts/hollywood_boulevard/README.md)
are art direction for lighting, material scale and composition; they are not
texture or geometry sources.
### Late Night TV Studio
The third venue is **not a separate real site**. It reuses the El Capitan block
and restages it as a studio arrival: the same stone facade and steps, plus a
bulb-lit canopy, glossy black car, barrier/stanchion line and audience queue.
`VENUE_SPECS["tv_studio"]` is a 30.0 m variant of the Hollywood spec with the
arrival staging; it shares this block's reference board.
## The Joke Shop
### Real site
| Item | Reference |
| --- | --- |
| Address | 8433 Sunset Blvd, West Hollywood, CA 90069 |
| Real venue | The Comedy Store (comedy club and theatre) |
| OpenStreetMap | Way [`428854155`](https://www.openstreetmap.org/way/428854155), centroid 34.095265, -118.373965 |
| Tags | `amenity=theatre`, `building=commercial`, `height=12.2`, `start_date=1940`, `wikidata=Q3520350` |
| Footprint | OSM polygon about 47.1 m × 41.2 m across the club and rear parcels |
| Street | Sunset Boulevard on the Sunset Strip |
| Setting | Dry Hollywood Hills with houses and palms above the roofline |
The building began as **Ciro's** (1940) and became The Comedy Store in the
early 1970s. Its street front is a low black hall under a deep,
signature-covered canopy, with a circular blade sign above the roof, red
curtains along the frontage and the main entrance set toward the west end.
### Game mapping
| Real element | Game implementation |
| --- | --- |
| Low black comedy hall | 27 m × 12 m footprint, 6.0 m street wall, matte black concrete, low plinth and parapet |
| Deep overhanging canopy | 4.9 m sloped slab at 11°, white signature fascia band and club name |
| Signature-covered front | two `Label3D` signature bands plus a wall panel |
| Circular blade sign | 3.9 m white disc on a roof pole, black `THE JOKE SHOP` wordmark |
| Red curtains | seven tied-back velvet drapes with scalloped hems and dark recesses |
| West-end entrance | recessed at x = -8.8 m, low step, brass pulls, door-lock indicator |
| Practicals | sconces, canopy downlights, entrance wash, ground uplights; off by day, switched at night |
| Neighbours | cream Mediterranean block with red awning; `WestHotelTower` mass behind; white commercial block east |
| Hillside | fog-free unshaded ramp with stucco houses, shrubs and palms; dark silhouette at night |
| Opposite Sunset | low retail row, rooftop billboard, neon and palms beyond the far kerb |
| Street dressing | palms, barriers, stanchions, bins, planters, A-frame, hydrant and parked sedan |
Key proportions are reconciled in the map budget and `VENUE_SPECS`; the full
measured table is retained in the 3D review history. The playable frontage is
27 m, the facade centre is at z = -8.0 and the front face at z = -2.0. The
sidewalk is 38 m × 14 m, meeting the kerb at z = 11.9; the street is 90 m × 24 m.
The guard starts west of the entrance at (-4.8, 0.1, 1.4), facing the approach.
## Deliberate simplifications
- Both blocks are stylized reconstructions, not survey models; show branding is
replaced by venue-neutral/fictional text while addresses and landmark
silhouettes are retained.
- Interiors, basements, side streets, the Metro entrance and the Comedy Store's
rear rooms are out of playable bounds.
- The Ovation frontage is one arcade block, not the full mall plan; neighbouring
businesses are generic low-rise masses and no current tenant is claimed.
- The Joke Shop's rounded west corner is kept as a hard 90° corner; its hillside
is a background ramp with simplified houses, not a terrain heightfield.
- Geometry uses boxes, cylinders and a small set of custom arch/star meshes to
stay inside the Compatibility/Web budget; concept frames are photoreal and the
runtime keeps a stylized material treatment.
- Day/night is a phase offset on one geometry/scenario, not a second baked map.
The night row starts in `LATE` while the checkpoint stores the offset.
## References
- [Hollywood Masonic Temple — Wikipedia](https://en.wikipedia.org/wiki/Hollywood_Masonic_Temple)
- [El Capitan Entertainment Centre — OpenStreetMap way 424024147](https://www.openstreetmap.org/way/424024147)
- [El Capitan Theatre — OpenStreetMap way 424024148](https://www.openstreetmap.org/way/424024148)
- [Ovation Hollywood — OpenStreetMap way 151485232](https://www.openstreetmap.org/way/151485232)
- [Hollywood Masonic Temple facade — Wikimedia Commons](https://commons.wikimedia.org/wiki/File:El_Capitan_Theater_frontal_fachade-The_Masonic_Temple.jpg)
- [Hollywood Masonic Temple and El Capitan Theatre, 2013 — Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Hollywood_Masonic_Temple_2013-10-13_10-02-25.jpg)
- [Comedy Store — OpenStreetMap way 428854155](https://www.openstreetmap.org/way/428854155)
- Committed photo boards: [`screens/refs/hollywood/`](screens/refs/hollywood/README.md),
[`screens/refs/joke-shop/`](screens/refs/joke-shop/README.md); three-up boards
are in [`screens/boards/`](screens/boards/README.md).
- Local-only research (never committed): `tmp/research/elcapitan-2026/`,
`tmp/research/jimmy-kimmel-entrance/`, `tmp/research/comedy-store/` and
`tmp/research/comedy-store-hillside/`. These are evidence for decisions, not
shipped assets.
## Verification
```bash
godot --headless --path . --script res://src/test/test_runner.gd
godot --path . --script res://src/test/capture_realism.gd -- --out=/tmp/gig-line-venues --stages=day,evening,late,joke_shop_day,joke_shop
python3 tools/check_docs.py
```
The map-budget tests and the venue light-falloff gate are part of the headless
suite; the screenshot/realism gates are optional review evidence.
+4 -4
View File
@@ -8,7 +8,7 @@ dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="" exclude_filter="tmp/*, tmp/**, build/*, build/**, docs/*, docs/**, tools/*, tools/**, src/test/*, src/test/**, .agents/*, .agents/**, packaging/*, packaging/**"
export_path="build/gig-line-linux.x86_64" export_path="build/gig-line-linux.x86_64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
@@ -39,7 +39,7 @@ dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="" exclude_filter="tmp/*, tmp/**, build/*, build/**, docs/*, docs/**, tools/*, tools/**, src/test/*, src/test/**, .agents/*, .agents/**, packaging/*, packaging/**"
export_path="build/gig-line-windows.exe" export_path="build/gig-line-windows.exe"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
@@ -75,7 +75,7 @@ dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="" exclude_filter="tmp/*, tmp/**, build/*, build/**, docs/*, docs/**, tools/*, tools/**, src/test/*, src/test/**, .agents/*, .agents/**, packaging/*, packaging/**"
export_path="build/gig-line-macos.zip" export_path="build/gig-line-macos.zip"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
@@ -114,7 +114,7 @@ dedicated_server=false
custom_features="" custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="" include_filter=""
exclude_filter="" exclude_filter="tmp/*, tmp/**, build/*, build/**, docs/*, docs/**, tools/*, tools/**, src/test/*, src/test/**, .agents/*, .agents/**, packaging/*, packaging/**"
export_path="build/web/index.html" export_path="build/web/index.html"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
+1 -1
View File
@@ -12,7 +12,7 @@ config_version=5
config/name="Gig Line" config/name="Gig Line"
config/description="Workplace security simulation about observation, access verification, and proportionate escalation." config/description="Workplace security simulation about observation, access verification, and proportionate escalation."
config/version="0.9.7" config/version="0.9.9"
run/main_scene="res://scenes/main.tscn" run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.7", "GL Compatibility") config/features=PackedStringArray("4.7", "GL Compatibility")
+83 -27
View File
@@ -413,38 +413,45 @@ func _build_skinned_body(npc_type: int) -> bool:
# Animation proxies: the existing _animate()/idle code drives these Node3D # Animation proxies: the existing _animate()/idle code drives these Node3D
# pivots; _sync_skeleton() copies their rotations onto the armature bones. # pivots; _sync_skeleton() copies their rotations onto the armature bones.
_bind_bones()
_head = Node3D.new() _head = Node3D.new()
_head.name = "Head" _head.name = "Head"
_head.position = Vector3(0, 1.62, 0) _head.position = _rest_origin(["Head"])
_rig.add_child(_head) _rig.add_child(_head)
for side in [-1.0, 1.0]: for i in 2:
var suffix := "L" if side < 0.0 else "R" var suffix := "L" if i == 0 else "R"
# The proxy pivots sit on the resolved rig bones (see RIG_BONES), so the
# proxy chirality and the exported joint naming never have to agree.
var hip_bone := bone_name("hip", i)
var knee_bone := bone_name("knee", i)
var shoulder_bone := bone_name("shoulder", i)
var elbow_bone := bone_name("elbow", i)
var hand_bone := bone_name("hand", i)
var hip := Node3D.new() var hip := Node3D.new()
hip.name = "Hip" + suffix hip.name = "Hip" + suffix
hip.position = Vector3(0.13 * side, 0.64, 0) hip.position = _rest_origin([hip_bone])
_rig.add_child(hip) _rig.add_child(hip)
var knee := Node3D.new() var knee := Node3D.new()
knee.name = "Knee" knee.name = "Knee"
knee.position = Vector3(0, -0.34, 0) knee.position = _rest_origin([knee_bone]) - hip.position
hip.add_child(knee) hip.add_child(knee)
_legs.append(hip) _legs.append(hip)
_knees.append(knee) _knees.append(knee)
var shoulder := Node3D.new() var shoulder := Node3D.new()
shoulder.name = "Shoulder" + suffix shoulder.name = "Shoulder" + suffix
shoulder.position = Vector3(0.27 * side, 1.38, 0) shoulder.position = _rest_origin([shoulder_bone])
_rig.add_child(shoulder) _rig.add_child(shoulder)
var elbow := Node3D.new() var elbow := Node3D.new()
elbow.name = "Elbow" elbow.name = "Elbow"
elbow.position = Vector3(0, -0.30, 0) elbow.position = _rest_origin([elbow_bone]) - shoulder.position
shoulder.add_child(elbow) shoulder.add_child(elbow)
var hand := Node3D.new() var hand := Node3D.new()
hand.name = "Hand" hand.name = "Hand"
hand.position = Vector3(0, -0.30, 0.02) hand.position = _rest_origin([hand_bone]) - _rest_origin([elbow_bone])
elbow.add_child(hand) elbow.add_child(hand)
_arms.append(shoulder) _arms.append(shoulder)
_elbows.append(elbow) _elbows.append(elbow)
_hands.append(hand) _hands.append(hand)
_bind_bones()
_add_accessory(str(outfit["accessory"]), cloth) _add_accessory(str(outfit["accessory"]), cloth)
_add_character_gear() _add_character_gear()
@@ -495,35 +502,84 @@ func _bind_bones() -> void:
for index in _skeleton.get_bone_count(): for index in _skeleton.get_bone_count():
_bone_indices[_skeleton.get_bone_name(index)] = index _bone_indices[_skeleton.get_bone_name(index)] = index
## First-found rest origin for a list of candidate bone names, in the skeleton's
## space (global rest), so proxy pivots sit on the actual joints.
func _rest_origin(names: Array) -> Vector3:
if _skeleton == null:
return Vector3.ZERO
for bone_name in names:
if _bone_indices.has(bone_name):
return _skeleton.get_bone_global_rest(_bone_indices[bone_name]).origin
return Vector3.ZERO
## Bone-name candidates per animation joint, in proxy order: index 0 is the
## avatar's left (-x) and index 1 its right (+x). The Quaternius rig labels its
## dot-suffixed joints with the opposite side (its `L` sits at +x), so the pairs
## carry the rig-specific mapping and nothing else has to know about it.
const RIG_BONES := {
"head": [["Head"], ["Head"]],
"hip": [["UpperLeg.R", "HipL"], ["UpperLeg.L", "HipR"]],
"knee": [["LowerLeg.R", "KneeL"], ["LowerLeg.L", "KneeR"]],
"shoulder": [["UpperArm.R", "ShoulderL"], ["UpperArm.L", "ShoulderR"]],
"elbow": [["LowerArm.R", "ElbowL"], ["LowerArm.L", "ElbowR"]],
"hand": [["Wrist.R", "HandL"], ["Wrist.L", "HandR"]],
}
## Resolved skeleton bone name for an animation joint on one side. Returns an
## empty string when the rig has neither candidate, so both the Quaternius
## 62-joint rig and the 13-core fallback resolve through the same call.
func bone_name(joint: String, side: int = 0) -> String:
if not RIG_BONES.has(joint):
return ""
var pair: Array = RIG_BONES[joint]
return _bone_of(pair[0], pair[1], side)
## Copy the proxy pivot rotations onto armature bones. Limb bones point down, so ## Copy the proxy pivot rotations onto armature bones. Limb bones point down, so
## an X rotation swings them forward/back; the Head bone points up, so a Y ## an X rotation swings them forward/back; the Head bone points up, so a Y
## rotation is the look yaw. ## rotation is the look yaw.
func _sync_skeleton() -> void: func _sync_skeleton() -> void:
if _skeleton == null or _bone_indices.is_empty(): if _skeleton == null or _bone_indices.is_empty():
return return
var names := ["HipL", "HipR"]
for i in 2: for i in 2:
_pose_bone(names[i], _legs[i].rotation.x, Vector3.RIGHT) var hip_swing := Quaternion(Vector3.RIGHT, _legs[i].rotation.x)
names = ["KneeL", "KneeR"] _swing_bone(bone_name("hip", i), hip_swing)
for i in 2: # `_swing_bone` writes the absolute global rotation, so a child bone has to
_pose_bone(names[i], _knees[i].rotation.x, Vector3.RIGHT) # receive the swing it inherits from its parent as well as its own.
names = ["ShoulderL", "ShoulderR"] _swing_bone(bone_name("knee", i), hip_swing * Quaternion(Vector3.RIGHT, _knees[i].rotation.x))
for i in 2: var shoulder_swing := Quaternion(Vector3.LEFT, _arms[i].rotation.x)
_pose_bone(names[i], _arms[i].rotation.x, Vector3.RIGHT) _swing_bone(bone_name("shoulder", i), shoulder_swing)
names = ["ElbowL", "ElbowR"] _swing_bone(bone_name("elbow", i), shoulder_swing * Quaternion(Vector3.LEFT, _elbows[i].rotation.x))
for i in 2:
_pose_bone(names[i], _elbows[i].rotation.x, Vector3.RIGHT)
if _head != null: if _head != null:
_pose_bone("Head", _head.rotation.y, Vector3.UP) var head := bone_name("head")
if head != "":
_swing_bone(head, Quaternion(Vector3.UP, _head.rotation.y))
func _pose_bone(bone_name: String, angle: float, axis: Vector3) -> void: ## First bone present from the left list (index 0) or the right list (index 1).
if not _bone_indices.has(bone_name): func _bone_of(left: Array, right: Array, index: int) -> String:
for bone_name in (left if index == 0 else right):
if _bone_indices.has(bone_name):
return str(bone_name)
return ""
## Swing a bone about a world axis through its head, keeping its rest orientation
## as the neutral. Writing the *global* pose is rig-agnostic: it does not depend
## on how the armature pack exported its rest rolls or on the pose/rest split the
## glTF importer chose, both of which differ between the 62-joint Quaternius rig
## and the 13-core fallback. Measuring `get_bone_global_pose()` after the call is
## the only reliable check (see the pose-capture gate).
##
## `rest` must be the **global** rest. `get_bone_rest()` is parent-relative and
## already carries the importer's axis conversion, so conjugating with it swung
## the limbs about the wrong axis and splayed the arms.
func _swing_bone(bone_name: String, swing: Quaternion) -> void:
if bone_name == "":
return return
var index: int = _bone_indices[bone_name] var index: int = _bone_indices[bone_name]
# Bone pose rotation is the absolute local orientation, not an offset from _skeleton.reset_bone_pose(index)
# rest; compose with the rest rotation so identity preserves the bind pose. var rest := _skeleton.get_bone_global_rest(index).basis.get_rotation_quaternion()
var rest := _skeleton.get_bone_rest(index).basis.get_rotation_quaternion() var current := _skeleton.get_bone_global_pose(index)
_skeleton.set_bone_pose_rotation(index, rest * Quaternion(axis, angle)) var target := Basis((swing * rest).normalized()).scaled(current.basis.get_scale())
_skeleton.set_bone_global_pose(index, Transform3D(target, current.origin))
func _build_body(npc_type: int) -> void: func _build_body(npc_type: int) -> void:
_rig = Node3D.new() _rig = Node3D.new()
Loaded 100 of 111 files, more files were not shown because too many files have changed in this diff. Show more