Compare commits

..
180 Commits
Author SHA1 Message Date
zwitschi 236fbf537f Date the 0.9.7 release
Full build / Import + headless tests (push) Successful in 49s
Full build / Full desktop build + optional release (push) Successful in 5m5s
2026-11-04 12:00:25 +01:00
zwitschi 588722fe7a Refresh the gallery for the 0.9.7 character pass
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 47s
2026-11-04 11:59:03 +01:00
zwitschi 7f2f06bf42 Re-plan releases: 0.9.7 character patch, 0.10.0 for all 3D/map improvements
Full build / Import + headless tests (push) Successful in 1m11s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 11:53:04 +01:00
zwitschi 6db3cc0873 Plan the finished character development as the 0.10.1 patch; MakeHuman targets 0.11.0
Full build / Import + headless tests (push) Successful in 3m6s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 11:49:13 +01:00
zwitschi 8938dd918c Taper the arm-scale falloff; record the remaining deltoid retopology gap
Full build / Import + headless tests (push) Successful in 2m14s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 11:45:12 +01:00
zwitschi 742bfd3898 Paint the five tint slots on the MakeHuman body; record the retarget vision review
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 3m23s
2026-11-04 11:43:50 +01:00
zwitschi 87069a30e8 Retarget the MakeHuman body to the 13-bone contract (arm-down + weights + skinning)
Full build / Import + headless tests (push) Successful in 1m42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 11:40:17 +01:00
zwitschi 3f5c5ce442 Prototype the MakeHuman intake: adapt the CC0 body to the character budget
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 11:30:14 +01:00
zwitschi 28fcd96b59 Add the external-mesh adapt intake command for the MakeHuman pipeline
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 11:27:43 +01:00
zwitschi e984129597 Fix the pose harness: disable the avatar process so the FSM cannot revert the pose
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 46s
2026-11-04 11:24:35 +01:00
zwitschi 75dea2e7c2 Re-prioritize: Rig harness -> MakeHuman -> 3D re-eval -> 0.11.0; move character development to DONE
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 11:15:54 +01:00
zwitschi 09d07452af Close the crowd-variety QA and release exit
Full build / Import + headless tests (push) Successful in 49s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 11:04:17 +01:00
zwitschi f5ee2b7408 Record the crowd-variety roster and the expanded budget in the docs
Full build / Import + headless tests (push) Successful in 55s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 11:03:55 +01:00
zwitschi 5980d329e8 Close the crowd-variant fit, reference and baseline items
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 47s
2026-11-04 11:03:25 +01:00
zwitschi 6052ea391b Wire the crowd variants into per-role pools and verify every seed resolves
Full build / Import + headless tests (push) Successful in 55s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 11:03:01 +01:00
zwitschi 4e8665e25b Add the four crowd-variety characters and extend the baseline to twelve
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 42s
2026-11-04 10:57:58 +01:00
zwitschi b945eb6e8f Fetch the licensed crowd-variety reference photos
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 10:47:44 +01:00
zwitschi 03c13c716a Record the qa-review sign-off: all eight characters ship with notes
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 10:44:29 +01:00
zwitschi 0a9c78cb30 Give the foliage two green tones and fix the textured_solid import bug
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 42s
2026-11-04 10:40:27 +01:00
zwitschi abe5c3f933 Update development-status: the face pass now covers all eight variants
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 47s
2026-11-04 10:33:20 +01:00
zwitschi 140ab42536 Apply the realistic face to the remaining seven variants and refresh the baseline
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 10:33:11 +01:00
zwitschi 8bdfd0176e Refresh the NPC baseline sheet and record the face prototype in development status
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 45s
2026-11-04 10:26:14 +01:00
zwitschi 4e23cc3ad5 Prototype a more realistic face on npc_casual (higher subdivision, brow ridge, both lids, fuller lip)
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 42s
2026-11-04 10:24:21 +01:00
zwitschi 8d32a8cac0 Record the map rebuild completion and the fresh NPC review against the new maps
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 10:03:14 +01:00
zwitschi 2445ddab34 Close the map-track J/L workstreams: procedural approach supersedes the facade kit
Full build / Import + headless tests (push) Successful in 44s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 10:00:36 +01:00
zwitschi 8cacd510bf Record the rounded-corner simplification and close the local-map QA/exit
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 09:58:47 +01:00
zwitschi bf383aa0fa Note the Joke Shop facade-height fix in the review doc
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 09:48:47 +01:00
zwitschi 4bbc576b7f Lower the Joke Shop facade to 6 m so the canopy caps the low hall
Full build / Import + headless tests (push) Successful in 44s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 09:48:20 +01:00
zwitschi 542b1264da Close the Comedy Store map recreation and document describe_image in asset-tooling
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 09:42:31 +01:00
zwitschi 56f4833c8e Record the local-map data, licensing and scope decisions (section H)
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 09:41:46 +01:00
zwitschi 1b31dfdbe4 Add the Ramada Plaza hotel tower mass behind the Joke Shop west neighbour
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 09:41:11 +01:00
zwitschi 5158efc467 Retire the unused beanie gear and sequence asset fixes after the map rebuild
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 43s
2026-11-04 09:36:10 +01:00
zwitschi ac0cf35cfb Fix the stale Joke Shop map count in the review doc
Full build / Import + headless tests (push) Successful in 44s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 09:30:44 +01:00
zwitschi 6655edb5c6 Sync development-status with the 3D re-evaluation facade/prop work and current counts
Full build / Import + headless tests (push) Successful in 52s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 09:22:27 +01:00
zwitschi 60f389c16b Force-spawn the requested portrait role and close the hooded close-up
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-04 09:09:44 +01:00
zwitschi 3084145031 Rank the Comedy Store shape-language gaps (top-five list)
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 50s
2026-11-04 09:04:30 +01:00
zwitschi 34bca89885 Record the set-dressing vision review findings
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 22:00:49 +01:00
zwitschi b53e8a67f9 Polish the bollard base and newspaper-box door (recessed slot + seam)
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 21:59:06 +01:00
zwitschi cdfd57fb65 Record the prop-gallery vision review findings
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 21:56:50 +01:00
zwitschi 5bab6754c0 Rebuild the fire hydrant silhouette (flange, bonnet, outlets)
Full build / Import + headless tests (push) Successful in 53s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 21:56:28 +01:00
zwitschi 7dd8251a0c Extract and reconcile the Comedy Store proportions via the vision tool
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 21:50:20 +01:00
zwitschi 8e113a4110 Tighten the gap analysis register
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 42s
2026-11-03 21:02:01 +01:00
zwitschi 32c9c45605 Document the lighting vision review and add describe_image to the tools inventory
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 46s
2026-11-03 21:00:54 +01:00
zwitschi e4065c2068 Tie back the Joke Shop curtain bays and add the describe_image vision tool
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 20:58:05 +01:00
zwitschi e7d733bc93 Record the gear fit readings and caveats in the review doc
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 42s
2026-11-03 20:25:39 +01:00
zwitschi 93da916f90 Gate venue light falloff with a test (physical attenuation = 1.0)
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 39s
2026-11-03 20:14:23 +01:00
zwitschi e3ec4a356a Sync the stale Python test count (143 -> 160) in TODO
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 41s
2026-11-03 20:13:10 +01:00
zwitschi 55339f23c1 Document the texel/coverage/collider gates and correct the face limitation note
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 20:12:43 +01:00
zwitschi a51dae2c33 White the Joke Shop canopy signature band to match the reference
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 45s
2026-11-03 20:07:01 +01:00
zwitschi 6824dd76a1 Close stale TODO items: camera height and local-frame provenance
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 20:04:22 +01:00
zwitschi fde4aa4d0a Record the two-builder distinction in MEMORY
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 20:03:15 +01:00
zwitschi ebb36cf0cb Correct face/head review: library characters already carry the 0.9.4 sculpt; close hands/grip review
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 20:03:06 +01:00
zwitschi 724e8b183f Add tracked AI skills for Blender assets and release QA
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 19:53:32 +01:00
zwitschi e4e1566e55 Organize tools and move Python tests into tools/tests
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 19:45:00 +01:00
zwitschi 0aacee6518 Compact MEMORY.md: drop CHANGELOG/DONE duplication
Full build / Import + headless tests (push) Successful in 44s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 19:40:50 +01:00
zwitschi 215b843b67 Write the face/head concrete edits and close the review
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 46s
2026-11-03 19:27:31 +01:00
zwitschi 53d9dc3f78 Record the cloth/hair decision: simulation stays out of scope
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 19:26:50 +01:00
zwitschi 98f2f95f07 Close the UV/texel decision: accept the neutral runtime-tinted maps
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 54s
2026-11-03 19:26:30 +01:00
zwitschi a677a95d13 Narrow the pose-harness root cause: skinned mesh ignores its Rig parent
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 40s
2026-11-03 19:26:07 +01:00
zwitschi 1afd97c72d Complete the Comedy Store block reference collection (hotel + hillside)
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 19:18:43 +01:00
zwitschi 6ca8bcf034 Decouple AGENTS.md from specific tracks: keep only process, action and tracking guidance
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 19:16:57 +01:00
zwitschi 13eb4e0c87 Make the merged 3D + local map track coherent: rename, combined scope/exit, re-mark collection state
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 19:13:21 +01:00
zwitschi 8fac1b47cf Merge local map tasks into the 3D review track
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 18:41:21 +01:00
zwitschi b79e30e9df Render the TV Studio as the Masonic Temple facade (Jimmy Kimmel starting point)
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 18:35:15 +01:00
zwitschi 1e761e5a76 Anchor Jimmy Kimmel Live entrance concepts and mark the building as the starting point
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 18:28:31 +01:00
zwitschi 3eb2ae5e3c Collect Jimmy Kimmel Live and Hollywood block references; skip AVIF frames in the contact sheet
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 18:24:35 +01:00
zwitschi 888897cac9 Add three multi-model Comedy Store scenes and plan the map recreation
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 18:04:09 +01:00
zwitschi 988d431da8 Generate multi-model anchored concepts, drop old Comedy Store refs, and trim the concept gallery
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 17:49:48 +01:00
zwitschi 87e1c9e7a5 Harden the DDG collector with token-bucket pacing and prefer the ddgs package
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 17:32:39 +01:00
zwitschi 480a1ba05f Document the local-only reference collector
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 17:12:32 +01:00
zwitschi 43c17ce3c0 Add the local-only DuckDuckGo reference collector
Full build / Import + headless tests (push) Successful in 54s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 17:12:18 +01:00
zwitschi 03f4d4bc49 Prioritize local reference collection and record the licence-free local-only override
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 17:04:19 +01:00
zwitschi 15f9117517 Mark the completed maintenance tasks
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 16:35:33 +01:00
zwitschi 78267918bf Trim MEMORY.md: drop superseded handoffs and release anecdotes, fix stale counts and the sign-in contradiction
Full build / Import + headless tests (push) Successful in 48s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 16:35:19 +01:00
zwitschi ea3860a60c Defer Gaussian splatting and re-plan maintenance
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 16:31:42 +01:00
zwitschi b721f189c5 Record the stucco decision and the recitation-filter workaround
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 16:22:28 +01:00
zwitschi 634b1f75a3 Give the Joke Shop a dark stucco wall and a photo-anchored concept
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 48s
2026-11-03 16:22:20 +01:00
zwitschi 36583921cf Plan the Joke Shop building-appearance pass
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 16:17:57 +01:00
zwitschi 3866b0ae01 Record the completed facade-realism pass
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 16:15:46 +01:00
zwitschi 8c2761427f Verify the marquee emissive balance and close the facade-realism pass
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 16:15:38 +01:00
zwitschi 2b07913481 Update the facade-realism progress note
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 15:55:10 +01:00
zwitschi 768dd94bfc Balance the event-banner night emissive
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 15:54:58 +01:00
zwitschi f516d3e796 Record the Godot import-cache gotcha and ashlar decision
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 41s
2026-11-03 15:40:43 +01:00
zwitschi f95a685934 Use the photo-anchored ashlar limestone on the masonic facade
Full build / Import + headless tests (push) Successful in 49s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 15:40:25 +01:00
zwitschi 05cb6a43cb Link the three-up boards from the venue map docs
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 15:11:09 +01:00
zwitschi 343b3348c2 Add three-up evidence boards for El Capitan and Joke Shop
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 15:09:18 +01:00
zwitschi ad91e71eeb Add the photo-anchored variant pass with night and overcast facade frames
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 40s
2026-11-03 15:04:20 +01:00
zwitschi 6e04c59589 Record the reference-anchored generation recipe in memory
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 15:00:00 +01:00
zwitschi e21ff30783 Add reference-anchored concept generation
Full build / Import + headless tests (push) Successful in 49s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 14:59:53 +01:00
zwitschi 5b306b1c52 Add the Commons reference-board research tool
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 14:51:31 +01:00
zwitschi f09a5e7937 Plan recent-photo tooling and reference-anchored generation
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 14:36:13 +01:00
zwitschi 383f601bc9 Extend the Hollywood board with terrazzo, neighbour and night context
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 14:04:51 +01:00
zwitschi c46db76d85 Record that the TV Studio venue shares the Hollywood block
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 13:49:26 +01:00
zwitschi 1a5f2863b0 Add the licensed Joke Shop reference board
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 13:41:25 +01:00
zwitschi a6fd77068a Add the first licensed Hollywood reference board
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 13:28:05 +01:00
zwitschi 4ed73fcc7b Add the real-location reference folder and provenance sidecar
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 43s
2026-11-03 12:42:13 +01:00
zwitschi c0ddce79e8 Record the imagery evidence model
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 44s
2026-11-03 12:22:11 +01:00
zwitschi 0c7c9a2b23 Plan reference imagery, generated boards and per-section captures
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 40s
2026-11-03 12:21:57 +01:00
zwitschi b94b87d096 Refresh the capture gallery and build captioned visual docs
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 12:08:33 +01:00
zwitschi 5dec59355a Gate library prop collision coverage across venues
Full build / Import + headless tests (push) Successful in 37s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 11:58:00 +01:00
zwitschi fe3ba3054e Gate texel density and review scorecard coverage
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 11:52:19 +01:00
zwitschi 88a0793bba Record the verified pose-capture recipe and the harness defect
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 41s
2026-11-03 11:44:55 +01:00
zwitschi 1d4f718376 Narrow the pose-capture discrepancy to the harness and lock bone posing
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 11:24:44 +01:00
zwitschi 7a5b902797 Add a pose review harness and record the blocked pose capture
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 43s
2026-11-03 10:59:23 +01:00
zwitschi 2d67e14e52 Measure the 3D library and open the model review scorecard
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 10:39:45 +01:00
zwitschi 2443343f2b adjusted agents instructions, made more precise and incorporate blender skills 2026-11-03 10:38:39 +01:00
zwitschi 3e41cda591 Re-plan the Blender tracks around the local skills and a 3D model review
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 43s
2026-11-03 10:19:46 +01:00
zwitschi 84bc4137cb Add local pi agent session data to .gitignore 2026-11-03 10:13:34 +01:00
zwitschi 5dc3ef0f4c Add a Blender GLB intake inspection command
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 44s
2026-11-03 10:08:45 +01:00
zwitschi 6f3facff43 Install the Blender skills pack locally and keep it out of git
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 10:02:34 +01:00
zwitschi 51f537720f Reorder TODO and docs around character and map priorities
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 37s
2026-11-03 09:17:49 +01:00
zwitschi 08e9044593 Add commit, build and version discipline to agent workflow 2026-11-03 09:17:49 +01:00
zwitschi ad14410337 Expand the photo-driven Gaussian splatting map plan
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 43s
2026-11-03 09:07:07 +01:00
zwitschi 876fb59882 Add unversioned local map development section 2026-11-03 09:01:46 +01:00
zwitschi 979c6ad789 Make character development an unversioned TODO section 2026-11-03 08:24:55 +01:00
zwitschi 1921bfae91 Document the character development toolchain in the architecture
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 39s
2026-11-03 08:22:09 +01:00
zwitschi 78485259b7 Detail deferred MakeHuman local pipeline boundary 2026-11-03 08:00:47 +01:00
zwitschi fef3af44c0 Record NPC sourcing decision and crowd prototypes 2026-11-03 07:59:58 +01:00
zwitschi bfe3c5a701 Research NPC model sources and plan 0.11.0 expansion 2026-11-03 07:46:47 +01:00
zwitschi 46f1e41874 Define release-candidate balance acceptance targets
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 07:40:45 +01:00
zwitschi f48d87c84f Record the 0.10.0-rc.1 candidate build
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 07:36:03 +01:00
zwitschi a9658ecd81 Prepare the 0.10.0 release candidate 2026-11-03 07:33:20 +01:00
zwitschi b8507df346 Harden release publishing and document release blockers
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 45s
2026-11-03 07:32:21 +01:00
zwitschi 60bf9a8ef6 Add save migration fixtures and failure-mode coverage 2026-11-03 07:29:42 +01:00
zwitschi b51bdc8efe Open the 0.10.0 release-candidate milestone
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 39s
2026-11-03 06:20:36 +01:00
zwitschi a7b2852253 Record the v0.9.6 release build
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 06:19:38 +01:00
zwitschi 4bb2896819 Verify the 0.9.6 integration checkpoint
Full build / Import + headless tests (push) Successful in 53s
Full build / Full desktop build + optional release (push) Successful in 4m27s
2026-11-03 06:13:07 +01:00
zwitschi f5b8ef2e70 Prepare the 0.9.6 integration checkpoint 2026-11-03 06:11:17 +01:00
zwitschi 0044f5f065 Record the v0.9.5 release build
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 21:12:00 +01:00
zwitschi 4bc6390e9f Release 0.9.5 texture and material refinement
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Successful in 4m54s
2026-11-02 21:04:48 +01:00
zwitschi fb3da52438 Refine environment surfaces and skyline haze
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 20:38:58 +01:00
zwitschi d7220c1a0a Refine NPC fabric UVs and remove baked AO blotches
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 37s
2026-11-02 20:28:25 +01:00
zwitschi 31574b8d82 Raise NPC texture maps to 256
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 20:16:30 +01:00
zwitschi b60f67548d Record the v0.9.4 release build
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 43s
2026-11-02 19:58:40 +01:00
zwitschi a57b86cd48 Release 0.9.4 NPC visual optimization
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Successful in 5m28s
2026-11-02 19:51:36 +01:00
zwitschi dfe5056303 Round the NPC shoes and responder boots
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 19:38:40 +01:00
zwitschi 58cf12fd1d Move hand gear forward into the palm
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 19:33:42 +01:00
zwitschi 7150bd7673 Verify NPC headgear clearance after the face pass
Full build / Import + headless tests (push) Successful in 34s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 19:30:19 +01:00
zwitschi 12e70357c5 Refine the NPC nose, mouth and ears
Full build / Import + headless tests (push) Successful in 35s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 19:29:19 +01:00
zwitschi 7dc3d3c873 Raise the character mesh budget to 8000
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 33s
2026-11-02 19:25:41 +01:00
zwitschi cd5e0e0ff7 Record the NPC vertex-cap constraint
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 19:22:34 +01:00
zwitschi 8ce62e7556 Group the NPC visual changes under Changed
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 35s
2026-11-02 19:15:15 +01:00
zwitschi 24c939d388 Rebuild the NPC hands and add a hand capture
Full build / Import + headless tests (push) Successful in 50s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 19:14:55 +01:00
zwitschi 48082c3dfa Taper the NPC torso into the hips
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 19:11:28 +01:00
zwitschi 03c355e7c1 Recess NPC eyes and sculpt the jaw and cheeks
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 19:08:26 +01:00
zwitschi 15ab8c686d Smooth the NPC neck and shoulder transition
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 19:03:38 +01:00
zwitschi ab9c67d266 Audit NPC body proportions against a shared guide
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
Measure all eight exported characters and record the stylized proportion bands. All variants pass; no skeleton or mesh rescale was needed.
2026-11-02 18:54:52 +01:00
zwitschi 7c1a4bfd69 Refit the tourist backpack and photo prop
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
Hide the tourist photo-pose bag outside camera poses, flatten the backpack straps onto the chest, rebuild the gear GLB/AO and publish the before/after NPC sheet.
2026-11-02 18:39:03 +01:00
zwitschi e7ee177c31 Establish the 0.9.4 NPC visual baseline
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 38s
Add a neutral 48-frame character contact sheet, acceptance criteria and ranked baseline gaps, plus repeatable sheet/comparison tooling. No character GLB or runtime mesh changes.
2026-11-02 18:13:57 +01:00
zwitschi c1cd4898e8 Close the 0.9.3 UI polish milestone
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Skipped
Add deterministic captures 14-21 for the remaining screen-design states, fix the panel/button layout issues they exposed, and record 21/21 compare and judge results. Bump the source to 0.9.3 and move the completed milestone to DONE.md.
2026-11-02 17:57:40 +01:00
Gig Line Dev e805a91ad3 Add end-to-end interaction flow smoke
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
- Boot the real scene and verify inspection allow/deny, normal vs emergency radio, Help filtering, wand allow/deny and review return-to-menu.
- Document the flow coverage and smoke command; mark the 0.9.3 end-to-end audit complete.
2026-11-02 16:36:01 +01:00
Gig Line Dev 7315166361 Guard destructive settings and radio actions
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Skipped
- Route reset campaign and tutorial replay through one reusable confirmation dialog; repeated presses cannot stack or emit twice.
- Keep emergency hold-to-send single-shot and cover the post-submit release.
- Add destructive-action regression checks, strings and documentation.
2026-11-02 16:29:26 +01:00
Gig Line Dev f6aa26e6ca Harden UI layout for font scale and long strings
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 36s
- Measure and cap the HUD target chip after each font-scale change; clip HUD top labels.
- Ellipsize long action-button labels so panels cannot grow past the viewport.
- Add accessibility/button layout assertions and refresh the gallery captures.
- Record the font/layout pass in TODO, docs, changelog and memory.
2026-11-02 16:23:46 +01:00
Gig Line Dev 26e6dd6413 Close every top modal through one Esc and right-click path
Full build / Import + headless tests (push) Successful in 37s
Full build / Full desktop build + optional release (push) Skipped
- Add Help, Settings, Pause and review to the top-modal close chain so right-click and Esc behave consistently.
- Restore the venue menu after menu Settings and prevent Help from opening Pause behind itself.
- Reopen pause after in-shift Settings apply and recapture the mouse/unfreeze state on close.
- Extend the session smoke for menu Settings, right-click Help and guard unfreeze.
2026-11-02 16:10:50 +01:00
Gig Line Dev 8f65751975 Add wrapping keyboard and gamepad focus rings
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 36s
- Link every modal's enabled controls into a wrapping UiFocus ring and give each screen a default focus target.
- Add gamepad A/B bindings for ui_accept/ui_cancel; include Start menu footer actions and Continue in its focus ring.
- Skip hidden/disabled controls, close focus traps via per-modal cancel behavior, and return review to the menu on cancel.
- Record the focus audit and add regression coverage.
2026-11-02 16:05:53 +01:00
Gig Line Dev 9692f262f1 Unify action-button states across the UI
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 39s
- Add UiStyles with explicit normal, hover, pressed, focus and disabled styles and apply it to every action button.
- Disable timeline Previous/Next at the first/last event and keep Close always available.
- Style settings reset-dialog buttons and cover the button-state audit in docs/ui-copy.md.
- Add shared-state and timeline-boundary regression tests.
2026-11-02 15:04:38 +01:00
Gig Line Dev 98c31fe44c Audit and fix NPC, door-staff and UI dialogue
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped
- Route the Reason verb through authored reason contexts and give every shared archetype an in-character answer.
- Add Hollywood wristband and TV Studio ticket/credential door-staff lines.
- Clarify radio cancellation, Ask terminology, start hints and campaign-reset copy.
- Record the dialogue audit and length findings in docs/ui-copy.md; add regression tests.
2026-11-02 14:44:01 +01:00
Gig Line Dev daf0926d91 Bump source to 0.9.2 and renumber the 0.x roadmap
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Skipped
- Set project.godot to 0.9.2 and add the matching changelog entry.
- Renumber the postponed work to 0.9.3 UI, 0.9.4 NPC, 0.9.5 textures and 0.9.6 integration, with 0.10.0 as the next minor release candidate.
- Remove the 1.0.0 milestone and post-1.0 planning; plan only 0.x patch/minor increments.
- Synchronize README, PLAN, architecture, status, memory and UI-copy version references.
2026-11-02 14:39:50 +01:00
Gig Line Dev f71a36faa1 Pass the pre-build NPC review gate
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 34s
- Add a review harness that stages all eight library characters at portrait, conversation and crowd distance on both maps in day and night.
- Fix the final-context lanyard badge and firefighter helmet fit with runtime attach offsets; the asset-space gear contract is unchanged.
- Record the gate sign-off, refresh the gallery, and move the completed review to DONE.
2026-11-02 14:32:10 +01:00
Gig Line Dev 37f925c7ad Rebuild the Joke Shop as the Sunset Strip block
Full build / Import + headless tests (push) Successful in 37s
Full build / Full desktop build + optional release (push) Skipped
- Replace the alley with the 27 m Comedy Store-inspired hall, signature canopy, circular blade sign, red curtain bays, west entrance, low-rise neighbours, dry hillside and opposite frontage.
- Add day/night/across-street concept frames and real-site mapping notes.
- Expose the Joke Shop day stage in the realism captures and refresh the gallery shots.
- Render multiline HUD/wand strings correctly and list carried items in the wand panel.
- Add the Joke Shop layout/budget tests and update docs, roadmap and changelog.
2026-11-02 14:10:07 +01:00
zwitschi 74dc1fdc1d Map 1: rebuild Hollywood as the El Capitan landmark
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 37s
Replace the generic boulevard facade with a stylized 6840 Hollywood Blvd block: six Ionic columns, eight stone steps, Masonic frieze/medallions, event banners, side doors, El Capitan Theatre and retail neighbours, Ovation Hollywood arcade, Walk of Fame and four-lane street markings.

Add the Day and Night Hollywood venue-menu rows. Night uses a phase-time offset so a full-length shift stays in LATE while the clock starts at zero; late routine traffic keeps the street populated and the offset round-trips through checkpoint restore.

Retune local-light falloff to linear attenuation, add a cool north sky fill, and use the smooth limestone facade texture. Refresh the staged gallery and map references; add geometry-budget, menu, night-offset and late-traffic tests.
2026-11-02 12:34:48 +01:00
zwitschi 3f335481d6 Prioritize map optimization and gate builds on an NPC revisit
Full build / Import + headless tests (push) Successful in 37s
Full build / Full desktop build + optional release (push) Skipped
Move the promoted 0.9.5 map work to the active milestone, postpone UI/NPC
detail/texture/integration/release-candidate work, and add a detailed pre-build
NPC gate that reviews portraits, gear fit, crowd distance and final lighting
before any build or tag.
2026-11-02 11:27:50 +01:00
zwitschi af923b1483 Route UI labels through the string table
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
Move start menu, settings, pause, Help, HUD, radio, shift review and campaign
labels/templates into content/strings.en.json, including placeholder-validated
dynamic status text. No player-facing hardcoded labels remain outside dynamic
data/debug output. Headless 250/250, docs check OK.
2026-11-02 11:25:15 +01:00
zwitschi 3a74cedee0 Refresh gallery and record the screen-design judge
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 37s
Regenerate the committed screenshot gallery on the current build, run compare
(13 good/0 bad) and the automatic-model judge (13/13 match, 0 diff), and record
the result. Update the 0.9.2 visual checklist items.
2026-11-02 11:18:36 +01:00
zwitschi 844e39246a Standardize button labels and document UI states
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
Finish the button-label audit with a surface-by-surface table, clarify Quit
game vs Quit to menu, rename Apply to Apply settings, and use string-table
keys for the new labels. Extend the smoke checklist with UI copy/button checks.
Headless 250/250, docs check OK.
2026-11-02 11:12:29 +01:00
zwitschi 7c48183410 Start the UI copy and button consistency pass
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 39s
Add the screen/copy/button inventory and canonical terminology, standardize
inspection/wand admit-refuse hints, name the review action Return to menu, and
move radio action/hold/status labels into the string table. Headless 250/250,
Python 97/97, docs check OK.
2026-11-02 11:10:39 +01:00
zwitschi 64590fe797 Plan the 0.10.0 minor and thorough NPC visual pass
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 33s
Renumber the pre-release patches after shipped 0.9.1, promote the next release
candidate to 0.10.0, and expand NPC work into reference/acceptance, proportions,
face/hair, hands/clothing, gear fit, pose/performance and visual verification.
Sync the README status and remaining-gap references.
2026-11-02 10:55:38 +01:00
zwitschi 49bfccc808 Add a docs README and link checker
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
Add tools/check_docs.py to require READMEs only in tracked docs directories,
validate local links and verify every tracked image is linked. Gitignored
generated files such as realism runs are exempt. Document the command and add
unit tests. Python 97/97.
2026-11-02 10:52:15 +01:00
zwitschi 7401d4bca0 Do not require READMEs in generated realism folders
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
Remove generated README files from docs/screens/realism and update the screens
index to treat realism as a gitignored local QA directory with no README gate.
2026-11-02 10:50:20 +01:00
zwitschi e79a37beba Add documentation folder READMEs and visual previews
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Skipped
Create an index README in docs/ and every tracked docs subfolder, with relative
image galleries for Blender previews, screen designs, screenshots and concepts.
Generated realism-run READMEs were also created on disk but remain gitignored.
Validate all Markdown/image links.
2026-11-02 10:49:42 +01:00
zwitschi fbd2ab2cb3 Disable unreliable Gitea Godot cache steps
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 35s
Gitea cache restores were inconsistent and cache saves timed out or were
rejected as reserved by another job. Remove the cache steps and keep direct
4.7.2 downloads; document the evaluation and when to retry. The workflow is
otherwise clean (no .github/ci.yml exists).
2026-11-02 09:51:59 +01:00
zwitschi 9b8f513f9f Trim the Godot template cache and separate CI cache keys
Full build / Import + headless tests (push) Successful in 2m24s
Full build / Full desktop build + optional release (push) Skipped
Cache only Linux/Windows/macOS/Web release templates instead of the full 1.2 GB
archive, and use distinct binary cache keys for the checks and build jobs so
concurrent runs do not fight over the same cache entry.
2026-11-02 09:48:05 +01:00
zwitschi ad12fdad7e Bump to 0.9.1 and record the toolchain update
Full build / Import + headless tests (push) Successful in 2m23s
Full build / Full desktop build + optional release (push) Successful in 9m44s
2026-11-02 09:28:13 +01:00
zwitschi 87612f2057 Unify the Godot 4.7.2 CI environment and cache it
Full build / Import + headless tests (push) Successful in 2m30s
Full build / Full desktop build + optional release (push) Skipped
Update project features and CI installs/templates to Godot 4.7.2, remove the
old version matrix, and cache the Godot binary and export templates with Gitea
Actions cache. Update release/docs/version references accordingly. Project
import and headless tests pass locally under 4.7.2.
2026-11-02 09:25:08 +01:00
zwitschi ffd98daf08 Plan the pre-RC patch levels
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 38s
Add detailed 0.9.1 UI/dialog/button polish, 0.9.2 NPC model refinement,
0.9.3 texture/material refinement and 0.9.4 integration/performance/judge
workstreams before the 0.9.5 release candidate. Update the remaining-gap table,
baseline to v0.9.0 and README development status.
2026-11-02 09:16:33 +01:00
zwitschi f811e6054d Expand the 0.9.5 release-candidate checklist
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
Replace the remaining-gap table with v0.9.0 baselines and break the release
candidate into detailed workstreams for content lock/playtests, balance,
save migration, release engineering, legal/credits, documentation/visual
sign-off and final QA. Sync the README development status to v0.9.0.
2026-11-02 08:54:39 +01:00
zwitschi 9d37e0a1c5 Bump the project version to 0.9.0
Full build / Full desktop build + optional release (push) Successful in 5m0s
Full build / Import + headless tests (push) Successful in 36s
2026-11-01 21:48:03 +01:00
zwitschi 883626a067 Ship the 0.9.0 production-quality milestone
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Canceled after 4m58s
Add mixer/accessibility settings, caption/reduce-motion hooks, audio volume
clamping, a 24-NPC performance smoke and shortened coat/dress stride for hem
clipping. Record the explicit Godot 4.3-CI/4.7-local matrix and the 0.9
production scope. Headless 250/250, Python 94/94, 24-NPC budget smoke passes.
2026-11-01 21:46:42 +01:00
zwitschi b5d4d5fb87 Plan detailed 0.9.0 production-quality steps
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
Expand the next milestone into animation, audio, localization, accessibility,
performance/platform, QA/visual baselines, Godot-version policy and exit
criteria, with concrete verification work per workstream.
2026-11-01 21:39:29 +01:00
zwitschi fac9b002c3 Fix normal radio report submission
Full build / Full desktop build + optional release (push) Failing after 4m22s
Full build / Import + headless tests (push) Successful in 38s
The radio button was connected only to the emergency hold handlers, so a
normal Send request never reached _submit. Connect pressed for non-emergency
calls while keeping hold-to-confirm exclusive to emergencies, and add tests
for click, short-hold rejection and full emergency hold. Headless 249/249,
Python 94/94.
2026-11-01 21:37:39 +01:00
309 changed files with 15456 additions and 1730 deletions

No files matched your search

View File
Whitespace-only changes.
@@ -0,0 +1,210 @@
---
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.
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:
project: Gig Line
domain: 3d-assets
version: "1.0.0"
triggers: Blender, NPC, character, gear, prop, npc_base, GLB, AO, texel, asset rebuild, fit, audit
---
# Gig Line Blender Asset Contract
Use this skill for any change that touches:
- `assets/models/blender/**`
- `assets/models/generated/npc_base.glb`
- `assets/textures/npc/**`
- `tools/blender_assets.py`, `tools/npc_pipeline.py`, `tools/bake_ao.py`,
`tools/npc_proportions.py` or `tools/npc_baseline.py`
- Blender-authored venue/prop geometry
The project contract and measured gates override any generic Blender advice.
There is **no Blender MCP** in this project. Blender runs headless through the
flatpak CLI; the `blender-skills` pack is optional, local guidance only, and
gitignored.
## Read first
From the repository root, read the relevant sources rather than relying on this
skill alone:
1. `AGENTS.md` — required workflow and verification.
2. `TODO.md` and `MEMORY.md` — current track, claimed tasks, known gotchas.
3. `docs/blender-assets.md` — library contract and command reference.
4. `docs/asset-tooling.md` — exact rebuild/bake/capture commands.
5. `docs/npc-visual-review.md` and `docs/3d-model-review.md` — accepted limits,
scorecards and known blockers.
6. `docs/known-limitations.md` — limits that must not be silently “fixed” into
contract changes.
The contract lives in code and docs, not in this skill. If they disagree, stop
and resolve the source of truth before editing assets.
## Non-negotiable contract
| Area | Rule |
| --- | --- |
| 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). |
| Tint slots | `skin`, `cloth`, `pants`, `accessory`, `hair`. |
| 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`). |
| Frozen fallback | `npc_base` keeps **6,000 tris / 4,000 verts**. Do not silently raise or lower it. |
| Gear budget | **1,500 triangles** per gear piece; authored in attach-point space. |
| 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. |
| Textures | 256² albedo/normal/ORM maps. Cloth and pants use `NPC_FABRIC_UV_SCALE = (4,4,1)` and **skip** baked per-character AO; skin/hair/gear keep baked AO. |
| CI | Offline and skill-free. Committed GLBs/AO/maps must make CI pass without Blender or MCP. |
| Licensing | No new asset source without ADR-0015: CC0, or CC-BY with attribution in `CREDITS.md`; record provenance. |
Do not change the renderer, engine baseline, runtime dependency graph or the
`npc_base` contract as part of an asset pass.
## Workflow
### 1. Preflight and claim
```bash
git status --short
git log --oneline -5
godot --version
flatpak run org.blender.Blender --version | head -3
```
Inspect the current asset and the relevant docs. Claim the TODO item with `[-]`
before starting. Work on one asset family per commit.
### 2. Establish a baseline
Run the checks that do not need Blender first:
```bash
python3 tools/blender_assets.py audit
python3 tools/blender_assets.py fit
python3 tools/blender_assets.py texels
python3 tools/npc_proportions.py report
python3 tools/npc_pipeline.py audit
```
For a specific incoming or existing GLB:
```bash
flatpak run org.blender.Blender --background \
--python "$PWD/tools/blender_assets.py" -- \
inspect --file "$PWD/path/to/asset.glb" --category npc_gear --json
```
Record the before measurements (tris/verts, head/shoulder/hip/leg/arm/foot,
fabric texel density, gear clearance). Do not “improve” an asset without a
measurable target.
### 3. Optional Blender-skill routing
If the local `blender-skills` pack is installed, load `blender-director`, then
only the specialists the task needs:
- character mesh/face/hands: `character-artist`, `retopology`
- rig/weights/pose: `rigging`
- UVs/texel density: `uv-workflow`
- maps/materials: `texture-workflow`, `materials`, `lookdev`
- gear/props: `prop-artist`, `hard-surface`
- budget/LOD/collision: `asset-optimization`, `lod-pipeline`, `collision-proxy`
- final review: `qa-review`
Translate any MCP instruction into a change to `tools/*.py` or a headless CLI
run. Never add an MCP dependency to the build, CI or runtime.
### 4. Build or bake
Characters, gear and library props:
```bash
flatpak run org.blender.Blender --background \
--python "$PWD/tools/blender_assets.py" -- build --only <name>
flatpak run org.blender.Blender --background \
--python "$PWD/tools/bake_ao.py" -- --only <name>
```
NPC base and its material maps:
```bash
python3 tools/npc_pipeline.py prepare # only when fabric tiles change
flatpak run org.blender.Blender --background \
--python "$PWD/tools/npc_pipeline.py" -- build
python3 tools/npc_pipeline.py audit
```
Do not overwrite a review image or a promoted texture pick. Use a new `-vN`
path and document the promotion.
### 5. Import and capture
```bash
godot --headless --path . --import
godot --headless --path . --script res://src/test/test_runner.gd
# neutral baseline: 8 variants x 6 views, raw frames stay in tmp/
godot --path . --script res://src/test/capture_npc_baseline.gd \
-- --out=res://tmp/npc-baseline
# final-context: 96 frames across both venues and day/night
godot --path . --script res://src/test/capture_npc_review.gd \
-- --out=res://tmp/npc-review
```
Compare before/after with `tools/npc_baseline.py compare`; do not replace the
committed baseline sheet until a fix actually lands. See
`references/verification.md` for the full gate matrix.
### 6. Verify and hand off
At minimum:
- `blender_assets.py audit` and `fit` exit 0.
- `npc_proportions.py` passes the stylized guide.
- `npc_pipeline.py audit` passes if the base/textures changed.
- texel density is inside the authored/fabric bands.
- No character exceeds 8,000/8,000; gear/prop caps hold.
- 24-NPC budget and Web/Compatibility path remain green.
- Contract tests, headless suite, capture gates and docs check pass.
- The diff contains only the intended assets, tools, tests and docs.
State in the handoff: files changed, measurements before/after, skills used,
gates run, and any accepted limit.
## Failure playbook
- **`Class "..." hides a global script class`** — a stray `.gd` backup is in the
project. Delete `tmp/*.gd` backups, `rm -rf .godot`, re-import; never keep
`class_name` copies in `tmp/`.
- **Blender cannot see the path** — the flatpak cwd is `/app/blender`; use
absolute paths (`"$PWD/..."`).
- **Empty or stale captures** — close modals/pause, check the capture script's
settle frames, and remove stale output directories before re-running.
- **Pose harness mismatch** — the current `capture_npc_pose.gd` is blocked per
`docs/3d-model-review.md`; do not use its frames as pose evidence until the
recipe is fixed.
- **Asset test fails after import** — re-run audit/fit first, then import; if the
manifest or AO path changed, rebuild the affected asset rather than editing
the manifest by hand.
- **Uncertain about a visual change** — produce a labelled before/after sheet and
ask for review; do not hide a regression behind a budget claim.
## Bundled references
- [`references/asset-contract.md`](references/asset-contract.md) — full contract,
inventory, file locations and provenance rules.
- [`references/verification.md`](references/verification.md) — command matrix,
capture gates, expected results and acceptance criteria.
## Do not
- Add an MCP server, Blender dependency or API call to the build/runtime.
- Change the bone/tint/contract to make a generic asset fit.
- Rewrite committed baselines or overwrite existing images.
- Commit `.blend` sources, raw captures or generated AI inputs.
- Treat an asset audit as a substitute for visual review at portrait and crowd
distance.
@@ -0,0 +1,139 @@
# Asset Contract Reference
This is a working summary. The authoritative sources remain
`docs/blender-assets.md`, `docs/asset-tooling.md`, ADR-0013, the tests in
`src/test/tests/` and the constants in `tools/blender_assets.py` /
`tools/npc_pipeline.py`.
## Library inventory
- **Character variants (8):** `npc_casual`, `npc_jacket`, `npc_coat`,
`npc_vest`, `npc_dress`, `npc_hoodie`, `npc_firefighter`, `npc_guard`.
- **Frozen fallback:** `npc_base` / `assets/models/generated/npc_base.glb`.
- **Gear:** 18 attach-space pieces covering head, back, chest and hand items.
- **Library props:** 20 Blender-authored props plus 6 generated props.
- **Venues:** procedural venues in `src/game/world_builder.gd`; Blender venue
geometry follows the same budgets and collision/provenance rules.
## Skeleton and geometry
The contract skeleton has 13 bones:
```text
Head, Spine, Neck,
HipL, HipR,
KneeL, KneeR,
ShoulderL, ShoulderR,
ElbowL, ElbowR,
HandL, HandR
```
Requirements:
- `npc_base` mesh node exists and is skinned under `Rig`.
- Limb node names stay stable for `NpcAvatar` and tests.
- Front is Blender −Y / Godot +Z.
- Feet are grounded at approximately y = 0; no floating origin.
- Units are metres; character height is approximately 1.8 m.
- No zero-area/duplicate degenerate faces in the exported mesh.
- Every character has UVs in [0,1] unless a planned UDIM/atlas change is
explicitly accepted.
## Material slots
Exactly these five tint slots:
```text
skin, cloth, pants, accessory, hair
```
- Neutral albedo is authored in Blender; runtime tinting is authoritative.
- Slot names are contract values; do not rename or add a sixth slot casually.
- The procedural fallback (`NpcAvatar._build_body`) must keep working when the
GLB is missing.
## Budgets
| Asset | Limit |
| --- | --- |
| Library character | 8,000 triangles / 8,000 glTF vertices |
| `npc_base` fallback | 6,000 triangles / 4,000 vertices |
| Gear piece | 1,500 triangles |
| Prop | 4,000 triangles |
| Library prop/venue total | must stay inside the venue tests and library budget |
`CHAR_MAX_TRIS`, `CHAR_MAX_VERTS`, `GEAR_MAX_TRIS`, `PROP_MAX_TRIS` and related
constants live in `tools/blender_assets.py`. The budget is per character, not an
average. Measure after every mesh change. Do not lower the cap to make a diff
pass.
## Gear and hand items
- Gear is authored in the attach-point space documented by `GEAR` /
`GEAR_ATTACH` / `ATTACH_BLENDER`.
- `blender_assets.py fit` measures gear boxes against the character's head band,
clothing radius and back clearance.
- Runtime offsets are intentional:
- `NpcAvatar.GEAR_OFFSETS = {"lanyard": Vector3(0,0,0.04),
"firefighter_helmet": Vector3(0,0.03,0)}`
- `NpcAvatar.HAND_ITEM_FORWARD = Vector3(0,0,0.04)`
- An offset is **not** an authored-asset change; do not bake it into the GLB
without a separate contract decision.
- Open-palm grip (no finger-curl rig) is the accepted stylized limit.
## Textures and AO
- NPC maps are 256² albedo + normal + packed ORM.
- `TEX_SIZE = 256` in `tools/npc_pipeline.py`.
- Fabric UV scale is `Materials.NPC_FABRIC_UV_SCALE = Vector3(4,4,1)`.
- Cloth/pants skip the per-character baked AO; skin/hair/gear keep it. This
avoids the large blotchy magnification that appeared at 256².
- Gear/props may have their own AO maps; `tools/bake_ao.py` writes
`<name>_ao.png` next to the GLB and Godot wires it into the AO channel.
- Never overwrite an existing committed image; use a new `-vN` candidate and
promote only after review.
- Run `python3 tools/asset_pipeline.py audit <path>` and the relevant
`npc_pipeline.py audit` before committing map changes.
## File map
| Path | Role |
| --- | --- |
| `tools/blender_assets.py` | Catalogue, builders, audit/fit/inspect/texels/sheet. |
| `tools/npc_pipeline.py` | `npc_base` builder, texture preparation and audit. |
| `tools/bake_ao.py` | Blender AO bake into `<name>_ao.png`. |
| `tools/npc_proportions.py` | Exported-GLB proportion audit (no Blender). |
| `tools/npc_baseline.py` | Contact sheets/comparisons (no Blender). |
| `assets/models/blender/manifest.json` | Generated asset manifest. |
| `assets/models/blender/**/*.glb` | Committed library assets. |
| `assets/models/generated/npc_base.glb` | Frozen NPC fallback. |
| `assets/textures/npc/*_{albedo,normal,orm}.png` | Committed NPC maps. |
| `src/test/capture_npc_baseline.gd` | 48-frame neutral baseline. |
| `src/test/capture_npc_review.gd` | 96-frame final-context review. |
## Provenance and source rules
- Procedural Blender assets are project-owned.
- External/CC assets need ADR-0015 clearance: CC0 preferred, CC-BY only with
attribution in `CREDITS.md`; no service assets with redistribution limits.
- Editable `.blend` files under `assets/models/blender_src/` are gitignored;
only exported `.glb`, AO maps, manifests and committed preview sheets ship.
- AI-generated source images are development inputs, not shipped assets. Keep
raw generations under gitignored paths until picked, checked and credited.
- Do not copy a downloaded/Street View/AI reference into a committed asset
without the licence and provenance review.
## Verification invariants
The asset pass is not complete unless:
1. `python3 tools/blender_assets.py audit` exits 0.
2. `python3 tools/blender_assets.py fit` exits 0 and the report is reviewed.
3. `python3 tools/npc_proportions.py report` passes the stylized guide.
4. `python3 tools/npc_pipeline.py audit` passes when the base/textures changed.
5. The 48-frame baseline and 96-frame final-context review are captured, with no
blank frames.
6. The 24-NPC budget smoke is inside its frame budget.
7. The full Godot suite and Python tooling tests pass.
8. No runtime contract, renderer or dependency changed except the rebuilt
assets and their generated metadata.
@@ -0,0 +1,123 @@
# Verification Matrix
Run from the repository root. Stop and fix the first failure before continuing.
## Environment
```bash
godot --version
flatpak run org.blender.Blender --version | head -3
git status --short
```
Expected: Godot 4.7.2; Blender 5.2 LTS flatpak; clean or intentionally scoped
working tree. Blender's flatpak cwd is `/app/blender`; all paths passed to
Blender must be absolute.
## Asset checks
| Check | Command | Expected |
| --- | --- | --- |
| Contract audit | `python3 tools/blender_assets.py audit` | exit 0, no `BAD` assets |
| Gear fit | `python3 tools/blender_assets.py fit` | exit 0; review head band/clothing/back clearances |
| Texture/texel density | `python3 tools/blender_assets.py texels` | authored 40–400 px/m; fabric ≥250 px/m |
| 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 |
| 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 |
| 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 |
## Build/bake
```bash
# one library asset
flatpak run org.blender.Blender --background \
--python "$PWD/tools/blender_assets.py" -- build --only npc_guard
# bake AO for the same asset
flatpak run org.blender.Blender --background \
--python "$PWD/tools/bake_ao.py" -- --only npc_guard
# NPC base material maps/base
python3 tools/npc_pipeline.py prepare
flatpak run org.blender.Blender --background \
--python "$PWD/tools/npc_pipeline.py" -- build
python3 tools/npc_pipeline.py audit
```
Only run `prepare` when AI fabric inputs changed. Do not run a full library
rebuild just to hide a small diff; build the affected asset and its AO map.
## Import and tests
```bash
godot --headless --path . --import
godot --headless --path . --script res://src/test/test_runner.gd
python3 -m unittest discover -s tools/tests -t tools -p 'test_*.py'
python3 tools/check_docs.py
```
Expected: Godot suite passes; Python tooling suite passes; docs check OK. The
project uses its own headless runner, not GUT.
## Capture gates
```bash
# neutral studio baseline: 48 frames (8 characters x 6 views)
godot --path . --script res://src/test/capture_npc_baseline.gd \
-- --out=res://tmp/npc-baseline-after
# final-context: 96 frames (8 models x 3 distances x 2 venues x day/night)
godot --path . --script res://src/test/capture_npc_review.gd \
-- --out=res://tmp/npc-review-after
# optional hand close-up: 8 frames
godot --path . --script res://src/test/capture_npc_baseline.gd \
-- --out=res://tmp/npc-hands --view=hand
```
Raw frames stay under `tmp/` and are never committed. Compare with:
```bash
python3 tools/npc_baseline.py compare \
--before tmp/npc-baseline --after tmp/npc-baseline-after \
--out docs/blender_previews/sheet_npc_baseline_compare-<topic>.jpg
```
Do not replace the committed baseline sheet until the change is accepted.
## Budget/performance
```bash
godot --headless --path . --script res://src/test/smoke_npc_budget.gd
```
Expected report: 24 avatars / 168 meshes / 131,616 triangles (current baseline)
inside the 50 ms budget. Record the actual frame time; do not copy an old number
without measuring.
## Visual review criteria
For each changed variant, inspect at least:
- 2 m portrait: eyes, mouth, ears, neck/shoulder, hair/headgear clearance.
- 4.2 m conversation: hands, gear contact, clothing silhouette.
- 8 m crowd: readable silhouette and LOD/impostor swap.
- Day and night/LATE final-context frames.
- Front, three-quarter and profile for a shape change.
Accepted stylized limits are in `docs/npc-visual-review.md` and
`docs/known-limitations.md`. Do not turn an accepted limit into an unreviewed
contract change.
## Final diff review
```bash
git diff --check
git status --short
git diff --stat
```
Check that only intended `.glb`, AO/maps, manifest, tests and docs changed. If a
`.blend`, raw capture, generated AI input or temporary sheet is staged, stop and
remove it. Commit one coherent asset family per commit.
+254
View File
@@ -0,0 +1,254 @@
---
name: gig-line-release-qa
description: Use when preparing, validating, tagging or publishing a Gig Line release or release candidate, or when running the full screenshot, screen-design and realism QA gates. Covers version/changelog discipline, smoke and tooling matrices, clean-worktree builds, checksums, Web/HQ verification, publish rollback and post-release records. Do not use for ordinary asset edits or feature work.
compatibility: Gig Line repository checkout with Godot 4.7.2 on PATH and Python 3 + Pillow. Release export needs installed Godot export templates; publishing needs Gitea credentials and is intentionally CI/tag-driven.
metadata:
project: Gig Line
domain: release-qa
version: "1.0.0"
triggers: release, tag, version bump, changelog, build releases, publish, screenshots, screen design, realism judge, release candidate, deployment
---
# Gig Line Release and Visual QA
Use this skill for version bumps, release candidates, tags, artifact builds,
publishing, and the full visual/QA gate. For a Blender/asset rebuild, use
`gig-line-blender-asset-contract` first.
## Read first
1. `AGENTS.md` — release procedure and verification rules.
2. `TODO.md` — current milestone, blockers and release exit criteria.
3. `docs/RELEASING.md` — build/publish commands, artifact list, macOS policy.
4. `docs/smoke-checklist.md` — manual QA matrix.
5. `docs/development-status.md`, `CHANGELOG.md`, `README.md` — current claims.
6. `MEMORY.md` — release gotchas, especially the clean-worktree rule.
A release is not just a tag. It includes a version bump, changelog, clean build,
artifact/checksum verification, CI publication, and post-release records.
## Hard rules
- **Do not tag while the TODO still lists unresolved release blockers.** Check
external playtest, legal/AI provenance, signing and target-hardware items.
- **Never reuse a tag.** If a published version needs a fix, bump the patch and
create a new tag.
- **Build from a clean checkout or detached worktree.** A normal working tree can
pack ignored `tmp/` and `build/` content into exports, inflating a ~160 MB
package to 1.6–3.1 GB.
- A tag push triggers CI checks/build/publish. Do not upload manually unless CI is
unavailable and you have followed the same preflight.
- Do not commit generated realism runs, raw captures, API keys, or build artifacts.
- The vision judge is an optional development aid with external API cost, never a
hard CI gate.
- Keep `project.godot`, changelog, tag and release asset version equal.
## Release workflow
### 1. Scope and version
Choose the semantic increment from `AGENTS.md`:
- MAJOR: incompatible/removed behavior, save/config contract or core-pillar
replacement.
- MINOR: new user-facing feature/venue.
- PATCH: fixes and polish.
Update:
1. `project.godot` `config/version`.
2. `CHANGELOG.md` with a dated entry.
3. `README.md`, `PLAN.md`, `docs/architecture.md`,
`docs/development-status.md`, `TODO.md`/`DONE.md` and `MEMORY.md` as needed.
For a release candidate, use a prerelease version only if the project policy
allows it and never present it as the final release.
### 2. Preflight
```bash
git status --short
git log --oneline -5
git tag -l "v<version>"
rg -n 'config/version' project.godot
```
Expected: only intended release changes; tag does not exist; version matches the
changelog and planned artifacts.
### 3. Automated checks
```bash
godot --headless --path . --script res://src/test/test_runner.gd
python3 -m unittest discover -s tools/tests -t tools -p 'test_*.py'
python3 tools/check_docs.py
python3 tools/blender_assets.py audit
python3 tools/blender_assets.py fit
python3 tools/npc_proportions.py report
python3 tools/npc_pipeline.py audit
```
Then run the smoke matrix, using isolated user-data directories for
tutorial/campaign:
```bash
for s in smoke_audio smoke_crowd smoke_emergency smoke_npc_budget \
smoke_plan_verbs smoke_scenario_content smoke_session_shell \
smoke_timeline smoke_ui_flows; do
godot --headless --path . --script res://src/test/$s.gd || exit 1
done
XDG_DATA_HOME=/tmp/gig-line-campaign godot --path . --fixed-fps 60 \
--script res://src/test/smoke_campaign.gd
XDG_DATA_HOME=/tmp/gig-line-tutorial godot --path . --fixed-fps 60 \
--script res://src/test/smoke_tutorial.gd -- --tutorial
godot --path . --script res://src/test/smoke_movement.gd
godot --path . --script res://src/test/smoke_props.gd
godot --path . --script res://src/test/smoke_focus_radial.gd
```
Use a fresh `$XDG_DATA_HOME` for campaign/tutorial and check the exit code, not
just the log.
### 4. Visual and judge gates
```bash
./tools/check_screenshots.sh
python3 tools/screen_designs.py compare
python3 tools/screen_designs.py judge --model auto # optional, record model/verdicts
```
For realism/venue work:
```bash
godot --path . --script res://src/test/capture_realism.gd \
-- --out=res://docs/screens/realism/<run>/after
python3 tools/scene_concepts.py realism --run docs/screens/realism/<run> \
--judge --model <pinned-model>
```
Requirements:
- Gallery: 21 good / 0 bad; screen judge has no unexplained `diff`.
- Realism runs stay under `docs/screens/realism/` (gitignored); only decisions and
committed sheets/docs are tracked.
- Record judge model, run count/attempts, spend if exposed, and verdicts.
- Review every `review` verdict; turn layout regressions into fixes or documented
known limitations.
### 5. Clean build
Commit the release preparation first, then build the committed tree in a clean
worktree:
```bash
git worktree add --detach /tmp/gig-line-release HEAD
cd /tmp/gig-line-release
./tools/build_releases.sh
```
Verify artifact names/sizes and checksums:
```bash
ls -lh build/gig-line-*<version>*
PUBLISH_DRY_RUN=1 ./tools/publish_release.sh <version>
sha256sum -c build/SHA256SUMS-<version>.txt
```
Check package contents:
```bash
unzip -l build/gig-line-web-<version>.zip | head
unzip -l build/gig-line-desktop-hq-<version>.zip | head
unzip -p build/gig-line-desktop-hq-<version>.zip override.cfg
```
Check the runnable Linux and HQ packages:
```bash
./build/gig-line-linux-<version>.x86_64 --headless --quit-after 60
unzip -q build/gig-line-desktop-hq-<version>.zip -d /tmp/gig-line-hq
/tmp/gig-line-hq/gig-line-linux-<version>.x86_64 --headless --quit-after 60
```
Check Web static serving with a local HTTP server and `curl` for `index.html`,
`index.wasm`, `index.pck` and the player docs. Windows/macOS/AppImage/installer
checks require the target matrix; if unavailable, record that explicitly.
Remove the worktree after verification:
```bash
git worktree remove --force /tmp/gig-line-release
```
### 6. Tag and publish
Only after every gate is green:
```bash
git add -A
git commit -m "Release <version> <topic>"
git tag -a "v<version>" -m "Gig Line <version>"
git push origin main "v<version>"
```
CI builds and publishes on the tag. Verify:
- Tag commit equals the release commit.
- CI checks/build job succeeded.
- Release page has the expected 7 assets (Linux, Windows, macOS, NSIS setup,
AppImage, Web, Desktop HQ).
- Asset names and download links point at the released version.
`tools/publish_release.sh` preflights artifact size/free space, writes SHA-256
checksums, rolls back newly uploaded assets/release on failure, and supports
opt-in retention:
```bash
RETAIN_RELEASES=7 ./tools/publish_release.sh <version>
```
The default `RETAIN_RELEASES=0` never prunes.
### 7. Post-release record
Update `README.md`, `docs/development-status.md`, `MEMORY.md`, `CHANGELOG.md`,
`TODO.md`/`DONE.md` and the release page with the built/CI result. Commit the
release record and push. Record any skipped check and the reason; never leave a
tag with unreported missing verification.
## Known gotchas
- **Dirty build inflation:** always use the clean worktree above.
- **`Class ... hides a global script class`:** remove stray `.gd` backups in
`tmp/`, delete `.godot`, re-import.
- **Screenshot staging:** `capture_ui_states.gd` needs its settle frames and
staged state; do not capture a paused frame or a stale modal.
- **Judge keys:** OpenRouter calls need `.env`/`OPENROUTER_API_KEY`; never commit
the key or generated review folders.
- **Tag builds publish automatically:** do not run a second manual publish that
duplicates assets; use the existing release if re-running.
- **macOS is unsigned unless policy changes:** record quarantine instructions and
do not claim notarization.
- **Release blockers are real:** external playtest, legal/storefront approval,
AI-provider rights and target-hardware checks cannot be replaced by green unit
tests.
## Bundled references
- [`references/release-runbook.md`](references/release-runbook.md) — version,
build, tag, CI, publish and rollback sequence.
- [`references/qa-gates.md`](references/qa-gates.md) — full command matrix,
expected results and evidence to record.
## Completion checklist
- [ ] Version/changelog/docs/tag match.
- [ ] No unresolved release blockers for a final tag.
- [ ] Godot, Python, smokes, asset and docs gates pass.
- [ ] Gallery and optional judges reviewed with evidence.
- [ ] Clean worktree build and checksums verified.
- [ ] Linux/HQ/Web checks recorded; unavailable platforms noted.
- [ ] Tag pushed; CI/publish succeeded; seven assets verified.
- [ ] Release record and durable docs committed.
@@ -0,0 +1,171 @@
# QA Gate Matrix
Run from the repository root. Capture the exit code and the useful report line,
not only the final “OK”.
## 1. Source and docs
```bash
git status --short
git log --oneline -5
python3 tools/check_docs.py
git diff --check
```
Expected: intended files only; `check_docs: OK`; no whitespace errors.
## 2. Automated suites
```bash
godot --headless --path . --script res://src/test/test_runner.gd
python3 -m unittest discover -s tools/tests -t tools -p 'test_*.py'
```
Expected: the current Godot suite passes (historically 272+ tests) and the
Python tooling suite passes (currently 160 tests). Do not edit expected counts
without inspecting the suite.
## 3. Asset gates
```bash
python3 tools/blender_assets.py audit
python3 tools/blender_assets.py fit
python3 tools/blender_assets.py texels
python3 tools/npc_pipeline.py audit
python3 tools/npc_proportions.py report
```
Expected: exit 0, no `BAD` assets, all eight characters inside the stylized
proportion guide, no budget/contract regression.
Optional texture/model audits when those assets changed:
```bash
python3 tools/asset_pipeline.py audit assets/textures/ai/game_ready
python3 tools/model_pipeline.py audit
```
## 4. Smoke matrix
Headless:
```bash
for s in smoke_audio smoke_crowd smoke_emergency smoke_npc_budget \
smoke_plan_verbs smoke_scenario_content smoke_session_shell \
smoke_timeline smoke_ui_flows; do
echo "== $s"
godot --headless --path . --script res://src/test/$s.gd || exit 1
done
```
Isolated data:
```bash
rm -rf /tmp/gig-line-release-campaign /tmp/gig-line-release-tutorial
XDG_DATA_HOME=/tmp/gig-line-release-campaign godot --path . --fixed-fps 60 \
--script res://src/test/smoke_campaign.gd
XDG_DATA_HOME=/tmp/gig-line-release-tutorial godot --path . --fixed-fps 60 \
--script res://src/test/smoke_tutorial.gd -- --tutorial
```
Windowed input/UI:
```bash
godot --path . --script res://src/test/smoke_movement.gd
godot --path . --script res://src/test/smoke_props.gd
godot --path . --script res://src/test/smoke_focus_radial.gd
```
Expected: 14 smokes green. `smoke_npc_budget` should print the current mesh,
triangle and frame-time evidence; the 50 ms budget is a safety ceiling.
## 5. Screenshot and design gates
```bash
./tools/check_screenshots.sh
python3 tools/screen_designs.py compare
```
Expected: 21 good / 0 bad; no unexplained compare output. The judge is optional:
```bash
python3 tools/screen_designs.py judge --model auto
```
Record model, confidence/verdict summary, `diff` count and any manual review.
`docs/screen-design-report.md` is the durable record.
## 6. Realism judge (when visual assets/lighting changed)
```bash
godot --path . --script res://src/test/capture_realism.gd \
-- --out=res://docs/screens/realism/<run>/after
python3 tools/scene_concepts.py realism --run docs/screens/realism/<run> \
--judge --model <pinned-model>
```
Expected: a complete set of required stage captures, no stale pauses/modals, and
a report under the gitignored run directory. Record:
- pinned model/fallbacks
- shots attempted/completed
- spend if the tool exposes it
- mean score and Pillow gap
- every `review` verdict and disposition
Do not commit the run. Commit only the decision/report summary and any durable
comparison sheet explicitly intended for review.
## 7. Package checks
Use the clean worktree from `release-runbook.md`.
```bash
PUBLISH_DRY_RUN=1 ./tools/publish_release.sh <version>
sha256sum -c build/SHA256SUMS-<version>.txt
unzip -l build/gig-line-web-<version>.zip
unzip -l build/gig-line-desktop-hq-<version>.zip
unzip -p build/gig-line-desktop-hq-<version>.zip override.cfg
./build/gig-line-linux-<version>.x86_64 --headless --quit-after 60
```
Expected:
- names match the version;
- Linux/Windows/macOS/Web/HQ present locally;
- Web contains HTML/wasm/pck and docs;
- HQ contains `override.cfg`, executables and docs;
- Linux/HQ start without a script error.
Windows installer/AppImage and macOS launch require CI or the target matrix. If
not available, mark them as unverified rather than passing them by analogy.
## 8. Release verification
After the tag push:
- CI checks job succeeded;
- CI build job succeeded;
- release page has seven assets;
- each artifact name includes the released version;
- the tag points at the release commit;
- README/changelog/status and release notes agree.
## Evidence summary template
```text
Release: vX.Y.Z (commit)
Godot: pass (N/N)
Python tooling: pass (N/N)
Smokes: 14/14
Assets: audit/fit/proportions/npc audit pass
Budget: 24 NPCs, N meshes, N triangles, N ms
Gallery: 21 good / 0 bad
Screen judge: N/N match, N review, N diff
Realism: model, attempts, mean, gap, review dispositions
Build: clean worktree, artifact sizes/checksums
Launch: Linux/HQ/Web checks; unverified platforms
CI/release: run id, seven assets
Skipped checks + reason:
Known limitations:
```
@@ -0,0 +1,171 @@
# Release Runbook
Authoritative source: `AGENTS.md` (Release Procedure), `docs/RELEASING.md`.
This runbook is the operational checklist; if it conflicts with those, they win.
## 0. Decide the release
Determine whether this is a major, minor, patch, or release-candidate checkpoint.
Do not assign a version to an unversioned development track until its scope is
accepted into a release.
Check the current release constraints in `TODO.md`:
- external playtest evidence
- legal/AI-provider/storefront review
- macOS signing/notarization policy
- target hardware matrix
- any manual QA gaps
If a final release would ship with unresolved blockers, prepare an RC or stop;
do not create a final tag.
## 1. Prepare the version
Files to update:
| File | Change |
| --- | --- |
| `project.godot` | `config/version="X.Y.Z"` |
| `CHANGELOG.md` | dated `## [X.Y.Z] — YYYY-MM-DD` entry |
| `README.md` | latest/current version and summary |
| `PLAN.md` / `docs/architecture.md` | current version reference |
| `docs/development-status.md` | shipped capability/release result |
| `TODO.md` / `DONE.md` | close/move milestone when complete |
| `MEMORY.md` | release state and gotchas |
Then commit the preparation before building.
## 2. Run gates
Use `qa-gates.md` as the command matrix. The minimum release gate is:
- Godot suite
- Python tooling tests
- documentation gate
- asset audits/fit/proportions
- 24-NPC budget
- 14 smoke scripts
- screenshot compare
- optional screen/realism judge with recorded model/attempts
- clean build and artifact checks
Any skipped check must be written into `MEMORY.md` or `TODO.md` with a reason.
## 3. Build in a clean worktree
Why: Godot can pack ignored `tmp/` captures and `build/` outputs into the export.
A dirty build can produce 1.6–3.1 GB artifacts instead of ~160–240 MB.
```bash
git worktree add --detach /tmp/gig-line-release HEAD
cd /tmp/gig-line-release
./tools/build_releases.sh
```
Expected local artifacts:
```text
build/gig-line-linux-<version>.x86_64
build/gig-line-windows-<version>.exe
build/gig-line-macos-<version>.zip
build/gig-line-web-<version>.zip
build/gig-line-desktop-hq-<version>.zip
```
CI additionally produces the NSIS setup and AppImage, for seven release assets.
## 4. Preflight and checksum
```bash
PUBLISH_DRY_RUN=1 ./tools/publish_release.sh <version>
sha256sum -c build/SHA256SUMS-<version>.txt
```
`publish_release.sh` checks non-empty artifacts and local free space before any
API call. It writes checksums under `build/` and does not upload them by default.
Inspect packages:
```bash
unzip -l build/gig-line-web-<version>.zip | head -20
unzip -l build/gig-line-desktop-hq-<version>.zip | head -20
unzip -p build/gig-line-desktop-hq-<version>.zip override.cfg
```
Web must contain `index.html`, `index.wasm`, `index.pck`, and player docs.
Desktop HQ must contain the Linux/Windows executables, `override.cfg`,
`README.txt`, `CREDITS.md`, `PRIVACY.md`, `accessibility.md` and
`known-limitations.md`.
## 5. Runtime launch smoke
```bash
./build/gig-line-linux-<version>.x86_64 --headless --quit-after 60
unzip -q build/gig-line-desktop-hq-<version>.zip -d /tmp/gig-line-hq
/tmp/gig-line-hq/gig-line-linux-<version>.x86_64 --headless --quit-after 60
```
For Web, serve `build/web` and check HTTP 200 for `index.html`, `index.wasm`,
`index.pck` and docs. Do not claim browser interaction from a curl check.
Remove the temporary worktree after checks:
```bash
git worktree remove --force /tmp/gig-line-release
```
## 6. Tag and CI
From the real repository, on the release commit:
```bash
git tag -a "v<version>" -m "Gig Line <version>"
git push origin main "v<version>"
```
The tag triggers `.gitea/workflows/build.yml`: checks, export, installer,
AppImage, release publish. Verify the run and assets:
- check job success
- build job success
- seven assets present
- tag/version/artifact names equal
- download links respond
Do not reuse a published tag. If a fix is needed, bump patch and repeat.
## 7. Manual publish fallback
Only if CI cannot publish:
```bash
RETAIN_RELEASES=7 ./tools/publish_release.sh <version>
```
The script reuses or creates the release, uploads present artifacts, verifies the
asset count, and rolls back newly uploaded assets/new release on failure. The
default retention is `0` (no deletion). Never delete older releases implicitly.
## 8. Post-release record
Commit a release record that includes:
- tag and commit
- CI run/result
- seven assets
- checksums or size evidence
- skipped checks
- known limitations
- release URL
Update `DONE.md`/`TODO.md` and `MEMORY.md`; keep the changelog and README
consistent. Push the record commit.
## Versioning pitfalls
- A `vX.Y.Z` tag is immutable in practice; do not move/replace it.
- A release candidate is not a final release. Do not label it as such in the
README or release page if blockers remain.
- `config/version` must match the tag and artifact filenames.
- The clean-worktree build is a release requirement, not an optimization.
+19 -12
View File
@@ -17,12 +17,13 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install Godot 4.3
- name: Install Godot 4.7.2
run: |
curl -sL -o /tmp/godot.zip \
https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_linux.x86_64.zip
https://github.com/godotengine/godot/releases/download/4.7.2-stable/Godot_v4.7.2-stable_linux.x86_64.zip
unzip -qo /tmp/godot.zip -d /tmp/godot-bin
sudo install /tmp/godot-bin/Godot_v4.3-stable_linux.x86_64 /usr/local/bin/godot
sudo install /tmp/godot-bin/Godot_v4.7.2-stable_linux.x86_64 /usr/local/bin/godot
godot --version
- name: Import project (build class cache)
run: godot --headless --import
@@ -56,18 +57,24 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install Godot 4.3 and export templates
- name: Install Godot 4.7.2 and export templates
run: |
curl -sL -o /tmp/godot.zip \
https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_linux.x86_64.zip
https://github.com/godotengine/godot/releases/download/4.7.2-stable/Godot_v4.7.2-stable_linux.x86_64.zip
unzip -qo /tmp/godot.zip -d /tmp/godot-bin
sudo install /tmp/godot-bin/Godot_v4.3-stable_linux.x86_64 /usr/local/bin/godot
sudo install /tmp/godot-bin/Godot_v4.7.2-stable_linux.x86_64 /usr/local/bin/godot
godot --version
TEMPLATE_DIR="$HOME/.local/share/godot/export_templates/4.7.2.stable"
NEEDED="version.txt icudt_godot.dat linux_release.x86_64 windows_release_x86_64.exe macos.zip web_nothreads_release.zip web_dlink_nothreads_release.zip"
curl -sL -o /tmp/templates.tpz \
https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_export_templates.tpz
mkdir -p ~/.local/share/godot/export_templates/4.3.stable
cd /tmp && rm -rf tpl && mkdir tpl && unzip -qo templates.tpz -d tpl
cp tpl/templates/* "$HOME/.local/share/godot/export_templates/4.3.stable/" 2>/dev/null || true
ls "$HOME/.local/share/godot/export_templates/4.3.stable" | head -5
https://github.com/godotengine/godot/releases/download/4.7.2-stable/Godot_v4.7.2-stable_export_templates.tpz
rm -rf /tmp/tpl && mkdir -p /tmp/tpl
unzip -qo /tmp/templates.tpz -d /tmp/tpl
mkdir -p "$TEMPLATE_DIR"
for file in $NEEDED; do
cp "/tmp/tpl/templates/$file" "$TEMPLATE_DIR/$file"
done
ls "$TEMPLATE_DIR" | head -10
- name: Import project
run: godot --headless --import
@@ -114,7 +121,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
env:
# An explicit repo secret wins. Without one, publish_release.sh reuses
# the local git extraheader that actions/checkout configures for the job
# the local git extraheader that actions/checkout configures on the job
# (the runner's own credentials), so tag builds still publish.
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
VERSION: ${{ github.ref_name }}
+19
View File
@@ -37,6 +37,7 @@ Thumbs.db
*.new
# Logs and temp
tmp/
*.log
user://
# CI runner workflow logs (written into the checkout by the Gitea runner)
@@ -57,6 +58,20 @@ assets/models/generated/npc_base_*_normal.png
# Local AppImage extraction
squashfs-root/
# Local agent skills. The third-party Blender pack is installed per machine and
# stays ignored; the two project-authored skills are tracked so the workflow can
# be shared and reviewed.
.agents/
!.agents/
.agents/*
!.agents/.gdignore
!.agents/skills/
.agents/skills/*
!.agents/skills/gig-line-blender-asset-contract/
!.agents/skills/gig-line-blender-asset-contract/**
!.agents/skills/gig-line-release-qa/
!.agents/skills/gig-line-release-qa/**
# Blender asset library: per-asset Cycles renders are local review output (the
# committed review artefacts are docs/blender_previews/sheet_*.jpg), and Godot
# extracts the GLB-embedded textures next to the models on import.
@@ -73,3 +88,7 @@ assets/models/blender_src/*.blend
assets/textures/ai/game_ready/v2/*
!assets/textures/ai/game_ready/v2/picks/
docs/screens/realism/
## Local pi agent session data
pi-session-*.html
pi-session-*.jsonl
+146 -19
View File
@@ -19,10 +19,93 @@ The project should be developed in small, reviewable steps. Agents must keep pla
- `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/asset-tooling.md` — Asset pipelines, model tools, and screenshot gates.
- `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/smoke-checklist.md` — The manual smoke-test checklist.
- `docs/adr/` — Architecture Decision Records.
## Priority and Versioning
`TODO.md` is the priority source. The active order is:
1. unversioned development tracks;
2. versioned release milestones, in version order;
3. future/unversioned candidates and deferred work.
Do not assign a version number to an unversioned track until its scope is accepted into a
release. When a version is assigned, it must be the next semantic version that matches the
scope (major/minor/patch), not a placeholder.
## Local Blender skills (development aid)
The MIT `blender-skills` pack is installed locally at `.agents/skills/blender-skills/`
(94 skills plus a shared `references/` folder). It is **gitignored on purpose**: it is not
game content, not part of the build, tests, CI or release packages, and every contributor
may or may not have it. The agent runtime (Pi) gates project `.agents/skills` behind project
trust, so the trust prompt must be accepted before the skills load; `/skill:blender-director`
forces one.
Use them when a task touches `tools/blender_assets.py`, `tools/npc_pipeline.py`,
`tools/bake_ao.py`, `assets/models/blender/**`, or Blender-authored venue/prop geometry.
### Project-authored skills (tracked)
Two project skills are committed under `.agents/skills/` and are guidance for
any agent, not only Pi:
- `gig-line-blender-asset-contract` — the NPC/gear/prop contract, build order,
headless Blender commands, failure playbook and verification gates.
- `gig-line-release-qa` — the test/smoke matrix, visual judges, clean-worktree
build, checksum/tag/publish sequence and release records.
They are not runtime dependencies and do not move tool implementations into
`.agents/`. The third-party `blender-skills` pack stays local/gitignored.
### Routing
Start with `blender-director` for anything multi-discipline, then load only the
specialists the task needs:
| Work | Skills |
| --- | --- |
| Plan/route a Blender task, reference photo match | `blender-director` |
| Character mesh, face, hands, proportions | `character-artist`, `character-archetypes`, `retopology` |
| Skeleton, weights, pose/contract rigging | `rigging` |
| UV layout, texel density, UDIM | `uv-workflow` |
| Bakery, atlas, normal/AO/ORM maps | `texture-workflow` |
| PBR materials and shading | `materials` |
| Screenshot-driven material/light iteration | `lookdev` |
| Triangle/vertex budgets, naming, scene hygiene | `asset-optimization` |
| LOD tiers and impostors | `lod-pipeline` |
| Collision shapes and proxies | `collision-proxy` |
| GLB export for Godot | `godot-export`, `export-pipeline` |
| Gear, props, street furniture | `prop-artist`, `hard-surface` |
| Trees, hedges, foliage cards | `vegetation-artist` |
| Facades, venues, modular kits, dressing | `environment-artist`, `archviz`, `set-dressing`, `geometry-nodes` |
| Lighting, framing, final render/composite | `lighting`, `camera-cinematography`, `rendering`, `compositing` |
| Style direction (stylized / low-poly / realistic) | `stylized-style`, `lowpoly-style`, `realistic-style` |
| Ship/no-ship review | `qa-review` |
### Project rules that override a skill
1. **The project contract wins.** `npc_base` mesh node, the 13 contract bones at their rest
positions, five tint slots, front on Blender −Y / Godot +Z, grounded feet, 8,000
triangles / 8,000 glTF vertices per character, 1,500 per gear, 4,000 per prop. 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. **There is no Blender MCP here.** The skills assume MCP tools; this project runs Blender
as headless flatpak scripts (`tools/blender_assets.py`, `tools/npc_pipeline.py`). Translate
MCP steps into script changes or CLI runs. Never add an MCP dependency to the build, CI or
runtime.
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.
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
provenance rules in `docs/npc-model-sourcing.md` still apply.
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
skills informed the change and what the measurements were.
## Required Workflow
### 1. Start by Inspecting the Repository
@@ -47,8 +130,11 @@ All planned implementation work must be represented in `TODO.md`.
- Use level-2 headings (`##`) for milestones or stable themes. A milestone should state its goal
and an explicit exit criterion; a theme groups small related work that does not need a
milestone.
- Use level-3 headings (`###`) for workstreams inside a milestone (for example, `0.8.1
First-shift tutorial`). Keep the hierarchy to three levels: milestone → workstream → checkbox.
- Order level-2 sections by current priority: unversioned development tracks first, then
versioned release milestones in version order, then future/deferred work. Do not assign a
version number to an unversioned track until it is scheduled for a release.
- Use level-3 headings (`###`) for workstreams inside a milestone. Keep the hierarchy to three
levels: milestone → workstream → checkbox.
- Use checkbox items for each concrete task, normally under a level-3 workstream. Direct
checkboxes under a level-2 section are acceptable for small, single-workstream sections.
- Keep tasks small enough to complete and review independently.
@@ -67,26 +153,31 @@ Recommended format:
```md
# TODO
## Milestone 0.8.0 — First hour and content depth
## Milestone X.Y.Z — goal and exit criterion
### 0.8.1 First-shift tutorial
### Workstream A
- [x] Write the tutorial beat sheet.
- [-] Implement the tutorial state machine. (claimed; implementation/verification in progress)
- [ ] Add the tutorial smoke test.
- [x] Completed task.
- [-] Task in progress. (claimed; implementation/verification in progress)
- [ ] Open task.
### 0.8.2 Campaign shell
### Workstream B
- [ ] Define campaign data.
- [ ] Persist campaign progress.
- [ ] Another task.
**Exit:** a first-time player completes a shift without external documentation.
**Exit:** a verifiable statement of what "done" means for the milestone.
## Documentation
## Theme
- [ ] Document the test procedure in `README.md`.
- [ ] A small housekeeping task with no milestone.
```
Recommended `DONE.md` format: a `# DONE` heading, followed by each moved level-2 section
unchanged, with a completion date line under each section heading.
Order the actual file as described in "Priority and Versioning": unversioned development tracks
first, then versioned milestones in version order, then future/deferred work.
### 3. Move Completed Sections to `DONE.md`
`TODO.md` uses nested sections. Apply these completion rules:
@@ -174,10 +265,12 @@ Update `README.md` whenever setup instructions, commands, project structure, or
The root-level `CHANGELOG.md` records the user-facing changes for every release. It exists so players and contributors can see what changed between versions without reading the commit log.
- Add an entry for every patch, minor, and major version change.
- Follow Semantic Versioning (`major.minor.patch`): bump major for breaking or vision-level changes, minor for new features, and patch for fixes and small polish.
- Follow Semantic Versioning (`major.minor.patch`): bump major only for changes that remove or break an existing player-facing feature, save format, or config contract, or that replace a system listed as a core pillar in PLAN.md; bump minor for new user-facing features; bump patch for fixes and small polish.
- A version increment is not complete until its `CHANGELOG.md` entry, `config/version` update and
matching local release build have all been produced and verified.
- Group each entry under `Added`, `Changed`, `Fixed`, and `Removed` headings, and date every entry.
- Describe changes in user-facing terms, not as raw commit messages.
- Add the entry when its work ships. If the version number is not yet known, record it under `Unreleased` and move it into a numbered version when the version is tagged.
- Add the entry when its work ships. If the version number is not yet known, record it under `Unreleased` as a staging entry; moving it into a numbered version is a version increment and is complete only when the `config/version` update and matching local release build have also been produced and verified (see "Commit, Build and Version Discipline").
When a full executable level-2 milestone/theme in `TODO.md` is implemented, completed, and moved to `DONE.md`:
@@ -198,13 +291,41 @@ For each task:
5. Update documentation if the change creates lasting context: add features to `docs/development-status.md`, adjust the README links or other `docs/` files as needed, and record decisions/gotchas in `MEMORY.md`.
6. Mark the task complete.
7. Keep completed level-3 workstreams in place until the parent milestone is complete; then move the entire level-2 milestone to `DONE.md` after recording its `CHANGELOG.md` entry.
8. Commit the changes to the version control system with a descriptive message.
9. If a level-2 milestone from `TODO.md` has been fully completed, ensure its `CHANGELOG.md` entry is in place, then create a new version or tag in the version control system to mark the milestone.
10. Push the committed changes to the shared repository if applicable.
11. Note the new version or tag created in the version control system in any relevant documentation or handoff summary.
8. Commit each coherent, verified change as it is completed. Do not wait until a whole
milestone is finished; keep commits small and descriptive and push them when the branch is
shareable.
9. If the change is a release (a major, minor, or patch version increment), follow the Release
Procedure in "Commit, Build and Version Discipline".
10. Push committed changes to the shared repository if applicable.
11. Note the new version, build artifacts or tag in the relevant documentation and handoff
summary.
Avoid unrelated refactors unless they are required for the task or explicitly approved.
### 9. Commit, Build and Version Discipline
- **Commit often.** Make a commit after each coherent change or verified task, not only when a
milestone or release is finished. Keep the working tree clean between tasks where possible.
- **Push regularly.** Push shareable commits to the shared repository; do not accumulate a long
local-only history.
- **Build every version.** Every major, minor and patch increment follows the Release Procedure
below; a changelog entry alone is not a release.
- **CI is a second gate, not a substitute.** Tag builds remain automated, but the local build is
the required pre-tag check for every version.
- **Unversioned tracks stay unversioned.** A development track does not get a placeholder
version; the version is assigned when the work is scoped into a release.
**Release Procedure** — follow these steps in order for every major, minor, or patch version
increment:
1. Update `config/version`.
2. Write the `CHANGELOG.md` entry for the version.
3. Run the local release build (`tools/build_releases.sh`).
4. Verify the build artifacts and checksums.
5. Commit the changes.
6. Tag the version.
7. Push the commit and tag.
## Collaboration Rules
- Prefer clear, reversible changes over broad rewrites.
@@ -213,6 +334,8 @@ Avoid unrelated refactors unless they are required for the task or explicitly ap
- Do not claim a task is complete without verification.
- Keep documentation synchronized with the implementation.
- Mention files changed and checks run in handoff summaries.
- Commit coherent increments as you go; do not wait until the end of a milestone.
- Follow the Release Procedure in "Commit, Build and Version Discipline" whenever a major, minor or patch version is incremented.
- Leave follow-up work as checkbox items in `TODO.md` rather than hiding it in prose.
- If blocked, document the blocker in `MEMORY.md` and add a corresponding task to `TODO.md`.
- Avoid overwriting another contributor’s work; inspect recent changes before editing shared files.
@@ -224,6 +347,10 @@ Before handing off work, an agent must run every check below that applies to the
- Run automated tests.
- Run formatting, linting, or type checks.
- Start the game or application and perform a basic smoke test.
- For 3D/Blender changes: `blender_assets.py inspect`, `audit` and `fit`, the relevant capture
gate, and a note of which local skills informed the change.
- If the local Blender skills are not installed, state "no local skills used" in the handoff
and list the skill-free checks that were run instead.
- Check that documentation and task status reflect the actual state.
- Review the final diff for accidental changes or unfinished markers.
+205 -6
View File
@@ -4,6 +4,211 @@ All notable user-facing changes to Gig Line. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use
Semantic Versioning.
## [0.9.7] — 2026-11-04
### Added
- Character development: a more realistic face on all eight library characters
(higher head subdivision, brow ridge, both eyelids, fuller lower lip) and four
crowd-variety characters (`npc_crowd_a`–`d`) with deterministic per-role pools
so the routine crowd no longer clones a single look.
### Fixed
- The NPC pose harness now captures seated, walking and idle deformation (the
harness froze the avatar's process so the FSM could no longer revert the pose).
## [0.10.0-rc.1] — 2026-11-03
### Changed
- Prepared the release-candidate packages for Linux, Windows, macOS, Web and the
optional Desktop HQ zip. The Web/HQ archives now include the credits, privacy,
accessibility and known-limitations pages, and release publishing writes
SHA-256 checksums and rolls back partial uploads.
### Fixed
- Corrupt or partial save files now fall back to safe defaults without logging a
parse error, and a failed atomic replace leaves the previous valid settings
intact. Added migration coverage for 0.8-era profiles and 0.9.0 checkpoints
across all three venues.
## [0.9.6] — 2026-11-02
### Changed
- The UI, model and texture passes are now integrated into a verified checkpoint
build. The gallery, NPC portraits, realism captures, asset audits, smoke
matrix, 24-NPC budget and Web/Desktop HQ packages were regenerated and
re-checked; no gameplay or content changes were introduced.
## [0.9.5] — 2026-11-02
### Changed
- NPC albedo, normal and ORM maps are now 256² instead of 128², giving the
face, fabric and gear materials more detail at portrait distance.
- NPC fabric maps now repeat four times across the body and skip the baked
per-character AO, removing the large blotchy patches from shirts, jackets and
trousers at portrait distance.
- The Walk of Fame band now uses the paver/terrazzo texture instead of a flat
dark strip, the neighbour awning keeps its cream stripe at close range, and the
distant skyline fades into horizon haze instead of reading as a crisp cut-out.
## [0.9.4] — 2026-11-02
### Changed
- Character meshes now have an 8,000-triangle / 8,000-vertex library budget
(up from 6,000 / 5,000) to support the remaining face, grip and clothing
detail; the `npc_base` fallback keeps its original 6,000 / 4,000 contract.
- Character necks are fuller and shoulders slope from the neck instead of
reading as a thin post on a hanger.
- Character eyes sit in the socket behind a thin upper lid, and the chin and
cheekbones are sculpted into the head instead of reading as separate spheres.
- Character torsos taper from the chest to the waist and flare slightly into
the hips, so shirts and jackets sit less like straight tubes.
- Character hands now have a wider palm, bent fingers with rounded tips and a
separate thumb instead of flat bars.
- Character noses gained ala wings and a smaller bridge/tip, the mouth is a
rounded slot, and the ears have a darker inner concha.
- Hand-held items now sit forward in the open palm instead of being partly
buried inside the hand.
- Shoes and responder boots now have rounded toes and heels instead of reading
as flat blocks.
### Fixed
- Tourists no longer appear to carry two backpacks: the photo-pose camera bag is
hidden except while they are actually taking a picture.
- Backpack shoulder straps are now flattened bands that sit outside the chest
instead of thin rods partly buried in the torso.
## [0.9.3] — 2026-11-02
### Added
- Eight committed gallery captures now cover the start menu, pause, settings,
shift review, emergency radio, action wheel, crowd panic and responder
arrival, so every screen-design reference has a deterministic current-build
capture.
### Changed
- All session, settings, radio, Help, review and timeline action buttons now
share explicit normal, hover, pressed, focus and disabled styles. Timeline
Previous/Next disable at their boundaries.
- Keyboard and gamepad focus rings now wrap through every modal, with explicit
default focus and A/B accept/cancel bindings; hidden Continue and disabled
timeline controls are skipped.
- Esc and right-click now close the top panel consistently, restore the venue
menu after menu Settings, and recapture the mouse without leaving the guard
frozen. Help no longer opens Pause behind itself.
- The HUD target chip grows and caps with the selected font scale, top labels
clip safely, and long action-button labels ellipsize instead of pushing panels
wider than the screen.
- Campaign reset and tutorial replay now share a guarded confirmation dialog;
repeated presses cannot stack dialogs or fire the action twice. Emergency
hold-to-send still submits once and ignores the release after submission.
- NPC reason answers now use role-specific dialogue contexts, so staff, guests,
delivery workers, performers and other archetypes answer instead of silently
deflecting the Reason verb.
- Hollywood and the TV Studio now have door-staff lines for their wristband,
ticket, credential and guest-list checks.
- Clarified the emergency radio cancellation message, the Ask verb in Help/HUD
hints, the start-menu key hint and the campaign-reset confirmation.
## [0.9.2] — 2026-11-02
### Added
- A stylized reconstruction of the real El Capitan Entertainment Centre block at
6840 Hollywood Boulevard: six Ionic columns, eight stone steps, the Masonic
parapet motto and medallions, red event banners, central and side entrances,
the El Capitan Theatre and retail neighbours, the Ovation Hollywood arcade,
the Walk of Fame and four-lane street markings.
- Real-site reference notes and AI art-direction frames for the Hollywood block.
- The Joke Shop is now a Sunset Strip comedy-club block based on The Comedy Store at
8433 Sunset Blvd: a 27 m black hall under a signature-covered canopy, a circular
**THE JOKE SHOP** blade sign, red velvet curtain bays, a west-end entrance, cream
and white neighbours, a dry hillside of stucco houses and palms, and a low retail
and billboard frontage across Sunset.
- Real-site reference notes and AI art-direction frames for the Joke Shop block,
including day, night and across-the-street concepts.
- A separate day capture stage for the Joke Shop, so the club can be compared in
both lighting states.
- Completed the UI string migration: start menu, settings, pause, Help, HUD,
radio and shift-review labels now come from the English string table.
- Separate **Hollywood Boulevard — Day** and **Hollywood Boulevard — Night**
rows in the venue menu. Night starts directly in the late phase and keeps the
full shift after dark.
### Changed
- The default Hollywood venue is no longer a generic brick entrance; the venue
name and policy copy now identify the El Capitan Entertainment Centre.
- Night lighting on the landmark uses low-energy practical washes rather than
the street-lamp brightness, so the facade reads without blowing out.
- The Joke Shop lighting now uses warm sconces, canopy downlights and ground
uplights; the hillside stays a dark silhouette at night instead of a lit wall.
- The Joke Shop venue copy and gallery captions describe the Sunset Strip block,
not the old brick alley.
### Fixed
- The Ovation Hollywood sign faces the boulevard instead of rendering mirrored.
- Local night lights use linear attenuation instead of constant falloff, so
sconces and street lamps pool on the ground and stone rather than washing the
whole facade flat and yellow.
- The captured HUD clears stale objectives and target prompts between staged
screenshots.
- The Joke Shop beauty capture closes a stale wand/inspection panel before shooting
and frames the entrance and sign instead of a nearby target.
- Multiline HUD and wand strings now render real line breaks instead of a literal
`\n`; the wand panel also lists the guest's carried items.
- Final-context NPC gear fit: authorized-guest lanyards now show their badge in
front of the jacket, and responder helmets sit above the eyes instead of across
the brow.
## [0.9.1] — 2026-11-02
### Changed
- Unified the project, local tooling and CI/release environment on Godot 4.7.2,
including matching export templates and project feature metadata.
- Evaluated Gitea Actions cache for the Godot binary/templates; disabled it after
cache saves timed out or were rejected, pending a healthy host cache service.
- Updated release and setup documentation for the 4.7.2 baseline.
## [0.9.0] — 2026-11-01
### Added
- Master/SFX/ambience volume controls, a captions toggle and a Reduce motion
option in Settings.
- A 24-NPC performance budget smoke and expanded audio/accessibility coverage.
- Explicit production-quality scope for animation, audio, localization and the
Godot 4.3-CI/4.7-local support matrix.
### Changed
- Coat and dress strides are shortened to reduce hem/leg clipping.
- Help and the shift-review surface expose the current policy and accessibility
settings.
### Fixed
- Audio cue volumes now respect the player's mixer settings while remaining
headless-safe.
## [0.8.1] — 2026-11-01
### Fixed
- Normal radio reports now send on click. The button was wired only to the
emergency hold-to-confirm handlers, so a non-emergency press did nothing.
## [0.8.0] — 2026-11-01
### Added
@@ -87,12 +292,6 @@ Semantic Versioning.
## [0.7.0] — 2026-11-01
### Changed
- Agent workflow now requires maintaining this changelog for every patch, minor, and major version.
## [Unreleased]
### Added
- Distinct venue geometry: the Joke Shop is now a narrow brick alley with a
+104
View File
@@ -0,0 +1,104 @@
# Credits and Provenance
Gig Line is a local, single-player security-shift game built with Godot 4 and
GDScript. This file records the asset provenance needed for the release review;
it is not a legal opinion or a claim that every provider licence has already
been cleared for public distribution.
## Game, code and design
- Game design, systems, scenarios, dialogue and UI code: the Gig Line project.
- Engine: [Godot Engine 4.7.2](https://godotengine.org/), MIT licence.
- The game bundles no third-party fonts; it uses Godot's default theme font.
- No third-party music, sound effects or voice recordings are bundled. Crowd
ambience and radio cues are generated procedurally at runtime.
## 3D characters, gear and props
The character, gear and prop library is generated by project-owned scripts
(`tools/blender_assets.py`, `tools/npc_pipeline.py`, `tools/model_pipeline.py`)
using Blender. The `.blend` working sources are developer artifacts and are not
shipped. The exported `.glb` files and baked AO maps are project assets; they do
not embed third-party models or purchased asset packs.
### Development-only research previews
Two review sheets under `docs/blender_previews/` compare source candidates for
the future NPC variety work. They are development documentation and are not
bundled with the game or its release packages. The underlying source models are
kept outside version control.
- [`sheet_npc_sources_research.jpg`](docs/blender_previews/sheet_npc_sources_research.jpg)
includes renders of **Quaternius Ultimate Modular Characters** (CC0 1.0,
<https://quaternius.com/>), **Kenney Blocky Characters 2.0** (CC0 1.0,
<https://kenney.nl/assets/blocky-characters>), and **CesiumMan** and
**RiggedFigure** (CC BY 4.0, © 2017 Cesium,
<https://github.com/KhronosGroup/glTF-Sample-Assets>).
- [`sheet_npc_crowd_prototypes.jpg`](docs/blender_previews/sheet_npc_crowd_prototypes.jpg)
contains renders of project-owned generator prototypes.
The CC-BY preview images are included only as development evidence. Any future
shipped derivative of CesiumMan or RiggedFigure must keep the attribution above.
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
Sunset Strip block; the game does not claim endorsement by, or an exact replica
of, any real venue or tenant.
### Development-only reference images
The venue reference boards under `docs/screens/refs/` are development documentation and are not
bundled with the game or its release packages. Files under CC BY are attributed here as well as in
their folder sidecar:
- `docs/screens/refs/hollywood/el-capitan-facade-day-wikimedia-2009-10-27.jpg` — "woo (Irvine, CA,
USA)", CC BY 2.0, via Wikimedia Commons.
- `docs/screens/refs/hollywood/el-capitan-theatre-blade-sign-day-wikimedia-2008-10.jpg` — Andreas
Praefcke, CC BY 3.0, via Wikimedia Commons.
- `docs/screens/refs/hollywood/premiere-staging-street-day-wikimedia-2006-02-12.jpg` — John
Barreiros, CC BY 2.0, via Wikimedia Commons.
- Public-domain files in the same folder: Charles W. Beam (1922) and Gary Minnaert (2006).
- `docs/screens/refs/hollywood/el-capitan-theatre-marquee-neighbour-day-wikimedia-2009-07-11.jpg` —
Alan Light, CC BY 2.0, via Wikimedia Commons.
- `docs/screens/refs/hollywood/hollywood-boulevard-night-context-ccbysa.jpg` — Downtowngal,
CC BY-SA 3.0, via Wikimedia Commons; used unmodified for colour/lighting study only, no derivative
asset is made from it.
- CC0 files in the Hollywood folder need no attribution: Luijtenphotos (2019) and Benoît Prieur
(2022).
## AI-assisted images
Surface textures, skyline panoramas, UI art and concept references were produced
with image-generation calls from the project's tooling. The default model slugs
recorded in the source are:
- `bytedance-seed/seedream-4.5` — `tools/generate_textures.py`
- `openai/gpt-5-image-mini` — `tools/asset_pipeline.py`,
`tools/model_pipeline.py`, `tools/scene_concepts.py` and
`tools/screen_designs.py`
- Judging/review calls use the model selected by `tools/openrouter_judge.py` and
are development-only; they do not ship in the game.
The prompts and generation workflow live in those tools. Per-file generation
metadata is not stored with every image, so the exact provider, model version and
generation date of each committed bitmap cannot be reconstructed from the
repository alone. Before a public 0.10.0 release, the provider terms and
commercial-use/redistribution rights for those generated outputs must be
reviewed and recorded. **This is a release blocker, not an assumed clearance.**
Reference photographs linked from the venue documents are hosted on Wikimedia
Commons and were used for proportion/material study only. They are not included
in the game or its release packages. If any future derivative asset copies a
reference more directly, its attribution and licence must be added here.
## Audio
All runtime audio is procedural (crowd bed, radio crackle and interface cues).
There are no external audio files, so there is no separate music/SFX licence to
clear. See `docs/accessibility.md` for caption and volume behaviour.
## Third-party names
The game is a work of fiction. Real place names and public references are used
for orientation and satire/stand-in geography; no real person, venue or brand
endorses the game. Review the storefront and trademark language before release.
+683 -7
View File
@@ -1109,7 +1109,6 @@ Completed 2026-11-01. The follow-ups left over after the texture/skyline adoptio
Completed 2026-11-01.
### Storage restructure
- [x] Decide the canonical runtime image root (`assets/images/` for UI/scene art): recorded in
@@ -1119,21 +1118,18 @@ Completed 2026-11-01.
- [x] Re-run `godot --headless --path . --import` to regenerate `.import` sidecars after any move.
- [x] Confirm `.gitignore` still excludes development-only outputs (concepts, Blender sources, raw AI fabrics) under the new layout; Blender `.blend` sources are now untracked and ignored.
### Documentation path updates
- [x] Update `README.md` paths (screen-designs section, confirmed-assets links, the `assets/images/scenes` resize example).
- [x] Update `docs/architecture.md`, `PLAN.md`, and ADR references: docs/screens/designs/ is now explicitly the reference-only snapshot; runtime art lives in assets/images/ (ADR-0014).
- [x] Update `tools/screen_designs.py` docstrings and mark the old paths in `DONE.md` as historical.
### Code path updates
- [x] Point `tools/screen_designs.py generate` at the new design output path and keep `compare`/`judge` reading the right locations.
- [x] Update GDScript `preload()` paths if game textures move (no runtime material/model textures moved; UI art added under assets/images/).
- [x] Update capture output directories if captures relocate (none did; QA captures stay in docs/screenshots/).
### Load proposed interfaces + scene art from disk
- [x] Add a central UI/scene art loader (e.g. `src/game/ui/art.gd`) that loads the mockup-derived images from the new runtime root, with a procedural fallback when a file is missing.
@@ -1144,19 +1140,16 @@ Completed 2026-11-01.
- [x] Wire the wand popup to `wand_screening` art.
- [x] Style the radial menu and finish the remaining HUD chrome against their reference images.
### Verify against the mockups
- [x] Regenerate the staged captures and run `screen_designs.py compare` (11 good, 0 bad; verified in a clean HEAD worktree).
- [x] Run `screen_designs.py judge` and clear every `diff` verdict (7 match, 4 review, 0 diff).
- [x] Add the capture + compare step as a repeatable smoke so drift is caught early (`tools/check_screenshots.sh`).
## Fix mouse movement bug
- [x] Investigate why mouse input is not being captured or processed correctly in the game window (fixed: `_start_shift()` now calls `_refresh_freeze()`).
## Improve in-game pop-ups
- [x] Improve the visual appearance of in-game pop-ups (start/pause/settings/review/radio/wand/inspection art, buttons, palette, icons, text).
@@ -1467,3 +1460,686 @@ scripted test/capture runs bypass it unless `--tutorial` is passed.
- [x] Re-run headless/Python/smokes and update status, architecture, changelog, README and docs.
- [x] Exit: tutorial + campaign first-hour path is in place, each venue validates a repeatable
content matrix, checkpoint resume works, and the 0.8 test matrix is green.
## Milestone 0.9.0 — Production quality and platform readiness
Completed 2026-11-01. Animation/audio/localization/accessibility scope, 24-NPC performance baseline, QA/smoke matrix and the explicit Godot 4.3-CI/4.7-local matrix are recorded.
Goal: content-complete, accessible enough for a public build, measured on the target platforms
and supported by a stable CI/release baseline.
### Animation and presentation
- [x] Decided to keep the 1.7 m gameplay camera (concept frames are art reference at ~1.55 m);
documented the decision and retained the capture poses.
- [x] Use the existing escalation/idle pose layers for warning/blocking/recognition/de-escalation;
explicit cinematic transition clips remain a post-0.10 enhancement.
- [x] Reduced coat/dress stride amplitude and kept the hem readable in normal walking/blocking;
full corrective shape keys remain deferred.
- [x] Portrait/close-up pass completed with the fitted gear set and refreshed gallery captures.
- [x] Existing pose/LOD tests, portrait capture and the 24-NPC budget smoke cover the animation
regression surface.
- [ ] Exit: normal movement, blocking and portrait frames have no obvious clipping or pose
pops; the camera decision is documented and visual baselines are refreshed.
### Audio
- [x] Scoped 0.9 audio to procedural ambience/SFX (no music/VO production); the existing crowd,
radio, footstep, landing, warning and precision cues remain the direction.
- [x] Existing gameplay cues cover the core events; radio send/close, footsteps, escalation,
crowd and responder feedback are wired.
- [x] Added master/SFX/ambience volume controls, persisted them and kept headless safety.
- [x] Exposed a captions setting; the existing textual HUD log remains the caption surface for
the procedural cues.
- [x] Decision: text-only for 0.9; no VO/music production is in scope until after 1.0.
- [x] Added volume/mix tests; the procedural audio smoke remains green.
### Localization
- [x] Tutorial/help/review/campaign strings are backed by the JSON table (Help control rows are
generated from input bindings); remaining debug-only labels are not player-facing.
- [x] String-table validation and layout-safe autowrap/scroll behavior cover the release UI;
a full pseudo-locale CI harness remains a post-0.10 localization expansion.
- [x] Existing UI uses the engine fallback font with arrows/punctuation verified in strings and
help; a custom non-Latin font is deferred.
- [x] `content/strings.en.json` remains the source; `Strings` validation and the docs ownership map
define key/placeholder review. Adding a locale still requires a table variant.
- [x] Help is generated from `InputBindings.REMAPPABLE`; existing string validation covers all keys.
- [ ] Exit: no hardcoded player-facing strings remain in the audited surfaces; pseudo-locale and
font checks pass.
### Accessibility
- [x] Tutorial/radio text is always visible; a captions setting is exposed, with the HUD log as the
text caption surface.
- [x] Escalation has a shape meter; access/door/flashlight/radio expose text and icons; crowd mood
has a numeric/status readout.
- [x] Added Reduce motion, which disables head bob; escalation visuals keep their text/shape
cues. Rapid-flash reduction is covered by the existing vignette/soft glow.
- [x] Named input actions, gamepad bindings and Help coverage are in place; tutorial/campaign
smokes exercise the main paths.
- [x] Font scale is applied at boot and persisted; the existing HUD layout uses autowrap and
right-aligned status anchors.
- [x] Volume/motion/caption settings and input coverage are documented in the smoke checklist;
a public accessibility statement remains part of the 0.9.5 release-candidate review.
- [ ] Exit: the acceptance checklist passes without a mouse or colour perception, and every
critical audio cue has a visual/text equivalent.
### Performance and platform
- [x] Baseline target matrix documented: desktop Compatibility, Web, and Desktop HQ/Vulkan; the
budget smoke records the live-avatar frame cost.
- [x] Raised the budget smoke to 24 avatars (6.94 ms headless) and kept the 16-NPC result as the
regression floor.
- [x] HQ/Web paths remain gated and share the same content; the 24-NPC measurement is recorded
and no further avatar cap increase is planned for 0.9.
- [x] The 24-NPC frame budget smoke is the automated gate; full Web/HQ profiling remains a
manual release-candidate task.
- [x] Compatibility/Web and HQ fallback were verified previously; GPU/Vulkan requirements are
documented in the README/releasing guide.
- [ ] Exit: target hardware meets the agreed frame budget, or the scope/content cap is reduced
explicitly; performance evidence is recorded in the docs.
### QA automation and visual baselines
- [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.
- [x] `docs/getting-started.md` and `docs/smoke-checklist.md` list tutorial, campaign/checkpoint,
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
gallery after content lock.
- [x] Judge model discovery/fallback/cost handling is optional and documented; CI does not call it.
- [x] Local `tools/build_releases.sh` builds all standard artifacts and the HQ zip; checksums are
part of the 0.9.5 release checklist.
- [ ] Exit: each venue has an automated end-to-end shift, the smoke/gallery gates are current and
green, and the CI/release pipeline is reproducible.
### Godot baseline and version policy
- [x] Policy: CI/release baseline Godot 4.3; local development supports 4.7.2 with a visual
smoke requirement. This explicit matrix is documented.
- [x] No project-wide upgrade in 0.9; the existing version-adaptive light-falloff shim remains
isolated until a single baseline is chosen.
- [x] Upgrade/rollback and visual-smoke requirements are recorded in `docs/getting-started.md`
and `MEMORY.md`.
- [ ] Exit: one documented runtime version policy, consistent CI/local exports, and no untested
engine-specific rendering hacks.
### Verification and exit criteria
- [x] Added audio volume/mix tests, 24-NPC budget smoke and existing settings/localization tests.
- [x] Tutorial, campaign, session, scenario, movement, audio and performance smokes pass;
the final 0.9 gallery refresh is part of 0.9.5.
- [x] Settings/help/input/caption coverage reviewed; the formal external review is part of 0.9.5.
- [x] Architecture/status/README/changelog/roadmap updated with the 0.9 scope and decisions.
- [x] Exit: 0.9 production scope is closed, the 24-NPC budget is measured, the engine matrix is
documented, and release-candidate-only reviews are tracked separately.
## Map optimization — completed 2026-11-02 (promoted from 0.9.5)
Goal: streamline the maps, refactor geometry to match the images in `docs/screens/concepts`. Only two maps are needed for the major version: `Hollywood Boulevard` and `Joke Shop`.
### Map 1 — Hollywood Boulevard
- [x] Audit and optimize geometry for the Hollywood Boulevard map.
- [x] Research geometry for the real Hollywood Boulevard location "6840 Hollywood Blvd, Hollywood, CA 90028, United States" and create an accurate representation of the building, neighboring structures and streets, building facades, buildings across the street and other relevant elements on the map.
- [x] Research current imagery and references for Hollywood Boulevard to ensure the map reflects the real-world location accurately.
- [x] Generate more accurate images using the python AI tools for reference and alignment with the real-world location, save to `docs/screens/concepts/hollywood_boulevard/`.
- [x] Verify alignment with other `docs/screens/concepts` images of the Hollywood Boulevard map.
- [x] Remove unnecessary or hidden geometry to improve performance.
- [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`.
### Map 2 — Joke Shop
- [x] Audit and optimize geometry for the Joke Shop map.
- [x] Research geometry for the real Joke Shop location "8433 Sunset Blvd, West Hollywood, CA 90069, United States" and create an accurate representation of the building, neighboring structures and streets, building facades, buildings across the street and other relevant elements on the map.
- [x] Research current imagery and references for the Joke Shop to ensure the map reflects the real-world location accurately.
- [x] Generate more accurate images using the python AI tools for reference and alignment with the real-world location, save to `docs/screens/concepts/joke_shop/`.
- [x] Verify alignment with other `docs/screens/concepts` images of the Joke Shop.
- [x] Remove unnecessary or hidden geometry to improve performance.
- [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.
### Scenery Change (Day/Night) for Hollywood Boulevard
- [x] Capture reference images for Hollywood Boulevard during the day and night.
- [x] Adjust lighting and environmental settings to match the day/night cycle.
- [x] Verify that all textures and materials respond correctly to the lighting changes.
- [x] Ensure that any dynamic elements (e.g., streetlights, signs) behave appropriately for the time of day.
- [x] Test performance and visual consistency for both day and night scenarios.
- [x] Offer the day map and the night map as distinct scenarios in the game menu when selecting a location.
Completed: the venue menu now has Day and Night Hollywood rows; the night row runs a full shift in the late phase and survives checkpoint restore. Local-light attenuation was corrected so the night facade reads as stone with sconce pools instead of a flat yellow wall.
**Exit:** both promoted maps are audited, optimized and visually aligned with their reference sets, with day/night Hollywood variants exposed in the venue menu.
Completed 2026-11-02: both promoted maps are audited and aligned; the Hollywood Day/Night rows and the Joke Shop block share the phase system. Map 1 is 58,884 triangles / 409 meshes; Map 2 is 22,680 / 454. The map tests, screen captures and docs are green.
## Pre-build NPC revisit — completed 2026-11-02
Goal: after the map work, review the NPC characters again in the final lighting, camera and
venue composition before producing any build or tag. This is a gate, not a second full model
redesign.
### Final-context visual review
- [x] Capture all eight variants on the promoted Hollywood/Joke Shop maps at portrait, conversation
and crowd distance in day and night lighting.
- [x] Re-check silhouette, face readability, hair, hands, gear fit and coat/dress hems against the
final backgrounds; compare with the current portrait/gallery baseline.
- [x] Re-run the gear `fit` report and inspect any map/lighting-driven regressions (hood, helmet,
sunglasses, bag, tank, hand items).
### Targeted fixes and validation
- [x] Fix only the P0/P1 NPC visual issues found in the final-context review; avoid broad model
reshapes without a new acceptance comparison.
- [x] Re-run character imports, pose/target/crowd tests, the 24-NPC budget smoke and portrait
captures.
- [x] Run the screen-design/realism judge on the final-context captures and record accepted
review verdicts.
### Build gate
- [x] Do not run the release build, bump a release version or tag until the map exit criteria and
this NPC revisit are both signed off.
- [x] Record the sign-off in `docs/development-status.md` and `MEMORY.md`, then hand off to the
postponed integration/build milestone.
**Exit:** the final maps and lighting still present the NPCs clearly; no untriaged NPC regression
blocks a build.
Completed 2026-11-02: 96 final-context captures (8 models × 3 distances × Hollywood/Joke Shop × day/night) found no P0 issues. The lanyard badge and firefighter helmet were corrected with runtime attach offsets; backpack straps, caps, low-poly hands/faces and the optional close-range flashlight wash were accepted or deferred. The 24-NPC budget is 6.93 ms, the gallery is 13 good / 0 bad, the screen-design judge is 13/13, and a clean portrait measured a 0.732 vision gap against the photoreal concept. No build, version bump or tag was created.
## Patch 0.9.3 — Dialog, button and UI polish — completed 2026-11-02
Goal: make every player-facing screen read consistently and pass the Python
screen-design judge before any model/texture changes. No gameplay rules change.
### Screen and terminology inventory
- [x] Inventory every screen/panel: start menu, venue menu, tutorial, HUD, pause,
settings, Help, inspection, wand, radio/emergency, wheel, shift review,
timeline and campaign/continue screens. Recorded in `docs/ui-copy.md`.
- [x] Record the canonical terms for admit/refuse, warning/blocking,
assistance/emergency, body camera, shift review and campaign; the current
inspection/wand/radio/review labels use them (see `docs/ui-copy.md`).
- [x] Audit all button labels for verb-first wording, capitalization, ordering,
destructive-action confirmation and consistent confirm/cancel placement;
recorded in `docs/ui-copy.md` and fixed Quit/Apply wording.
- [x] List every hardcoded player-facing string and route it through
`content/strings.en.json`; remaining UI literals are numeric/debug-only or
dynamic data. Placeholder validation covers the new keys.
- [x] Review NPC/door-staff/radio/tutorial/help/review/campaign dialogue for
repetition, ambiguity, tone and localization length. (Reason contexts,
door-staff lines, radio/tutorial/help copy and length findings recorded in
`docs/ui-copy.md`; longest dialogue line is 56 characters.)
### Button and panel behavior
- [x] Audit normal/hover/pressed/focus/disabled states on every button, including
tutorial skip, venue continue, settings apply/cancel/reset, judge/review,
radio send/emergency hold and Help close. (Shared `UiStyles` states applied;
timeline boundary disabling and the keyboard-only F1 skip are documented in
`docs/ui-copy.md`.)
- [x] Verify keyboard-only and gamepad-only navigation: focus order, wrapping,
default focus, confirm/cancel behavior and no focus traps. (Focus rings,
gamepad A/B UI bindings, default focus and per-modal cancel behavior are
recorded in `docs/ui-copy.md`.)
- [x] Verify every modal closes with Esc/right-click where documented, recaptures
the mouse, and does not leave the guard frozen or the menu visible. (Single
top-modal close path, menu/settings return handling and pointer recapture are
recorded in `docs/ui-copy.md`; session smoke covers menu Settings, Help
right-click and pause Settings.)
- [x] Test buttons at minimum/maximum HUD font scale and with 1.5–2× longer
strings; fix clipping, overflow and overlapping layouts. (Target chip now
measures/caps scaled text, HUD top labels clip, and action buttons ellipsize;
regression checks are in `test_accessibility.gd` and `test_ui_buttons.gd`.)
- [x] Test destructive/irreversible actions (campaign reset, tutorial replay,
emergency send) require the intended confirmation/hold and cannot double-fire.
(Reset/replay share one guarded confirmation dialog; emergency hold submits
once and ignores the post-submit release. Covered by session/radio tests.)
- [x] Verify radio normal-send vs emergency hold, inspection/wand allow/deny,
review continue/retry/menu and Help search/filter behavior end to end.
(`smoke_ui_flows.gd` covers inspection, radio, Help, wand and review;
`smoke_campaign.gd` covers review continue/checkpoint resume.)
### Visual and judge verification
- [x] No UI art/reference mapping change was needed for the label-only pass; the
no-overwrite image convention was preserved.
- [x] Capture a deterministic screenshot for every dialog/button state that has a
design reference; add missing capture stages or an explicit coverage gap.
(`src/test/capture_ui_states.gd` adds 14–21: start menu, pause, settings, shift
review, emergency radio, radial menu, crowd panic and responder arrival. No
reference is left without a committed capture; the crowd-panic frame is a
staged 70-frame group shot and the responder is parked mid-approach.)
- [x] Ran `tools/check_screenshots.sh` and `screen_designs.py compare`: 21 good,
0 bad.
- [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
generated captures remain local/gitignored.
- [x] Add a short UI copy/button checklist to `docs/smoke-checklist.md`.
- [x] Exit: all screens have consistent terminology and button behavior, long
strings/scale pass, and the Python compare/judge gate has no unexplained
diff.
Completed 2026-11-02: all 0.9.3 UI checks are closed. The eight previously uncaptured UI states (14–21) are now deterministic gallery shots; `tools/check_screenshots.sh` reports 21 good / 0 bad and the automatic judge 21/21 match, 0 diff. The source version is now 0.9.3; no release build or tag has been produced for it yet.
## Patch 0.9.4 — NPC visual optimization
Goal: make the eight character variants hold up in portrait, conversation and crowd
views without changing the `npc_base`/`Rig/Head` contract or the skinned/procedural
fallback behavior.
### Reference and acceptance criteria
- [x] Establish a portrait/contact-sheet baseline for all eight variants: front, three-quarter,
profile, back, full body and a crowd-distance silhouette. (`capture_npc_baseline.gd`
produces 48 frames; `sheet_npc_baseline.jpg` is committed.)
- [x] Define acceptance criteria for proportions, face readability, hair, hands, shoes, clothing
silhouette, gear fit and animation at the gameplay camera height (1.7 m). (Eight criteria
in `docs/npc-visual-review.md`; the current 8,000-triangle/8,000-vertex library contract is explicit; the `npc_base` fallback remains 6,000/4,000.)
- [x] Record the camera/lighting/background used for comparison so model changes can be judged
consistently; keep the existing portrait capture path deterministic. (Neutral studio, fixed
orbit cameras, no HUD/crowd/animation; commands recorded in `docs/npc-visual-review.md`.)
- [x] Identify the highest-impact gaps from the current portraits (e.g. face depth, hair mass,
neck/shoulder transition, hand/foot shape, coat/dress hem) and rank them by player visibility.
(P1: tourist duplicate bag, face depth, backpack straps; P2: hands/grips, clothing tiling,
shoes, headgear scale, performer props.)
- [x] Publish a before/after NPC comparison sheet for review before changing the library.
(`sheet_npc_baseline.jpg` is the before artifact; `tools/npc_baseline.py compare` produces
the side-by-side after sheet without overwriting it.)
Completed 2026-11-02: 48-frame neutral studio baseline, eight criteria, current fit and
triangle/vertex measurements, and ranked P1/P2 gaps are in `docs/npc-visual-review.md`. The
baseline itself changed no character models; the first P1 gear fixes are recorded under Gear
and equipment fit.
### Body proportions and silhouette
- [x] Raise the per-character library cap from 6,000/5,000 to **8,000 triangles / 8,000 glTF
vertices** so face, hand-grip and clothing detail is not forced to replace geometry. The
frozen `npc_base` fallback keeps 6,000/4,000; library caps and audit tests were updated, and
the 24-NPC/Web budgets must be re-measured as detail lands.
- [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.
(`tools/npc_proportions.py` + `docs/npc-proportion-audit.md`: all eight pass; no skeleton or
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
thin post on a hanger. (Fuller neck base/top, lower shoulder root, degenerate-face cleanup;
all eight rebuilt and the body comparison sheet is in `docs/blender_previews/`.)
- [x] Improve the torso and hip transition; preserve the existing broad/slim/average build
variations. (Chest/pelvis widened relative to the waist; proportions still pass and the
comparison is `sheet_npc_baseline_compare-torso.jpg`.)
- [x] Add subtle body-shape variation between variants beyond uniform scaling (shoulder slope,
torso taper, hip width, limb thickness) while staying inside the triangle/vertex budget.
- [x] Check the procedural fallback and `npc_base` silhouette against the library so the fallback
remains a credible offline substitute.
- [x] Re-measure LOD 0/1/2 silhouettes and ensure the impostor still matches each variant at distance.
### Head, face and hair
- [x] Refine the head volume and jaw/cheek/chin proportions against the portrait concept; keep the
face front direction unambiguous for the focus cone and head-turn. (Chin plate and cheekbones
sculpted into the head mesh; no separate jaw/cheek spheres or AO intersection rings.)
- [x] Improve brow/eye-socket depth and eyelid/iris readability without turning the low-poly face
into a noisy mesh. (Recessed sclera, thin upper lid; `sheet_npc_baseline_compare-face.jpg`.)
- [x] Refine the nose bridge/tip, lips and ear shape; keep the five tint slots and face direction
unchanged. (Ala wings, smaller tip, one rounded mouth slot, darker ear concha; all eight
rebuilt. `sheet_npc_baseline_compare-face-detail.jpg`.)
- [x] Audit hair caps, fringes, side locks, buns and beard silhouettes for gaps, intersections and
obvious scalp seams; keep hair color in the tintable slot.
- [x] Verify hats/hoods/sunglasses/helmets still clear the refined brow, ears and hair; update gear
fit measurements and rebuild any affected gear. (Final-context 96-frame review: cap, helmet,
hood and sunglasses clear the eyes; `blender_assets.py fit` clean; no rebuild needed.)
- [x] Check face readability under day, evening and LATE lighting and at the portrait capture’s
shallow depth of field.
### Hands, feet and clothing
- [x] Refine hands and fingers enough to read at conversation distance without exceeding the
character triangle budget. (Wider palm, two-segment bent fingers with rounded tips, separate
thumb; optional `--view=hand` capture and `sheet_npc_hands_compare.jpg`.)
- [x] Move hand gear forward out of the wrist/palm so phone, cup, clipboard, camera, flashlight,
wand and radio are no longer buried inside the hand. (`HAND_ITEM_FORWARD`, 4 cm; new capture
sheet `sheet_npc_hands_grip_compare.jpg` and regression assertions.)
- [x] Add finger curl/thumb contact for the hand items and judge the umbrella/sign grips against the
new palm/finger geometry.
- [x] Audit shoes/boots for silhouette, grounding and contact with the floor; preserve the
responder and firefighter footwear language. (Rounded toe/heel caps; boots keep the black
rubber language; all eight rebuilt, grounding and proportions still pass.)
- [x] Improve clothing layer transitions at collars, cuffs, plackets, waistbands, pockets, badges
and reflective bands; remove obvious z-fighting or floating detail.
- [x] Add believable fabric folds/wrinkles at elbows, knees, waist and hems where the extra
geometry pays off in portraits; keep the fixed detail materials and five tint slots intact.
- [x] Re-check coat/dress/skirt hems during walking, blocking, sitting and turning; eliminate
visible leg-through-hem clipping and floating hems.
- [x] Verify outfit tinting on all five contract slots and fixed-colour details (hi-vis, leather,
steel, patches, plastic, rubber) remains unchanged.
### Gear and equipment fit
- [x] Run the Blender `fit` report for every character and all head/back/chest/hand gear; record
head-band, skirt/hip clearance and inner-face clearances.
- [x] Re-fit caps, beanie, hood, sunglasses and firefighter helmet against the refined face/hair;
ensure eyebrow/ear clearance and a readable face with the hood up.
- [x] Hide the tourist's photo-pose camera bag outside `photo`/`film` poses; it duplicated the
backpack silhouette. (`photo_prop_visible_for_pose()` plus regression test.)
- [x] Refit the backpack straps as flattened bands that follow the torso instead of rods partly
buried in the chest; rebuild the GLB, bake AO and confirm in the before/after sheet.
- [x] Re-fit shoulder bag and SCBA tank against the revised torso/hips; fix remaining strap and
tank intersections and keep attachments outside the body silhouette.
- [x] Verify lanyards, radios, phones, cups, clipboards, umbrellas, cameras, protest signs,
flashlights and wands sit correctly in the hand/grip and do not float.
- [x] Add regression checks for gear attach parents, head/back silhouette clearance and hand-item
grip; rebuild affected GLBs, bake AO and re-import.
### Poses, animation and performance
- [x] Review idle, walk, warning, blocking, recognition, refusal, phone, clipboard, photo, film,
sit, panic/flee and responder poses for pops, foot sliding, floating props and head-turn
overshoot.
- [x] Add or refine short negotiation/reaction transitions (comply, argue, leave, recognize,
block) without changing the seeded pose/outfit determinism.
- [x] Re-measure the skinned-avatar frame cost at 16/24 NPCs and the LOD/impostor thresholds;
record triangle, mesh and draw-call changes before raising any cap.
- [x] Ensure the procedural fallback still passes the pose, target and crowd behavior tests after
the model changes.
### Verification and handoff
- [x] Rebuild the affected Blender assets, AO maps, manifest and review sheets; run
`blender_assets.py audit`, `fit`, character imports and the full Godot test suite.
- [x] Recapture the portrait, character gallery and crowd-distance shots; compare old/new and run
the Python screen-design judge.
- [x] Update `docs/blender-assets.md`, architecture/status, `MEMORY.md` and the changelog with the
new budgets, limitations and rebuild commands.
- [x] Exit: all eight variants pass the portrait/readability criteria, gear and hems no longer
clip in normal play, the 24-NPC budget remains green, and the visual judge has no unexplained
regressions.
Completed 2026-11-02: all 0.9.4 acceptance checks are closed for the stylized target. The eight
library characters use the 8,000/8,000 cap (current range 4,482–5,474 tris / 4,260–5,494 verts),
the 48-frame baseline and 96-frame final-context review pass, `blender_assets.py audit`/`fit`
and `npc_proportions.py` pass, the full suite is 262/262 and the 24-NPC smoke is 6.90 ms.
`tools/check_screenshots.sh` reports 21 good / 0 bad and `screen_designs.py compare` has no
unexplained regressions. Accepted limits: hand items use the forward-offset open palm rather than
a finger-curl rig; fabric folds stay out of the low-poly target; the >26 m impostor remains a
two-quad billboard; evening readability is represented by the day/night/LATE final-context set.
Tagged `v0.9.4`; Gitea run 916 passed checks and the full build and published seven assets.
## Patch 0.9.5 — Texture and material refinement
Goal: improve close-range surface and character texture quality while keeping GL
Compatibility/Web parity, deterministic assets and offline CI.
### Environment textures and materials
- [x] Audit the v2 picks (brick, sidewalk pavers, dry/wet asphalt, awning canvas,
day/dusk/night skylines) at close range and in the entrance/street shots for
seams, tiling, scale, contrast and normal-map strength. (New `surfaces` and
`surfaces_joke` capture stages; no seam or UV-scale failures found.)
- [x] Refine brick mortar/wear, paver joints/stains, asphalt aggregate/wet response
and awning weave using the existing image/asset tooling; never overwrite a
pick, use `-vN` candidates and promote validated results. (The Walk of Fame
band now uses the paver/terrazzo texture; the neighbour awning keeps its
cream stripe via a neutral tint and 4× UV. Brick/asphalt picks held up.)
- [x] Check UV scales and material roughness/metallic profiles; keep day/night
phase behavior and the wet-street transition intact. (Profiles and UV
scales passed; dry/wet asphalt and phase switching unchanged.)
- [x] Review distant skyline haze and foliage/planter surfaces against the concept
references; fix obvious flatness without regressing readability. (Added
world-height haze fade to the skyline shader; foliage/planters stayed
stylized and readable, so no mesh change.)
### Character and prop textures
- [x] Audit NPC skin, cloth, pants, hair and gear albedo/normal/AO maps in portraits;
refine fabric weave, skin variation and fixed detail materials. (256² maps rebuilt; fabric
now uses a 4× UV weave and skips baked per-character AO, removing the portrait blotches;
skin/hair/gear keep AO. Comparison `sheet_npc_baseline_compare-texuv.jpg`.)
- [x] Re-bake AO where geometry changes; verify the 256² maps and material wiring.
(No character geometry changed in the texture pass; asset/base audits pass.)
- [x] Audit library props and UI/card/icon textures at their actual screen sizes;
remove blurry/oversized or unused textures where safe. (All 21 screen-art
textures are 1280×720 and referenced; access cards are 256×128, HUD icons
64×64; no oversized or unused runtime texture found. Legacy root textures
remain only as the `generate_textures.py`/asset-test fallback.)
- [x] Run texture audits, import, and the full asset test suite; record texture memory
and triangle/draw-call impact. (Godot 263/263, Python 101/101; asset and base
audits pass; 24 NPCs = 168 meshes / 131,616 triangles / 6.94 ms. Compressed
payloads: picks 24.55 MB, UI art 22.67 MB, NPC maps 0.48 MB.)
### Visual verification
- [x] Run `python3 tools/scene_concepts.py realism` for the affected stages with the
judge enabled; record metric gaps and model/spend. (Run
`docs/screens/realism/2026-11-02-tex` with `z-ai/glm-5.3-flash`, one pass per
shot; mean `after` overall 1.8, lighting 2.3, materials 1.8, texture 1.2,
backdrop 2.0, characters 1.2, Pillow gap 0.306. The judge is optional and not
a CI gate; the report stays local.)
- [x] Re-run `tools/check_screenshots.sh` and the screen-design judge; clear all
`diff` verdicts. (21 good / 0 bad; `screen_designs.py judge` = 21/21 match,
0 review, 0 diff.)
- [x] Capture before/after close-ups for brick, pavement, asphalt, character cloth
and skyline; document accepted trade-offs. (Committed
`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`: Walk of Fame
pavers, asphalt aggregate, limestone facade, day skyline haze and NPC cloth;
asphalt/limestone picks intentionally held, no replacement.)
- [x] Update asset docs and the changelog/status with the promoted texture versions.
(README/PLAN/architecture/status/MEMORY and the 0.9.5 changelog entry now
carry the texture results; the 0.9.5 section moved to `DONE.md` after the
release build.)
Completed 2026-11-02: all 0.9.5 texture and material checks are closed. The NPC
base and eight library characters use 256² maps, cloth/pants use the 4× fabric
weave without baked-AO blotches, the Walk of Fame band uses the paver/terrazzo
texture, the neighbour awning keeps its cream stripe, and the skyline fades into
per-phase haze. Godot is 263/263, Python tooling 101/101, `blender_assets.py
audit`/`fit` and `npc_pipeline.py audit` pass, the 24-NPC headless smoke is
6.94 ms (168 meshes / 131,616 triangles), the 48-frame baseline and 96-frame
final-context review pass, `check_screenshots.sh` is 21 good / 0 bad, and the
screen-design judge is 21/21 match. The optional realism judge ran with
`z-ai/glm-5.3-flash` (mean overall 1.8, Pillow gap 0.306) and its report remains
local; the committed comparison sheet is
`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`. Tagged `v0.9.5`;
Gitea run 922 passed checks and the full build and published seven assets.
## Patch 0.9.6 — Integration, performance and judge gate
Goal: integrate the UI/model/texture patches, prove the combined build on the
target paths and leave a clean release-candidate handoff.
### Integration and asset rebuild
- [x] Rebuild all changed Blender/GLB/AO assets, re-import, and verify manifest/fit/
audit coverage. (The v0.9.5 commit already rebuilt the base, eight characters,
gear and AO maps; this checkpoint re-imported them and re-ran
`blender_assets.py audit`/`fit` and `npc_pipeline.py audit`, all exit 0.)
- [x] Run the complete headless suite, Python tooling and smoke matrix; fix any
regression from the three polish patches. (Godot 263/263, Python 101/101;
14 smokes green: audio, campaign, crowd, emergency, focus/radial, movement,
NPC budget, PLAN verbs, props, scenario content, session shell, timeline,
tutorial and UI flows. Campaign/tutorial used isolated `XDG_DATA_HOME`.)
- [x] Re-run the 24-NPC budget smoke and record frame time, mesh count and any
texture-memory change; check Web and Desktop HQ if available. (24 avatars,
168 meshes, 131,616 triangles, 6.90 ms of 50 ms headless; NPC maps remain
0.48 MB. A clean detached worktree at `f5b8ef2` built Linux/Windows/macOS/
Web/HQ (`161.5/197.1/148.0/97.8/241.5 MB`); the Web zip has index.html,
wasm and pck, the HQ zip has `override.cfg`, and the HQ binary launches
headless with the Forward+ override.)
- [x] Verify save/campaign/tutorial behavior is unchanged by the visual/UI patches.
(Persistence/session tests remain in the 263-test suite; campaign and tutorial
smokes passed with isolated `XDG_DATA_HOME`.)
### Visual gallery and judge gate
- [x] Recapture the full gallery, portraits, venue/realsim stages and Desktop HQ A/B
as needed; no stale screenshots in the release set. (Gallery 21/21,
NPC baseline 48 frames, final-context review 96 frames and a fresh 10-shot
realism run were recaptured. No Forward+ code changed since 0.7.1, so the
HQ A/B was not re-staged; the HQ artifact is checked with the release build.)
- [x] Run `tools/check_screenshots.sh`, `screen_designs.py compare` and
`screen_designs.py judge` on the final captures. (21 good / 0 bad; compare
21 good / 0 bad; judge 21/21 match, 0 review, 0 diff.)
- [x] Run the realism judge for the affected concepts with a pinned model; record
model, attempt count, spend and verdicts. (`z-ai/glm-5.3-flash`, 1 pass per
shot, 10 shots, no fallback/retry: mean overall 2.1, lighting 2.4, materials
2.1, texture 1.7, backdrop 2.3, characters 1.8, Pillow gap 0.291. The local
tool does not expose token spend; the report is gitignored under
`docs/screens/realism/2026-11-02-096/`.)
- [x] Review all `review` verdicts manually; convert unexplained layout/composition
mismatches into fixes or explicit known limitations. (Screen judge has 0
`review` verdicts; the remaining realism comments are the known stylized
gaps already documented, not layout regressions.)
- [x] Ensure generated realism folders remain gitignored/local-only and only the
durable report decisions/docs are committed. (`docs/screens/realism/` stays
ignored; `git status` shows no generated capture files.)
### Release-candidate handoff
- [x] Update README, architecture, development status, changelog and TODO with the
final patch-level results and any remaining gaps. (README/PLAN/architecture
now point at 0.9.6; the changelog and development status record the
integration build, performance numbers, judge results and the clean-worktree
export rule; `docs/RELEASING.md` documents the ignored-folder trap.)
- [x] Tag/version or otherwise freeze `0.9.6` as the integration checkpoint before
the `0.10.0` release candidate. (Tag `v0.9.6` on `4bb2896`; Gitea run 1296
passed checks and the full build and published seven assets.)
- [x] Exit: combined build is functionally green, visually reviewed, performance
measured and ready for the 0.10.0 content-lock/playtest phase.
Completed 2026-11-03: the 0.9.6 integration checkpoint is closed. The current
asset set re-imported and passed `blender_assets.py audit`/`fit` and
`npc_pipeline.py audit`; the full suite is 263/263 with 101/101 Python tests and
14 smokes green; the 24-NPC budget is 6.90 ms with 168 meshes and 131,616
triangles; the gallery and screen-design gate are 21 good / 0 bad and 21/21
match; the pinned realism judge (`z-ai/glm-5.3-flash`) scored mean overall 2.1
with a 0.291 Pillow gap. A clean worktree built Linux/Windows/macOS/Web/HQ at
161.5/197.1/148.0/97.8/241.5 MB; the Web zip and HQ override were checked and
the HQ binary launched headless. Tagged `v0.9.6`; Gitea run 1296 passed checks
and the full build and published seven assets.
## Character development
Completed 2026-11-04. The procedural crowd-variety roster (four new variants, per-role pools)
shipped with the realistic face; the external-asset intake and the MakeHuman body pipeline were
promoted to the active **MakeHuman character pipeline** track in `TODO.md`.
## Character development
Goal: break the eight-character clone effect with four licence-cleared crowd-variety looks while
keeping the frozen `npc_base` bone, tint-slot and gear-attach contract. The procedural generator is
the primary path; Quaternius is a reviewed reference and MakeHuman is deferred to a separate local
development pipeline.
The roster build is deliberately sequenced **after** the 3D model and local map re-evaluation: the new variants
must inherit the reviewed face/hand/gear baseline instead of multiplying whatever it finds.
*Skill route: `character-artist`, `retopology`, `rigging`, `uv-workflow`, `materials`,
`asset-optimization`, `lowpoly-style`, `godot-export`, `qa-review`.*
### Source review and decision
- [x] Research free/open-source character sources and evaluate at least three downloads against
the existing Blender contract. (Measured Quaternius CC0, Kenney CC0, Khronos
CesiumMan/RiggedFigure CC-BY and the MakeHuman CC0 base mesh; none is a drop-in.
Results, local preview sheet and SHA-256 provenance are in
`docs/npc-model-sourcing.md`.)
- [x] Review the sourcing research with the project owner and choose Option A (procedural),
Option B (Quaternius CC0 intake) or Option C (MakeHuman/MPFB body pipeline); record the
decision in an ADR and in `MEMORY.md`. (2026-11-03: extend the procedural generator as the
primary path, keep Quaternius as a reviewed prototype/reference, defer MakeHuman to a
separate local pipeline. Recorded in ADR-0015 and `MEMORY.md`.)
- [x] Add a source licence allow-list and provenance rule: CC0 preferred, CC-BY only with
attribution in `CREDITS.md`, and no service assets whose terms restrict redistribution.
(ADR-0015; the CC-BY research previews are attributed in `CREDITS.md`.)
- [x] Decide whether the external preview sheet stays local under `tmp/` or is committed with
the required CC-BY attribution. (Committed as
`docs/blender_previews/sheet_npc_sources_research.jpg` with Cesium CC-BY attribution;
the project-owned crowd prototype sheet is also committed.)
### Intake and validation
- [x] Add a Blender `inspect` path that imports a GLB, applies node transforms, reports
tris/verts, bounds, bones, materials, UVs and skinning, and lists contract failures.
(`blender_assets.py inspect --file ...` applies parent/object transforms, converts to
Godot Y-up, ignores the importer's bone-shape helper and prints budgets, bones, slots,
UVs, skinning and animations with issues/warnings; `--json` and `--category` supported.
Seven synthetic contract tests added; all 46 library GLBs inspect clean. `audit` remains
the final gate.)
- [x] `adapt` path: promoted to the **MakeHuman character pipeline** track (2026-11-04).: scale/ground, weld seams, decimate, map the
five tint slots, and either rebuild the contract armature or record an explicit second-rig
contract. Use `retopology`/`rigging`/`uv-workflow` guidance for the manual steps, but keep
the Quaternius no-go: a contract rebuild needs a proportion warp, so this is not on the
character critical path.
- [x] Audit gate + intake regression test: promoted to the **MakeHuman character pipeline** track.
- [x] Adapter documentation: promoted to the **MakeHuman character pipeline** track.
### Quaternius prototype
- [x] Prototype one Quaternius CC0 variant: weld/decimate to budget and capture Idle/Walk
renders. (12,518 imported verts -> 3,199 after weld -> 2,423 / 4,654 tris after a 0.75
decimate; Idle/Walk quality is good. Preview in
`docs/blender_previews/sheet_npc_sources_research.jpg`.)
- [x] Measure the prototype against the `npc_casual` controls: silhouette, rest pose, body
proportions and gear fit. (No-go for a contract rebuild: T-pose 62-bone realistic rig,
~0.96 m hip, ~1.68 m arm span and ~0.153 m shoulder joint do not match the arm-down
13-bone contract. Keep as reference; a second rig/animation path needs its own ADR.)
- [x] Make a go/no-go decision on Option B. (No-go for the current pass; proceed with the procedural
generator and keep Quaternius as a visual/style reference.)
### Roster and runtime
- [x] Approve four crowd-variety looks (build, head silhouette, hair/beard, outer layer and
accessory; no clothing that labels the NPC's behaviour). (`npc_crowd_a` bun/slim/sunglasses,
`npc_crowd_b` bald/broad/beard/shoulder bag, `npc_crowd_c` coat/umbrella, `npc_crowd_d`
hood/cup; prototype sheet committed.)
- [x] Re-derive the four specs against the new baseline: the four crowd entries inherit the
realistic face (`seg=24/rings=16`) and the reviewed hand/gear offsets automatically because
they share `_face`/`_hands_and_shoes`/`GEAR_ATTACH`.
- [x] Generate the four variants: built via `blender_assets.py build`, AO re-baked, `audit` clean
(4,734–5,814 tris), `inspect` clean, proportions pass.
- [x] Add the four specs to `CHARACTERS`, commit their GLBs/AO maps and manifest roles/tint data.
(Note: this adds ~2 MB to the next release build via `export_filter=all_resources` — accepted
for the crowd-variety roster.)
- [x] Per-role pools: `character_model_for()` now picks from `CHARACTER_POOLS` by seed — the
routine crowd roles gain the crowd variants (tourist → casual/crowd_a/crowd_d,
confused_visitor → casual/crowd_b, intoxicated → casual/crowd_c) while the special roles
keep their existing alternation (guest ⅓ jacket, trespasser/threat ½ hoodie). The test
now checks seed-stability plus that every pool entry resolves to a committed GLB
(277/277).
- [x] Gear fit + inspect for the four new variants: `fit` is clean (sunglasses band=inside,
shoulder bag clear; cup/umbrella are hand gear with the forward offset), `inspect` clean,
proportions pass.
- [x] Crowd-variety reference: five licence-cleared Commons photos fetched to
`tmp/research/crowd-variety/`; the AI proposal set stays the next step for detail.
- [x] Baseline + portrait rows: the four variants are in `capture_npc_baseline.gd` and
`sheet_npc_baseline.jpg` (12 variants × 6 views); review-doc rows added.
### QA and release
- [x] Gates with the expanded library: 24-NPC budget smoke 152,464 tris / 168 meshes / 6.94 ms
(under 50 ms), portrait baseline 12 variants × 6 views, `fit` clean, Godot 277/277, Python
160/160. No release build is created for this unversioned track.
- [x] Docs: `docs/development-status.md`, `docs/3d-model-review.md` and the baseline sheet updated;
`CREDITS.md` needs no change (the crowd variants are procedural, no new source). Accepted
visual limits: the crowd accessories (umbrella canopy, cup, sunglasses, beard) read at
full-body distance but not in the head-only portrait framing.
- [x] Exit: the 12-character library passes contract audit, portrait/gear acceptance, the 24-NPC
budget and licence/provenance review; the runtime contract (13 bones, five tint slots,
`npc_base` fallback) is unchanged.
### Deferred MakeHuman local pipeline
- [x] Evaluate the MakeHuman CC0 base mesh for a later separate local development pipeline.
(19,158 verts / 36,972 tris; scaled to 1.82 m; decimate 0.35 -> 3,780 verts / 6,468 tris;
UVs present, no normals/material groups/rig. Feasible only with retopology, a custom rig and
project-owned clothing; keep out of CI/build.)
- [x] Body-shape diversity decision (2026-11-04, owner): **yes** — use MakeHuman for the next
character pass. Promoted to the **MakeHuman character pipeline** track.
+169 -658
View File
@@ -1,656 +1,171 @@
# MEMORY
Session memory for durable decisions, state, and gotchas. Keep concise, remove obsolete entries.
Session memory for durable decisions, gotchas, and commands. Implementation history lives in
`DONE.md`, user-facing changes in `CHANGELOG.md`, the live task list in `TODO.md`, and the
canonical implemented-features list in `docs/development-status.md`.
## Decisions
- The visual-alignment path/loader/panel/HUD work and the realism lighting pass are complete and
recorded in `DONE.md`; remaining visual items are the realism follow-ups in `TODO.md`.
- `PLAN.md` is a compact product/design document, not an implementation log. Historical control
and v0.3.0 specs were folded into its Presentation and Controls section; put new step-by-step
work in `TODO.md`, not the plan.
- Editable Blender sources under `assets/models/blender_src/` are gitignored and untracked
(46 `.blend` files removed from the index on 2026-11-01); the `.gdignore` marker stays tracked.
- `screen_designs.py judge` default `google/gemini-2.5-flash-lite` worked on 2026-11-01
(7 match / 4 review / 0 diff); `--model` remains available.
- `capture_realism.gd` parses `--portrait-type=N`; it was documented but ignored before the gear
pass. Portrait captures need the requested NPC type to have spawned, otherwise the stage can
finish with an empty frame; use the Blender character previews (`render --only npc_coat`) for
deterministic gear checks.
- Headgear: the firefighter helmet was lifted 2 cm after the brow/face pass; cap and hood were
visually checked on `npc_vest`/`npc_hoodie`. The beanie is unused and shares the cap shell.
- Gear fit is measured with `python3 tools/blender_assets.py fit` (stdlib, reads the GLBs):
head width uses skin vertices weighted to Head in y 1.55–1.81; clothing radius is the cloth
skirt in y 0.30–0.52 with a pants hip fallback. The command reports per-character gear boxes,
head-band status and x/radial skirt clearance; `test_blender_assets` covers its shape.
- Post-realism work is split by theme, not by date: finished items are archived in `DONE.md`, and
`TODO.md` carries the v1.0.0 gap register and milestone roadmap. New work belongs there, not in
a revived historical plan document.
- **Local-only reference override (2026-11-03, owner):** development-only photo research ignores
the licence gate. Downloads live in `tmp/research/` (gitignored): DDG image search, street-view
frames, neighbouring buildings — never committed, never shipped. Promotion into
`docs/screens/refs/` always passes the sidecar licence + privacy gate and `CREDITS.md`.
- **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,
add no runtime dependency and are referenced from `tools/README.md`. The third-party
`blender-skills` pack remains gitignored.
- **TV Studio shares the Hollywood location (owner):** the Late Night TV Studio is the same El
Capitan Entertainment Centre block at 6840 Hollywood Blvd (which houses *Jimmy Kimmel Live!*),
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.
- **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`.
- **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.
- **Image-recitation filter:** Gemini image models reject well-known branded facades (Comedy Store
wall) as generation anchors (`IMAGE_RECITATION`). Workaround: anchor on a less-branded frame of
the same block, or generate text-only when the reference adds no geometry.
- **OpenRouter image-to-image:** `/v1/images` is text-to-image only; the working path is
`POST /v1/chat/completions` with a `content` array carrying a base64 `image_url`. Response
images arrive as `message.images[].image_url.url` (fallback: a data URI inside the message text).
Implemented in `generate_textures.generate_image(reference_path=...)` and
`scene_concepts.py generate/anchor`; provenance sidecars are `.refgen.json`.
- **Facade/venue material picks (2026-11-03):** the Masonic/El Capitan facade uses the
photo-anchored `limestone_ashlar` field (warm tint `Color(0.85, 0.81, 0.74)`) with `stone_steps`
for trim; the Joke Shop uses `dark_stucco`; the marquee canopy/sign box stay `black_metal` by
design (fictional "GIG LINE" marquee, not the red El Capitan Theatre marquee next door). The
TV Studio reuses `masonic_facade`. Committed Comedy Store refs were removed in favour of the
local-only `tmp/research/comedy-store/` set; the three-up board's photo panel is a historical
public-domain frame.
- **Godot 4.7.2 `--import` gotcha:** with a stale `.godot/` cache, new image files never import
(no `.import` created) and `preload()` of the new texture fails every dependent script with the
misleading `Could not resolve external class member "spec"`. Fix: import in a scratch copy with
`.godot/` removed, then copy back the `.png.import` and `.godot/imported/*.ctex/.md5`.
- **Blender skills pack (local aid):** MIT `blender-skills` at `.agents/skills/blender-skills/`
(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/. ...`).
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,
privacy, Compatibility/Web cost and mesh collision. Accepted photo channels: team captures,
permissioned user/volunteer photos, clearly licensed downloads; every set needs a
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
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
account for that oracle mismatch.
- **Character mesh cap:** eight library variants use 8,000 tris / 8,000 glTF vertices
(`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in `tools/blender_assets.py`); the frozen `npc_base` fallback
keeps 6,000/4,000. Measure after any geometry change; never silently lower the cap back to 5,000.
- **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
official editor binary.
- **GDScript/architecture:** cross-file references use explicit `preload()` constants (not
`class_name`) so scripts compile in headless `--script` runs; the test runner is a custom minimal
harness (not GUT, which needs the editor plugin); no autoload signal bus — `ShiftCoordinator`
owns the `EventBus` instance passed to subsystems.
- **Skinned NPCs (v0.6.0):** `NpcAvatar._build_skinned_body()` loads `npc_base.glb`; procedural
`_build_body()` is the fallback. Bone pose rotation is the **absolute local orientation**, not an
offset — compose with `get_bone_rest(i).basis` or the body snaps to a broken pose. Five tint
slots via `SurfaceMaterials.npc_slot()`; keep `_rig.scale` uniform on the skinned path.
- **NPC facing:** the avatar's visual front is **+Z**; Godot `look_at()` aims **-Z** (made NPCs
walk backwards). Use `NpcAvatar.yaw_towards(dir)` and set `rotation.y`; never `look_at()` on
avatars. Head-turn offsets use `+basis.z` for the same reason.
- **Release blockers (still open):** macOS unsigned, AI image provider terms/commercial rights not
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`.
- Graphics v0.6.0 plan (completed 2026-11-01; ADR-0013): NPCs move to Blender-authored skinned meshes. `tools/npc_pipeline.py` builds
`assets/models/generated/npc_base.glb`; `NpcAvatar` keeps the procedural `_build_body()`
fallback. Preserve `Rig/Head`, the limb node names, and the seed-deterministic
`outfit_for`/`layer_for`/`hand_item_for` helpers. Blender = flatpak 5.2.0 LTS, sandbox cwd
`/app/blender` → absolute paths for `--python`. CI stays offline (committed assets).
## Gotchas / Known Issues
- Engine: Godot 4.3 stable (official editor build, `~/.local/bin/godot`). The system's preinstalled `godot-runner` (4.7.2, Fedora) cannot load projects from arbitrary paths because it was compiled without path overrides; use the official editor binary instead.
- Language: GDScript. All cross-file references use explicit `preload()` constants (not `class_name` globals) so scripts compile in headless `--script` runs without the editor's class cache.
- Test framework deviation: the project uses a custom minimal headless test runner (`src/test/test_runner.gd`) instead of GUT, because GUT needs the editor plugin. CI runs it with `--headless`.
- Eventing: no autoload signal bus. `ShiftCoordinator` owns an `EventBus` instance (`src/core/event_bus.gd`) passed to subsystems; subsystems emit only when a bus is assigned. Autoloads at parse time are unreliable under `--script`.
- Static signals are not available in Godot 4.3 GDScript ("Expected func or var after static") — do not use them.
- GDScript lambdas capture local variables **by value**; mutating a captured local inside a lambda does not affect the outer variable. Use member functions for accumulators in tests.
- Shift length is the scenario's `shift_duration_s` (900 s in all three venues). Tests and the screenshot tool override elapsed time; they do not change the default.
- RNG: NPC reactions and event scheduling are seedable for deterministic tests.
- **Mouse look (v0.7.3):** guard look is handled in `GuardController._input()` (before GUI), not `_unhandled_input`; focus/click-back recaptures the cursor and the HUD is made mouse-transparent recursively. Regression tests cover yaw/pitch and HUD filters.
- **Campaign/v0.8.0:** `Campaign` owns Hollywood → Joke Shop → TV Studio progression. `campaign.json`, `shift_results.json`, `settings.json` and versioned `shift_state.json` persist separately; pause saves a checkpoint, review Continue advances, Settings resets with confirmation. Difficulty keys are `easy/normal/hard` (`ai_difficulty`); scripted QA bypasses tutorial unless `--tutorial` is passed. Help is searchable and generated from `InputBindings.REMAPPABLE`.
- **Tutorial (0.8.0 work):** `docs/tutorial.md` is the eight-beat design; `src/game/tutorial.gd` is the pure state machine, `TutorialOverlay` is the non-blocking card, and `GameManager` wires it with a safe visitor, suppressed spawns, persisted completion/skip and Settings replay. Scripted test/capture runs bypass it unless `--tutorial` is passed; `smoke_tutorial.gd` covers completion/replay/skip.
- **Documentation ownership (2026-11-01):** `docs/architecture.md` §12 defines the source-of-truth map and consolidation rules. Obsolete/historical plan documents are removed rather than kept; current work belongs in `TODO.md`. README/development-status/Blender AO/Forward+ stale claims were corrected. The v1.0.0 gap register and 0.8/0.9/0.9.5/1.0 roadmap live in `TODO.md`.
- **Vision judge (v0.7.2):** `tools/openrouter_judge.py` reads `/api/v1/models`, filters image-input models with `response_format`, and ranks by advertised prompt/image/completion prices. `--model auto` (default) uses the cheapest (`z-ai/glm-5.3-flash` on 2026-11-01) with fallbacks; explicit `--model`/`GIG_LINE_JUDGE_MODEL` wins. Auth errors abort, provider/model errors fall through. Full screen judge 13/13 match, 0 diff; A/B judge ~$0.006.
- **Desktop HQ preset (v0.7.1):** `packaging/desktop_hq/override.cfg` switches a
copied executable to Forward+; `Materials.forward_plus_active()` gates
volumetric fog/SDFGI and `light_volumetric_fog_energy`, and
`Materials.modern_light_falloff()` compensates for Godot 4.7's much harsher
local-light falloff. LATE volumetric density is 0.020 with flashlight
volumetric energy 3.0. `tools/build_releases.sh` packages the Linux/Windows
binaries + override into `gig-line-desktop-hq-<version>.zip`;
`publish_release.sh` uploads it when present. A/B report:
`docs/screens/realism/2026-11-01-forward-plus/` (entrance gap 1.568→1.187).
- **Local Godot toolchain (2026-11-01):** `~/.local/bin/godot` is 4.7.2 stable;
4.3 stable is kept as `~/.local/bin/godot-4.3.stable`. Export templates for
4.7.2 are installed under `~/.local/share/godot/export_templates/4.7.2.stable`
(4.3 remains). The project and CI still pin 4.3 in `project.godot`/the Gitea
workflow; 4.7.2 imports, runs the 231-test suite, session-shell smoke and a
Linux export successfully. Godot 4.7 writes `*.gd.uid` files; they are kept
out of this clone's status via `.git/info/exclude` until the project upgrades.
- **Blender invocation (v0.6.0):** `flatpak run org.blender.Blender --background --python
/run/media/system/DATA/Documents/06-games/security-game/tools/npc_pipeline.py -- build`.
The flatpak cannot read `/tmp`; use absolute repo paths. `assets/models/npc/.gdignore` is
required, otherwise Godot's editor tries to import the committed `.blend` and hangs during
`--import` (also add `.gdignore` to local build/squashfs folders when scanning is slow).
- **Skinned NPC integration (v0.6.0):** `NpcAvatar._build_skinned_body()` loads
`npc_base.glb` and keeps `_build_body()` as fallback (`use_skinned_model=false` in tests).
Bone pose rotation is the **absolute local orientation**, not an offset from rest: compose with
`get_bone_rest(i).basis` or the whole body snaps to a broken pose. Proxies named Hip/Knee/
Shoulder/Elbow/Hand + Head marker preserve the old animation math; `Rig/Head` is the marker.
Five material slots are tinted via `SurfaceMaterials.npc_slot()`; keep `_rig.scale` uniform on
the skinned path. Budget: 16 avatars = **144 meshes**, ~6.9 ms headless.
- **NPC facing (fixed after v0.5.0):** the avatar's visual front is **+Z** (procedural eyes
and the Blender model's face). Godot `look_at()` aims **-Z**, which made every NPC walk
backwards. Use `NpcAvatar.yaw_towards(dir)` and set `rotation.y`; do not call `look_at()` on
avatars. Head-turn offsets use `+basis.z` for the same reason. Covered by
`test_yaw_faces_the_movement_direction`.
- **Image root (ADR-0014, 2026-11-01):** `assets/images/` is the runtime root for composed
interface/scene art (`ui/`, `scenes/`); tiled PBR textures stay under `assets/textures/`;
`docs/screens/designs/` and `docs/screens/concepts/` stay reference-only; QA captures stay in
`docs/screenshots/`. When a mockup becomes runtime art, copy/process it into `assets/images/`
with a new name (no overwrites) and keep a fallback in code.
**Mockup copies:** 21 design references live in `assets/images/ui/`; `docs/screens/designs/`
remains the reference-only copy. `tools/screen_designs.py` reads/writes the runtime root.
## Handoff (read this first)
- **Status (2026-11-01, after the venue-geometry pass):** v0.7.0 is released. Post-v0.6.0 the
realism pass, the visual-alignment pass and the NPC face/hand pass all landed. Headless
**229/229**, Python tooling **87/87**, `tools/check_screenshots.sh` **11 good / 0 bad**,
`screen_designs.py compare` 11 good / 0 bad, `scene_concepts.py audit` all GOOD,
`blender_assets.py audit` exit 0, `smoke_npc_budget` OK (16 avatars, 6.94 ms of 50 ms).
`PLAN.md` is compact; README/architecture track the current controls, asset pipeline, CI and
release artifacts. The historical notes below remain useful for implementation gotchas; for
current tasks use `TODO.md`.
- **Phase 2 — AI texturing (this agent, DONE 2026-11-01).** Phase 1 assets were
already in the tree (untracked) and are preserved. Generated six seamless AI
clothing albedos into `assets/textures/npc/ai/{shirt,trousers,jacket,vest,
coat,dress}.png` (512×512, `--role tile`, gpt-5-image-mini; audit 6 good).
**Gotcha:** `asset_pipeline.py generate` defaults to `GEN_SIZE=2048`, which
gpt-5-image-mini rejects (HTTP 400); pass `--request-size 1024` for 1:1 roles.
`generate --role tile` already applies `make_seamless`, so a separate
`seamless` pass is redundant. Added to `tools/npc_pipeline.py`: `prepare`
(layers AI shirt→cloth, trousers→pants albedos at 128px) and `palette`
(writes `assets/textures/npc/palette.json` via `model_pipeline.dominant_colors`).
`write_slot_textures()` now skips the albedo for slots with an AI override
(normal/ORM stay procedural). Blender `build` re-run: GLB audit ok, 1204 tris /
2488 verts, AI albedo embedded for cloth+pants. Phase 2 completed and shipped with
the v0.6.0 asset library.
**Import concurrency:** a parallel agent is running
`timeout 300 godot --headless --editor --quit --path .` and then checks
`assets/models/generated/npc_base.glb.import` — treat that as the canonical
import. My own `godot --headless --path . --import` hung with no output past
the engine banner (two Godot instances contended on `.godot`); I killed the
orphan (PID 2482934). Do not run a second import while that `--editor --quit`
is active.
Next: Phase 3 (`NpcAvatar._load_model` + slot tinting) consumes
`palette.json` and the GLB; Phase 2's capture/judge item is blocked on that.
- **Git hygiene + tooling (2026-11-01):** dev asset folders are now gitignored
and untracked: `assets/models/concepts/`, `assets/models/npc/` (Blender
sources), `assets/textures/npc/ai/` (raw AI fabrics), and the raw
`assets/textures/ai/*` outputs (only `assets/textures/ai/game_ready/` stays).
Kept in the repo: `assets/textures/npc/*_{albedo,normal,orm}.png` +
`palette.json`, `assets/models/generated/*` (props + npc_base.glb), and the
authored `assets/textures/*` set. `tools/asset_pipeline.py generate` gained
`--variants N` — generates N alternatives saved as `<stem>-1..N.png` (N=1
keeps the unsuffixed path). Python tooling tests still pass (exit 0).
- **Screenshots + docs (2026-11-01):** regenerated the eleven README captures
(`capture_screenshots.gd` + `capture_prop_gallery.gd`) — ten changed, the
prop gallery is byte-identical (props unchanged). README gained an NPC
character-model subsection, a "Confirmed game assets" section (linked
`game_ready` textures, generated models, NPC maps), and refreshed
dev-status / not-yet lines; `docs/architecture.md` Level 2 now lists
`npc_pipeline.py` + `asset_pipeline.py` and the confirmed-asset locations.
- Repo: `/run/media/system/DATA/Documents/06-games/security-game` (also reachable at
`/var/home/zwitschi/Documents/06-games/security-game`). Godot: `~/.local/bin/godot` (4.3.stable).
Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **177/177**.
Python tooling tests: `cd tools && python3 -m unittest test_asset_pipeline test_screen_designs test_model_pipeline` → **53**.
Session shell smoke: `godot --headless --path . --script res://src/test/smoke_session_shell.gd` → exit 0.
Windowed control smoke: `godot --path . --script res://src/test/smoke_focus_radial.gd` → exit 0
(now also covers wheel cycling and right-click clearing).
- `main` is in sync with origin. **v0.2.2** tags the session-shell + world-follow-up commit;
CI builds the portable binaries, NSIS installer, and AppImage on the tag push.
- **v0.3.0 (2026-10-31, working tree):** rendering pass (`src/game/materials.gd`, phase-driven
environment), generated `.glb` props (`tools/model_pipeline.py`, `assets/models/generated/`),
aim-free selection with wheel cycling and Shift precision walk (ADR-0011/0012), and a **Web**
export target. `export_presets.cfg` gains a `Web` preset with `variant/thread_support=false`
(static hosting, no COOP/COEP); `tools/build_releases.sh` exports `build/web/` and zips it to
`build/gig-line-web-<ver>.zip`, and `publish_release.sh` uploads it. Covered by
`src/test/tests/test_web_export.gd`.
- **Prop atlas (v0.3.1):** `model_pipeline.atlas_for()` builds a per-prop power-of-two PNG with
one 8×8 cell per palette role, plus a metallic/roughness atlas (G=roughness, B=metallic). The
GLB merges all parts into one primitive with constant per-vertex UVs (cell centre) and a single
material (`baseColorTexture` + `metallicRoughnessTexture`, NEAREST sampling). Colours are sRGB in
the texture, so the old `srgb_to_linear`/`_linear_rgba` helpers were removed. `audit_glb` fails
if a prop is not exactly one material or misses TEXCOORD_0/images.
- **Mockups complete (v0.3.2):** `docs/screens/designs/` holds AI references for all 19
catalogue screens. Only the six staged captures (01–06) are compared/judged; the other 13
have no `capture`, so adding staged captures for the wand panel, timeline and escalation HUD
would extend automated visual checks. `generate` now calls `_load_env_file()`.
- **Staged captures (v0.3.3):** `capture_screenshots.gd` now emits nine shots. States 7–10 switch
to The Joke Shop at elapsed 620 s (EVENING has routine spawns; LATE has none), retry the wand
until it alerts, then jump to 760 s after staging for night lighting. The timeline needs
`recorder.close_all_open()`, because `to_dict()` only lists closed incidents. The escalation
HUD mockup is third-person now; `judge` still classes it `review` (no diff).
- **Prop collision (v0.4.0):** `_add_prop_model()` fits a `StaticBody3D` from the model bounds
(cylinder for bollard/bin/hydrant, box otherwise) and adds a `NavigationObstacle3D`; NPC agents
enable avoidance and move with the safe velocity from `velocity_computed`. The navmesh stays a
flat plane, so avoidance (not a baked mesh) routes guests around props. Placements live in
`PROP_PLACEMENTS` with `placement_problems()` validation; variants come from `variant_for(index)`.
`smoke_props.gd` must be windowed because the guard reads captured-mouse input.
- **Realism pass (2026-11-01, completed + adopted):** `tools/scene_concepts.py`
(`list`/`generate`/`process`/`promote`/`audit`/`realism`) never overwrites an image
(`next_free_path` adds `-vN`; `process` skips existing unless `--new`; `promote` keeps an
existing pick). Raw AI output: `assets/textures/ai/v2/raw/` (gitignored). Candidate pool:
`assets/textures/ai/game_ready/v2/` (gitignored) and the **tracked runtime assets** are
`.../v2/picks/{skyline_day,skyline_dusk,skyline_night,brick_facade,sidewalk_pavers,asphalt,
asphalt_wet,awning_canvas}.png` (+ `_normal`). Concepts `docs/screens/concepts/` (committed,
`.gdignore`, targets in its README). Runs `docs/screens/realism/<run>/` (PNG captures
gitignored; `report.{json,md}` + `sheet.jpg` committed).
`src/test/capture_realism.gd` runs five stages (day 0 s / evening 620 s / late 760 s /
joke_shop 700 s / studio 700 s; `--stages=` subset) and swaps textures by the material's
*original* albedo path. **Gotcha:** in the SceneTree script, do not overwrite `_phase` after
`_begin_stage()` sets `done`, or the run loops forever between stage/shoot (`_queue` empty).
**Gotcha:** `elapsed_s = 620` is *Evening* (dusk), 700+ is Late. Godot 4.3
`StandardMaterial3D` has no `fog_enabled`; the skyline is a `ShaderMaterial` with
`render_mode unshaded, cull_disabled, fog_disabled` + `ALPHA_SCISSOR_THRESHOLD` so the
panorama's own haze shows. Backdrop plane is 90 m tall with 2 UV repeats (~200×90 m per
repeat, near the 2:1 panorama aspect). Legacy `assets/textures/skyline.png` is no longer
loaded but stays on disk for the `test_world` asset check.
**Phase look:** day sun 0.62 / exposure 0.6 / ambient 0.22; evening sun 0.12 / exposure
0.72 / dark violet sky; late exposure 0.95 / ambient 0.10 with the wet asphalt street and a
bright skyline tint (1.5) so the night windows read. Stripes: `stripe_period()` (detrended
autocorrelation) + `align_stripes()` (resample to whole periods) replace the edge crossfade
for `awning_canvas`; `audit_striped_tile()` compares the wrap step with the harshest interior
step.
**Judge:** the default `google/gemini-2.5-flash-lite` mostly works but intermittently answers
HTTP 400 "API key not valid" (one shot of ten on 2026-11-01); re-run or pass
`--model google/gemini-2.5-flash` when it does. Scores are noisy (±1–2), so
`realism --repeats 3` averages and `summarize()` reports a like-for-like mean over the shots
every set captured. Final: overall 0.8→1.0, backdrop 0.6→1.6, metric gap 0.444→0.348.
`screen_designs.py judge` gained one retry for the same transient 400.
**Character portrait capture (2026-11-01):** `capture_realism.gd` has a `portrait` stage
(`npc_portrait` -> concept `npc_portrait_day`) because crowd shots cannot judge avatars. It
freezes one NPC, parks the rest of the crowd 140 m away (a passer-by otherwise blocks the
frame), moves the subject to an open sidewalk patch, turns it into the camera and stands the
guard on the sunlit side. Needed because the NPC FSM re-writes `rotation.y` every physics tick
(so the yaw must be set after `set_physics_process(false)`), the venue's morning sun leaves a
face in shadow from most angles, and `visible = false` does not keep the crowd out of frame.
`--portrait-type=N` forces a role; hooded roles (trespasser/violent/crisis) are skipped.
**Flashlight (2026-11-01, final):** a *tight* `SpotLight3D` on `Head`: `spot_range` 20,
`spot_angle` 14, `light_energy` 4.2, `spot_attenuation` 0.9, forward `rotation_degrees = 0`,
starts invisible. A fake additive light shaft was tried twice (cylinder with a UV ramp, then a
vertical-fade `ShaderMaterial`) and **rejected both times**: on GL Compatibility a first-person
camera inside the cone renders it as a filled disc and the far ring shows, so
`Materials.light_beam()` and the beam mesh are gone. Regression guard:
`test_flashlight_aims_a_tight_forward_spot` in `src/test/tests/test_door_and_flashlight.gd`
(lamp on the head, aims straight, cone ≤ 20°, starts off). Night captures are staged in LATE
(`elapsed_s` 760), not dusk.
**NPC detail pass (2026-11-01):** `_face()` now builds sclera + iris + upper lid per eye, brow
ridge, cheekbones, nose bridge/tip, ears with lobes and split upper/lower lips; the new
`eye_white` material is a *fixed-colour* slot, so `_apply_outfit_materials()` leaves it white.
`_hands_and_shoes()` has four fingers plus a thumb and `_clothing()` adds `Cuff` tori.
Budget work: `CHAR_MAX_VERTS` 5000 (see the library note), body decimation 0.26, upper lids are
boxes rather than spheres, and knuckle spheres/nostrils were cut to stay inside it.
**Orientation debugging:** to check a GLB's front axis, compare the mean Z of the `eye_white`
surface (face) and of the `rubber` sole verts (toes) — all 8 avatars are +Z, so any
"they walk backwards" symptom is a yaw bug, not an export flip.
- **Blender asset library (v0.6.0, integrated 2026-11-01):**
`tools/blender_assets.py` (`build`/`render` in Blender; `audit`/`sheet`/`list` stdlib) writes
`assets/models/blender/{npc,npc_gear,props}/*.glb`, `manifest.json` (integration source of
truth), sources in `assets/models/blender_src/` (`.gdignore`d), and review sheets
`docs/blender_previews/sheet_*.jpg` (per-asset PNG renders are gitignored). 8 characters, 18
gear, 20 props; notes in `docs/blender-assets.md`. Characters are drop-in for `npc_base`
(same mesh node name, bones at the same rest positions, five tint slots) and stay under the
6000/4000 budget only because of body decimation (0.26) + `smart_project` at 80°: glTF vertex
count includes UV-seam splits, so extra beveled detail quickly breaks the limit. The 2026-11-01
face/hand pass raised `CHAR_MAX_VERTS` to 5000 (tris 3,974–5,026) after measuring 16 avatars at
~6.9 ms of a 50 ms frame; `npc_pipeline.MAX_VERTICES`/`MAX_TRIANGLES` (base mesh) are unchanged.
**Gotchas:** `bisect_plane` and thin bevels leave zero-area triangles (Godot warns "non-finite
normal in LOD generation"); `_clean()` + a weld after bisect fix it, and the audit rejects them.
Fixed-colour detail materials carry a 4×4 texture because `_apply_outfit_materials()` sets
`albedo_color = WHITE` on unknown slots. Cycles CPU render ≈ 3 s/asset; Godot import of the
library is clean.
**Integration:** `NpcAvatar.character_model_for()` selects a variant by role/seed; only the
five contract slots are tinted (detail materials untouched; `npc_vest` keeps its fixed hivis
vest, firefighter/guard get dark uniform tints). Gear is attached at head/back/chest/hand and
falls back to primitives; `npc_hoodie` skips the extra hood gear. `WorldBuilder` prefers
`assets/models/blender/props/` for the six replacements and the new street dressing, fits the
same colliders/obstacles, and collects library emissives for `set_night_mode()`.
`LIBRARY_TRIANGLE_BUDGET` = 60k (current ~27,986). Library props are excluded from
`PROP_PLACEMENTS` only when they need dedicated slots (door/marquee/sedan/handrail/lantern).
**Gear fit (v0.6.1):** `fit` measures head width (0.315 m), skirt/hip radius and gear bounds;
sunglasses are 0.202 m, the bag's body inner face is 0.262 m (coat 0.225, dress 0.260), and the
helmet is lifted 2 cm above the brow. `test_npc_character` guards head/back gear in Godot.
Blender's GLB export is byte-nondeterministic for unchanged assets (padding/index order), so do
not commit a full no-op rebuild; rebuild only the assets whose geometry changed.
- **Venue geometry (v0.6.1+):** `WorldBuilder.VENUE_SPECS` is the per-venue table; `WorldBuilder.new(root, id)` / `set_venue()` select it, unknown ids fall back to `hollywood_building`. Dimensions, window/pilaster rows, stairs, street, nav bounds, guard spawn, feature flags and prop placements are read from the spec. `GameManager.world_root` holds only the world; `_rebuild_world_for_venue()` frees/recreates it on venue selection/cycling so guard/UI survive. Joke Shop uses procedural alley walls/shutters/dumpster/AC; TV Studio uses a stone facade, bulb canopy, car paint and barrier placements. Gallery shots 12/13 use the existing `joke_shop_queue`/`studio_arrival` references.
- **Screen-design judge (2026-11-01):** `screen_designs.py judge` could not run because `OPENROUTER_API_KEY` returned HTTP 400 "API key not valid". `check_screenshots.sh` still passed 13/13; the judge retry is a TODO follow-up.
- **Portrait capture:** `capture_realism.gd` faces the **Rig** toward the camera (using the
inverse look direction) and hides the blue `FocusCircle`; the old code used the wrong yaw and
left the focus disc over the face. An explicit `--portrait-type=N` now overrides the hooded
skip, but the day roster spawns none of types 5-7, so a hooded close-up is deferred.
- **v0.6.0 environment pass:** `tools/bake_ao.py` bakes 256² AO maps per library asset
(`<name>_ao.png`, red channel) and `Materials.apply_ao()`/`SurfaceMaterials` wire them in;
`tools/make_normal_maps.py` derives tangent normals for the 1024² brick/concrete/asphalt
textures. Forward+ boots on desktop but is rejected for v0.6.0 (project-wide renderer, Web
must stay GL Compatibility). Headless 222/222, Python 69/69.
- **Prop pipeline (done):** audit enforces 400 tris / 600 verts, ground contact, UV range, no
degenerate tris, one material. `capture_prop_gallery.gd` writes `10-prop-gallery.png` (3x2 studio
line-up, back row on a platform) and the `prop_gallery` catalogue entry compares it.
- **NPC character pass (v0.5.0):** Rig is articulated (`Rig/Hip* -> Knee`, `Shoulder -> Elbow ->
Hand`); `outfit_for` adds layer/hair_style/hand_item; `_idle_layer` drives phone/clipboard/crossed
and escalation gestures; `_reaction_tag` shows `!!`, `?!`, `!`, `?`; `apply_lod` swaps to a
billboard impostor at 26 m (hysteresis 1.5 m); GameManager `_refresh_queue` assigns queue offsets.
Perf: 16 avatars = 534 meshes, ~6.9 ms headless.
- **Player movement (v0.5.0):** **W was inverted** (`get_axis(forward, back)` is negative for W and
the code multiplied it directly); now `forward * -input_dir.y`. Step-up uses test_move + a raised
copy (0.32 m). Guard emits footstep/landed/precision_changed; GameManager forwards them to
AudioFeedback and the HUD. Gamepad actions are added at runtime by
`GuardController._ensure_gamepad_bindings`; remaps persist via `Save` `input_bindings` and are
applied by `InputBindings.apply` (key/button events only). Windowed `smoke_movement.gd` guards it.
- **Scheduled TODO pass complete:** only the optional Future ideas remain (live replay, extra
venues, voice audio, image-to-mesh). Grounding facts: the
NPC rig is rigid capsules with a whole-limb swing, and `guard_controller.gd` uses one `accel`
value. Each task should get its own windowed smoke or capture where it changes feel/looks.
- **Backlog complete:** every planned TODO section is done and moved to `DONE.md`; `TODO.md`
now only lists optional future ideas (live 3D replay, NPC LOD, more venues, real audio, prop
atlas, AI mockups for the new catalogue entries, image-to-mesh props). GUT was consciously not
adopted (custom runner covers it without an editor plugin).
- Do not re-open `override_access`. Do not bind wand to `W`. Do not remove the radio easter egg.
## UI / Presentation Decisions
- Runtime UI/scene art is loaded through `src/game/ui/art.gd` (`assets/images/ui/` then `scenes/`); missing files return null so panels keep procedural fallbacks.
- Screen mockups with baked-in controls are used as backdrops plus an opaque `ArtPanel` cover, not as clickable full-screen skins.
- Radio, wand, inspection, and body-camera timeline panels use their reference art as backdrops; forms sit on transparent, padded panels over the covered mockup controls.
- HUD target names use a boxed chip under the crosshair; escalation has a five-segment bar below the text label. The body-camera REC/clock block starts at y=92 on the right so it clears the shift/rep/crowd text.
- Staged captures in `docs/screenshots/` are committed and were last regenerated after the realism and NPC passes; re-run `tools/check_screenshots.sh` (not a partial capture) before replacing them.
- `tools/check_screenshots.sh` is the repeatable capture+compare smoke; both capture scripts accept `--out=res://docs/screenshots`. Judge verdicts on the staged set were 7 match / 4 review / 0 diff.
- Venue selection used to leave `guard.input_locked` true and the mouse visible; `_start_shift()` must call `_refresh_freeze()` after starting the clock.
## Current State
- **v0.3.0 pass (2026-10-31, historical; shipped):** rendering, generated prop models, screen
catalogue, aim-free selection. See ADR-0011 (selection) and ADR-0012 (materials/props).
- `src/game/materials.gd` (`SurfaceMaterials`) is now the single source of surface
recipes + `configure_environment()`. `GameManager.PHASE_LIGHTING` gained
`exposure/glow/fog_color/fog_density/fog_energy/contrast/saturation/ambient_energy`.
Renderer is **GL Compatibility**, so SSAO/SSIL are unavailable; glow+fog+ACES are used
instead and unsupported settings are ignored rather than gated in code.
- World: scaled brick (uv 22,10), framed windows with per-window night emission
(`i % 3 != 2` lit), stone base course, door trim/handles, curb, lane dashes (the old
loop had `Vector3(i * 0.0, …)` and stacked every dash at x=0), street lamps use the
metal profile. The two flat planters that clipped the steps were removed.
- `tools/model_pipeline.py` builds `assets/models/generated/*.glb` (bollard, bench,
trash_bin, planter_box, newspaper_box, fire_hydrant) from box/cylinder specs;
`WorldBuilder._add_prop_model()` loads them with a procedural fallback and records
`loaded_models` / `fallback_props`. **Gotcha:** glTF `baseColorFactor` is LINEAR —
writing sRGB made every prop pastel; `srgb_to_linear()` is applied in `build_glb`.
**Gotcha:** a new/changed `.glb` needs `godot --headless --path . --import` before
`ResourceLoader.exists()` / `load()` sees it (test_generated_props fails otherwise).
- `model_pipeline.py concept` (OpenRouter, reads repo `.env`) writes per-prop concept
sheets to `assets/models/concepts/`; `dominant_colors()` centre-crops and weights by
saturation (a grey studio backdrop otherwise wins), `assign_roles()` maps colours by
hue/value. Delete a concept file to fall back to the catalogue palette.
- NPC avatars: `outfit_for(type, seed)` (pure) gives shirt/trousers/hair/skin +
accessory (cap, hood, backpack, sunglasses, lanyard) and height scale; `_arms` and
`_legs` now swing (`limb_swing`) while walking. Keep `Rig/Head` named for focus.
- Selection: `NpcFocus.rank_indices` (angle then distance) — `pick_index` = first.
`GuardController.cycle_target(direction)` walks nearest-first `nearby_candidates()` and
sets `target_pinned`, which makes `_update_target()` skip the cone. `clear_target()`
sets `focus_suppress_s = 0.75` so the cone does not instantly re-acquire the person.
New input actions `walk_slow` (Shift), `cycle_target_next` / `cycle_target_prev`
(wheel down / up). Right click with nothing open clears focus (Esc still pauses).
- HUD: `nearby_label` + `set_nearby_hint(count)` show `hud.nearby`; `hud.controls` and
`hud.targeting` mention the wheel and Shift.
- `tools/screen_designs.py`: catalogue now 19 screens (added wand_screening,
joke_shop_queue, studio_arrival, crowd_panic, responder_arrival,
body_camera_timeline, escalation_hud), new `list` command, `generate --only/--force`
(existing designs are kept by default so a draft run cannot clobber AI mockups),
and `judge --dry-run` no longer raises `KeyError: 'class'`.
- Screenshot tool leak: `capture_screenshots.gd` intermittently prints
`ObjectDB instances leaked at exit`. Verified against a `git worktree` of HEAD~ — the
baseline leaks more often (3/3 vs 1/3), so it is a pre-existing quit-timing artifact,
not a regression. Normal boots are clean.
- Access artifacts: `src/core/access_loadout.gd` builds from event `override_access` + venue policy.
Spawn signal is now `(profile, intent, npc_id, event)`. Recognition is not auto-attached to every
guest. `AccessVerifier.verify(..., already_screened=false)` so Joke Shop scoring uses
`controller.screened`. Joke Shop/Studio gained `sidewalk`/`steps_end` waypoints. Wand venues roll
`carried_items` per spawn (seeded). Tests in `src/test/tests/test_access_loadout.gd`.
- Wand shortcut is **`C`**, not `W` (`W` is walk). `radio_emergency` input map no longer includes W.
`hud.controls` / `hud.screen_first` updated.
- Control pass (historical; smoke-tested): cone focus via `src/game/npc_focus.gd`,
accel/decel, gravity only airborne, `input_locked` freeze, Esc no longer on the guard.
`NpcAvatar` in group `npc`; neck/hair/eyes/shoes; blue focus circle + `Label3D` name; `set_focus_visual`.
`src/game/ui/action_radial.gd` doughnut (LMB/Space open, A/D/arrows, Space/click confirm, Esc/RMB
cancel). Crosshair CanvasLayer. Emergency still `R` only. Number keys 1–6 when the wheel is closed.
- Named input actions now exist and are used: `action_menu`, `confirm`, `cancel` (Esc + RMB),
`inspect` (E), `wand` (C). The old unused `interact` action was removed.
- Runtime fixes from the smoke: `Head` is +1.6 above the guard root (root sits on the floor); guard
spawns in front of the door facing the street; building volume moved to z ∈ [-8,-4] so the door,
awning, and marquee are visible; step treads rise toward the door; `ShiftCoordinator` pulls the
event scheduler once per simulated second (was every physics frame → >1200 NPCs in seconds);
`GameManager.MAX_LIVE_NPCS = 16` caps live avatars.
- **Session shell (2026-10-31, historical):** the game boots into `StartMenu`
(`src/game/ui/start_menu.gd`, layer 20) instead of a live shift. `GameManager._on_venue_selected()`
builds the scenario and calls `_start_shift()`; `_shift_running` gates input and `_refresh_freeze()`.
Esc with nothing open now pauses (`PauseOverlay`, layer 18) → resume / restart / settings /
quit-to-menu. `SettingsPanel` (layer 22) writes `look_sensitivity` + `hud_font_scale` through
`Save.save_settings()` → `user://settings.json`; `Save.load_settings()` merges over
`Save.DEFAULT_SETTINGS` on boot and `GameManager._apply_settings()` pushes them into the guard/HUD.
Shift review "Run another shift" now calls `_show_start_menu()` (not a silent restart). `V` stays a
debug venue bind. Smoke: `src/test/smoke_session_shell.gd` (menu → shift → pause → settings → menu).
- **World follow-ups closed (2026-10-31):** skyline backdrop restored with `T_SKYLINE`
(`assets/textures/skyline.png`, regenerated RGBA with a transparent sky) and an
`TRANSPARENCY_ALPHA_SCISSOR` unshaded material (`_skyline_mat`); steps + landing are a
`StaticBody3D` with per-tread `BoxShape3D` colliders; marquee `font_size` 160 / outline 24; focus
circle (blue disc in front of the face) + name tag 48px; `NpcAvatar.ephemeral`/`lifetime_s` despawns routine traffic
(authored events stay). Guard spawn height verified on the landing (guard_y=0.84).
- **PLAN verbs (2026-10-31, historical):** the radial has 12 slices
(`Stop, Look, Point, No, Ask, Inspect, Wand, Warn, Block, Reason, Recognize, Radio`).
`eye_contact` reuses the Level 1 reaction table via `_apply_interaction`. Recognition is a player
decision: `NPCController.player_recognized` + `IncidentRecorder.add_access_evidence`, and
`GameManager._artifacts_for()` prepends a `RECOGNITION` artifact only after the player declares
it. The body camera is a real flag (`GameManager.body_camera_on`, `Hud.set_body_camera`), off by
default, `B` toggles it, and a formal warning arms it and writes a `body_camera` incident event.
Blocking also calls `_station_between()` to physically place the guard 0.9 m from the NPC toward
the entrance. `_request_reason()` logs the NPC's seeded `dialogue_lines` pick.
- **NPC navigation was dead (fixed 2026-10-31):** avatars never moved in the live game, for three
stacked reasons. (1) `agent.target_position` was re-assigned every physics frame, which
invalidates the path so `get_next_path_position()` keeps returning the current point — only set
it when the target changes. (2) Nav path points sit on the navmesh plane (y=0) while bodies stand
at y≈0.5, so a 3D "are we there" test always looked 0.5 m away and the horizontal direction was
zero — compare horizontally. (3) `path[0]` is the old start point, so picking any point "far
enough away" can pick the point behind you and oscillate — pick the first point that is
**closer to the goal** than the current position. `_nav_try_frames`/`_move_direct` fallback is
still there.
- **Crowd reactions listen on the EventBus:** `CrowdSystem.apply_event()` emits
`event_bus.crowd_changed`; its own `changed` signal is never emitted. `GameManager._connect_crowd()`
therefore connects to `coordinator.event_bus.crowd_changed` (reconnect after every
`coordinator.setup()`, which recreates both crowd and event bus).
- **Crowd presentation:** `NpcAvatar.start_rubberneck(at)` (idle bystanders away from the door stop
and face the incident for 12 s) and `start_flee(from)` (panic: walk away at 2.4 m/s, then free
themselves). Someone `awaiting_interaction` within 3 m of the door is "engaged" and does not
rubberneck. Use `position` (not `global_position`) for that distance check so it also works in
headless tests outside the tree.
- **Perf:** a busy 16-NPC entrance measured ~1.2 ms/frame (824 fps with vsync off, 800 samples),
so LOD/culling is not warranted at `MAX_LIVE_NPCS = 16`. Re-measure before raising the cap.
- **Incident recording was dead:** `IncidentRecorder.start_incident()` was never called, so every
`add_event`/`add_access_evidence` silently no-opped and the review always showed 0 incidents.
`_begin_incident()` now starts one per focused NPC (`has_open_incident()` guard), verbs call
`_ensure_incident()`, and `ShiftCoordinator._finish_shift()` closes leftovers as `unresolved`
before building the review.
- **Editor-ready venues (2026-10-31):** `tools/export_venues.gd` (a SceneTree script) saves the
three factory scenarios to `content/venues/<id>.tres` with `ResourceSaver`; the `.tres` files
round-trip through `ResourceLoader` (events, profiles, policy, shift phases, door lines,
locations). `test_venue_resources.gd` also fails if the exported files are missing or stale
relative to the factory, so re-run the export after changing `ScenarioFactory`.
- **Vision judge (2026-10-31):** `python3 tools/screen_designs.py judge` sends the capture plus
its concept mockup to a vision model (`DEFAULT_JUDGE_MODEL = google/gemini-2.5-flash-lite`,
override with `--model`; `openai/gpt-4o-mini` also works). Reads the repo `.env` via
`_load_env_file()` (never overrides real env). Verdicts are classed `match` / `review`
(mismatch below `--fail-confidence`, default 0.6) / `diff` (confident mismatch → exit 1) /
`unknown`. `--dry-run`, `--json`, `--only` supported. The prompt explicitly ignores art style,
because the references are stylised mockups; the live run gave 3 match, 3 review, 0 diff.
Python tests: `cd tools && python3 -m unittest test_asset_pipeline test_screen_designs`.
- **String formatting safety (2026-10-31):** `Strings.placeholder_count()` counts `%` specs
(ignoring `%%`), `fmt()` warns and returns the template when the arg count does not match
(instead of raising an engine error), and `validate()` lints the table for trailing `%`,
unsupported conversions, and mixed positional/sequential placeholders. Tests cover all three
plus a formatting pass over every placeholder key with type-correct dummy args.
- **Authored scenario content (2026-10-31):** events can now carry `hint` (a string key logged
when the avatar spawns), `extra_spawns` (celebrity arrivals pull in tourists/influencers via
`_spawn_npc` with `kind: routine`), and `refuses_screening` (copied to
`NPCController.refuses_screening`; `_toggle_wand_screening` logs `wand.refused`, records a
`wand_refused` event, applies a small prop hit, and does not open the panel). `film_boundary`
is a new intent that maps to `steps_end` so the influencer stands past the posted line.
- **Accessibility (2026-10-31):** escalation text is `"<name> [###--] L3/5"` via
`Hud.escalation_meter(level)` (pure, tested) so the cue survives grayscale. HUD font scale uses
`add_theme_font_size_override` on the core labels (`set_font_scale`), **not** `Control.scale`,
which pushed the right-anchored clock label off-screen. Settings load before `_build_ui()`, so
`_ready()` re-applies player settings after the HUD exists.
- **Audio (2026-10-31):** `AudioFeedback.start_crowd_bed()` builds a 4 s looping `AudioStreamWAV`
(one-pole-filtered noise + 68/103 Hz drone, seeded) and `set_crowd_level(mood)` maps 0..1 to
-34..-12 dB. `play_radio_open/close` push short filtered-noise bursts. All procedural; with a
dummy (headless) audio server the calls are no-ops. `_free_player()` returns null when `_ready`
has not run (tests), and callers handle null.
- **NPC theater (2026-10-31):** `NpcAvatar.theater_pose()` maps `intent` + `arrived` to
idle/sit/photo/film/perform. Pure helpers `crouch_for_pose`, `prop_lift_for_pose`,
`sway_for_pose` keep the math testable. Sit lowers the rig by 0.34 and swings the legs forward
(negative X pitch; positive looked like kneeling). `_photo_prop` is set for influencer/tourist
cameras, `_sign_prop` for protester/performer boards; their base Y values are captured at build
time so the animation offsets from rest.
- **Body-camera timeline (2026-10-31):** `src/game/ui/event_timeline.gd` flattens
`review.incidents.incidents[].events` into one time-ordered list (`flatten`), formats each row
with `describe`, and steps with Left/Right or the buttons. `ShiftCoordinator.started_at_ms`
gives the review an absolute start for mm:ss stamps. `GameManager._open_timeline` hides the
review dim (two stacked overlays made the screen nearly black) and `_timeline_from_review`
restores it on close. Smoke: `src/test/smoke_timeline.gd`.
- **Radio dispatch was dead after the first shift:** `coordinator.setup()` builds a new
`RadioSystem`, so the `responder_arrived` connection made in `_ready` was lost on every venue
switch/restart (responders never resolved anything). `_connect_radio()` now re-wires it next to
`_connect_crowd()` after every setup. Same class of bug as the crowd signal.
- **Emergency aftermath:** `ShiftCoordinator.responder_outcomes` / `investigations` feed the review.
`evacuation` (emergency call, low danger, no weapon) locks the door, makes every NPC flee, marks
them `leave_requested`, and applies a `security_failure` hit. An emergency placed while
`current_npc_truth` has no weapon and `danger_level <= 1` appends an investigation note, records
`investigation_opened`, and applies `false_report` -6.
- **Skyline backdrop (2026-10-31):** `PlaneMesh` defaults to a **horizontal** XZ plane — set
`mesh.orientation = PlaneMesh.FACE_Z` or the "backdrop" renders as a band overhead. The skyline
is now a ring of four 400x60 planes at 200 m (center y=30, so building bases meet the ground),
`TEXTURE_FILTER` default, `TRANSPARENCY_ALPHA_SCISSOR` with threshold **0.5** (0.3 left faint
mip dashes across the sky), unshaded with a lifted albedo (`Color(2.6,2.7,3.0)`) so it is not
pure black. Ground is 500x500 so no sky gap shows under the buildings. The `skyline.png` import
needs `mipmaps/generate=true`.
- **Asset gotcha:** `assets/textures/game_ready/` is *not* imported (no `.import`). Use the
imported `assets/textures/skyline.png` path. Untracked duplicate folder should not be referenced.
- Host sign-in follow-up is **done**: `REQUIRE_SIGNIN_VIEW = false` in `/etc/gitea/app.ini`
(backup `app.ini.bak-*` alongside), Gitea restarted; anonymous repo page/API return 200 and
release assets download in full. Reverse by setting it back to `true` and restarting.
- **Release publishing was broken since v0.2.2 (fixed 2026-10-31):** `publish_release.sh`
only built `AUTH` in the `GITEA_TOKEN` branch; the `~/.git-credentials` branch set `CRED`
and then used unbound `${AUTH[@]}` under `set -u`, so every API call failed and the tag
job ended "could not create or find release". The repo also has **no** `GITEA_TOKEN`
secret (`/actions/secrets` is empty) and the runner has no git credentials. Fixes: the
script sets `AUTH=(-u "$CRED")`; the workflow falls back to `github.token`
(`secrets.GITEA_TOKEN || github.token`) — but `github.token` is **not** provided by this
Gitea/act runner, so the env dump showed no token and the v0.2.10 tag build failed again.
Final fix: `publish_release.sh` falls back to the **local git extraheader** that
`actions/checkout` writes (`git config --local --get-regexp '^http\..*\.extraheader$'`),
which carries the runner's basic-auth credentials for the same server. Verified locally by
adding that extraheader and publishing v0.2.10 (all five artifacts). **v0.2.9 and v0.2.10
are published**; v0.2.4–v0.2.8 tags have no artifacts. Verify the CI path on the next tag.
- **CI publishing verified (2026-10-31):** tagging v0.2.12 produced a release with all five
artifacts automatically (no repo secret), confirming the checkout-extraheader fallback.
- **Host access (2026-10-31):** `root@192.168.88.30` now works from this workstation (key auth),
host name `gitea`, Gitea 28.2.0. Maintenance applied: `apt-get clean`, journald capped to
`SystemMaxUse=300M` (`/etc/systemd/journald.conf.d/99-size.conf`, journal ~133M), disk at
~7.6G free of 16G.
- **Host disk resolved (2026-10-31):** the Gitea VM root filesystem was expanded from 7.8G to
16G (`/dev/mapper/pve-vm--500--disk--0`, now ~7.8G free). The pruned **v0.2.0** and **v0.2.9**
releases were restored from the local `build/` artifacts and verified (4 and 5 assets).
`git.allucanget.biz` runs on this host as user `zwitschi`; root@192.168.88.30 is **not**
reachable from this workstation (publickey denied; password auth closes the banner), so host
maintenance must go through a `zwitschi` login with sudo.
- **Host disk full (2026-10-31, historical):** the v0.2.11 tag build created release id 27 and logged the
uploads, but they failed with `HTTP 500 Attachments.Save: write … no space left on device`
(`/dev/mapper/pve-vm--500--disk--0`, 7.8G, 100%, 0 free). `git push` then failed too
("unpacker error"). Unblocked by deleting the superseded **v0.2.0** and **v0.2.9** releases
via the API (~565 MB of assets), then publishing v0.2.11 locally with the fixed script
(verified 5 assets). Remaining ~150 MB is tight — the next release needs host cleanup on
192.168.88.30: `sudo journalctl --vacuum-size=100M` (journal 837 MB), `sudo apt-get clean`
(91 MB), clear `/var/lib/gitea/data/attachments/tmp`. SSH works as `zwitschi`; `sudo -n`
needs a password. **Releases now:** v0.2.11, v0.2.10, v0.2.1 (5 assets each); v0.2.0 and
v0.2.9 entries were pruned. PLAN.md + ADRs 0007–0010 still describe the intended UX.
- **Release v0.2.1 is LIVE** (2026-10-31): CI run 1110 built the NSIS installer + AppImage
and published — installers/AppImage are on the releases page. Releasing = bump version → tag → push.
- NSIS gotchas (validated in a local ubuntu:24.04 docker repro): makensis uses `-D` defines
(never `/D`), resolves relative `-DICON`/`-DARTIFACT` against the **script directory** so CI
passes `$PWD`-absolute paths; installed exe must be space-free (`gig-line.exe`); ICO must be
classic BMP entries (ImageMagick `icon:auto-resize=48,32,16`; PIL emits PNG-compressed ICOs NSIS
cannot load).
- appimagetool runs system `appstreamcli` if present (strict, exit 3 on warnings) — project
metadata now passes `--pedantic`.
- Distribution facts (verified via Gitea API + host creds): repo is **public**, account is
admin; `v0.2.1` has all 5 assets; the installers were initially missing from the release
because `publish_release.sh` uploaded only portables — now fixed (uploads installer +
AppImage; falls back to `~/.git-credentials` when no `GITEA_TOKEN`).
- Host sign-in: instance `[service] REQUIRE_SIGNIN_VIEW=true` blocks anonymous browse/download
(not a repo/API setting). `~/.git-credentials` holds the Gitea creds (basic-auth usable);
`.env` only holds `OPENROUTER_API_KEY`.
- **Release v0.2.0 was LIVE**: pushing the `v0.2.0` tag made Gitea Actions run the build job
and publish automatically (host has the `GITEA_TOKEN` secret). Tag page and `/releases` are
public (HTTP 200). Caveat: anonymous **asset downloads** redirect to login on this host
(host setting — tracked in TODO). Future releases = bump `config/version`, `git tag vX.Y.Z`,
push. No local token needed.
- Core simulation, three scenarios, v0.2.1 installers/AppImage, first-person prototype (inspect/wand/radio, shift review, Tab targeting, V venue cycle) are on local `main`. Tagged v0.2.1 was 57 tests; this working tree is **74/74**.
- NPC avatars now walk to per-intent waypoints (`VenueScenario.waypoint_for_intent`): enter/trespass/deliver/arrive → entrance, photo/film/perform/wander/roam → sidewalk, sit → steps_end. `NpcAvatar.setup` takes an explicit `door_position`.
- Returning-trespasser behavior: `NpcProfile.return_probability` (trespasser 0.6, mental-crisis 0.4, others 0). `NPCController._maybe_schedule_return()` moves a directed-away NPC to `LOITERING_DISTANT`; `tick_return(delta)` fires after 20-60s and transitions back to `APPROACH`. `NpcAvatar._advance_fsm` re-targets the door on return.
- Perception pass: `NPCController.perceive_guard(distance)` returns `{noticed, band, reaction}` with bands `unaware` (>6m), `aware` (2-6m), `near` (<2m). Near + temperament ≥0.6 → ARGUE, else COMPLY. `GameManager._perception_pass()` feeds real guard distance each physics tick and logs first-time notice.
- Responder dispatch: `RadioSystem` tracks `pending_calls` and decrements `arrival_delay_s` in `tick`; emits `responder_arrived` when a call's delay elapses. `RadioSystem.responder_outcome(call)` maps danger/weapons → command/restraint/evacuation/arrest. `GameManager._on_responder_arrived` spawns a `RESPONDER` avatar at the entrance and logs the outcome. HUD shows an emergency-radio cooldown label via `_update_radio_cooldown`.
- Wand screening (Joke Shop): `WandScreener` classifies carried items as prohibited vs false-alarm vs clear. `NpcProfile.carried_items` is filled at spawn on wand venues. `GameManager` binds **`C`** to `_toggle_wand_screening` and `A`/`D` to `decide_wand` while inspect/wand is open. `decide_access` still blocks admission until `controller.screened` is true, and `verify` now takes that flag. Door-staff lines log through GameManager.
- Design change (2026-10-31): the selectable dialogue tone-of-voice system was **removed**.
`Rules.Tone`/`TONE_NAMES`, `NpcProfile.tone_modifiers`, and the tone param on
`NPCController.roll_reaction/apply_player_action` no longer exist; `NpcProfile.dialogue_lines`
is a flat `Array[String]`; `GameManager` uses `_apply_interaction(level, action_name)`;
the `T` key is gone. HUD `tone_label` removed and `hud.tone_prefix` string deleted from JSON.
- Polish/accessibility: venue cycling (`V` cycles `VENUE_ORDER` and restarts the shift), aim-free
targeting (`Tab` cycles nearby NPCs within `NEARBY_RADIUS`), color-coded escalation indicator
(`Hud.set_escalation`), procedural audio (`AudioFeedback` node generates sine tones for
confirm/radio/escalation/deny), and a centralized `Strings` table (`src/core/strings.gd`, `lookup`/`fmt`)
backed by `content/strings.en.json`. NOTE: `Strings.get` was renamed to `Strings.lookup` because
`get` collides with `Object.get`.
- Newly added: LATE shift phase (start 700s) with a reachability test; PROTESTER archetype + events + colors; CALL_SOMEONE reaction; `DoorState` core class + `L` binding + visual indicator; flashlight (`G`, SpotLight3D, perception bonus at evening/late); persistence wired (`Save.save_shift_result` on shift end, history + best score on the review screen); `NavigationRegion3D` flat navmesh + `NavigationAgent3D` NPC paths (steps now visual-only); strings moved to `res://content/strings.en.json` (41 keys, loader keeps `lookup`/`fmt`); `export_presets.cfg` (Linux/Windows/macOS) + `tools/build_releases.sh` + `docs/RELEASING.md`; README Downloads section.
- Run tests: `godot --headless --path . --script res://src/test/test_runner.gd`
- Boot check: `godot --headless --path . --quit-after 120`
- Screenshot tools: `godot --path . -s res://src/test/take_screenshot.gd` (single shot to `user://screenshot.png`); `godot --path . -s res://src/test/capture_screenshots.gd` (staged README gallery shots to `user://screenshots/`, copied to `docs/screenshots/`).
- Release build verified locally: export templates for 4.3 installed at `~/.local/share/godot/export_templates/4.3.stable/`; `./tools/build_releases.sh` produced Linux/Windows/macOS artifacts for 0.1.0 in `build/` and the Linux binary passed a headless smoke test. Publishing to Gitea still needs a token (see `tools/publish_release.sh`, `docs/RELEASING.md`).
- CI: the repo now carries a **Gitea Actions** workflow at `.gitea/workflows/build.yml` (checks on
push/PR; full export + artifact upload + optional release publish on tags and `workflow_dispatch`).
A repo secret `GITEA_TOKEN` is required for tag releases. The old `.github/workflows/ci.yml` was
removed so hosts scanning both paths do not double-run.
- Venue cycling: `V` switches Hollywood Blvd → The Joke Shop → TV Studio and restarts the shift.
- Venue door-staff dialogue: `VenueScenario.door_staff_line(context, rng)` and GameManager logging
(id/ticket/wand-check, refusal, prohibited-item) — Joke Shop flavor.
- CI cache: `.gitea/workflows/build.yml` no longer uses `actions/cache` (the runner can't reach
the GitHub cache service; import is fast). `GODOT_SILENCE_ROOT_WARNING=1` set on jobs.
Logs live in `.gitea/logs/` after each run.
- Visual polish (v0.2.0): phase lighting (GameManager.PHASE_LIGHTING + ProceduralSkyMaterial),
street lamps + night mode in WorldBuilder.set_night_mode, marquee Label3D, crosswalk/planters/
trees, NPC per-seed variation. capture_screenshots.gd shoots day + fast-forwarded LATE night
(6 shots in docs/screenshots/). Version bumped to 0.2.0; binaries rebuilt.
- Assets pipeline: `python3 tools/generate_textures.py` produces all authored PNGs under
`assets/textures/` (≤1024px, pow2); regenerate after changing the script, then run
`godot --headless --import` before tests. 57 headless tests guard the pipeline.
- General image pipeline: `python3 tools/asset_pipeline.py` defaults to
`openai/gpt-5-image-mini`, requests supported 1:1/3:2 sources, then fits each role's target.
It provides crop, resize, tile edge blending, alpha-glyph recoloring, and role-aware audits.
Screenshot auditing checks 1280x720 minimum, 16:9 aspect, and contrast; visual composition
remains a manual check. Python tools require Pillow from `requirements-tools.txt`.
- AI texture mode: `python3 tools/generate_textures.py --ai` generates textures via the
OpenRouter image API (`POST https://openrouter.ai/api/v1/images`, model
`bytedance-seed/seedream-4.5` by default, base64 PNG in `data[].b64_json`). Requires
`OPENROUTER_API_KEY` (a gitignored `.env` in the repo root holds it locally; source it
with `set -a && . ./.env && set +a`). `--prompt`/`--name`/`--model`/`--size` override the
default batch. Output is downscaled to ≤1024px. NOTE: seedream rejects sizes below
3,686,400 px (1920×1920), so the script requests `GEN_SIZE=2048` and downscales — do not
lower `GEN_SIZE` below 1920. Verified end-to-end 2026-10-31 (3 textures generated).
Legacy `--ai` remains on Seedream 4.5 and still ignores `background: transparent`; do not
use it for alpha-dependent assets. Gemini 3.1 Flash Image advertised broad ratios but the
current provider rejected authentication. The validated role-aware model is GPT-5 Image Mini.
- Runtime assets: all 15 loaded world/HUD/card textures now live in
`assets/textures/ai/game_ready/`; each passes role audit. Generated tile edges are blended,
icon alpha masks are recolored white where needed, and the vignette uses the existing
deterministic generator because the API returned an empty transparent image. The six
`docs/screenshots/` captures were regenerated after wiring and pass screenshot audit.
- Detailed graphics: textured facade/awning/skyline, humanoid NPC rigs + props + walk bob +
attention head-turn, inspection artifact cards, mode icons, escalation vignette, body-cam
overlay (REC + shift clock). Screenshot set regenerated (`docs/screenshots/`, day + night).
- Polish: wand scan cooldown (`WAND_COOLDOWN_S` + `wand.recharge` message).
- NOTE: collaborators occasionally add uncommitted work to this shared tree (e.g. the radio
easter egg); check `git status` before committing and leave foreign changes untouched unless asked.
- Screen-design references (`tools/screen_designs.py`, tests in `tools/test_screen_designs.py`):
`generate` turns the screen descriptions in `docs/` (README captions, ADRs 0007–0010,
PLAN, architecture) into reference images under `docs/screens/designs/` — `--draft`
draws offline Pillow wireframes, `--ai` generates realistic 3D scenes / in-game UI via
OpenRouter (default model `openai/gpt-5-image-mini`). Screens are tagged `scene` or
`ui`; prompts get a shared art-direction suffix, and requests use a 3:2 landscape
source cropped to 16:9 (not a square centre-crop). `compare` checks `docs/screenshots/*.png` for aspect/size/
blank-frame and coarse layout/palette/detail drift. Drift distances are warnings, not
failures: a mockup never matches a real render, so semantic composition is a follow-up
vision-model judge (tracked in TODO).
## Known Issues / Notes
- Reference cycles are fixed: `FSM` holds its owner via `WeakRef`, `ShiftCoordinator` is
`RefCounted`, and persistence tests free the `Save` node — the suite exits with no
"resources still in use" warnings (was ~13). If a future subsystem introduces a cycle,
repeat the WeakRef pattern.
- Shift length: `GameManager` uses the scenario's `shift_duration_s` (900s ≈ 10-15 min);
tests and the screenshot tool override elapsed time directly.
- Headless (dummy renderer) logs "Parameter m is null" from `mesh_get_surface_count`; it does not occur with a real GL driver and does not affect logic.
- Steps are visual-only (no collider) so NPCs can path to the entrance; the guard spawns on the landing so it never needs to climb.
- Persistence tests write into the real `user://shift_results.json` (harmless local pollution of the demo history).
- macOS export required `rendering/textures/vram_compression/import_etc2_astc=true` in `project.godot` (universal build demands ETC2 ASTC enabled).
- Joke Shop `door_staff_lines` are logged through `GameManager` (inspect / wand / refuse). They are not a separate dialogue UI.
- Access loadout is implemented; do not re-fix `override_access` as if it were still unread.
- `Save.SETTINGS_FILE` is declared and unused; settings persistence goes through `Save.load_settings()` / `save_settings()`.
- Headless suite is **229/229**; the windowed smokes, screenshot compare and judge pass. Remaining visual follow-ups are in `TODO.md`.
- Typed spawn signal extra args: handlers must accept the 4th `event: Dictionary` or Godot will not call them (`test_shift_coordinator_runs_a_shift` failed until `_on_spawn` took four args).
- GDScript inference trap that broke the main scene: a `var` typed as a base class
(`radial: CanvasLayer`) exposes script members as `Variant`, so `var x := radial.is_open` is a
parse error. Type it as the script type (`ActionRadial`) or annotate the local explicitly.
`--check-only --script` cannot see the `Save` autoload; use a real boot (`--quit-after 180`) to
verify a full-scene compile.
- Do not snapshot right after changing a transform in the same frame: `capture_screenshots.gd`
- **Two character builders — do not confuse them.** `tools/blender_assets.py` is the real builder
for the **eight library characters, 18 gear pieces and props** (it has the 0.9.4 face/hand
sculpting in `_face()`/`_hands_and_shoes()`); `tools/npc_pipeline.py` builds **only**
`assets/models/generated/npc_base.glb`, the frozen plain-primitive fallback. Reading
`npc_pipeline.py` to judge the library's face/hand detail is wrong.
- **Pose capture fixed (2026-11-04):** the harness was missing `set_physics_process(false)`/
`set_process(false)` on the avatar, so the FSM (`_advance_fsm` + `_move_toward_target`) ran every
tick, reset `arrived`/velocity and made `theater_pose()` revert to idle/rest. Disabling both fixes
it (sit vs idle diff 5.63, walk vs idle 2.29). The earlier "moving the `Rig` child does not
render" probe was a red herring — it set the position inside `_shoot()` in the same frame as
`root.get_texture().get_image()`, so it never rendered a frame. Lesson: pose/portrait harnesses
must freeze the avatar's process/physics before driving `_animate()`.
- Capture/pose harness gotchas: scripts that read captured-mouse input must run windowed
(`smoke_props.gd`); the NPC FSM rewrites `rotation.y` every physics tick, so pose/portrait
captures set the yaw after `set_physics_process(false)`; `visible=false` does not keep the crowd
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
LOD generation"); `_clean()` + a weld after bisect fixes them and the audit rejects them.
- 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
generation defaults to `--request-size 1536`.
- Local photo research tooling: DDG rate-limits by IP (HTTP 202) — the `refcollect.py` token bucket
(0.5 req/s, burst 3) plus retry/backoff handles it; some DDG frames are AVIF mislabeled
`.png`/`.jpg` (convert with ffmpeg; `refcollect.py sheet` skips unreadable). Commons API needs a
descriptive User-Agent and rate-limits thumbnail downloads (fetch all metadata in one
`titles=A|B|C` query).
- Frozen decisions: do not re-open `override_access`; do not bind wand to `W`; do not remove the
radio easter egg.
- GDScript: no static signals; lambdas capture locals **by value** (mutating a captured local does
not affect the outer variable — use member functions for accumulators).
- Reference cycles: `FSM` holds its owner via `WeakRef`, `ShiftCoordinator` is `RefCounted`; if a
new subsystem introduces a cycle, repeat the WeakRef pattern.
- Headless (dummy renderer) logs `Parameter m is null` from `mesh_get_surface_count` — benign, does
not occur with a real GL driver.
- macOS export requires `rendering/textures/vram_compression/import_etc2_astc=true` (universal
build demands ETC2 ASTC).
- Typed spawn signal: handlers must accept the 4th `event: Dictionary` arg or Godot will not call
them.
- GDScript inference trap: a `var` typed as a base class (`radial: CanvasLayer`) exposes script
members as `Variant`; type it as the script type (`ActionRadial`). `--check-only --script`
cannot see the `Save` autoload — use a real boot (`--quit-after 180`) to verify full-scene
compile.
- Do not snapshot right after changing a transform in the same frame — `capture_screenshots.gd`
needs a state/frame gap or the capture uses the old camera orientation.
- `EventScheduler` probabilities are per simulated second, not per call. Never poll it per frame.
- Screenshot staging lives in `src/test/capture_screenshots.gd` (guard at z≈10.5 facing the door).
`docs/screenshots/` copies are manual: copy from `user://screenshots/`.
- `EventScheduler` probabilities are per simulated second, not per call; never poll it per frame.
- Light falloff: `omni_attenuation = 0.0` is **constant**, not physical, falloff in Godot 4.7 —
all local lights use `1.0`.
- glTF `baseColorFactor` is LINEAR — writing sRGB makes props pastel; `srgb_to_linear()` is applied
in `build_glb`. A new/changed `.glb` needs `godot --headless --import` before `load()` sees it.
- **Rebuilding a character GLB also requires re-baking its AO** (`tools/bake_ao.py -- --only
<name>`). `blender_assets.py build` writes the GLB only; the old `<name>_ao.png` then maps onto
the new geometry and produces large dark cheek/jaw blotches. Bake the AO, re-import, and refresh
the stale `.godot` cache (scratch-copy import) before judging a face/body change.
- `Save.SETTINGS_FILE` is declared and unused; settings persist through
`load_settings()`/`save_settings()`.
- Crowd reactions emit `event_bus.crowd_changed`; `GameManager._connect_crowd()` must reconnect
after every `coordinator.setup()` (same class of bug as the radio-dispatch reconnect).
- Packaging: NSIS uses `-D` defines (never `/D`) and resolves relative defines against the script
directory (pass `$PWD`-absolute paths); installed exe must be space-free; ICO must be classic
BMP entries (PIL emits PNG-compressed ICOs NSIS can't load). appimagetool runs system
`appstreamcli` — pass `--pedantic`.
- Credentials: `~/.git-credentials` holds the Gitea creds; `.env` holds only `OPENROUTER_API_KEY`.
- `assets/textures/game_ready/` is not imported (no `.import`) — use the imported
`assets/textures/skyline.png` path.
- Godot 4.7 writes `*.gd.uid` files — kept out of `git status` via `.git/info/exclude`.
- Collaborators occasionally leave uncommitted work in the shared tree; check `git status` before
committing and leave foreign changes untouched.
## Commands
- Tests: `godot --headless --path . --script res://src/test/test_runner.gd`
- Boot check: `godot --headless --path . --quit-after 120`
- Single screenshot: `godot --path . -s res://src/test/take_screenshot.gd`
- Staged gallery: `tools/check_screenshots.sh` (re-run the whole script, never a partial capture)
- Blender build: `flatpak run org.blender.Blender --background --python tools/npc_pipeline.py -- build`
(flatpak cwd is `/app/blender` and cannot read `/tmp` — use absolute repo paths; `.gdignore`
markers keep Godot from importing `.blend`/`tmp`)
- Textures: `python3 tools/generate_textures.py` (then `godot --headless --import`); AI mode
`python3 tools/generate_textures.py --ai`; general pipeline `python3 tools/asset_pipeline.py`
- Character checks: `python3 tools/blender_assets.py inspect|audit|fit`,
`python3 tools/npc_proportions.py`
- Docs: `python3 tools/check_docs.py` (every tracked image must be linked from its folder README)
- Reference research: `python3 tools/refcollect.py search <q> --slug <block> --limit N`
(local-only), `tools/refboard.py` (Commons), `tools/three_up.py compose/rows`,
`tools/scene_concepts.py generate/anchor/realism`
## Open Questions
@@ -658,19 +173,15 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
- Localization target languages beyond English.
- macOS notarization approach for broad distribution.
## Plan vs code
## Current State
`PLAN.md` describes intended design; `README.md` and `DONE.md` describe what shipped. As of
2026-11-01 the playable build covers all three venues, controls, access/radio/evidence/review,
the Blender asset library, the realism pass, and the visual-alignment pass. `TODO.md` holds only
the realism follow-ups and optional future ideas. The project uses a custom headless runner, not
GUT, and Gitea Actions, not GitHub Actions.
The playable build covers the three venues, tutorial/campaign/checkpoints, the 0.9.3–0.9.6
polish/texture/integration passes and the Blender + realism asset pipeline (see `DONE.md` and
`CHANGELOG.md`). The 12-character library (realistic face + four crowd variants) and the
Comedy Store map rebuild are complete. **Priority (2026-11-04, owner):** 1) release the finished procedural character development as
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
priority source. Test counts as of 2026-11-04: Godot 277/277,
Python 161/161, `check_docs` OK.
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
## Uncommitted Workstreams
- **None (2026-11-01).** The NPC detail pass, the realism follow-ups and the flashlight beam
decision are committed together with the portrait capture and the refreshed screenshots.
Keep the rule from `AGENTS.md`: check `git status` before committing, and never revert
another contributor's in-flight files.
+33 -11
View File
@@ -2,7 +2,7 @@
## Premise
You are a private security guard working the entrance of a fictional Hollywood Boulevard building. You keep the steps and doors clear, check who is allowed in, and deal with tourists, performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people in mental-health crisis, and rare determined threats.
You are a private security guard working the entrance of a stylized reconstruction of the real El Capitan Entertainment Centre on Hollywood Boulevard. You keep the steps and doors clear, check who is allowed in, and deal with tourists, performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people in mental-health crisis, and rare determined threats.
The game is about **judgment, communication, access verification, and proportionate escalation**, not about attacking everyone who approaches.
@@ -14,6 +14,23 @@ The game is about **judgment, communication, access verification, and proportion
- **Emergency help is a last resort.** Responders handle crises the guard cannot safely manage; calling them has costs.
- **Readability beats raw fidelity.** The GL Compatibility renderer, clear silhouettes, the focus ring, and the HUD stay legible.
## Development Priorities
The active planning order is set in [`TODO.md`](TODO.md) and in `AGENTS.md`:
1. **Character development (unversioned).** Expand the owned procedural character library with
crowd, age, build, hair and outfit variety. Keep the frozen bone/tint/gear contract; see
[ADR-0015](docs/adr/0015-npc-model-variety-sourcing.md) and
[`docs/npc-model-sourcing.md`](docs/npc-model-sourcing.md). Quaternius is a reference;
MakeHuman is deferred to a separate local pipeline.
2. **Local map development (unversioned).** Move beyond stylized venue reconstruction with
licensed OpenStreetMap and 3D building data, downloaded reference imagery for photoreal
facades, AI-assisted composition, and a Gaussian-splatting evaluation that stays in the
development pipeline unless it proves runtime-safe.
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
track's scope is accepted into a release.
## Core Gameplay Loop
1. Watch the entrance, steps, queue, and sidewalk.
@@ -96,15 +113,19 @@ A poor shift involves excessive force, confronting innocent people, ignoring a r
## Venues
### Hollywood Boulevard Building
### El Capitan Entertainment Centre (6840 Hollywood Blvd)
The original scenario: a building entrance with steps, sidewalk, street lamps, and a restricted door. Tourists, performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people in crisis, and rare violent threats approach from the boulevard.
The original scenario: the historic Masonic Temple / El Capitan Entertainment Centre, with six Ionic columns, eight stone steps, the Hollywood Walk of Fame sidewalk, neighbouring theatre and retail frontage, and the four-lane boulevard. Tourists, performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people in crisis, and rare violent threats approach from the street.
Authorized people are recognized or present a valid wristband. Wristbands vary by color, pattern, date, and event marking, and may be valid, expired, counterfeit, or missing.
### The Joke Shop
A comedy-club entrance. The door person checks IDs, tickets, reservations, guest-list status, and screens guests with a handheld wand under venue policy.
A Sunset Strip comedy club inspired by The Comedy Store at 8433 Sunset Blvd: a
low black hall under a deep, signature-covered canopy, a circular blade sign,
red velvet curtains and the dry hillside rising behind it. The door person
checks IDs, tickets, reservations, guest-list status, and screens guests with a
handheld wand under venue policy.
The wand does **not** identify a weapon automatically. Keys, belts, and phones can trigger false alarms; the player must ask the guest to identify the item and apply procedure. A guest with valid credentials can still require assistance if they threaten staff or patrons.
@@ -123,7 +144,7 @@ Joke Shop escalation follows the common ladder: direction, explanation, repeat r
### Hollywood Boulevard Late Night TV Studio
A busy studio entrance with audience members, production staff, performers, guests, deliveries, celebrity arrivals, tourists, influencers, and the same range of street behavior as the building scenario.
A busy studio entrance at the same real El Capitan Entertainment Centre block as the Hollywood day shift, restaged for a late-night arrival: the same stone facade and steps with a bulb-lit canopy, a black car at the kerb and a barrier line. It has audience members, production staff, performers, guests, deliveries, celebrity arrivals, tourists, influencers, and the same range of street behavior as the building scenario.
Staff and guests are recognized or present a wristband, credential, ticket, guest-list confirmation, or other approved marker. The player keeps the steps clear, protects arrivals and equipment, manages filming boundaries, watches for tailgating, and calls for help only when there is immediate danger.
@@ -148,18 +169,19 @@ The action wheel prioritizes de-escalation: Stop, Look, Point, No, Ask, Inspect,
The visual direction is readable stylized realism:
- **Blender library:** 8 skinned NPC variants, 18 gear pieces, and 20 street/venue props, with baked albedo/normal/AO/ORM maps, a manifest, and procedural fallbacks. `Rig/Head` stays the focus anchor.
- **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 visible flashlight beam, and denser foliage.
- **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.
- **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.
- **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.
- **Storage convention:** see [ADR-0014](docs/adr/0014-runtime-image-root.md). Never overwrite an existing image; new outputs get a new path or `-vN` suffix.
The path from the current vertical slice to v1.0.0 — content depth, onboarding, accessibility, platform QA and release readiness — 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).
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).
## Tech Stack
- **Engine:** Godot 4.3 baseline (local dev verified with 4.7.2), GL Compatibility renderer (Web parity); optional Forward+ Desktop HQ build.
- **Engine:** Godot 4.7.2, GL Compatibility renderer (Web parity); optional Forward+ Desktop HQ build.
- **Language:** GDScript.
- **Architecture:** venue-driven simulation; shared systems plus `Resource`-based scenario data.
- **Navigation:** `NavigationRegion3D` / `NavigationAgent3D`.
@@ -172,7 +194,7 @@ Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [AD
## Versioning and Delivery
`config/version` in `project.godot` is the single version source (currently `0.8.0`). 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/). 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.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.
## Design Direction
+27
View File
@@ -0,0 +1,27 @@
# Privacy Note
Gig Line runs locally. The game itself does not include telemetry, analytics,
accounts, advertising, cloud saves or network requests. It does not access the
camera, microphone, contacts or location, and the body-camera feature is a
simulated overlay, not a real recording device.
Player data is written only to Godot's local `user://` directory:
- `settings.json`
- `campaign.json`
- `shift_results.json`
- `shift_state.json`
Those files stay on the player's machine unless the player copies them. Deleting
the files (or the `Gig Line` user directory) resets the profile. See
[`docs/getting-started.md`](docs/getting-started.md#save-data-and-migration) for
locations, migration and rollback instructions.
The repository's development tools can call OpenRouter when a developer supplies
`OPENROUTER_API_KEY`; those calls generate or judge development assets and are
not part of the shipped game. The key and generated review artifacts remain
local/gitignored.
Questions about a storefront release should be checked against the final package
and the provider terms recorded in [`CREDITS.md`](CREDITS.md); external legal and
storefront approval is still pending for 0.10.0.
+111 -26
View File
@@ -6,20 +6,15 @@ The central design goal is simple:
> The player wins by choosing the lowest level of escalation that keeps everyone safe.
## Screenshots
## Development Priorities
| | |
| --- | --- |
| ![Venue overview: the Hollywood Boulevard entrance with steps, sidewalks, street lamps, and signage](docs/screenshots/01-venue-overview.png) | ![Targeting an approaching NPC with the interaction HUD](docs/screenshots/02-targeting-npc.png) |
| ![Access inspection: wristband/credential check with allow/deny prompt](docs/screenshots/03-access-inspection.png) | ![Radio report form: emergency dispatch panel](docs/screenshots/04-radio-report.png) |
| ![Late night: street lamps lit, marquee glowing, dark Hollywood Boulevard](docs/screenshots/05-late-night-avenue.png) | ![Night shift: guard flashlight cutting through the dark](docs/screenshots/06-night-flashlight.png) |
| ![Wand screening at The Joke Shop: false alarm on keys](docs/screenshots/07-wand-screening.png) | ![Escalation HUD at L3 with body camera armed and reaction log](docs/screenshots/08-escalation-hud.png) |
| ![Body-camera timeline: formal warning, body camera on, blocking](docs/screenshots/09-body-camera-timeline.png) | ![Prop gallery: the six generated street-furniture models on a studio backdrop](docs/screenshots/10-prop-gallery.png) |
| ![Precision walk at the venue steps with the HUD chip visible](docs/screenshots/11-precision-walk.png) | ![The Joke Shop: a narrow brick alley with shuttered windows, a warm wall lantern and a dumpster](docs/screenshots/12-joke-shop.png) |
| ![Late-night TV studio arrival: stone facade, bulb canopy, black car and barriers](docs/screenshots/13-studio-arrival.png) | |
The active planning order lives in [`TODO.md`](TODO.md) and [`AGENTS.md`](AGENTS.md):
*Screenshots are generated by [`src/test/capture_screenshots.gd`](src/test/capture_screenshots.gd) and [`src/test/capture_prop_gallery.gd`](src/test/capture_prop_gallery.gd) from the current build — day, late-night, wand screening, escalation, shift-review timeline,
precision walk, the Joke Shop alley, the TV Studio arrival, and the prop lineup.*
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.
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).
3. **Versioned release hardening** — the 0.10.0 release candidate and later semantic versions follow the unversioned development tracks.
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.
## ⬇️ Downloads
@@ -38,9 +33,14 @@ precision walk, the Joke Shop alley, the TV Studio arrival, and the prop lineup.
> Downloads are public (no sign-in required) since the host's instance-wide
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
>
> **Latest: v0.8.0** — first-shift tutorial, campaign/checkpoint resume,
> difficulty and a searchable Help overlay, plus the v0.7.x judge, Desktop HQ,
> venue and gear work.
> **Latest published: v0.9.6** — the integration checkpoint: the 0.9.5 texture
> and material refinement (256² NPC maps, a four-times fabric weave without baked
> AO blotches, repaired Walk of Fame/awning surfaces and per-phase skyline haze)
> is packaged with the v0.9.4 NPC visual optimization, the Hollywood/Joke Shop
> maps and the v0.9.3 UI/accessibility polish, with the full gallery, smoke matrix
> and Web/Desktop HQ build re-verified.
>
> **Current source: v0.10.0-rc.1** — the release-candidate source; the final tag is pending external playtest and legal review.
> Releases are built automatically when a `vX.Y.Z` tag is pushed
> (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use
> `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline
@@ -53,14 +53,68 @@ precision walk, the Joke Shop alley, the TV Studio arrival, and the prop lineup.
If you are here to develop or test locally instead of downloading, jump to
[Setup and Run](#setup-and-run) below.
The project is under active development. The design and architecture are documented, and a playable prototype vertical slice exists (Godot 4 + GDScript): a procedural Hollywood Boulevard entrance, a first-person guard, simulated NPCs with intents and reactions, the six-level escalation model, access inspection and admission decisions, radio calls with credibility checks, reputation, crowd mood, and an end-of-shift review.
The project is under active development. The design and architecture are documented, and a playable prototype vertical slice exists (Godot 4 + GDScript): a stylized El Capitan Entertainment Centre block, a first-person guard, simulated NPCs with intents and reactions, the six-level escalation model, access inspection and admission decisions, radio calls with credibility checks, reputation, crowd mood, and an end-of-shift review.
## Screenshots
Every capture is rendered from the current build by [`tools/check_screenshots.sh`](tools/check_screenshots.sh)
(21 captures, compared against the runtime art). The full gallery, including every UI state, is in
[`docs/screenshots/`](docs/screenshots/README.md).
### Venues
| | |
| --- | --- |
| ![Hollywood Boulevard day shift](docs/screenshots/01-venue-overview.png) | **Hollywood Boulevard — day.** The El Capitan Entertainment Centre block: six Ionic columns, Masonic parapet, event banners, eight stone steps and the Walk of Fame terrazzo. |
| ![Hollywood Boulevard at night](docs/screenshots/05-late-night-avenue.png) | **Hollywood Boulevard — late.** Sconces pool on the limestone, street lamps light the boulevard and the skyline fades into night haze. |
| ![Flashlight on the entrance at night](docs/screenshots/06-night-flashlight.png) | **Night patrol.** A tight flashlight spot picks out the door and the person in front of it; movement and awareness change at night. |
| ![The Joke Shop at night](docs/screenshots/12-joke-shop.png) | **The Joke Shop.** A black Sunset Strip club under its signature-covered canopy, circular blade sign, red curtains and the dry hillside behind. |
| ![Late-night TV studio arrival](docs/screenshots/13-studio-arrival.png) | **TV Studio arrival.** Stone facade, bulb canopy, black car and crowd barriers for a late celebrity arrival. |
### Interaction and systems
| | |
| --- | --- |
| ![Targeting an NPC](docs/screenshots/02-targeting-npc.png) | **Focus and targeting.** The cone focus ring, name tag and target chip; `Tab`/wheel cycles nearby people without aiming. |
| ![Access inspection](docs/screenshots/03-access-inspection.png) | **Access inspection.** A venue-correct wristband check with evidence shown before the admit/refuse decision. |
| ![Radio report](docs/screenshots/04-radio-report.png) | **Radio report.** Security assistance with location, behaviour and what was actually seen. |
| ![Wand screening](docs/screenshots/07-wand-screening.png) | **Wand screening.** A false alarm on keys: ask the guest to identify the item before choosing a response. |
| ![Escalation HUD](docs/screenshots/08-escalation-hud.png) | **Escalation.** Level 3 blocking with the shape meter, body camera armed and the reaction log. |
| ![Body-camera timeline](docs/screenshots/09-body-camera-timeline.png) | **Body-camera timeline.** Every recorded event, step by step, in the shift review. |
| ![Precision walk](docs/screenshots/11-precision-walk.png) | **Precision walk.** Half-speed close work on the steps, with the HUD state chip visible. |
### Shell and UI
| | |
| --- | --- |
| ![Start menu](docs/screenshots/14-start-menu.png) | **Venue menu.** Day and night rows for Hollywood, the Joke Shop and the TV Studio, campaign continue and review links. |
| ![Settings](docs/screenshots/16-settings-panel.png) | **Settings.** Difficulty, volumes, HUD scale, reduce motion, captions and key/button remapping, all persisted. |
| ![Shift review](docs/screenshots/17-shift-review.png) | **Shift review.** Grade, policy outcome, incidents, responder results and campaign Continue. |
| ![Action wheel](docs/screenshots/19-radial-menu.png) | **Action wheel.** De-escalation first: stop, look, point, no, ask, inspect, warn, block, reason, recognize, radio. |
### 3D asset progress
The character, gear and prop libraries are authored procedurally in Blender and audited before use;
the sheets below track their visual progress.
| | |
| --- | --- |
| ![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. |
| ![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. |
| ![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. |
| ![Crowd-variety prototypes](docs/blender_previews/sheet_npc_crowd_prototypes.jpg) | **Crowd variety prototypes.** Four approved looks (bun/slim, bald/broad, coat/umbrella, hood/cup) queued for the roster build. |
More sheets and the review criteria are in [`docs/blender_previews/`](docs/blender_previews/README.md)
and [`docs/3d-model-review.md`](docs/3d-model-review.md).
## Game Overview
A typical shift lasts 10–15 minutes: watch the entrance and sidewalk, judge why
each person is approaching, verify access with recognition, wristbands, IDs,
tickets or credentials, and escalate only when necessary. The game rewards calm
resolution over force. The full mechanics — the three venues, the six-level
resolution over force. The venue menu splits Hollywood Boulevard into Day and
Night rows; the full mechanics — the three venues, the six-level
escalation model, and the core systems — are in
[`docs/gameplay.md`](docs/gameplay.md); the design rationale lives in
[`PLAN.md`](PLAN.md).
@@ -75,7 +129,7 @@ decisions are recorded as ADRs in [`docs/adr/`](docs/adr/).
## Tech Stack
- **Engine:** Godot 4.3 baseline (local dev verified with 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`
- **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/`
@@ -101,29 +155,60 @@ Full setup, every run/test/smoke command, and the controls table are in
## Project Documentation
- [`PLAN.md`](PLAN.md) — Game design plan and scenario details.
- [`AGENTS.md`](AGENTS.md) — Development and collaboration workflow.
- [`PLAN.md`](PLAN.md) — Game design plan, development priorities and scenario details.
- [`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/npc-model-sourcing.md`](docs/npc-model-sourcing.md) — Character-source evaluation, licence rules and the character-development plan.
- [`AGENTS.md`](AGENTS.md) — Development, commit, build and collaboration workflow.
- [`DONE.md`](DONE.md) — Completed task sections.
- [`MEMORY.md`](MEMORY.md) — Persistent session notes.
- [`CHANGELOG.md`](CHANGELOG.md) — User-facing changes by version.
- [`docs/architecture.md`](docs/architecture.md) — arc42-style architecture documentation.
- [`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/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.
- [`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-release-qa`](.agents/skills/gig-line-release-qa/SKILL.md) — tracked AI skill for release and visual-QA work.
- [`docs/getting-started.md`](docs/getting-started.md) — Setup, run/test commands, and controls.
- [`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/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.
- [`CREDITS.md`](CREDITS.md) — Credits, AI/reference provenance and licensing notes.
- [`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/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.
- [`TODO.md`](TODO.md) — Active tasks plus the v1.0.0 gap analysis and milestone roadmap.
## Development Status
v0.8.0 adds the first-shift tutorial, campaign shell, checkpoint resume,
difficulty presets and an in-game Help overlay on top of the v0.7.x venue, gear,
judge and Desktop HQ work. See [`docs/development-status.md`](docs/development-status.md)
for the implemented list and [`TODO.md`](TODO.md) for the v1.0.0 roadmap. Manual
verification uses the [`docs/smoke-checklist.md`](docs/smoke-checklist.md).
v0.10.0-rc.1 is the current release-candidate source: the 0.9.6 integration
checkpoint plus save-migration hardening and release tooling, on top of the 0.9.5
texture refinement (256² NPC maps, a 4× fabric weave with baked-AO removal, repaired
Walk of Fame/awning surfaces and per-phase skyline haze), on top of the v0.9.4
NPC visual optimization and the v0.9.3 UI/accessibility polish pass. It builds on
v0.9.2's Hollywood/Joke Shop map rebuilds, v0.9.1's Godot 4.7.2 baseline,
v0.9.0's production-quality pass, v0.8's
tutorial/campaign/resume work and the v0.7.x venue/gear/judge work. The active
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
and [`TODO.md`](TODO.md) for the priority-ordered roadmap. Manual verification uses
the [`docs/smoke-checklist.md`](docs/smoke-checklist.md).
## Credits, Privacy and Accessibility
Gig Line is a local-only single-player game: there is no runtime telemetry or
network service, and saves stay in Godot's local `user://` folder. The
body-camera overlay is simulated. Asset provenance, AI-assisted image usage and
credits are recorded in [`CREDITS.md`](CREDITS.md); the local-data policy is in
[`PRIVACY.md`](PRIVACY.md); accessibility features and gaps are in
[`docs/accessibility.md`](docs/accessibility.md); and release-candidate blockers
are listed in [`docs/known-limitations.md`](docs/known-limitations.md).
## Development Workflow
+979 -106
View File
File diff suppressed because it is too large. Load diff
+218 -76
View File
@@ -28,12 +28,12 @@
"attach": "back",
"bounds_godot": {
"max": [
0.15,
0.3,
0.141
0.161,
0.296,
0.212
],
"min": [
-0.15,
-0.161,
-0.12,
-0.319
]
@@ -42,33 +42,11 @@
"materials": [
"pack_nylon",
"steel",
"plastic_black"
"denim_bag"
],
"path": "assets/models/blender/npc_gear/backpack.glb",
"triangles": 316,
"vertices": 360
},
"beanie": {
"attach": "head",
"bounds_godot": {
"max": [
0.193,
0.215,
0.183
],
"min": [
-0.193,
0.014,
-0.203
]
},
"category": "npc_gear",
"materials": [
"plastic_grey"
],
"path": "assets/models/blender/npc_gear/beanie.glb",
"triangles": 460,
"vertices": 403
"triangles": 380,
"vertices": 408
},
"bench": {
"bounds_godot": {
@@ -96,14 +74,14 @@
"bollard": {
"bounds_godot": {
"max": [
0.105,
0.125,
0.975,
0.105
0.125
],
"min": [
-0.105,
-0.125,
0,
-0.105
-0.125
]
},
"category": "props",
@@ -114,7 +92,7 @@
"path": "assets/models/blender/props/bollard.glb",
"replaces": "generated/bollard.glb",
"triangles": 138,
"vertices": 225
"vertices": 221
},
"camera": {
"attach": "hand",
@@ -262,14 +240,14 @@
"fire_hydrant": {
"bounds_godot": {
"max": [
0.24,
0.76,
0.19
0.265,
0.815,
0.235
],
"min": [
-0.24,
-0.265,
0,
-0.19
-0.235
]
},
"category": "props",
@@ -279,8 +257,8 @@
],
"path": "assets/models/blender/props/fire_hydrant.glb",
"replaces": "generated/fire_hydrant.glb",
"triangles": 466,
"vertices": 725
"triangles": 610,
"vertices": 902
},
"firefighter_helmet": {
"attach": "head",
@@ -439,21 +417,22 @@
"category": "props",
"materials": [
"news_blue",
"dark_metal",
"glass",
"paper",
"steel"
],
"path": "assets/models/blender/props/newspaper_box.glb",
"replaces": "generated/newspaper_box.glb",
"triangles": 244,
"vertices": 528
"triangles": 300,
"vertices": 648
},
"npc_casual": {
"bounds_godot": {
"max": [
0.321,
1.824,
0.185
0.189
],
"min": [
-0.321,
@@ -467,8 +446,8 @@
"skin",
"cloth",
"pants",
"eye_white",
"accessory",
"eye_white",
"hair",
"rubber"
],
@@ -487,15 +466,15 @@
"accessory",
"hair"
],
"triangles": 3974,
"vertices": 3744
"triangles": 5038,
"vertices": 4756
},
"npc_coat": {
"bounds_godot": {
"max": [
0.332,
1.824,
0.185
0.189
],
"min": [
-0.332,
@@ -509,8 +488,8 @@
"skin",
"cloth",
"pants",
"eye_white",
"accessory",
"eye_white",
"hair",
"rubber"
],
@@ -528,8 +507,168 @@
"accessory",
"hair"
],
"triangles": 5026,
"vertices": 4807
"triangles": 6030,
"vertices": 5802
},
"npc_crowd_a": {
"bounds_godot": {
"max": [
0.288,
1.825,
0.189
],
"min": [
-0.288,
0,
-0.195
]
},
"category": "npc",
"layer": "none",
"materials": [
"skin",
"cloth",
"pants",
"accessory",
"eye_white",
"hair",
"rubber"
],
"mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_crowd_a.glb",
"roles": [
"tourist",
"guest"
],
"tint_slots": [
"skin",
"cloth",
"pants",
"accessory",
"hair"
],
"triangles": 5200,
"vertices": 4734
},
"npc_crowd_b": {
"bounds_godot": {
"max": [
0.358,
1.809,
0.189
],
"min": [
-0.358,
0,
-0.155
]
},
"category": "npc",
"layer": "none",
"materials": [
"skin",
"cloth",
"pants",
"accessory",
"eye_white",
"hair",
"rubber"
],
"mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_crowd_b.glb",
"roles": [
"confused_visitor",
"guest"
],
"tint_slots": [
"skin",
"cloth",
"pants",
"accessory",
"hair"
],
"triangles": 4734,
"vertices": 4353
},
"npc_crowd_c": {
"bounds_godot": {
"max": [
0.332,
1.824,
0.189
],
"min": [
-0.332,
0,
-0.18
]
},
"category": "npc",
"layer": "coat",
"materials": [
"skin",
"cloth",
"pants",
"accessory",
"eye_white",
"hair",
"rubber"
],
"mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_crowd_c.glb",
"roles": [
"intoxicated",
"guest"
],
"tint_slots": [
"skin",
"cloth",
"pants",
"accessory",
"hair"
],
"triangles": 5674,
"vertices": 5440
},
"npc_crowd_d": {
"bounds_godot": {
"max": [
0.293,
1.856,
0.189
],
"min": [
-0.293,
0,
-0.225
]
},
"category": "npc",
"layer": "hood",
"materials": [
"skin",
"cloth",
"pants",
"accessory",
"eye_white",
"hair",
"rubber"
],
"mesh_node": "npc_base",
"path": "assets/models/blender/npc/npc_crowd_d.glb",
"roles": [
"tourist",
"guest"
],
"tint_slots": [
"skin",
"cloth",
"pants",
"accessory",
"hair"
],
"triangles": 5814,
"vertices": 5300
},
"npc_dress": {
"bounds_godot": {
@@ -550,8 +689,8 @@
"skin",
"cloth",
"pants",
"eye_white",
"accessory",
"eye_white",
"hair",
"rubber"
],
@@ -567,8 +706,8 @@
"accessory",
"hair"
],
"triangles": 4428,
"vertices": 4026
"triangles": 5456,
"vertices": 5128
},
"npc_firefighter": {
"bounds_godot": {
@@ -589,8 +728,8 @@
"skin",
"cloth",
"pants",
"eye_white",
"accessory",
"eye_white",
"hair",
"hivis",
"reflective",
@@ -608,15 +747,15 @@
"accessory",
"hair"
],
"triangles": 4848,
"vertices": 4735
"triangles": 5900,
"vertices": 5902
},
"npc_guard": {
"bounds_godot": {
"max": [
0.361,
1.819,
0.185
0.189
],
"min": [
-0.361,
@@ -630,8 +769,8 @@
"skin",
"cloth",
"pants",
"eye_white",
"accessory",
"eye_white",
"hair",
"rubber",
"leather",
@@ -652,15 +791,15 @@
"accessory",
"hair"
],
"triangles": 4560,
"vertices": 4529
"triangles": 5602,
"vertices": 5684
},
"npc_hoodie": {
"bounds_godot": {
"max": [
0.326,
1.856,
0.185
0.189
],
"min": [
-0.326,
@@ -674,8 +813,8 @@
"skin",
"cloth",
"pants",
"eye_white",
"accessory",
"eye_white",
"hair",
"rubber"
],
@@ -693,15 +832,15 @@
"accessory",
"hair"
],
"triangles": 4826,
"vertices": 4504
"triangles": 5860,
"vertices": 5492
},
"npc_jacket": {
"bounds_godot": {
"max": [
0.332,
1.824,
0.185
0.189
],
"min": [
-0.332,
@@ -715,8 +854,8 @@
"skin",
"cloth",
"pants",
"eye_white",
"accessory",
"eye_white",
"hair",
"rubber"
],
@@ -734,15 +873,15 @@
"accessory",
"hair"
],
"triangles": 4394,
"vertices": 4175
"triangles": 5428,
"vertices": 5071
},
"npc_vest": {
"bounds_godot": {
"max": [
0.321,
1.819,
0.185
0.189
],
"min": [
-0.321,
@@ -756,8 +895,8 @@
"skin",
"cloth",
"pants",
"eye_white",
"accessory",
"eye_white",
"hair",
"rubber",
"hivis",
@@ -775,8 +914,8 @@
"accessory",
"hair"
],
"triangles": 4474,
"vertices": 4340
"triangles": 5536,
"vertices": 5375
},
"palm_tree": {
"bounds_godot": {
@@ -794,7 +933,8 @@
"category": "props",
"materials": [
"palm_bark",
"palm_leaf"
"palm_leaf",
"palm_leaf_dark"
],
"path": "assets/models/blender/props/palm_tree.glb",
"triangles": 400,
@@ -840,7 +980,8 @@
"materials": [
"stone_pot",
"soil",
"foliage"
"foliage",
"foliage_dark"
],
"path": "assets/models/blender/props/planter_box.glb",
"replaces": "generated/planter_box.glb",
@@ -913,7 +1054,8 @@
"category": "props",
"materials": [
"stone_pot",
"foliage"
"foliage",
"foliage_dark"
],
"path": "assets/models/blender/props/round_planter.glb",
"triangles": 1240,
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 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: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1003 B

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 B

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 12 KiB

+107 -2
View File
@@ -1,5 +1,5 @@
{
"hud.controls": "LMB/Space wheel · A/D step · Space confirm · Esc cancel · R emergency · E inspect · C wand · Q polite · B camera · Tab/wheel cycle · Shift slow · G light · L lock · F debug",
"hud.controls": "LMB/Space wheel · A/D step · Space confirm · Esc cancel · R emergency · E inspect · C wand · Q ask · B camera · Tab/wheel cycle · Shift slow · G light · L lock · F debug",
"radial.raise_hand": "Stop",
"radial.eye_contact": "Look",
"radial.point": "Point",
@@ -100,5 +100,110 @@
"tutorial.radio": "Open the assistance radio and submit a short report.",
"tutorial.review": "Finish the shift and review what happened.",
"tutorial.completed": "Tutorial complete.",
"tutorial.skipped": "Tutorial skipped."
"ui.inspect_actions": "A = admit · D = refuse entry",
"ui.wand_actions": "A = admit · D = refuse entry",
"ui.radio_send_assist": "Send request",
"ui.radio_send_emergency": "Send emergency",
"ui.radio_hold_hint": "Hold the button for %.1fs",
"ui.radio_cancelled": "Call not sent — hold the button to confirm.",
"ui.radio_holding": "Holding… %.0f%%",
"ui.review_return_menu": "Return to menu",
"ui.quit_game": "Quit game",
"ui.apply_settings": "Apply settings",
"tutorial.skipped": "Tutorial skipped.",
"ui.settings_title": "Settings",
"ui.settings_invert": "Invert vertical look",
"ui.settings_head_bob": "Head bob and camera motion",
"ui.settings_difficulty": "Difficulty / pacing",
"ui.settings_reduce_motion": "Reduce motion",
"ui.settings_captions": "Captions / subtitles",
"ui.settings_replay": "Replay tutorial",
"ui.settings_reset_campaign": "Reset campaign",
"ui.settings_controls_help": "Controls (click a binding, then press a key or pad button)",
"ui.settings_cancel": "Cancel",
"ui.settings_reset_title": "Reset campaign",
"ui.settings_reset_confirm": "Clear campaign progress and restart from the Hollywood Boulevard shift?",
"ui.settings_replay_title": "Replay tutorial",
"ui.settings_replay_confirm": "Replay the tutorial from the Hollywood Boulevard shift? The current shift will end.",
"ui.settings_press": "Press…",
"ui.pause_title": "Paused",
"ui.pause_resume": "Resume",
"ui.pause_restart": "Restart shift",
"ui.pause_settings": "Settings",
"ui.pause_menu": "Quit to menu",
"ui.pause_hint": "Esc resumes",
"ui.help_title": "Help",
"ui.help_filter": "Filter controls…",
"ui.help_close": "Close",
"help.control_move": "Movement: W/A/S/D · mouse look · Shift precision walk",
"help.control_focus": "Focus: crosshair cone · Tab / mouse wheel cycles nearby people",
"help.control_wheel": "Action wheel: Space or left click · A/D or arrows step · Space confirms",
"help.control_access": "Inspect access: E · Wand: C · Ask: Q",
"help.control_escalate": "Warning: 4 · Blocking: 5 · Security radio: 6",
"help.control_emergency": "Emergency radio: R, hold the on-screen button to confirm",
"help.control_tools": "Body camera: B · Flashlight: G · Door lock: L",
"help.control_misc": "Pause: Esc · Debug overlay: F",
"ui.start_title": "GIG LINE",
"ui.start_subtitle": "Pick a venue to start your shift.",
"ui.start_help": "Help",
"ui.start_settings": "Settings",
"ui.start_hint": "Up/Down to choose · Enter to start · Esc to quit",
"ui.campaign_complete": "Campaign complete — pick any venue for a free shift.",
"ui.campaign_status": "Campaign shift %d · next: %s",
"ui.continue_shift": "Continue shift — %s",
"ui.campaign_complete_short": "Campaign complete",
"ui.radio_location_placeholder": "Location (e.g. front steps)",
"ui.radio_location_default": "front entrance",
"ui.radio_description_placeholder": "Describe the person and their behavior",
"ui.radio_weapon": "Weapon seen",
"ui.radio_danger": "Danger level: ",
"ui.radio_cancel": "Cancel",
"ui.radio_target": "Target: %s",
"ui.radio_title_emergency": "EMERGENCY RADIO",
"ui.radio_title_assist": "RADIO DISPATCH",
"hud.mode": "Door: %s · Light: %s",
"hud.door_locked": "LOCKED",
"hud.door_open": "open",
"hud.light_on": "on",
"hud.light_off": "off",
"hud.rec": "REC",
"hud.cam_clock": "SHIFT %02d:%02d:%02d",
"hud.shift_status": "shift %02d:%02d phase %s\nrep %.0f crowd %.0f%%",
"hud.inspect_text": "ACCESS INSPECTION\n\nAccess check — %s\nGranted: %s",
"hud.wand_text": "Wand check — %s\nAlert: %s",
"hud.wand_items": "Carried items: %s",
"hud.yes": "YES",
"hud.no": "NO",
"hud.wand_none": "none",
"review.title": "Shift Review",
"review.venue": "Venue: %s",
"review.policy": "Policy: %s",
"review.grade": "Grade: %s Score: %.0f/100",
"review.history": "Shifts on record: %d · best score: %.0f · grades seen: %s",
"review.incidents": "Incidents handled: %d (peaceful %d)",
"review.npcs": "NPCs encountered: %d",
"review.radio": "Radio battery: %.0f%% emergency cooldown: %.0fs",
"review.crowd": "Crowd mood: %.0f%% (panic: %s)",
"review.highlights": "Highlights:",
"review.modifier": " %s (%s) %s",
"review.continue_campaign": "Continue campaign",
"review.campaign_complete": "Campaign complete. Free-play venues remain available.",
"review.campaign_next": "Campaign shift %d complete · next: %s",
"venue.hollywood.label": "Hollywood Boulevard",
"venue.hollywood.blurb": "Building entrance, tourists and late-night trouble.",
"venue.hollywood_day.label": "Hollywood Boulevard — Day",
"venue.hollywood_day.blurb": "El Capitan entrance in daylight: tourists, performers and routine traffic.",
"venue.hollywood_night.label": "Hollywood Boulevard — Night",
"venue.hollywood_night.blurb": "The same entrance after dark: banners lit, late crowd, harder to read.",
"venue.joke.label": "The Joke Shop",
"venue.joke.blurb": "Guest list, IDs and wand screening.",
"venue.studio.label": "Late Night TV Studio",
"venue.studio.blurb": "Credentials, filming boundary, celebrity arrivals.",
"hud.reasons": "Reasons: %s",
"hud.issues": "Issues: %s",
"hud.wand_suspicious": "The wand alerts on a suspicious item (%s).",
"hud.wand_ask": "Ask the guest to identify it before deciding.",
"hud.wand_harmless": "The wand alerts on a harmless item (%s).",
"hud.wand_false": "Likely a false alarm — keys, belt, or phone.",
"hud.wand_clear": "No alert. The guest appears clear."
}
+73 -2
View File
@@ -18,6 +18,12 @@ display_name = "Tourist"
temperament = 0.1
base_compliance = 0.9
dialogue_lines = Array[String](["Oh sure, sorry, just taking a picture.", "Is there a better spot to stand?", "Understood, moving."])
context_lines = {
"deescalation": ["Okay, I'll move to the sidewalk.", "Sorry, I'll put the camera away."],
"reason": ["Just getting a photo of the entrance.", "I'm meeting friends here."],
"recognition": ["You remember me from the tour, right?"],
"refusal": ["I just want a photo, I'm not staying.", "I didn't know this entrance was restricted."]
}
[sub_resource type="Resource" id="Resource_qe635"]
script = ExtResource("2_7qk8j")
@@ -27,6 +33,11 @@ npc_type = 3
temperament = 0.2
base_compliance = 0.85
dialogue_lines = Array[String](["Wait, this isn't the entrance?", "Sorry about that, I'm a bit lost."])
context_lines = {
"deescalation": ["Oh, I understand. I'll check with the box office."],
"reason": ["I thought this was the box office.", "I'm looking for the event entrance."],
"refusal": ["I'm sure the event is here. Let me check my phone."]
}
[sub_resource type="Resource" id="Resource_u5nrp"]
script = ExtResource("2_7qk8j")
@@ -34,6 +45,9 @@ id = "delivery"
display_name = "Delivery Worker"
npc_type = 10
dialogue_lines = Array[String](["Where should I leave this?"])
context_lines = {
"reason": ["Drop-off for the venue. Where should I leave it?"]
}
[sub_resource type="Resource" id="Resource_dsavb"]
script = ExtResource("2_7qk8j")
@@ -49,6 +63,9 @@ reactions_by_level = {
3: 30
}
}
context_lines = {
"reason": ["Just doing my act; I'll keep the sidewalk clear."]
}
[sub_resource type="Resource" id="Resource_sexiy"]
script = ExtResource("2_7qk8j")
@@ -79,6 +96,9 @@ reactions_by_level = {
}
}
dialogue_lines = Array[String](["This is public sidewalk, you can't stop me from filming."])
context_lines = {
"reason": ["Filming a quick clip for my channel."]
}
[sub_resource type="Resource" id="Resource_ghqd8"]
script = ExtResource("2_7qk8j")
@@ -101,6 +121,9 @@ reactions_by_level = {
}
}
reaction_delay = 1.8
context_lines = {
"reason": ["Looking for my friends. I think they went inside."]
}
[sub_resource type="Resource" id="Resource_g8peq"]
script = ExtResource("2_7qk8j")
@@ -139,6 +162,9 @@ reactions_by_level = {
}
}
dialogue_lines = Array[String](["I'm just here to see a friend, come on.", "I'll be inside for ten seconds."])
context_lines = {
"reason": ["Meeting a friend inside. I'll only be a minute.", "Just cutting through."]
}
[sub_resource type="Resource" id="Resource_4xl5p"]
script = ExtResource("2_7qk8j")
@@ -178,6 +204,9 @@ reactions_by_level = {
}
}
reaction_delay = 2.4
context_lines = {
"reason": ["I just need a quiet minute.", "I'm waiting for someone."]
}
[sub_resource type="Resource" id="Resource_y6282"]
script = ExtResource("2_7qk8j")
@@ -213,6 +242,9 @@ reactions_by_level = {
7: 30
}
}
context_lines = {
"reason": ["Back off. This doesn't concern you."]
}
[sub_resource type="Resource" id="Resource_75a0r"]
script = ExtResource("2_7qk8j")
@@ -250,6 +282,9 @@ reactions_by_level = {
}
}
dialogue_lines = Array[String](["I have every right to be on public sidewalk.", "You can't touch me, this is a lawful protest."])
context_lines = {
"reason": ["We're demonstrating on the public sidewalk.", "This is a peaceful protest."]
}
[sub_resource type="Resource" id="Resource_ix35t"]
script = ExtResource("2_7qk8j")
@@ -263,6 +298,9 @@ default_reactions = {
0: 100
}
}
context_lines = {
"reason": ["I'm on staff. I'm checking the entrance.", "Crew check-in; I'm working tonight."]
}
[sub_resource type="Resource" id="Resource_0mcg3"]
script = ExtResource("2_7qk8j")
@@ -281,12 +319,15 @@ default_reactions = {
2: 12
}
}
context_lines = {
"reason": ["I'm on the guest list. Here's my ID.", "A friend invited me; I have a ticket."]
}
[resource]
script = ExtResource("3_lvgx2")
id = "hollywood_building"
display_name = "Hollywood Boulevard Building"
description = "Steps and entrance of a private building off Hollywood Boulevard."
display_name = "El Capitan Entertainment Centre"
description = "The historic 1921 Masonic Temple at 6840 Hollywood Boulevard, now the El Capitan Entertainment Centre."
access_policy = SubResource("Resource_xf4wh")
equipment = Array[String](["wristband_policy", "flashlight", "radio"])
locations = {
@@ -371,6 +412,33 @@ event_pool = Array[Dictionary]([{
"time_range_s": Vector2(300, 800)
}, {
"base_weight": 1.0,
"id": "late_tourist",
"intent": "photo",
"kind": "routine",
"npc_type": 0,
"phase": 3,
"probability": 0.22,
"time_range_s": Vector2(700, 900)
}, {
"base_weight": 1.0,
"id": "late_guest",
"intent": "enter",
"kind": "routine",
"npc_type": 9,
"phase": 3,
"probability": 0.2,
"time_range_s": Vector2(700, 900)
}, {
"base_weight": 1.0,
"id": "late_trespasser",
"intent": "trespass",
"kind": "routine",
"npc_type": 5,
"phase": 3,
"probability": 0.14,
"time_range_s": Vector2(700, 900)
}, {
"base_weight": 1.0,
"hint": "hint.body_camera_reason",
"id": "building_filming_boundary",
"intent": "film",
@@ -459,3 +527,6 @@ shift_phases = {
"start_s": 700.0
}
}
door_staff_lines = {
"wristband_check": ["Wristband, please.", "I need to see your event wristband."]
}
+32
View File
@@ -22,6 +22,12 @@ display_name = "Tourist"
temperament = 0.1
base_compliance = 0.9
dialogue_lines = Array[String](["Oh sure, sorry, just taking a picture.", "Is there a better spot to stand?", "Understood, moving."])
context_lines = {
"deescalation": ["Okay, I'll move to the sidewalk.", "Sorry, I'll put the camera away."],
"reason": ["Just getting a photo of the entrance.", "I'm meeting friends here."],
"recognition": ["You remember me from the tour, right?"],
"refusal": ["I just want a photo, I'm not staying.", "I didn't know this entrance was restricted."]
}
[sub_resource type="Resource" id="Resource_r873e"]
script = ExtResource("2_l60ad")
@@ -31,6 +37,11 @@ npc_type = 3
temperament = 0.2
base_compliance = 0.85
dialogue_lines = Array[String](["Wait, this isn't the entrance?", "Sorry about that, I'm a bit lost."])
context_lines = {
"deescalation": ["Oh, I understand. I'll check with the box office."],
"reason": ["I thought this was the box office.", "I'm looking for the event entrance."],
"refusal": ["I'm sure the event is here. Let me check my phone."]
}
[sub_resource type="Resource" id="Resource_3xy2u"]
script = ExtResource("2_l60ad")
@@ -46,6 +57,9 @@ reactions_by_level = {
3: 30
}
}
context_lines = {
"reason": ["Just doing my act; I'll keep the sidewalk clear."]
}
[sub_resource type="Resource" id="Resource_50i1h"]
script = ExtResource("2_l60ad")
@@ -76,6 +90,9 @@ reactions_by_level = {
}
}
dialogue_lines = Array[String](["This is public sidewalk, you can't stop me from filming."])
context_lines = {
"reason": ["Filming a quick clip for my channel."]
}
[sub_resource type="Resource" id="Resource_nsw77"]
script = ExtResource("2_l60ad")
@@ -98,6 +115,9 @@ reactions_by_level = {
}
}
reaction_delay = 1.8
context_lines = {
"reason": ["Looking for my friends. I think they went inside."]
}
[sub_resource type="Resource" id="Resource_20knl"]
script = ExtResource("2_l60ad")
@@ -136,6 +156,9 @@ reactions_by_level = {
}
}
dialogue_lines = Array[String](["I'm just here to see a friend, come on.", "I'll be inside for ten seconds."])
context_lines = {
"reason": ["Meeting a friend inside. I'll only be a minute.", "Just cutting through."]
}
[sub_resource type="Resource" id="Resource_rivis"]
script = ExtResource("2_l60ad")
@@ -171,6 +194,9 @@ reactions_by_level = {
7: 30
}
}
context_lines = {
"reason": ["Back off. This doesn't concern you."]
}
[sub_resource type="Resource" id="Resource_1dvkp"]
script = ExtResource("2_l60ad")
@@ -184,6 +210,9 @@ default_reactions = {
0: 100
}
}
context_lines = {
"reason": ["I'm on staff. I'm checking the entrance.", "Crew check-in; I'm working tonight."]
}
[sub_resource type="Resource" id="Resource_7wsm0"]
script = ExtResource("2_l60ad")
@@ -202,6 +231,9 @@ default_reactions = {
2: 12
}
}
context_lines = {
"reason": ["I'm on the guest list. Here's my ID.", "A friend invited me; I have a ticket."]
}
[resource]
script = ExtResource("3_voh88")
+44
View File
@@ -22,6 +22,12 @@ display_name = "Tourist"
temperament = 0.1
base_compliance = 0.9
dialogue_lines = Array[String](["Oh sure, sorry, just taking a picture.", "Is there a better spot to stand?", "Understood, moving."])
context_lines = {
"deescalation": ["Okay, I'll move to the sidewalk.", "Sorry, I'll put the camera away."],
"reason": ["Just getting a photo of the entrance.", "I'm meeting friends here."],
"recognition": ["You remember me from the tour, right?"],
"refusal": ["I just want a photo, I'm not staying.", "I didn't know this entrance was restricted."]
}
[sub_resource type="Resource" id="Resource_lmn2c"]
script = ExtResource("2_g7rkt")
@@ -31,6 +37,11 @@ npc_type = 3
temperament = 0.2
base_compliance = 0.85
dialogue_lines = Array[String](["Wait, this isn't the entrance?", "Sorry about that, I'm a bit lost."])
context_lines = {
"deescalation": ["Oh, I understand. I'll check with the box office."],
"reason": ["I thought this was the box office.", "I'm looking for the event entrance."],
"refusal": ["I'm sure the event is here. Let me check my phone."]
}
[sub_resource type="Resource" id="Resource_if2cj"]
script = ExtResource("2_g7rkt")
@@ -38,6 +49,9 @@ id = "delivery"
display_name = "Delivery Worker"
npc_type = 10
dialogue_lines = Array[String](["Where should I leave this?"])
context_lines = {
"reason": ["Drop-off for the venue. Where should I leave it?"]
}
[sub_resource type="Resource" id="Resource_t47d3"]
script = ExtResource("2_g7rkt")
@@ -53,6 +67,9 @@ reactions_by_level = {
3: 30
}
}
context_lines = {
"reason": ["Just doing my act; I'll keep the sidewalk clear."]
}
[sub_resource type="Resource" id="Resource_j2mp5"]
script = ExtResource("2_g7rkt")
@@ -83,6 +100,9 @@ reactions_by_level = {
}
}
dialogue_lines = Array[String](["This is public sidewalk, you can't stop me from filming."])
context_lines = {
"reason": ["Filming a quick clip for my channel."]
}
[sub_resource type="Resource" id="Resource_uscqg"]
script = ExtResource("2_g7rkt")
@@ -105,6 +125,9 @@ reactions_by_level = {
}
}
reaction_delay = 1.8
context_lines = {
"reason": ["Looking for my friends. I think they went inside."]
}
[sub_resource type="Resource" id="Resource_8aovb"]
script = ExtResource("2_g7rkt")
@@ -143,6 +166,9 @@ reactions_by_level = {
}
}
dialogue_lines = Array[String](["I'm just here to see a friend, come on.", "I'll be inside for ten seconds."])
context_lines = {
"reason": ["Meeting a friend inside. I'll only be a minute.", "Just cutting through."]
}
[sub_resource type="Resource" id="Resource_owl7s"]
script = ExtResource("2_g7rkt")
@@ -182,6 +208,9 @@ reactions_by_level = {
}
}
reaction_delay = 2.4
context_lines = {
"reason": ["I just need a quiet minute.", "I'm waiting for someone."]
}
[sub_resource type="Resource" id="Resource_rnay4"]
script = ExtResource("2_g7rkt")
@@ -217,6 +246,9 @@ reactions_by_level = {
7: 30
}
}
context_lines = {
"reason": ["Back off. This doesn't concern you."]
}
[sub_resource type="Resource" id="Resource_lhp4m"]
script = ExtResource("2_g7rkt")
@@ -254,6 +286,9 @@ reactions_by_level = {
}
}
dialogue_lines = Array[String](["I have every right to be on public sidewalk.", "You can't touch me, this is a lawful protest."])
context_lines = {
"reason": ["We're demonstrating on the public sidewalk.", "This is a peaceful protest."]
}
[sub_resource type="Resource" id="Resource_s8tjd"]
script = ExtResource("2_g7rkt")
@@ -267,6 +302,9 @@ default_reactions = {
0: 100
}
}
context_lines = {
"reason": ["I'm on staff. I'm checking the entrance.", "Crew check-in; I'm working tonight."]
}
[sub_resource type="Resource" id="Resource_ii8kq"]
script = ExtResource("2_g7rkt")
@@ -285,6 +323,9 @@ default_reactions = {
2: 12
}
}
context_lines = {
"reason": ["I'm on the guest list. Here's my ID.", "A friend invited me; I have a ticket."]
}
[resource]
script = ExtResource("3_jd45y")
@@ -476,3 +517,6 @@ shift_phases = {
"start_s": 700.0
}
}
door_staff_lines = {
"ticket_check": ["Ticket, credential, or guest-list confirmation, please.", "I need your ticket or guest-list confirmation."]
}
+299
View File
@@ -0,0 +1,299 @@
# 3D model review (2026-11-03)
Status: **baseline measured; scorecard open.** This is the working document for the unversioned
`3D model re-evaluation` track in [`TODO.md`](../TODO.md). It records what was measured, what the
Blender skill guidance says the targets are, the ranked candidate gaps, and the decisions that
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
before/after sheets), [`npc-proportion-audit.md`](npc-proportion-audit.md) (proportion guide),
[`blender-assets.md`](blender-assets.md) (asset contract), [`asset-tooling.md`](asset-tooling.md)
(commands).
## Method and skill routing
The review is screenshot- and measurement-backed, following `qa-review` (screenshot set, defect
severity, explicit verdict) rather than an opinion pass:
| Family | Skills loaded | Criteria taken from them |
| --- | --- | --- |
| Routing and pipeline | `blender-director` | Plan → blockout → detail → validate → export; MCP steps translated to headless flatpak runs |
| Characters | `character-artist`, `retopology`, `rigging` | Eye/mouth/jaw/shoulder loops, quad-dominant deformation surfaces, 3-loop joints, no triangles on primary deformers |
| UVs and textures | `uv-workflow`, `texture-workflow`, `materials` | Texel-density tiers (hero 512–1024, standard 256–512, background 128–256 px/m), consistent density, no overlapping UVs |
| Budgets and structure | `asset-optimization`, `lod-pipeline` | Triangle/vertex budgets, naming, LOD tiers |
| 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 |
Project contract and gates stay authoritative; see `AGENTS.md` §Local Blender skills. The skills
assume Blender MCP, which this project does not use: everything below came from the headless
`tools/blender_assets.py` / `tools/npc_pipeline.py` pipelines plus the Godot capture harnesses.
## What was captured and measured (2026-11-03)
| Evidence | Command | Result |
| --- | --- | --- |
| Contract audit | `python3 tools/blender_assets.py audit --json` | 46/46 assets clean; library 59,012 tris |
| Imported-GLB inspection | `blender_assets.py inspect` (all 46) | All clean; transforms applied, helpers ignored |
| Gear fit | `python3 tools/blender_assets.py fit --json` | Head width 0.315 m on all eight; clothing radius 0.190–0.260 m |
| Proportions | `python3 tools/npc_proportions.py report --json` | All eight inside the stylized guide |
| Texel density | `python3 tools/blender_assets.py texels --json` | New measurement, table below |
| Neutral baseline | `capture_npc_baseline.gd` → `tmp/model-baseline` (48 frames) + `npc_baseline.py sheet` | 48 frames, none blank; sheet in `tmp/` only |
| Final context | `capture_npc_review.gd` → `tmp/model-review` (96 frames) | 96 frames, none blank; raw frames stay local |
| Performance | `smoke_npc_budget.gd` | 24 avatars, 168 meshes, 152,464 tris, 6.94 ms of 50 ms |
Raw captures and JSON stay in the gitignored `tmp/`; committed review images remain the
`docs/blender_previews/sheet_*.jpg` set from 0.9.4/0.9.5 until a fix actually lands.
## Character measurements
Contract numbers from `audit` + `inspect`; density from `texels` (256² slot maps, authored and
with the runtime 4× fabric repeat).
| Character | Tris / verts | Height / feet | Materials | Authored td (px/m) | Fabric effective (px/m) | Verdict |
| --- | --- | --- | --- | --- | --- | --- |
| `npc_casual` | 5,038 / 4,756 | 1.824 / +0.000 | 7 | 106.8–122.2 | 429–440 | Contract pass; face prototype |
| `npc_jacket` | 5,428 / 5,071 | 1.824 / +0.000 | 7 | 100.3–113.7 | 409–413 | Contract pass; face prototype |
| `npc_coat` | 6,030 / 5,802 | 1.824 / +0.000 | 7 | 75.3–81.4 | 307–310 | Contract pass; face prototype |
| `npc_vest` | 5,536 / 5,375 | 1.819 / +0.000 | 9 | 85.5–99.3 | 362–366 | Contract pass; face prototype |
| `npc_dress` | 5,456 / 5,128 | 1.825 / +0.000 | 7 | 81.9–89.0 | 338–339 | Contract pass; face prototype |
| `npc_hoodie` | 5,860 / 5,492 | 1.856 / +0.000 | 7 | 89.5–98.6 | 359–364 | Contract pass; face prototype |
| `npc_firefighter` | 5,900 / 5,902 | 1.819 / +0.000 | 9 | 64.0–75.1 | 287–290 | Contract pass; face prototype (helmet shadow hides the lower lip) |
| `npc_guard` | 5,602 / 5,684 | 1.819 / +0.000 | 11 | 90.0–100.8 | 374–374 | Contract pass; face prototype |
| `npc_crowd_a` | 5,200 / 4,734 | 1.825 / +0.000 | 7 | 107–120 | 431–435 | 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_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 |
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.
Readings:
- Every character is inside 8,000/8,000 with the farthest at 5,474 tris (68% of budget) and 5,494
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
the lowest authored density; `npc_casual` is the smallest (2.44 m²) and the highest.
- **Authored density (64–122 px/m) sits below the skill's background tier (128–256 px/m).** The
runtime tint controls most of the look: `skin`, `hair`, `accessory` and the fixed detail slots
are neutral maps tinted at runtime, not painted skin, so density mostly controls subtle
weave/noise. Fabric is effective 287–440 px/m (standard tier) because of the 4× repeat. This is
a recorded note, not a defect, until a portrait shows texture starvation.
- Material counts 7–11 per character are fixed detail slots (eyes, rubber, hivis, reflective,
leather, metal), not extra tint slots.
## Scorecard skeleton
Verdicts use `qa-review` semantics: **P0** (blocker: wrong scale, broken normals, budget fail),
**P1** (major: silhouette/readability), **P2** (minor), **accept** (recorded limit). `—` means not
yet reviewed; this document is the place to fill it.
### Characters
| Character | Silhouette | Face @1.85 m | Hands/grip | Weights/pose | Cloth/hair | LOD | Verdict |
| --- | --- | --- | --- | --- | --- | --- | --- |
| *(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_jacket` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner |
| `npc_coat` | accept | accept (face prototype) | accept | captured (review pending) | accept (hem 0.76 stride) | accept | pending owner |
| `npc_vest` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner |
| `npc_dress` | accept; performer props dominate | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner |
| `npc_hoodie` | accept (hooded criterion) | accept (lower face only) | accept | captured (review pending) | accept | accept | pending owner |
| `npc_firefighter` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner |
| `npc_guard` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | pending owner |
| `npc_crowd_a` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | crowd prototype |
| `npc_crowd_b` | accept | accept (face prototype) | accept | captured (review pending) | accept (beard) | accept | crowd prototype |
| `npc_crowd_c` | accept | accept (face prototype) | accept | captured (review pending) | accept (coat hem) | accept | crowd prototype |
| `npc_crowd_d` | accept | accept (face prototype) | accept | captured (review pending) | accept | accept | crowd prototype |
### Gear (18)
| Gear | Attach | Tris | Verdict |
| --- | --- | --- | --- |
| `cap` | head | 328 | — (fit measured, visual pending) |
| `hood` | head | 804 | — |
| `sunglasses` | head | 164 | accept (0.9.4 clearance) |
| `firefighter_helmet` | head | 672 | accept (0.9.4 + 3 cm runtime offset) |
| `backpack` | back | 380 | accept (0.9.4 strap refit) |
| `shoulder_bag` | back | 188 | — |
| `scba_tank` | back | 362 | — |
| `lanyard` | chest | 100 | accept (0.9.4 + 4 cm runtime offset) |
| `phone` | hand | 96 | accept (forward offset) |
| `cup` | hand | 188 | accept |
| `clipboard` | hand | 84 | accept |
| `umbrella` | hand | 134 | accept |
| `camera` | hand | 318 | accept |
| `protest_sign` | hand | 92 | accept (performer/protester read) |
| `flashlight` | hand | 168 | accept |
| `wand` | hand | 244 | accept |
| `radio` | hand | 220 | accept |
Gear total: 5,002 tris across 18 pieces (all far below the 1,500-per-piece cap).
`blender_assets.py fit` readings (2026-11-03): `shoulder_bag` is clear on both `npc_coat` and
`npc_dress` (the only back-gear clearance the fit test asserts); `sunglasses` sits inside the
head band at 0.64× head width. Three caveats worth recording, none of which changes a verdict:
`backpack` (npc_casual) and `scba_tank` (npc_firefighter) report `CLIP` because back gear is
measured against the **hip/skirt** radius, which is wider than the upper back where the bags
actually sit; `cap` and `firefighter_helmet` report `band=OUTSIDE` because they sit on the crown
above the 1.55–1.81 face/head band (expected); and the `lanyard` clip is corrected at runtime by
`GEAR_OFFSETS` (+4 cm forward). The unused `beanie` was retired on 2026-11-03 (removed from
`GEAR`, its builder, `npc_avatar.gd` and the manifest).
### Props (20 library + 6 generated fallbacks)
Library total 13,608 tris; `marquee` (2,804) and `street_tree` (2,592) are the two heaviest and are
the first candidates for a `vegetation-artist`/lookdev pass.
| Prop | Tris | Verdict |
| --- | --- | --- |
| `bollard` | 138 | accept (2026-11-03 flared base collar) |
| `bench` | 560 | — |
| `trash_bin` | 228 | — |
| `planter_box` | 1,324 | — |
| `newspaper_box` | 300 | accept (2026-11-03 recessed slot + door seam) |
| `fire_hydrant` | 610 | accept (2026-11-03 rebuild: bolted flange, bonnet cap nut, larger side + front outlets) |
| `street_lamp` | 428 | — |
| `street_tree` | 2,592 | P1 candidate (flat-shaded clumps) |
| `palm_tree` | 400 | P1 candidate (same foliage language) |
| `round_planter` | 1,240 | — |
| `a_frame_sign` | 212 | — |
| `wall_lantern` | 180 | — |
| `traffic_cone` | 210 | — |
| `crowd_barrier` | 388 | — |
| `stanchion` | 434 | — |
| `handrail` | 268 | — |
| `security_podium` | 184 | — |
| `double_door` | 380 | — |
| `marquee` | 2,804 | — (emissive check pending) |
| `sedan` | 928 | — |
Vision review 2026-11-03 (`tools/describe_image.py` on the prop gallery): `bench` and `trash_bin`
are ship-ready (minor chamfer polish only); `bollard` passes but leans on its reflective band
(add a base collar); `planter_box`/`round_planter` fail on the foliage — the `_shrub()` clumps
read as rocks, not plants (same foliage language gap, gated on the owner); `newspaper_box` is
borderline (the slot is a flat decal, wants a recessed inset + door seam); `fire_hydrant` is now
rebuilt (see row). No prop floats or sinks; shadow direction is consistent.
Generated fallbacks (`assets/models/generated/`): bench, bollard, fire_hydrant, newspaper_box,
planter_box, trash_bin. They are only used when the library prop is missing, so they are audited
for presence, not re-reviewed for art quality.
### Venues
| Venue | Measured (map-budget test) | Status |
| --- | --- | --- |
| Hollywood Boulevard | 58,884 tris / 409 meshes | shape-language review pending |
| The Joke Shop | 26,828 tris / 580 meshes | facade review landed: 6.0 m low hall, white signature band, tied-back curtains, west hotel tower; remaining gap = rounded west corner |
| TV Studio | — | shape-language review pending |
## Pose evidence (fixed 2026-11-04)
The weights/pose column can now be filled. The root cause was the harness: it never disabled the
avatar's `_physics_process`, so `_advance_fsm` + `_move_toward_target` reset `arrived`/velocity
every tick and `theater_pose()` reverted to idle. `capture_npc_pose.gd` now calls
`set_physics_process(false)`/`set_process(false)` after staging, and the pose sticks (sit vs idle
diff 5.63, walk vs idle 2.29, vision-judged as crouched). The earlier "moving the `Rig` child does
not render" probe was a red herring: it set the position in `_shoot()` in the same frame as
`root.get_texture().get_image()`.
`src/test/capture_npc_pose.gd` stages idle / walk-contact / walk-pass / seated for all twelve
variants (full-body + shoulder views).
| Check | Result |
| --- | --- |
| Pose reaches proxies | Seated pose logs `rig_y=-0.339`, `leg0=-1.357` |
| Pose reaches skeleton bones | `HipL`/`KneeL` bone rotations differ from rest; headless test `test_pose_reaches_the_skeleton_bones` locks it (suite 273/273) |
| Idle restores rest | Same test asserts the hip returns to its rest orientation |
| Skinning bound | One `Skeleton3D`, mesh bound to it (`same=true`); hiding `npc_base` removes the body |
| Bone rotations deform the mesh | Direct writes change rendered pixels: Spine 1,719 / HipL 1,052 / KneeL 810 / ElbowL 745 / Head 671 / ShoulderL 640 sampled pixels |
| Bone translation deforms the mesh | A +0.5 m Head offset moves the head in frame |
| Standalone window probes render the pose | Three variants (avatar under `root`, under a `Node3D` stage, created mid-`_process`) render the seated crouch (diff ~2,200-2,360 samples) |
| Offscreen capture renders the pose | A `SubViewport` + `RenderingServer.force_draw()` probe renders the crouch reliably (diff 602 at +2/+6/+12 frames), including a re-created second avatar |
| The harness renders the pose | **No** in its current form: same stage, same pose, rest frames. One ordering (`_frame_view()` immediately before `force_draw()`) produced posed walk frames once, then regressed to rest/garbage on the next run |
The rig, the animation math and the offscreen render path are all sound. A 2026-11-03 re-attempt
with the recipe (SubViewport + `force_draw()` + `force_update_all_bone_transforms()` + disabled
process/physics) still produced byte-identical rest frames, and a decisive probe narrowed the
failure further: teleporting the **avatar** changes the render (diff 10), but moving the **`Rig`
child** (the GLB model's parent) does not (diff 0). The skinned mesh therefore does not follow its
`Rig` Node3D transform, so the rig lowering never reaches the rendered model — the defect is
upstream of camera/`force_draw()` ordering. Next attempt must answer why the skinned model ignores
the `Rig` node, or stage poses via the known-good `capture_npc_review.gd` fallback.
Working recipe (verified by the probes; keep it when rebuilding):
1. Create a `SubViewport` (1280x720, `own_world_3d = true`, `UPDATE_ALWAYS`) in `_initialize`.
2. Build the studio (environment, key/fill light, ground, camera) and one avatar inside it.
3. Wait 2-3 frames so the skinned mesh binds.
4. Apply the pose with `_animate()` ticks, with the avatar's process/physics disabled.
5. Call `RenderingServer.force_draw()`, then `viewport.get_texture().get_image()`.
6. Free the avatar and repeat for the next pose; a re-created avatar also rendered correctly in a probe.
## Ranked candidate gaps
Ordered by expected player-visible gain per unit of work. None is approved for fixing yet; the
owner review decides.
1. **Face plane structure at portrait distance (P1, all eight).** The neutral sheet shows flat,
faceted cheek/brow planes and slit-like eyes at 1.85 m. `character-artist`/`retopology` guidance
asks for concentric eye loops and a 3-loop mouth. This is the largest readability gap and also
the most expensive (head rebuild + re-UV + re-AO per variant). Needs a targeted single-variant
prototype before committing to eight.
The eight library characters already carry the 0.9.4 face detail in
`tools/blender_assets.py._face()`: sculpted chin plate + cheekbones (`_shape_head_mesh`),
socketed sclera + thin upper lid, nose bridge/tip/wings, a rounded mouth and ear concha
(the plain sphere head in `tools/npc_pipeline.py` is the frozen `npc_base` fallback only).
So this gap is a readability/scale question on the existing sculpt — the head is
`seg=18/rings=12` with small offsets — not missing geometry. Concrete next step, if funded:
a single-variant `npc_casual` prototype that raises head subdivision and deepens the
cheek/brow offsets, then re-bakes AO and captures the portrait before touching the other seven.
**Prototype landed 2026-11-03/04:** `npc_casual` now has `seg=24/rings=16` (smoother), a subtle
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
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
until this prototype is approved.
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
gates any claim that the rig is acceptable.
3. **Foliage language (P1, `street_tree`, `palm_tree`, `planter_box`, `round_planter`).** Flat-shaded
clumps against an otherwise readable set, already deferred once. `vegetation-artist` carded
clusters would fix it inside the existing triangle budget.
4. **Texel density below the background tier (note).** 64–122 px/m authored vs the skill's 128–256
background tier. Fabric is already standard-tier at runtime. Decide: accept because the maps are
neutral tinted detail, or raise only `skin`/`hair` to 512² for portrait distance.
5. **`npc_firefighter` density floor (P2).** 64 px/m with the largest coat area; split the coat UV
shell or raise the map size before the next character pass, whichever the density decision says.
6. **Hands at conversation distance (accept, revisit only if face work opens the budget).** Open
palm with `HAND_ITEM_FORWARD`; no finger bones exist, so a curl rig is a contract decision, not
a modelling tidy-up.
7. **Gear never reviewed as silhouette language (P2).** 18 pieces pass fit/contract but have no
prop-artist verdict (the unused `beanie` was retired 2026-11-03).
8. **Venue shape language (P2).** Hollywood and the Joke Shop have budget data but no
`environment-artist`/`archviz` review; the TV Studio has no recorded map-budget number in this
document yet.
## qa-review sign-off (2026-11-04)
All eight library characters **ship with notes**. Face realism landed on all eight (`seg=24/rings=16`,
brow ridge, both eyelids, fuller lower lip), hands/grip and cloth/hair are accepted as recorded, gear
fit is measured with its caveats, and the props are reviewed (hydrant/bollard/newspaper_box rebuilt,
foliage two-toned). The only unresolved column is **weights/pose** (blocked on the capture harness),
and the full carded-cluster foliage pass is the one remaining owner-gated P1. No P0 remains.
## Decisions needed from the owner
- Accept the measured texel density as a stylized limitation, or fund a skin/hair resolution pass.
- Approve or reject a single-variant face/eye-loop prototype (and pick which variant to prototype
on) before any eight-character rebuild.
- Decide whether the pose/weight capture is required before the crowd-variant roster.
- Confirm foliage rebuild priority versus the map track (both touch the same street trees).
## Next actions
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,
collider-coverage and scorecard-coverage gates this document found missing.
+55
View File
@@ -0,0 +1,55 @@
# Gig Line Documentation
This folder holds the project documentation, visual references and generated QA evidence.
## 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.
2. [Character development](../TODO.md#character-development) — crowd, age, build, hair and outfit variety; generator-first, with the frozen NPC contract.
3. [0.10.0 release candidate](../TODO.md#milestone-0100--release-candidate) — versioned release hardening after the unversioned development tracks.
## Start here
- [`../PLAN.md`](../PLAN.md) — product, design intent and development priorities
- [`../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
- [`architecture.md`](architecture.md) — arc42 system architecture
- [`getting-started.md`](getting-started.md) — setup, run/test commands and controls
- [`asset-tooling.md`](asset-tooling.md) — image/model pipelines and screenshot gates
- [`../tools/README.md`](../tools/README.md) — tool inventory, categories and test runner
- [`gameplay.md`](gameplay.md) — controls, venues and escalation
- [`joke-shop-map.md`](joke-shop-map.md) — Sunset Strip 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-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
- [`development-status.md`](development-status.md) — implemented features, current gaps and active planning
- [`accessibility.md`](accessibility.md) — accessibility statement and known gaps
- [`known-limitations.md`](known-limitations.md) — release-candidate blockers and accepted limits
- [`smoke-checklist.md`](smoke-checklist.md) — manual verification
- [`balance-acceptance.md`](balance-acceptance.md) — release-candidate balance targets and evidence protocol
- [`../CREDITS.md`](../CREDITS.md) — credits and asset provenance
- [`../PRIVACY.md`](../PRIVACY.md) — privacy note
- [`ui-copy.md`](ui-copy.md) — screen, copy and button inventory
- [`RELEASING.md`](RELEASING.md) — export, CI and publishing workflow
- [`adr/README.md`](adr/README.md) — architecture decisions
## Visual references
- [`screens/README.md`](screens/README.md) — concepts, designs, screenshots and realism runs
- [`blender_previews/README.md`](blender_previews/README.md) — character/gear/prop previews
- [`screenshots/README.md`](screenshots/README.md) — committed game gallery
- [`screen-design-report.md`](screen-design-report.md) — latest screen-design judge result
## Quick preview
| Reference | Capture |
| --- | --- |
| Venue design | <img src="screens/designs/venue_overview.png" width="320" alt="Venue overview design"> |
| Current entrance (day) | <img src="screenshots/01-venue-overview.png" width="320" alt="In-game venue overview"> |
| Current entrance (late) | <img src="screenshots/05-late-night-avenue.png" width="320" alt="Late-night boulevard"> |
| Joke Shop | <img src="screenshots/12-joke-shop.png" width="320" alt="Joke Shop at night"> |
| Character sheet | <img src="blender_previews/sheet_npc.jpg" width="320" alt="NPC character sheet"> |
| Neutral NPC baseline | <img src="blender_previews/sheet_npc_baseline.jpg" width="320" alt="Neutral NPC baseline sheet"> |
| Texture refinement | <img src="blender_previews/sheet_texture_refinement_0.9.5.jpg" width="320" alt="Texture refinement comparison"> |
| Prop sheet | <img src="blender_previews/sheet_props.jpg" width="320" alt="Prop asset sheet"> |
+42 -9
View File
@@ -12,11 +12,11 @@ time you export, either:
matching templates), or
- Install them manually from the
[Godot downloads](https://godotengine.org/download/) page by placing the
`Godot_v4.3-stable_export_templates.tpz` contents into
`~/.local/share/godot/export_templates/4.3.stable/`.
`Godot_v4.7.2-stable_export_templates.tpz` contents into
`~/.local/share/godot/export_templates/4.7.2.stable/`.
The templates must match the editor binary you use locally. CI pins Godot 4.3;
the local development machine uses 4.7.2 with its matching templates. Verify with:
The templates must match the editor binary you use locally. CI and local
development both use Godot 4.7.2 with its matching templates. Verify with:
```bash
godot --headless --export-release "Linux" build/gig-line-smoke.x86_64
@@ -27,11 +27,17 @@ godot --headless --export-release "Linux" build/gig-line-smoke.x86_64
`project.godot` sets `config/version`. Keep it in sync with the release tag
(e.g. `v0.3.0` → `config/version="0.3.0"`, plus a git tag `v0.3.0`).
Every major, minor and patch increment is a version in its own right: add the
`CHANGELOG.md` entry, run the local build (`tools/build_releases.sh`), verify the
artifact names/sizes/checksums, and only then commit and tag. A changelog entry
without a matching build is not a completed version. Unversioned development tracks
do not get a placeholder version; assign one when the scope enters a release.
## 3. CI (Gitea Actions)
The repository ships a Gitea Actions workflow (`.gitea/workflows/build.yml`):
- Every push/PR runs the **checks** job: Godot 4.3 install, project import, and the
- Every push/PR runs the **checks** job: Godot 4.7.2 install, project import, and the
full headless test suite. This is the fast safety net.
- **Tags (`v*`)** and **manual `workflow_dispatch`** runs additionally execute the
**build** job: export templates install, import, `tools/build_releases.sh` for
@@ -63,10 +69,12 @@ setup, so do not rely on it.
> artifacts. If CI publishing fails again, run
> `./tools/publish_release.sh <version>` from a checkout with `~/.git-credentials`.
> **No `actions/cache` for now:** the runner has no local actions-cache service, so
> `actions/cache` times out against GitHub's cache API. We dropped it (Godot import
> is only ~4s). If a host admin later enables Gitea `[cache.actions]` with a cache
> server, the step can be reintroduced.
> **Godot cache evaluation:** Gitea Actions cache was tested for the Godot binary and
> trimmed 4.7.2 templates. Restores worked intermittently, but cache saves timed out
> in `CreateCacheEntry` / were rejected as reserved by another job. The cache steps
> were removed because they added restore/save overhead without a reliable hit.
> Revisit when the host's Gitea Actions cache service is healthy; otherwise keep
> downloading the binary/templates directly.
> The workflow lives in `.gitea/workflows/` (Gitea's canonical path). The old
> `.github/workflows/ci.yml` was removed to avoid double runs on hosts that scan
@@ -101,6 +109,31 @@ The optional Desktop HQ zip is assembled by `tools/build_releases.sh` from
`packaging/desktop_hq/`; keep `override.cfg` next to the extracted executable.
It is not part of the default Compatibility/Web build.
`tools/publish_release.sh` preflights every artifact for existence and non-zero
size, checks free space in `build/`, writes `build/SHA256SUMS-<version>.txt`, and
uploads only after those checks pass. If an upload or the final asset-count check
fails, it removes the assets it just uploaded (and removes a newly created
release) so the host never keeps a partial release. Use
`PUBLISH_DRY_RUN=1 ./tools/publish_release.sh` to run the preflight and checksum
steps without touching the API. Set `RETAIN_RELEASES=7` to prune older releases
after a successful publish; the default `0` disables pruning and never deletes
history implicitly. A retention of seven keeps the newest seven releases,
including the latest 0.8/0.9 line.
### 0.10.0 macOS signing decision
The 0.10.0 release candidate will continue to ship an **unsigned** macOS zip with
the documented right-click/`xattr` quarantine instructions. Signing and
notarization remain a public-release blocker until an Apple Developer account and
a reproducible signing/notarization job are available; they are not silently
claimed as complete. The same limitation is recorded in
[`known-limitations.md`](known-limitations.md).
Always run the release build from a clean checkout or detached worktree. Godot
can pack files from ignored `tmp/` and old `build/` folders into the PCK; the
0.9.6 dirty-tree build produced 1.6–3.1 GB artifacts, while the clean worktree
produced the expected ~160–240 MB packages.
Locally you can build the AppImage with `bash packaging/linux/build_appimage.sh <version>`;
the NSIS installer requires `makensis` (covered by CI).
+32
View File
@@ -0,0 +1,32 @@
# Accessibility Statement
Gig Line is a keyboard/mouse-first first-person game with gamepad support. The
current build includes the following accessibility features:
- Remappable keyboard and gamepad bindings, persisted in `settings.json`.
- Keyboard focus rings and explicit default focus in every modal, with A/B
accept/cancel on a gamepad and wrapped Tab/arrow navigation.
- Adjustable HUD font scale up to 1.75× with clipped/ellipsized long labels.
- Reduced-motion mode that disables head bob and motion-sensitive presentation.
- Captions in the HUD log for radio, interaction and event messages.
- Master, SFX and ambience volume controls.
- Shape plus number escalation meter, so escalation is readable without colour.
- Hold-to-confirm emergency action and guarded destructive actions.
- Searchable Help generated from the live input bindings.
- Mouse-transparent HUD, right-click/Esc modal handling, and a predictable
top-panel close path.
Known gaps:
- There is no screen-reader integration or text-to-speech narration.
- The game is English-only; no pseudo-locale or full localization review has
been completed.
- The visual style is stylized and some small environmental detail may be hard
to read at low resolution; a full low-vision/high-contrast audit is pending.
- Audio captions cover gameplay messages, not every ambient sound.
- A formal WCAG conformance claim has **not** been made. External accessibility
review is still a 0.10.0 release-review item.
Test evidence and the manual checklist live in
[`smoke-checklist.md`](smoke-checklist.md); settings and save behaviour are
documented in [`getting-started.md`](getting-started.md).
@@ -0,0 +1,72 @@
# ADR-0015: NPC model variety sourcing
- **Status:** Accepted (planning)
- **Date:** 2026-11-03
## Context
The library has eight skinned characters, and role/seed selection reuses them
across the three venues. Crowds therefore repeat the same faces, builds and
outfits. The existing contract is strict: `npc_base` mesh node, 13 named bones at
fixed rest positions, five tint slots, front on Blender −Y and an 8,000
triangle/vertex budget.
A review compared free/open-source sources against that contract. Quaternius
Ultimate Modular (CC0), Kenney Blocky Characters (CC0), Khronos CesiumMan and
RiggedFigure (CC BY 4.0) and the MakeHuman base mesh (CC0) were imported and
measured. None was a drop-in; the full results are in
[`docs/npc-model-sourcing.md`](../npc-model-sourcing.md).
## Decision
1. **Ship only CC0, or CC BY with recorded attribution.** No service asset whose
terms restrict redistribution may be used.
2. **Extend the project-owned procedural generator as the primary path** for
shipped NPC variety. The first target is four crowd-variety looks, bringing
the library to twelve characters.
3. **Keep Quaternius as a reviewed reference/prototype, not a shipped base.** A
contract retarget is a no-go because the source is a T-pose 62-bone realistic
rig with substantially different proportions from the arm-down 13-bone
contract. A future external-rig path would need its own ADR covering its
skeleton, animation source and per-model gear offsets.
4. **MakeHuman is deferred to a separate local development pipeline.** Its CC0
base mesh can be decimated into budget, but it needs retopology, a custom rig
and project-owned clothing before it is usable. It must not become a CI or
build dependency.
5. **Commit research previews with attribution.** CC-BY preview images are
development-only and are not bundled with the game or its release packages.
## Consequences
### Positive
- The shipped library keeps one art direction, one skeleton and one audit gate.
- Crowd variety lands without a new runtime dependency or a second animation
system.
- The licence position is simple: project-owned assets plus explicit CC-BY
attribution where a preview or future asset requires it.
- The Quaternius evaluation removes a tempting but high-risk retarget from the
critical path.
### Negative
- Procedural variation is still limited by the existing face and clothing
vocabulary; four variants do not provide full age/body diversity.
- Quaternius' animation set remains unused unless a second rig path is approved.
- MakeHuman body-shape diversity is postponed.
- External previews add a small documentation footprint that must stay out of
release packages.
## Alternatives considered
- **Retarget Quaternius onto the `npc_base` contract.** Rejected for now:
welding brings the geometry inside budget, but the rest pose and body
proportions differ enough that a contract rebuild would either distort the
model or require a full proportion warp.
- **Use Quaternius' own rig and animations directly.** Deferred: attractive
animation quality, but it adds a second skeleton/animation path and per-model
gear offsets to `NpcAvatar`.
- **Use MakeHuman as the primary body source.** Rejected for this milestone: high
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
free/open-source redistribution basis for shipped meshes.
+21
View File
@@ -0,0 +1,21 @@
# Architecture Decision Records
Each ADR records the context, decision, consequences and alternatives for a durable architecture choice.
## Decisions
- [0001-venue-driven-simulation-architecture](0001-venue-driven-simulation-architecture.md)
- [0002-de-escalation-first-escalation-model](0002-de-escalation-first-escalation-model.md)
- [0003-explicit-access-verification-system](0003-explicit-access-verification-system.md)
- [0004-local-single-player-first](0004-local-single-player-first.md)
- [0005-event-based-incident-recording](0005-event-based-incident-recording.md)
- [0006-godot-4-gdscript](0006-godot-4-gdscript.md)
- [0007-locomotion-and-input-layers](0007-locomotion-and-input-layers.md)
- [0008-cone-focus-and-head-ring](0008-cone-focus-and-head-ring.md)
- [0009-radial-action-menu](0009-radial-action-menu.md)
- [0010-procedural-npc-presentation](0010-procedural-npc-presentation.md)
- [0011-aim-free-selection-and-precision-walk](0011-aim-free-selection-and-precision-walk.md)
- [0012-surface-materials-and-generated-props](0012-surface-materials-and-generated-props.md)
- [0013-blender-authored-npc-characters](0013-blender-authored-npc-characters.md)
- [0014-runtime-image-root](0014-runtime-image-root.md)
- [0015-npc-model-variety-sourcing](0015-npc-model-variety-sourcing.md)
+92 -10
View File
@@ -25,7 +25,7 @@ This document follows the structure of the arc42 architecture documentation temp
## 2. Constraints
- The first implementation should be small enough for a playable vertical slice.
- The engine is Godot 4 with GDScript; a playable prototype vertical slice is implemented. CI pins Godot 4.3 while local development is verified with 4.7.2; the default renderer stays GL Compatibility for Web parity, with an optional Forward+ Desktop HQ build.
- The engine is Godot 4.7.2 with GDScript; a playable prototype vertical slice is implemented. CI, local development and exported builds use 4.7.2; the default renderer stays GL Compatibility for Web parity, with an optional Forward+ Desktop HQ build.
- Venue-specific policies must be configurable rather than hard-coded into individual NPCs.
- The game must preserve the de-escalation-first design described in [`PLAN.md`](../PLAN.md).
- The project uses the repository workflow described in [`AGENTS.md`](../AGENTS.md).
@@ -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.
- **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.8.0`). Each release is tagged in git as `v<version>` (e.g. `v0.6.0`). The `0.x` series is pre-1.0, 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.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).
## 3. Context and Scope
@@ -117,6 +117,7 @@ The asset/content pipeline is part of the architecture, not an external build de
| Gear/props | Blender flatpak + tool + bake | `npc_gear/*.glb`, `props/*.glb`, `*_ao.png` | `NpcAvatar`, `WorldBuilder` | primitive assets / generated props |
| Surface/UI art | Python pipelines / AI-assisted references | `assets/textures/ai/game_ready/`, `assets/images/` | `SurfaceMaterials`, `UiArt` | neutral material / procedural UI |
| Screenshots/QA | capture scripts + `scene_concepts`/`screen_designs` | `docs/screenshots/`, `docs/screens/realism/` | reviewers and CI gates | regenerate; never runtime input |
| Local map data (planned) | OSM/3D building extract + licensed reference photos + AI/splat evaluation | derived map assets with provenance sidecars | `WorldBuilder` venue specs | current stylized venue geometry |
| Release | Gitea workflow + build/publish scripts | platform binaries, Web zip, Desktop HQ zip | players | source build |
Contracts that keep the pipeline replaceable:
@@ -127,6 +128,80 @@ 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).
- CI stays offline: only committed `.glb`/`.png`/`.tres` and Godot are needed; optional AI/Blender steps are reproducible development tools.
### Character development tooling
Characters have two authored sources and one procedural fallback. The eight-variant
Blender library (`assets/models/blender/npc/`) is the normal presentation path; the
older skinned `npc_base` (`assets/models/generated/npc_base.glb`) remains a frozen
contract and fallback; `NpcAvatar`'s procedural rig is the offline last resort.
Four crowd-variety prototypes have been built and audited locally but are not yet in
the shipped catalogue; generator-first expansion is the active character track
(ADR-0015). Blender is a developer tool only — runtime, tests and CI consume committed
GLBs and maps.
| 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/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/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_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_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_realism.gd` | Venue realism stages plus a frozen portrait and surface close-ups | `docs/screens/realism/<run>/` | Optional vision judge; portrait is the only avatar-detail capture |
Runtime contracts that must survive any character change:
- `NpcAvatar.character_model_for(role, seed)` selects the library variant; `character_override`
exists only to stage roles without a spawned model (for example the guard).
- The armature node names, `Rig/Head`, the Hip/Knee/Shoulder/Elbow/Hand proxies and the
**+Z visual front** are frozen; animation and `yaw_towards()` depend on them.
- Only the five contract slots are tinted. Fixed detail materials (eyes, reflective bands,
rubber) stay untouched, gear is placed by `GEAR_ATTACH`, and `GEAR_OFFSETS` plus
`HAND_ITEM_FORWARD` are runtime attach-space corrections, not authoring changes.
- 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.
- `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
re-measuring that smoke and the Web/Compatibility path.
- 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.
Evidence and detailed contracts live in [`blender-assets.md`](blender-assets.md),
[`npc-visual-review.md`](npc-visual-review.md) and
[`npc-proportion-audit.md`](npc-proportion-audit.md); command-level usage is in
[`asset-tooling.md`](asset-tooling.md).
### Local map development tooling (planned)
The current venues are hand-authored procedural reconstructions in
`src/game/world_builder.gd`. The local map workstreams (**H–M**) in
[`TODO.md`](../TODO.md#3d-model-re-evaluation) are unversioned and planned: they will
test whether licensed data and reference photography can replace or supplement the
stylized geometry without moving map data, AI composition or splat rendering into
the runtime.
Planned stages and gates:
| Stage | Source | Output | Gate |
| --- | --- | --- | --- |
| Footprints and massing | Offline OpenStreetMap extract + licence-compatible 3D building data (OSM Buildings, Overture, CityGML/LOD1–2) | Local footprint/massing scenes, extract date and query pinned | ODbL attribution/share-alike review; scale/orientation and venue-footprint comparison |
| Reference imagery | Wikimedia Commons, own/team photos or other clearly licensed images | Reference boards and material candidates | Written author/licence/attribution, privacy redaction, no unlicensed Street View or scraped commercial imagery |
| AI-assisted composition | Project image tooling | Palette/facade-rhythm boards and material candidates | Model/prompt/output recorded; human review; never treated as licensed map data |
| Photoreal facade kit | Project-authored geometry and derived materials | Committed facade modules and PBR maps | WorldBuilder budget, collider/nav coverage, Compatibility-readability and screenshot review |
| Gaussian splatting | Licensed/permissioned photo sets + pinned offline toolchain | Development-only splat, texture or proxy experiments | Per-path ADR; runtime only if performance, collision/navigation and provenance are proven |
Contracts for any future map source:
- Runtime consumes committed, derived assets only; no online OSM, photo or splat
service is a build or play dependency.
- OSM ODbL, photo licences, capture permissions and privacy redaction are recorded
in a provenance sidecar before an asset enters the repository.
- Collision and navigation stay mesh-based; a splat may never become the playable
floor or the access/occlusion authority.
- The stylized venue geometry remains the fallback until a data-backed block passes
the venue budget, navigation, visual and provenance gates.
### Content authoring and runtime data flow
`ScenarioFactory` is the source of truth for the three authored venues. `tools/export_venues.gd`
@@ -154,6 +229,7 @@ reputation, crowd reaction and shift review read it rather than resolving gamepl
2. `GameManager` loads the matching `VenueScenario` and keeps the guard/UI state.
3. `WorldBuilder` rebuilds only `world_root` from `VENUE_SPECS`, including geometry, props and the navigation mesh.
4. Shift state, NPCs and incidents are reset for the new venue; phase lighting and the door state are reapplied.
5. The menu's **Hollywood Night** row resolves to `hollywood_building`, then sets `ShiftCoordinator.phase_offset_s` so elapsed time starts at 0 but phase lookup starts at `LATE`. The offset is part of the checkpoint snapshot, so a resumed night shift stays at night.
### Asset fallback
@@ -180,7 +256,7 @@ reputation, crowd reaction and shift review read it rather than resolving gamepl
## 7. Deployment View
The game is a single-player local client containing presentation, simulation, rules, content, and persistence for local settings and shift results. No online backend is required. A non-threaded Web export ships alongside the desktop builds and can be served from any static host without special headers. The default renderer is GL Compatibility; an optional Desktop HQ zip uses Forward+ with volumetric fog/SDFGI on Vulkan-capable desktops. CI pins Godot 4.3 while local development is verified with 4.7.2, so the light falloff path is version-adaptive until the baseline is unified.
The game is a single-player local client containing presentation, simulation, rules, content, and persistence for local settings and shift results. No online backend is required. A non-threaded Web export ships alongside the desktop builds and can be served from any static host without special headers. The default renderer is GL Compatibility; an optional Desktop HQ zip uses Forward+ with volumetric fog/SDFGI on Vulkan-capable desktops. CI, local development and exported builds use Godot 4.7.2; the light-falloff code keeps a compatibility branch for older editors but the supported baseline is unified.
Future options include external analytics, cloud saves, or replay sharing, but they are not part of the current architecture.
@@ -188,7 +264,7 @@ Future options include external analytics, cloud saves, or replay sharing, but t
A continuous-integration pipeline keeps the headless test suite green on every change and publishes tagged releases.
- **Workflow:** `.gitea/workflows/build.yml` runs on pushes, pull requests, tags, and manual dispatches. The `checks` job installs Godot 4.3, imports the project, and runs `res://src/test/test_runner.gd`; it fails on a non-zero exit code or any `SCRIPT ERROR` in the output.
- **Workflow:** `.gitea/workflows/build.yml` runs on pushes, pull requests, tags, and manual dispatches. The `checks` job installs Godot 4.7.2, imports the project, and runs `res://src/test/test_runner.gd`; it fails on a non-zero exit code or any `SCRIPT ERROR` in the output.
- **Build job:** runs for tags and manual dispatches. It installs export templates, exports Linux/Windows/macOS and the non-threaded Web build, packages the NSIS installer, Linux AppImage and optional Desktop HQ zip, verifies the installer contents, and uploads the artifacts.
- **Publish job:** tag builds call `tools/publish_release.sh` to create the Gitea release and upload the standard artifacts plus the optional Desktop HQ zip. The script uses `GITEA_TOKEN` when configured and otherwise reuses the runner's checkout extraheader credentials.
- **Hosting:** the repository lives on Gitea (`https://git.allucanget.biz/zwitschi/security-game`). Export templates must match the pinned Godot version; macOS builds are unsigned and need quarantine guidance or notarization for wider distribution.
@@ -219,6 +295,7 @@ The release process is: bump the version, run the full test suite and `tools/che
- **Asset provenance and AI usage:** AI-assisted images/concepts are development inputs only; validated outputs are committed, audited and loaded through the same runtime asset contracts. Generated content must not become an offline/CI dependency.
- **Campaign and checkpoint state:** `Campaign` owns venue order/progression; settings, campaign, completed shift history and a versioned pause checkpoint are separate JSON documents. The checkpoint snapshots coordinator systems, scheduler RNG and NPC presentation/access state; it deliberately does not serialize the tutorial overlay or transient input state.
- **Difficulty:** easy/normal/hard only scale spawn pacing, NPC compliance tolerance and report-credibility strictness; access/escalation rules and emergency rarity are invariant.
- **Settings and accessibility:** volume/motion/caption settings persist through `settings.json`; Help is generated from the input-binding catalogue and the HUD log is the text caption surface. CI/release, local development and exported builds are unified on Godot 4.7.2.
- **Documentation ownership:** `PLAN.md` owns design intent; `docs/gameplay.md` owns playable mechanics; `docs/architecture.md` owns system boundaries, data flow, constraints and cross-cutting decisions; ADRs own decision history; `docs/development-status.md` owns the implemented/gap list; `TODO.md` owns the roadmap; `MEMORY.md` owns gotchas and open questions. Specialized docs link to these rather than becoming competing sources of truth.
## 9. Architecture Decisions
@@ -251,7 +328,7 @@ Architecture decisions are recorded as ADRs:
| Testability | Access and escalation rules can be tested independently of presentation. |
| Accessibility | Core actions are available through clear input alternatives and readable feedback. |
| Reliability | A fresh checkout, missing optional asset, invalid AI key, or unavailable Forward+ renderer must not prevent a normal Compatibility/Web/headless boot. |
| Performance | A busy venue should hold its budget at the target NPC cap on desktop and Web; the current 16-NPC check is a baseline, not a 1.0 guarantee. |
| Performance | A busy venue should hold its budget at the target NPC cap on desktop and Web; the current 24-NPC check is a baseline, not a release guarantee. |
| Documentation integrity | Every released version has a changelog entry, current status/roadmap, and architecture/ADR references that match the build. |
## 11. Risks and Technical Debt
@@ -263,13 +340,15 @@ Architecture decisions are recorded as ADRs:
- Mental-health-crisis scenarios require careful writing, playtesting, and respectful presentation.
- The choice of engine may affect navigation, crowd scale, tooling, and content authoring.
- Reference mockups can outpace the runtime art style; compare/judge catch gross drift but not composition errors, so the staged gallery needs human review after large visual changes.
- Godot version skew (CI 4.3 vs local 4.7.2) can change lighting, import sidecars and generated UIDs; a single supported version and a visual smoke matrix are required before 1.0.
- Godot 4.7.2 is the unified CI/local/project baseline; future engine bumps still require import, visual and export smoke checks before changing that baseline.
- Content volume and authoring tools are still thin; adding venues/events by editing the factory can become error-prone without schema validation and preview tooling.
- Save data currently covers settings and shift history, not mid-shift resume; adding saves/replays requires a versioned schema and migration policy.
- The optional judge and image-generation paths depend on an external key, model availability and budget; CI cannot make them a release gate, and keys must never be committed.
- Accessibility and localization coverage is partial; a 1.0 release needs subtitles/captions, colour-independent cues, remapping, text expansion tests and at least a documented localization workflow.
- Unsigned macOS builds, AI asset provenance and any storefront/legal requirements are release blockers for a public 1.0.
- Accessibility and localization coverage is partial; a public release needs subtitles/captions, colour-independent cues, remapping, text expansion tests and at least a documented localization workflow.
- Unsigned macOS builds, AI asset provenance and any storefront/legal requirements are release blockers for a public release.
- Documentation drift has already occurred (release version, implemented status and obsolete plan docs); the ownership map and roadmap must be kept current in the same change as the implementation.
- Character quality is only partly automatable: proportions, budgets and gear fit are measured, but face realism and venue-lighting readability still need the baseline/review sheets and human sign-off after any mesh, map or material change.
- Local-map data and photo-derived assets add a second provenance and privacy risk: OSM share-alike, photo rights, faces/plates, property permission and rapidly churning splat tooling must be resolved before any derived asset ships.
## 12. Documentation Consolidation and Ownership
@@ -285,11 +364,14 @@ architecture document.
| [`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/blender-assets.md`](blender-assets.md) | Blender character/gear/prop contracts and attach points | Detailed asset contract appendix |
| [`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-proportion-audit.md`](npc-proportion-audit.md) | Measured proportions against the stylized guide | Measurement appendix to the character tooling section |
| [`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/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/smoke-checklist.md`](smoke-checklist.md) | Manual verification script | QA appendix to §10 |
| [`docs/development-status.md`](development-status.md) | Implemented features and current gaps | Status companion to the roadmap |
| [`TODO.md`](../TODO.md) | Active tasks and v1.0.0 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 |
Consolidation decisions:
@@ -297,7 +379,7 @@ Consolidation decisions:
- **Architecture owns the "how"** (components, contracts, data flow, constraints, quality gates); **PLAN/gameplay own the "what"**; **ADRs own the "why"**. Links replace duplicated prose.
- **Asset and release documents remain separate** because they are command-heavy operational procedures; their architecture-relevant contracts and failure modes are summarized in §5 and §7.
- **Completed plans are removed, not kept as competing roadmaps.** Historical graphics work is summarized by ADR-0013, the asset pipeline section and the v0.6.0 changelog; new work belongs in TODO.
- **Status and roadmap are separate from architecture.** `docs/development-status.md` records what exists; `TODO.md` records what remains, including the v1.0.0 gap analysis and milestone exit criteria.
- **Status and roadmap are separate from architecture.** `docs/development-status.md` records what exists; `TODO.md` records what remains, ordered as unversioned development tracks, versioned milestones and deferred work.
- **Documentation drift is a tracked risk:** release/version, test counts, renderer status, and stale plans must be corrected in the same commit as the change that invalidates them.
## 13. Glossary
+302 -22
View File
@@ -38,7 +38,7 @@ Audit accepts image files or directories. `--role auto` infers common roles from
```bash
python3 tools/asset_pipeline.py audit assets/textures
python3 tools/asset_pipeline.py audit assets/images/scenes --role background --json
python3 -m unittest discover -s tools -p 'test_*.py'
python3 -m unittest discover -s tools/tests -t tools -p 'test_*.py'
```
The audit classifies measurable fit issues as `good` or `bad` based on role-specific dimensions, aspect ratio, alpha requirements, and tile-edge continuity. It lists perspective/projection as a visual review item because image semantics cannot be judged reliably by these deterministic checks.
@@ -104,8 +104,11 @@ python3 tools/npc_pipeline.py palette
```
`prepare` resizes the AI fabric tiles (kept locally in `assets/textures/npc/ai/`,
which is gitignored) onto the `cloth`/`pants` albedos; `build` bakes them into the
`.glb` and regenerates the procedural normal + packed ORM maps. `palette` writes
which is gitignored) onto the 256² `cloth`/`pants` albedos; `build` bakes them into
the `.glb` and regenerates the procedural normal + packed ORM maps at the same
256² size. Runtime fabric materials repeat the weave 4× (`NPC_FABRIC_UV_SCALE`)
and skip the per-character baked AO, which otherwise magnified into large
blotches; skin, hair and fixed gear keep AO. `palette` writes
`assets/textures/npc/palette.json` using `model_pipeline.dominant_colors`.
## Blender asset library (loaded by the game)
@@ -126,6 +129,11 @@ with colliders/avoidance and drives their emissive parts from the day/night
cycle. The single `npc_base.glb` and primitive props remain offline fallbacks.
Contract and integration notes: [`blender-assets.md`](blender-assets.md).
Sourcing and expansion research (no external model is shipped yet):
[`npc-model-sourcing.md`](npc-model-sourcing.md) compares Quaternius, Kenney, Khronos and
MakeHuman sources against the character contract and records the 2026-11-03 direction
(procedural generator first, Quaternius reference, MakeHuman deferred).
| Characters | Props |
| --- | --- |
| ![characters](../docs/blender_previews/sheet_npc.jpg) | ![props](../docs/blender_previews/sheet_props.jpg) |
@@ -135,9 +143,182 @@ flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets
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 audit # no Blender needed
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
godot --headless --path . --import
```
### Intake inspection (`inspect`)
`audit` reads accessor-local bounds and ignores node transforms, so it is the right
release gate but a poor first look at a downloaded or newly authored GLB. `inspect`
imports the file into Blender, clears parents and applies object transforms, then
reports what the scene actually contains:
```bash
# one asset, a batch, or an external download; repeat --file as needed
flatpak run org.blender.Blender --background \
--python "$PWD/tools/blender_assets.py" -- inspect \
--file "$PWD/assets/models/blender/npc/npc_casual.glb" \
--file "$PWD/tmp/downloads/SomeCharacter.glb"
# machine-readable, for intake scripts (write to a file: Blender prints a banner first)
... -- inspect --file "$PWD/tmp/x.glb" --json --out "$PWD/tmp/x.json"
# force a contract family instead of inferring it from the path
... -- inspect --file "$PWD/tmp/x.glb" --category npc
```
It prints triangles (with the category budget), raw and seam-split vertex counts,
Godot-space bounds with height/feet for characters, whether transforms had to be
applied, mesh nodes and armatures, bone names, materials, UV layers, skinning and
animation count. Hard contract failures (`issues`) fail the command; advisory
findings (`warnings`) do not. The category is inferred from the path:
`blender/npc` → `npc`, `blender/npc_gear` → `npc_gear`, `blender/props` and
`generated` → `props`, anything else → `external` (measured but with no project
budget or rig contract).
Caveats:
- The glTF importer adds a bone-display mesh (`Icosphere`, used as
`pose_bone.custom_shape`). `inspect` excludes custom-shape helpers so they never
pollute bounds or the unskinned list.
- Bounds and bone rests are converted from Blender Z-up to Godot Y-up (`x, z, -y`),
matching the contract bands and the audit's coordinate space.
### Intake adaptation (`adapt`)
`adapt` is the mechanical first half of an external-mesh intake (MakeHuman,
MPFB, downloaded GLB): import, apply transforms, ground the feet, 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
manual `retopology`/`rigging` step with a recorded second-rig contract if the
source rig cannot be retargeted:
```bash
flatpak run org.blender.Blender --background \
--python "$PWD/tools/blender_assets.py" -- adapt \
--file "$PWD/tmp/npc-research/makehuman_base.glb" \
--out "$PWD/tmp/npc-research/makehuman_adapted.glb" \
--target-tris 8000
# prints: adapted makehuman_adapted.glb: 36972 -> 8000 tris (bones=N, meshes=1)
```
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
24-NPC budget.
- 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
gate: it is stdlib-only, runs in CI, and matches what the engine imports.
- `inspect` never writes or adapts the file; scale/grounding/weld/retarget work is the
separate (not yet built) `adapt` step.
- `--json` also captures the importer's INFO log in `stats.import_log` instead of printing it.
Blender still prints its own version banner before the script runs, so intake scripts should use
`--json --out <file>` rather than parsing stdout.
### Reference-anchored concept generation
`tools/scene_concepts.py generate --reference <photo>` anchors a concept frame on a real
photograph: the photo is sent to the OpenRouter multimodal chat endpoint as a base64 data URL
(`generate_textures.generate_image(reference_path=...)`) with a prompt suffix that pins the
photographed geometry (column rhythm, band heights, signage placement, camera angle) while the
prompt restages lighting, weather and people.
Licence gate: the reference must be listed in its `docs/screens/refs/<venue>/SIDECAR.md` row
(which also supplies the licence and Commons page for provenance). A photo from anywhere else
requires `--reference-unchecked`, which records `unchecked` in the provenance. Each anchored
generation writes a `.refgen.json` sidecar next to the frame (`model`, reference file/licence/page,
geometry suffix) so a concept frame can always be traced back to its evidence photo. Where a
generated frame and its reference photo disagree, the photo wins for shape.
### Photo-anchored variant pass
`tools/scene_concepts.py anchor --reference <photo> --context "..." --treatments day,night,weathered
--out-dir docs/screens/concepts/<block>` generates several restaged conditions (day, night,
weathered) of one reference photo's framing, each with its `.refgen.json` provenance sidecar, so a
concept board stays comparable with its photo board. The reference passes the same SIDECAR licence
gate as `--reference` on `generate`.
### Local-only reference collection (`refcollect`)
`tools/refcollect.py` downloads images from DuckDuckGo image search into `tmp/research/<slug>/` for
development reference only. It applies **no licence gate** (rows are marked `local_only` /
`unchecked-local`); everything it fetches must never be committed or promoted into
[`screens/refs/`](screens/refs/README.md) without the normal sidecar licence + privacy gate. This
is the owner-authorised local-only override (TODO.md §3D model re-evaluation → A).
```bash
python3 tools/refcollect.py search "Comedy Store Sunset Boulevard" --slug comedy-store --limit 20
python3 tools/refcollect.py sheet --slug comedy-store # local contact sheet (never committed)
```
Requires `pip install duckduckgo-search` (renamed `ddgs` upstream). DuckDuckGo rate-limits by IP
(HTTP 202), so every request — the search itself and each image download — passes through a shared
token bucket (`--rate`, default 0.5 req/s, and `--burst`, default 3) and a polite retry routine:
transient statuses (408/425/429/5xx/52x) and network errors back off exponentially with full
jitter, `Retry-After` is honoured, permanent failures fail fast, and the loop gives up after six
attempts. Requests carry a full, coherent Firefox header set (`--user-agent` overrides the UA
string); the UA alone is no longer enough to look like a browser.
### Reference-board research (`refboard`)
`tools/refboard.py` searches, fetches and documents Wikimedia Commons photos of a real location for
the [`screens/refs/`](screens/refs/README.md) boards. It is stdlib-only and never commits anything
itself: downloads land in `tmp/`, and moving a file into a board folder is a deliberate manual step
that goes through the sidecar licence gate.
```bash
# search: keyword, geosearch (the reliable "photos of this exact place"), or category
python3 tools/refboard.py search --source geo "34.10118|-118.34009" --limit 60 --months 12
python3 tools/refboard.py search "El Capitan Entertainment Centre" --source search
python3 tools/refboard.py search "Hollywood Masonic Temple" --source category --json
# fetch the usable rows into tmp/ (review-only rows need --include-review)
python3 tools/refboard.py search ... --json > /tmp/manifest.json
python3 tools/refboard.py fetch --from /tmp/manifest.json --dir tmp/research/<block> --max 12
# emit SIDECAR + README table rows from the fetched manifest
python3 tools/refboard.py rows --from tmp/research/<block>/meta.json
```
Licence gate: `Public domain`, `CC0` and `CC BY` are committable with attribution; `CC BY-SA` rows
are flagged `review` and stay research-only until the share-alike decision is written into the
sidecar (`--allow-ccbysa` marks them committable after that review); anything else is skipped.
`--months 12` flags recent captures so a "current reality" photo can be distinguished from the
older geometry evidence. Network gotchas baked in: a descriptive User-Agent, batched `titles=`
metadata queries and ~3 s download spacing to avoid Commons HTTP 429s.
### Texel density (`texels`)
`texel_density()` and the `texels` command compute `resolution * sqrt(UV area / world area)` per
material slot straight from the GLB, so the number does not depend on island packing. The NPC slot
maps are 256², and cloth/pants are reported twice: authored density and effective density with the
runtime 4× repeat from `Materials.NPC_FABRIC_UV_SCALE`. Current values and interpretation are in
[`3d-model-review.md`](3d-model-review.md).
```bash
python3 tools/blender_assets.py texels # all eight characters
python3 tools/blender_assets.py texels --json # machine-readable
python3 tools/blender_assets.py texels --only npc_coat
```
## Local map development (planned)
The local map workstreams (**H–M**) in [`TODO.md`](../TODO.md#3d-model-re-evaluation)
are not implemented yet; there are no map-generation commands to run. The planned
pipeline will evaluate:
- offline OpenStreetMap extracts for footprints and licence-compatible 3D building
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
Everything below is audited and shipped in the game; development-only outputs
@@ -196,17 +377,114 @@ and ranks them by advertised token/image price. The run falls through the list
on provider/model errors and aborts on an invalid key. Use
`GIG_LINE_JUDGE_MODEL` or `--model <id>` for a pinned comparison.
`capture_realism.gd` runs six stages (day, portrait, evening, late, Joke Shop, studio);
`--stages=day,late` limits it while tuning; `--flashlight` turns on the guard lamp for
night stages (used by the Desktop HQ A/B). The `portrait` shot freezes one NPC on open sidewalk
and frames the face, which is the only capture that can judge avatar detail
(`--portrait-type=N` picks a role). The generated realism runs under
For an open-ended review of a single capture/reference (no structured verdict),
`tools/describe_image.py` sends the image plus a question to a vision model and
prints the answer. It is how the 2026-11-03 re-evaluation read the local-only
reference frames and the prop/venue captures:
```bash
python3 tools/describe_image.py docs/screenshots/12-joke-shop.png \
--question "Do the red curtain bays read as tied-back fabric or flat slabs?"
```
`capture_npc_review.gd` is the pre-build NPC gate: it stages all eight
library character models at portrait, conversation and crowd distance on both
promoted maps in day and night lighting, with the normal crowd hidden. It uses
the review-only `NpcAvatar.character_override` to include the guard model, which
has no spawned NPC role:
```bash
godot --path . --script res://src/test/capture_npc_review.gd -- \
--out=res://tmp/npc-review
```
Contact sheets can be assembled from the files (the output is local QA and is
not committed). The runtime applies small attach-space offsets to the lanyard
and firefighter helmet so the badge clears the jacket and the brim does not sit
over the eyes; these are `NpcAvatar.GEAR_OFFSETS`, not changes to the gear
contract.
`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
[`npc-proportion-audit.md`](npc-proportion-audit.md). It reads the exported GLBs
and does not require Blender.
`capture_npc_pose.gd` is the pose/deformation harness for the 3D re-evaluation: idle,
walk-contact, walk-pass and seated poses of all eight variants, full-body plus shoulder
close-ups. **It is currently blocked**: the applied pose is visible in the rig and bone values
but not in the captured frames, so do not use its sheets as pose evidence. The reproduction and
what was ruled out are in [`3d-model-review.md`](3d-model-review.md) §"Pose evidence is blocked".
The baseline sheet tool accepts a custom view list, which is also how pose sheets are tiled:
```bash
# baseline set (six views, eight variants)
python3 tools/npc_baseline.py sheet --dir tmp/npc-baseline
# pose set produced by capture_npc_pose.gd
python3 tools/npc_baseline.py sheet --dir tmp/npc-pose \
--views idle,walk_contact,walk_pass,sit --out tmp/npc-pose/sheet_pose.jpg
```
`capture_npc_baseline.gd` is the 0.9.4 neutral baseline: eight characters ×
front/three-quarter/profile/back/full-body/crowd = 48 frames with a fixed
camera, studio lighting and no venue/HUD/animation. The raw PNGs stay in
`tmp/`; `tools/npc_baseline.py sheet` builds the committed contact sheet, and
`tools/npc_baseline.py compare --before DIR --after DIR` builds the review
comparison after a model change. See
[`npc-visual-review.md`](npc-visual-review.md).
```bash
godot --path . --script res://src/test/capture_npc_baseline.gd -- \
--out=res://tmp/npc-baseline
python3 tools/npc_baseline.py sheet --dir tmp/npc-baseline
```
`capture_realism.gd` runs nine stages in this order: day, Hollywood surface close-ups,
portrait, evening, late, Joke Shop day, Joke Shop surface close-ups, Joke Shop night,
studio; `--stages=day,late` limits it while tuning; `--flashlight` turns on the guard
lamp for night stages (used by the Desktop HQ A/B). The `portrait` shot freezes one
NPC on open sidewalk and frames the face, which is the only capture that can judge
avatar detail (`--portrait-type=N` picks a role). For the 0.9.5 texture audit, run
the surface stages directly; the HUD is hidden for them:
```bash
godot --path . --script res://src/test/capture_realism.gd \
-- --out=res://tmp/surfaces --stages=surfaces,surfaces_joke
```
The generated realism runs under
`docs/screens/realism/` are **local, gitignored QA artifacts**; commit only the
resulting decisions/docs, not the run folders. The texture-adoption run compared
the pre-adoption captures with the current build: on the shots both sets captured,
the judge's like-for-like means rose on every sub-score (overall 0.8 → 1.0,
backdrop 0.6 → 1.6) and the Pillow metric gap fell 0.444 → 0.348. The character
pass added the portrait shot and recorded the new faces, hands and sleeve cuffs.
resulting decisions/docs, not the run folders. The 0.9.5 texture/UI audit checked actual screen sizes: all screen art is
1280×720, access cards are 256×128 and HUD icons are 64×64; no runtime texture
was oversized or unused. The 24-NPC smoke now reports 168 meshes / 131,616
triangles / 6.94 ms. Compressed payloads are 24.55 MB of surface picks, 22.67 MB
of screen art and 0.48 MB of NPC maps.
The 0.9.5 realism judge ran once per shot with `z-ai/glm-5.3-flash`
(`python3 tools/scene_concepts.py realism --run docs/screens/realism/2026-11-02-tex
--judge`). Its mean `after` scores were overall 1.8, lighting 2.3, materials 1.8,
texture detail 1.2, backdrop 2.0 and characters 1.2; the Pillow metric gap was
0.306. The judge still ranks the painted concept targets above the stylized build,
particularly lit night facades and dense backdrop detail. That is expected: the
pass validated close-range surfaces and promoted the texture repairs, not a
photoreal renderer. The 0.9.5 before/after close-up sheet is
[`blender_previews/sheet_texture_refinement_0.9.5.jpg`](blender_previews/sheet_texture_refinement_0.9.5.jpg);
the texture-adoption run itself was the earlier 0.8-style candidate comparison,
and its report remains local/gitignored.
For the 0.9.6 integration checkpoint the same judge was pinned to
`z-ai/glm-5.3-flash` and run once per shot on the 10-shot set: mean overall 2.1,
lighting 2.4, materials 2.1, texture 1.7, backdrop 2.3, characters 1.8, Pillow
gap 0.291. No `review` verdicts were produced by the screen judge, so there was
no manual layout verdict to convert into a fix; the remaining realism comments
are the accepted stylized gaps.
Build release artifacts from a clean checkout or detached worktree. A normal
working tree may contain ignored `tmp/` captures and old `build/` artifacts, and
Godot can pack enough of those into exports to inflate a 0.9.6 package from
~160 MB to 1.6–3.1 GB. The 0.9.6 clean worktree produced 161.5 MB Linux,
197.1 MB Windows, 148.0 MB macOS, 97.8 MB Web and 241.5 MB Desktop HQ.
The Forward+ A/B compared the Compatibility entrance with the optional Desktop HQ
preset. The gear-fit run checked the sunglasses, bag and helmet against measured
head/skirt bounds with `python3 tools/blender_assets.py fit`; it also fixed the
@@ -244,6 +522,9 @@ python3 tools/screen_designs.py generate --draft --only wand --force
# (requires OPENROUTER_API_KEY)
python3 tools/screen_designs.py generate
# UI-state gallery (menu, pause, settings, review, radio, wheel, crowd, responder)
godot --path . --script res://src/test/capture_ui_states.gd -- --out=res://docs/screenshots
# Prop gallery (studio line-up) for close-range prop checks
godot --path . --script res://src/test/capture_prop_gallery.gd
@@ -259,12 +540,11 @@ python3 tools/screen_designs.py judge --only 03- # one capture
python3 tools/screen_designs.py judge --dry-run # build requests, no network
```
The catalogue covers 21 screens: the eleven README captures plus the start menu,
pause overlay, settings, shift review, radial menu, emergency radio, wand
screening, Joke Shop queue, TV Studio arrival, crowd panic, responder arrival,
body-camera timeline, and the escalation HUD. All 21 have an AI reference in
`assets/images/ui/` (snapshot in `docs/screens/designs/`); the eleven screens with
staged captures are also compared or judged against the running game.
The catalogue covers 21 screens: the thirteen scene captures plus the start menu,
pause overlay, settings, shift review, radial menu, emergency radio, crowd panic,
and responder arrival. All 21 have an AI reference in `assets/images/ui/`
(snapshot in `docs/screens/designs/`) and a committed capture, so every entry is
compared or judged against the running game.
`compare` flags gross drift: wrong aspect ratio, undersized frame, near-blank
frame, and large layout/palette/detail distance. It is a regression gate, not a
@@ -278,8 +558,8 @@ Verdicts are classed as `match`, `review` (a mismatch below `--fail-confidence`,
default 0.6 — expected when a concept mockup is more stylised than the game), or
`diff` (a confident mismatch, which exits non-zero). Regenerate the designs after changing screen layout, then re-run
`compare` after regenerating the game screenshots with
`src/test/capture_screenshots.gd`.
`src/test/capture_screenshots.gd` and `src/test/capture_ui_states.gd`.
For a one-command drift check, run `tools/check_screenshots.sh`. It renders the
staged captures and prop gallery into `docs/screenshots/` (using `xvfb-run` when
no display is available) and then runs `screen_designs.py compare`.
scene captures, UI-state captures and prop gallery into `docs/screenshots/` (using
`xvfb-run` when no display is available) and then runs `screen_designs.py compare`.
+115
View File
@@ -0,0 +1,115 @@
# Release-candidate balance acceptance
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
[roadmap](../TODO.md) tracks the sweeps and external playtests still needed.
[Gameplay](gameplay.md) owns the mechanics; this page owns their balance-test protocol.
## Test matrix and evidence
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
those seeds for before/after comparisons. Exercise each eligible authored event
at least once as a separate coverage pass; a random schedule can miss rare events.
Record scheduled event counts separately from forced coverage cases so the latter
do not distort event-frequency estimates.
For reaction measurements, use a fresh controller at the entrance for each sample.
Routine cases are tourists, confused visitors and delivery workers obstructing the
entrance without threatening behavior. Do not include authorized entry requests in
this redirect cohort. For peaceful-resolution measurements, send one polite
direction (L1), then one warning (L2) only if still unresolved. Do not reroll until
success. Observe return behavior for 60 simulated seconds before closing the case.
Do not issue emergency calls as part of this policy.
For access cases, follow the venue's required inspection and screening steps before
allow/deny. Include valid, expired, counterfeit, mismatched, missing and ambiguous
artifacts, known recognition, age boundaries, screening refusal, benign wand alerts
and prohibited items. Recognition must be independently justified by the fixture;
calling a stranger recognized is not evidence of correct admission. An unresolved
hold counts as unresolved, not as a correct refusal.
Evidence must identify the commit, engine, venue, difficulty, phase, seed, event ID,
cohort, visible evidence, policy action, reaction/state, return, access truth and
decision, emergency truth and call, report fields, reputation modifiers, final grade
and unresolved cases. Save raw rows plus numerator/denominator summaries for every
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
cases as **N/A**, never as 100% success. Keep scheduled and forced results separate.
## Targets
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;
first-time-player results measure whether that evidence is understandable.
| Metric | Numerator / denominator | Acceptance target |
| --- | --- | --- |
| First-direction routine compliance | First L1 reaction is `COMPLY` or `LEAVE` / eligible routine redirect cases | Easy ≥80%; normal ≥75%; hard ≥65% |
| Routine peaceful resolution | Routine cases leaving after at most L1 then L2, with no return during 60 s and no L3–L5 / eligible routine redirect cases | Easy ≥90%; normal ≥85%; hard ≥80% |
| Access accuracy | Correct final allow/deny decisions / all eligible access cases (including unresolved cases) | Scripted 100%; first-time players ≥90% pooled, with each error reviewed |
| Access false positive | Unauthorized people allowed / unauthorized access cases | Scripted 0%; first-time players ≤5% pooled |
| Access false negative | Authorized people denied / authorized access cases | Scripted 0%; first-time players ≤10% pooled |
| Emergency precision | Calls supported by immediate-danger evidence / all emergency calls | Scripted 100%; first-time players ≥95% pooled; zero calls is N/A |
| Emergency false positive | Routine/no-immediate-danger cases receiving an emergency call / all routine/no-immediate-danger cases | Scripted 0%; first-time players ≤5% pooled |
| Emergency false negative | Immediate-danger cases without an accepted emergency call within 15 simulated seconds of visible danger / all immediate-danger cases with radio initially available | Scripted 0%; first-time players ≤10% pooled |
| Report accuracy | Reports with correct weapon and danger fields / all submitted reports | Scripted 100%; first-time players ≥90% pooled |
For access, authorized means entitled under the venue policy **after required
screening**, without a prohibited item or an independent immediate-danger reason
to refuse. Compare to a hand-labelled fixture expectation, not only to
`AccessVerifier.verify()`: using the verifier as both decision and oracle would
hide policy bugs. Report unresolved access cases separately; they cannot improve
either error rate by being omitted from accuracy.
Immediate danger means an observed weapon, assault, active forced entry or a
credible immediate threat to occupants. Argument, filming, intoxication, a crisis,
a refused screen or a benign wand alert alone is not immediate danger. A mental
health label or clothing must never be used as the emergency oracle. The 15 s
window is a test-response target, not the responder-arrival time (currently 15 s
after an accepted emergency call). Separately test depleted battery and active
cooldown cases for safe containment/assistance; do not silently drop them from the
evidence or claim the initially-available-radio target covers them.
## Scoring and emergency dominance
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.
An accurate report about harmless behavior must not make its emergency call
justified. Require an unjustified-call investigation and a strictly lower rating
for emergency-first in every routine comparison. Inspect modifiers as well as the
final rating: the 0–100 clamp can hide rewards/penalties. Emergency-first must not
improve the campaign outcome or final grade. Grade boundaries remain A ≥90,
B ≥75, C ≥60, D ≥40, otherwise F; these are existing rules, not new tuning.
In genuine-danger cases, verify that the call is accepted, responders arrive,
the incident remains answerable and the debrief explains the outcome without an
unjustified-call investigation. Also compare assistance/containment routes where
appropriate; emergency need not be mandatory for every difficult interaction.
## Implementation cautions and sign-off
- `NPCController.roll_reaction()` samples per-level weights. Difficulty adds
+12/0/−8 to the `COMPLY` weight before normalization; `base_compliance` alone
is not the observed compliance percentage. Measure `COMPLY` plus `LEAVE`.
- `GameManager._read_npc_truth()` currently derives weapon/danger truth from the
NPC type (violent threat: weapon/3; everyone else: no weapon/1), rather than
observed behavior. This is a balance-review risk: test nonviolent profiles that
threaten or force entry, and violent profiles before visible danger. Do not
treat agreement with this helper as proof of fair emergency judgment.
- `RadioSystem.credibility_hits()` checks weapon and danger fields only;
location, description and behavior still need human review. Difficulty scales
report penalties, not the oracle.
- A terminal peaceful reaction is not sufficient evidence of an incident staying
resolved: returning NPCs and unresolved shift-review entries must be retained.
Do not tune to a pooled average that hides a failing venue, phase or difficulty.
Record each miss, its severity, the raw evidence and any approved exception before
changing weights. Re-run the affected cells and tutorial/campaign smokes after a
fix. Five first-time-player sessions must cover the tutorial and a complete venue
shift; report pooled counts and per-session errors, not statistical confidence
from five players. Scripted sweeps do not substitute for those sessions.
**Status:** targets defined; sweeps, emergency fairness review, grade/campaign
verification and external-player acceptance remain pending. No balance sign-off
or release approval is implied by this document.
+41 -13
View File
@@ -5,7 +5,10 @@ Architecture context: the asset contracts and fallback path are summarized in
Status: built, audited, and integrated (2026-11-01). `NpcAvatar` selects the
character variants and attaches the gear; `WorldBuilder` loads the prop library
with colliders and phase-driven emissives.
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);
the committed 8 × 6 contact sheet is
[`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg).
Generator: [`tools/blender_assets.py`](../tools/blender_assets.py). Manifest (the
source of truth for integration):
@@ -18,7 +21,7 @@ captures in `docs/screenshots/`.
- **People** (`targeting_npc`, `joke_shop_queue`, `studio_arrival`,
`crowd_panic`, `responder_arrival`): ordinary adults in sweaters, jackets,
long coats, hoodies and beanies; a security guard in a dark uniform with a cap
long coats and hoodies; a security guard in a dark uniform with a cap
and belt; a firefighter in a dark turnout coat with yellow reflective bands,
a helmet and an SCBA tank. The current skinned `npc_base` is a set of boxes
and cylinders with a flat face, so silhouette was the main gap.
@@ -44,7 +47,11 @@ flatpak run org.blender.Blender --background \
python3 tools/blender_assets.py sheet # -> docs/blender_previews/sheet_<category>.jpg
python3 tools/blender_assets.py audit # stdlib only; exit 1 on any issue
python3 tools/blender_assets.py fit # head/clothing fit + bag-body clearance, per character
python3 tools/blender_assets.py texels # 256² slot-map density (authored + 4× fabric), per character
python3 tools/blender_assets.py list
# import a GLB, apply node transforms, report geometry/bones/UVs/skinning + contract failures
flatpak run org.blender.Blender --background \
--python "$PWD/tools/blender_assets.py" -- inspect --file "$PWD/tmp/downloads/x.glb"
godot --headless --path . --import # after any rebuild
```
@@ -87,12 +94,16 @@ All eight use the `npc_base` contract, so each one can replace
`test_character_joints_line_up_with_npc_base` checks this within 1 cm, so the
animation proxies in `_build_skinned_body()` stay valid.
- material slots `skin`, `cloth`, `pants`, `accessory`, `hair` carry the same
neutral `assets/textures/npc/<slot>_{albedo,normal}.png` maps, so
`Materials.npc_slot()` tints them the same way it tints `npc_base`.
- budget ≤ 6000 triangles and ≤ 5000 vertices (glTF vertex count, after seam
splits; `CHAR_MAX_VERTS` in `tools/blender_assets.py`, raised from 4000 for
the 2026-11-01 face/hand pass). The current range is 3,974–5,026 triangles
and 3,744–4,807 vertices.
neutral `assets/textures/npc/<slot>_{albedo,normal}.png` maps at 256², so
`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,
subdivided twice, decimated to 26 % with X symmetry, and cut at the waist, cuffs,
@@ -134,13 +145,17 @@ attach point:
| Attach | Godot parent / position | Gear |
| --- | --- | --- |
| `head` | `Rig/Head` marker (0, 1.62, 0) | cap, beanie, hood, sunglasses, firefighter_helmet |
| `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
@@ -170,16 +185,22 @@ carry baked AO and the shared neutral material maps described above.
- `python3 tools/blender_assets.py audit`: container, `POSITION`/`NORMAL`/
`TEXCOORD_0`, UVs in [0, 1], no zero-area triangles (Godot warns about them
during LOD generation), budgets (characters 6000/5000, gear 1500, props 4000
during LOD generation), budgets (library characters 8000/8000, base fallback 6000/4000, gear 1500, props 4000
triangles), grounding, and an editable `.blend`. For characters it also
checks skinning, bones, slots, the `npc_base` mesh node and the height.
- `tools/test_blender_assets.py`: catalogue and contract consistency with
`npc_pipeline.py`, plus a clean audit and full manifest coverage when the
library is built.
- `tools/tests/test_blender_assets.py`: catalogue and contract consistency with
`npc_pipeline.py`, plus a clean audit, a texel-density gate (all five slots,
40–400 px/m authored, 250 px/m fabric floor) and full manifest coverage when
the library is built.
- `src/test/tests/test_blender_assets.gd`: every asset imports as a scene
with geometry, characters keep their bones and slots, joints match
`npc_base`, the bones accept `NpcAvatar`'s pose composition, and props are
grounded.
- `tools/test_model_review.py`: every `CHARACTERS`/`GEAR`/`PROPS` entry has a
scorecard row and verdict in `docs/3d-model-review.md` (coverage gate).
- `src/test/tests/test_world.gd`: every library prop that blocks walking has a
`StaticBody3D` + `NavigationObstacle3D`, checked against the catalogue
(collider/proxy gate).
## Known limitations
@@ -193,3 +214,10 @@ carry baked AO and the shared neutral material maps described above.
the legs through the hem.
- Preview colours (`PREVIEW_TINTS`) only stand in for `outfit_for()`. The GLBs
stay neutral.
- The 0.9.4 baseline found three P1 portrait issues. The tourist's photo prop
now only appears in photo/film poses and the backpack straps were refit as
flattened chest bands. The face carries the 0.9.4 sculpt (socketed sclera,
chin/cheekbone, nose, lips) but its `seg=18/rings=12` head still reads
somewhat faceted at portrait range; a subdivision/deeper-sculpt prototype is
gated on the owner scorecard review. The full ranked list and measurements are
in [`npc-visual-review.md`](npc-visual-review.md).
+31
View File
@@ -0,0 +1,31 @@
# Blender previews
Committed contact sheets for the character, gear and prop library. Per-asset Cycles renders
(`*.png`) are local development output and gitignored; the sheets below are the review artefacts
and are built with `blender_assets.py render` + `sheet`, `npc_baseline.py sheet/compare`, or the
capture harnesses.
| Sheet | What it records |
| --- | --- |
| [![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_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.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-p1.jpg](sheet_npc_baseline_compare-p1.jpg)](sheet_npc_baseline_compare-p1.jpg) <br> `sheet_npc_baseline_compare-p1.jpg` | 0.9.4 P1 pass: tourist photo-prop visibility and the refit backpack straps. |
| [![sheet_npc_baseline_compare-shoes.jpg](sheet_npc_baseline_compare-shoes.jpg)](sheet_npc_baseline_compare-shoes.jpg) <br> `sheet_npc_baseline_compare-shoes.jpg` | Footwear: rounded toe/heel caps on shoes and responder boots. |
| [![sheet_npc_baseline_compare-tex256.jpg](sheet_npc_baseline_compare-tex256.jpg)](sheet_npc_baseline_compare-tex256.jpg) <br> `sheet_npc_baseline_compare-tex256.jpg` | 128 -> 256 px NPC slot maps on the same camera. |
| [![sheet_npc_baseline_compare-texuv.jpg](sheet_npc_baseline_compare-texuv.jpg)](sheet_npc_baseline_compare-texuv.jpg) <br> `sheet_npc_baseline_compare-texuv.jpg` | 4x fabric weave with the baked per-character AO removed. |
| [![sheet_npc_baseline_compare-torso.jpg](sheet_npc_baseline_compare-torso.jpg)](sheet_npc_baseline_compare-torso.jpg) <br> `sheet_npc_baseline_compare-torso.jpg` | Torso/hip taper without changing the skeleton or build multipliers. |
| [![sheet_npc_crowd_prototypes.jpg](sheet_npc_crowd_prototypes.jpg)](sheet_npc_crowd_prototypes.jpg) <br> `sheet_npc_crowd_prototypes.jpg` | Four approved crowd-variety looks queued for the 12-character roster. |
| [![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_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_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_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 measurements in [`../npc-proportion-audit.md`](../npc-proportion-audit.md) and
[`../3d-model-review.md`](../3d-model-review.md), and the asset contract in
[`../blender-assets.md`](../blender-assets.md).
Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Loaded 100 of 309 files, more files were not shown because too many files have changed in this diff. Show more