# DONE Completed task sections moved from `TODO.md`. Sections are moved only when every item is finished and verified. ## Project Setup - [x] Confirm Godot 4.x editor version to standardize on (Godot 4.3 stable, official build). - [x] Initialize a git repository and add a sensible root `.gitignore` for Godot and export artifacts. - [x] Scaffold the Godot project (`project.godot`, input map, folder structure). - [x] Add the GUT test framework and a runnable headless test command. (Deviation: GUT requires the editor plugin; replaced with a lightweight custom harness in `src/test/test_runner.gd` that runs without the editor.) - [x] Document setup, run, and test commands in `README.md`. ## Prototype Scene - [x] Build a placeholder Hollywood Boulevard building entrance with steps, sidewalk, and a restricted door (procedural `WorldBuilder`). - [x] Add a controllable guard character with first-person camera controls (`GuardController`). - [x] Add basic keyboard/mouse input: move, look, interact, and cancel. - [x] Add a debug overlay showing NPC intent, escalation level, and shift timer (`F` toggles). ## Core Simulation — Systems - [x] Implement `ShiftCoordinator` to start, advance, and end a shift. - [x] Implement `VenueScenario` as a `Resource` with locations, rules, NPC pools, and event pools. - [x] Implement an event scheduler that emits routine traffic and occasional scripted events (`EventScheduler`). - [x] Implement `NPCController` with movement toward waypoints, doors, and queues (movement handled by `NpcAvatar`). ## NPC Interaction - [x] Define `NpcProfile` resources with intent, temperament, compliance, and escalation tolerances. - [x] Implement a lightweight finite state machine for NPC states (`FSM`). - [x] Implement the observation phase (Level 0) without forced intervention. - [x] Implement simple deterrence (Level 1): raise hand, point, eye contact, and head shake. - [x] Implement polite dialogue prompts and reaction logic driven by escalation level and profile. - [x] Implement formal warnings (Level 2) and make NPC reactions depend on profile and level. - Note (2026-10-31): the selectable dialogue tone-of-voice system (Professional/Friendly/Firm/ Suspicious) was intentionally removed from code and docs; reactions now depend solely on escalation level and NPC profile. - [x] Implement blocking and physical positioning (Level 3) without default force. - [x] Wire typical NPC reactions: leave, argue, ignore, record, move closer, or return later. ## Access Verification - [x] Implement `AccessVerifier` with input from recognition, wristbands, IDs, tickets, credentials, and guest lists. - [x] Add wristband inspection UI showing color, pattern, date, and event marking. - [x] Model valid, expired, counterfeit, mismatched, missing, and ambiguous access artifacts. - [x] Record admit, redirect, hold, and refuse decisions for shift review. - [x] Add feedback scoring for incorrectly denying an authorized person or admitting an unauthorized one. ## Escalation and Radio — Core - [x] Implement `EscalationManager` with explicit levels 0 through 5 and available-action gating. - [x] Implement the security-assistance radio call (Level 4) with a report form and arrival delay. - [x] Implement the emergency radio (Level 5) with hold-to-confirm and a report form. - [x] Add radio cooldown and battery limits to prevent spam. ## Incident, Reputation, and Shift Review - [x] Implement `IncidentRecorder` emitting structured events (actor, behavior, action, evidence, escalation, outcome). - [x] Implement body-camera evidence tied to incident events. - [x] Implement `ReputationEvaluator` for peaceful resolutions, accuracy, complaints, damage, and entrance security. - [x] Implement `CrowdSystem` reactions to calm, aggressive, armed, and emergency situations. - [x] Implement the end-of-shift review screen summarizing decisions, consequences, and justification. ## Scenario Content — Venue Data - [x] Author a minimal Hollywood Boulevard building scenario with tourist, confused-visitor, and simple-trespasser NPCs. - [x] Author a rare violent-threat event that requires justified emergency escalation. - [x] Add The Joke Shop scenario data: ID checks, guest lists, and wand-screening policy. - [x] Add the Hollywood Boulevard Late Night TV Studio scenario data with the full NPC mix and credential checks. - [x] Add a mental-health-crisis event with careful, respectful de-escalation guidance. ## Tooling and Quality - [x] Add headless test coverage for access verification rules. - [x] Add headless test coverage for escalation transitions and scoring. - [x] Add fixed-seed scenario tests for deterministic NPC behavior. - [x] Set up GitHub Actions to run headless tests on push (`ci.yml`). - [x] Add a smoke-test checklist for launching one full shift (in `README.md`). ## Bug Fixes — LATE shift phase - [x] Add a `Rules.Phase.LATE` entry (`start_s` 700) to `shift_phases` in all three venues. - [x] Confirm the LATE-tagged `violent_emergency_event` is now pulled from `EventScheduler.pull_due`. - [x] Add a test asserting the LATE phase is reached and the event is eligible in phase. ## Missing PLAN Features — Protester NPC - [x] Add `PROTESTER` to `Rules.NpcType` and `NPC_TYPE_NAMES`. - [x] Add a protester profile (id `"protester"`) with ARGUE/IGNORE/RECORD/CALL_SOMEONE reactions. - [x] Add a `TYPE_COLORS` entry for `PROTESTER` in `npc_avatar.gd`. - [x] Add routine protester events to the Hollywood Boulevard and TV Studio event pools. - [x] Add scenario tests asserting the protester profile and events exist. ## Missing PLAN Features — “Call someone else” reaction - [x] Add `CALL_SOMEONE` to `Rules.Reaction`. - [x] Handle `CALL_SOMEONE` in `NPCController._update_state_for_reaction` (transitions to ARGUING). - [x] Give influencer, trespasser, and protester profiles CALL_SOMEONE weights. - [x] Add a reaction log line in `GameManager` and a `Strings` entry. - [x] Add a test that CALL_SOMEONE can be rolled and updates the FSM. ## Missing PLAN Features — Door lock/secure - [x] Add a `DoorState` core class with locked/unlocked state and `attempt_force()`. - [x] Add a visible door lock indicator (green open / red locked) to the world. - [x] Add the `L` key binding toggling the door lock, with HUD indication. - [x] Make FORCE_ENTRY reactions fail while the door is locked (thwarted outcome). - [x] Add `Strings` entries and a headless test for the lock state machine. ## Missing PLAN Features — Flashlight - [x] Add a `G` key binding toggling the guard's flashlight. - [x] Attach a `SpotLight3D` to the guard that toggles with the flashlight. - [x] Widen NPC perception radius while lit at evening/late phases (`perceive_guard` bonus). - [x] Add `Strings` entries and a headless test for the perception effect. ## Persistence - [x] Call `Save.save_shift_result(review)` from `GameManager._on_shift_ended`. - [x] Load past results and show record count + best score on the shift review screen. - [x] Add a test that a saved review round-trips through the `Save` helper. ## Navigation - [x] Add a `NavigationRegion3D` with a hand-authored flat navmesh covering sidewalk, steps, and entrance. - [x] Switch `NpcAvatar` from direct movement to `NavigationAgent3D` path following (with direct-movement fallback). - [x] Keep per-intent waypoint and returning-trespasser re-targeting on the navmesh. - [x] Make the step geometry visual-only (no collider) so pathing to the entrance works. ## Localization — JSON strings - [x] Move the string table to `res://content/strings.en.json` (41 keys). - [x] Load the JSON at startup; `Strings.lookup`/`Strings.fmt` signatures unchanged. - [x] Update the strings tests to confirm every JSON key resolves to a real value. ## Distribution — Downloadable executables - [x] Commit `export_presets.cfg` with Linux, Windows, and macOS desktop presets. - [x] Add `tools/build_releases.sh` for headless release exports. - [x] Add `docs/RELEASING.md` with template installation, build, and Gitea release steps. - [x] Add a prominent **Downloads** section at the top of `README.md` linking to the release page. - [x] Add `tools/publish_release.sh` (Gitea API release + asset upload, requires `GITEA_TOKEN`). ## CI/CD — Gitea Actions full build - [x] Add `.gitea/workflows/build.yml` with a fast checks job (import + headless tests on push/PR) and a full build job (export templates, Linux/Windows/macOS exports, artifact upload) on tags and `workflow_dispatch`. - [x] Tag builds automatically publish the Gitea release via `tools/publish_release.sh` using the `GITEA_TOKEN` secret. - [x] Remove the old `.github/workflows/ci.yml` to avoid double runs on hosts scanning both paths. - [x] Document the CI flow in `docs/RELEASING.md`. ## Gameplay — Venue selection - [x] Add a `V` control that cycles Hollywood Blvd → The Joke Shop → TV Studio and restarts the shift (`GameManager._cycle_venue`), with a `hud.venue_switched` string entry. ## 🎨 Visual polish pass (v0.2.0, pre-release graphics) - [x] Phase-based lighting: per-phase sun direction/energy/color, sky gradient (ProceduralSkyMaterial), and ambient in `GameManager.PHASE_LIGHTING`; applied on phase change and shift start. - [x] Street lamps: poles with emissive bulbs + OmniLights on the sidewalk; `WorldBuilder.set_night_mode` switches bulbs/lights on at night. - [x] Marquee signage: emissive “GIG LINE” strips + Label3D above the entrance. - [x] Street dressing: crosswalk stripes, planters, and low-poly trees. - [x] NPC visual variety: per-seed height/brightness variation (deterministic LCG). - [x] Denser routine traffic (tourists/performers/influencers/trespassers). - [x] `capture_screenshots.gd` now fast-forwards to the LATE phase and captures a night set (lamps + marquee + flashlight cone); README gallery is now 2×3 (day + night); six shots regenerated in `docs/screenshots/`. - [x] `config/version` → `0.2.0`; `build/` artifacts rebuilt and Linux binary smoke-tested. - [x] New world/lighting headless tests (56 total). ## Screenshot Refresh - [x] Regenerate and verify the six README screenshots from the current build. ## 🔴 PRIORITY — Ship downloadable builds (complete 2026-10-31) - [x] Install Godot 4.3 export templates locally. - [x] `./tools/build_releases.sh` produces Linux/Windows/macOS artifacts (0.2.0). - [x] Linux binary smoke-tested (headless, 0 script errors). - [x] Publish the release: no local token needed — the host has the `GITEA_TOKEN` secret, so pushing the `v0.2.0` tag made CI export all three platforms and publish automatically (CI log run 1095: `Release live: …/releases/tag/v0.2.0`, Job succeeded). - [x] Confirm links resolve: tag page and `/releases` return HTTP 200 publicly. Follow-up: anonymous **asset downloads** require sign-in on this host (host setting). - [x] CI packaging follow-up was already live (`.gitea/workflows/build.yml` on tags). ## 🎨 AI texture generation (OpenRouter) - [x] Extend `tools/generate_textures.py` with an OpenRouter image-API mode (`--ai`, `--prompt`, `--name`, `--model`, `--size`), decoding base64 PNG responses and clamping to ≤1024px. - [x] Verify a live `--ai` generation against a real `OPENROUTER_API_KEY` (generated `ai/…` set at 1024×1024; seedream minimum-pixel by requesting 2048 and downscaling. ## 🧹 Runtime hygiene + timeline/polish items - [x] RefCounted cycle cleanup: `FSM` now holds its owner via `WeakRef`; `ShiftCoordinator` is `RefCounted` (logic-only, no tree needs); persistence tests free the `Save` node. Test suite now exits with **zero** “resources still in use” warnings. - [x] Full-length shifts: the prototype now uses each scenario's `shift_duration_s` (900s ≈ 10-15 min per PLAN.md) instead of a hardcoded 300s test duration. - [x] Wand cooldown feel: a short `WAND_COOLDOWN_S` recharge between wand scans with a `wand.recharge` message (Open Polish item). ## 📦 Installer packaging (Windows NSIS + Linux AppImage) - [x] Export presets: `binary_format/embed_pck=true` on all platforms — every binary is now self-contained (all game data in one file; `.pck` no longer shipped). - [x] App icon: `assets/textures/icon.png` (1024) + multi-size `icon.ico`, copied to `packaging/windows/gig-line.ico` and `packaging/linux/gig-line.png` via the generator. - [x] NSIS script `packaging/windows/gig-line.nsi` (installs `Gig Line.exe`, uninstaller, shortcuts, registry entry; `makensis /DVERSION=… /DARTIFACT=…`). - [x] AppImage: `packaging/linux/gig-line.desktop`, `AppRun`, appdata.xml, and `build_appimage.sh` (appimagetool, `APPIMAGE_EXTRACT_AND_RUN=1`). - [x] Local end-to-end: rebuilt embedded exports and produced `gig-line-0.2.0-x86_64.AppImage`, ran it headless (0 script errors). - [x] CI: NSIS + AppImage steps added to `.gitea/workflows/build.yml` build job; publish glob already uploads `build/gig-line-**` (covers setup/AppImage names). - [x] CI run 1110 verified end-to-end (v0.2.1): NSIS installer `gig-line-setup-0.2.1.exe` built, `7z l` shows `gig-line.exe` + `uninstall.exe`, AppImage built (33.9 MB), release `v0.2.1` published, `Job succeeded`. - Gotchas fixed on the way: makensis needs `-D` defines (not `/D`) and absolute paths (it resolves relatives against the script dir); installed exe must be space-free (`gig-line.exe`); icon.ico must use classic BMP entries (ImageMagick; PIL emits PNG-compressed ICOs NSIS rejects). ## 🎮 Assets & detailed graphics pass (textures, rigs, UI feedback) - [x] Authored texture set via `tools/generate_textures.py` (deterministic PIL): brick facade, seamless concrete, asphalt, awning, skyline, vignette, HUD icons, access-card art — all ≤1024px and power-of-two; imported under `assets/textures/`. - [x] Environment: textured facade + glass door, concrete sidewalk, asphalt street, striped awning with “GIG LINE”, generated skyline backdrop planes, soft sun shadows. - [x] NPCs: humanoid rig (torso/head/limbs) with archetype props (camera, delivery box, hat, sign, backpack), procedural walk bob, head-turns-toward-guard cue. - [x] UI/feedback: inspection artifact cards, mode icons (flashlight/door/wand), escalation vignette, body-camera overlay (corner brackets + REC + shift clock). - [x] Assets convention documented in README; `test_asset_textures_present_and_pow2` guards the pipeline (57 tests total). - [ ] (remaining, in TODO) crowd performance profiling and LOD/culling when the crowd grows. ## CI — cache timeout mitigation - [x] Remove `actions/cache@v4` from the checks job (runner cannot reach GitHub's cache service; ~2 min of retry timeouts per run; Godot import is fast without it). - [x] Add `GODOT_SILENCE_ROOT_WARNING=1` to both jobs. - [x] Confirmed in run 1090: zero `CacheService` lines, 54/54 tests pass, job succeeds. - [x] Document the cache situation and re-enable path in `docs/RELEASING.md`. - [x] Ignore runner logs in `.gitignore` (`.gitea/logs/`). ## Gameplay — Venue door-staff dialogue - [x] Add `VenueScenario.door_staff_line(context, rng)` picking a seeded-random line from `door_staff_lines` (empty for unknown contexts/venues). - [x] Wire `GameManager` to log venue lines: id/ticket check line when inspection opens, wand-check line when screening starts, prohibited-item line on correct wand refusal, and refusal line on denied entry (The Joke Shop flavor only). - [x] Add a headless test covering all Joke Shop contexts, unknown contexts, empty venues, and deterministic seeded picks. ## Core Simulation — NPC Perception - [x] Implement a perception pass so NPCs react to the guard's gestures and proximity. - [x] Give NPCs local waypoints per intent (photo spots, queue, stage door, sidewalk exit) instead of all walking to the entrance. - [x] Add returning-trespasser behavior: an NPC that was directed away may circle back later in the shift. ## Escalation and Radio — Responder Dispatch - [x] Spawn a dispatched security NPC / armed responder avatar that arrives at the venue after the call delay. - [x] Give the responder command, restraint, evacuation, and arrest outcomes that resolve the incident. - [x] Add a visible emergency-radio cooldown indicator in the HUD. ## Scenario Content — Wand Screening (The Joke Shop) - [x] Implement the handheld wand inspection interaction with alerts from keys, belts, phones, and weapons. - [x] Enforce screening-before-entry flow and record false alarm / prohibited item decisions. - [x] Add venue-specific dialogue lines for door staff. ## Polish and Accessibility - [x] Add aim-free targeting (Tab) and venue cycling (V). (The earlier dialogue tone picker was superseded and removed with the tone-of-voice system.) - [x] Add interaction alternatives that do not require precise aiming (e.g. scan-and-select pinned list of nearby NPCs). - [x] Add visual and audio feedback per escalation level (colors, tones, shake). - [x] Add audio for crowd ambience, radio responses, and confirmation prompts. - [x] Externalize dialogue and UI text for localization. ## Generalized Game Asset Tooling - [x] Reuse the OpenRouter image backend for role-aware arbitrary asset generation. - [x] Add exact-dimension image crop and aspect-preserving resize commands. - [x] Audit existing assets for role-specific dimensions, aspect, alpha, and tile seams; report projection/perspective checks for visual review. - [x] Add automated tests and document asset-pipeline usage. ## Generated Asset Integration - [x] Regenerate the AI asset set at role-specific aspect ratios and dimensions. - [x] Audit, process, and wire every generated asset into the game runtime. - [x] Add screenshot auditing and refresh the README screenshot gallery. ## Access artifacts actually match the venue Completed in the working tree (uncommitted with the control pass). Headless **74/74**. - [x] Build artifacts from the event (`override_access`, carried items) and the venue policy, not from `NpcType` alone. (`src/core/access_loadout.gd`; spawn signal is `(profile, intent, npc_id, event)`.) - [x] Joke Shop: valid ID+ticket, ID without ticket, expired/missing ID. Recognition does not auto-grant on every `AUTHORIZED_GUEST`. - [x] Wand venues roll `carried_items` per spawn (seeded); override can force a pocket. - [x] Studio counterfeit / follow-through events carry `override_access`. - [x] `AccessVerifier.verify(..., already_screened)` so a screened guest is not still denied. - [x] Joke Shop / Studio `sidewalk` and `steps_end` waypoints so film/photo intents do not walk to the entrance. - [x] Wand shortcut is `C`, not `W`. Tests: `src/test/tests/test_access_loadout.gd`. ## Player control, focus circle, and radial action menu Completed 2026-10-31 with ADR-0007–0010. Headless **77/77**, plus a windowed smoke (`src/test/smoke_focus_radial.gd`) that opens the wheel and saves screenshots. ### Locomotion - [x] Wand off `W` (now `C` + wheel). `A`/`D` allow/deny only while inspect/wand is open (they step the wheel while it is open). `Q` remains a closed-menu polite shortcut. - [x] Accel/decel on walk; gravity only when airborne; `floor_snap_length` 0.3. - [x] Mouse look only while captured and `not input_locked`. Freeze walk+look while radial / inspect / wand / radio / review is up. Recapture on close. - [x] `cancel` (Esc / RMB) closes the top modal first; with nothing open, Esc toggles mouse capture and RMB does nothing. ### Focus and focus circle - [x] Focus cone in `src/game/npc_focus.gd` (~10 m, ~10°). Closest-to-forward wins; occlusion ray skips building hits. `Tab` still cycles 12 m. - [x] 8-frame miss grace so walk-bob does not drop focus. - [x] Billboarded blue circle in front of the face on `Rig/Head`; brighter while the radial is open; `Label3D` `display_name`. HUD target line uses the display name, not the node name. - [x] Screen-center crosshair. ### Radial HUD - [x] `src/game/ui/action_radial.gd`: LMB or `Space` opens when someone is focused. Opening click ignored for 120 ms. Slices: raise hand, point, head shake, polite, inspect, wand (grey if no wand), warning, block, radio assist. Emergency stays on `R`. - [x] Highlight by mouse angle, or `A`/`D` / arrows. Click slice or `Space` confirms. `Esc` / RMB closes with no action (no recorder event). - [x] Inspect / wand / radio slices close the wheel then open the existing panels. Freeze until that panel closes. - [x] Number keys `1`–`6` still work when the wheel is closed. ### NPC display - [x] Procedural rig: neck, shoes, hair cap from seed, two eye spheres. Props, height variation, walk bob, notice-head-turn kept. Collision capsule scaled to rig height. Named `Head` is the ring attach point. Group `npc`. ### Input, copy, tests - [x] Named input map: `action_menu` (Space/LMB), `confirm` (Space/Enter), `cancel` (Esc/RMB), `inspect` (E), `wand` (C). `GameManager` uses `is_action_pressed`. - [x] Windowed smoke: focus circle + name, wheel open/step/confirm/cancel (`src/test/smoke_focus_radial.gd`, exits non-zero on failure). - [x] README controls table, smoke checklist, test count, implemented/not-yet lists. ## Visual polish — Skyline backdrop transparency Found during the control smoke test; verified with PIL analysis. - [x] Regenerated `assets/textures/game_ready/skyline.png` with transparent background (commented out opaque border line 234 in `tools/generate_textures.py`). - [x] Integrated backdrop into `WorldBuilder` via `_build_skyline_backdrop()`: creates a PlaneMesh with T_SKYLINE texture at Vector3(-80, 12, -50) behind the building. - [x] PIL verification confirms RGBA mode with transparent pixels in bottom rows. ## Runtime boot and spawn fixes Found while running the windowed smoke; all verified with a boot run and regenerated screenshots. - [x] `GameManager` did not compile: `radial` was typed `CanvasLayer`, so `radial.is_open` / `confirm_selected()` made `:=` inference fail. Typed as `ActionRadial` (the parse error would have broken the whole main scene). - [x] Camera sat at the guard's feet: `Head` had no height offset, and the new gravity pulled the guard to the floor. `Head` is now at y=1.6, and the guard spawns on the landing. - [x] Guard spawned inside the building collider and fell through the world. Building volume moved back to z ∈ [-8, -4] so the door / awning / marquee sit on its front face; the guard spawns in front of the door facing the street. - [x] Steps rose away from the door; treads now rise toward the entrance. - [x] Event scheduler was polled every physics frame (~60 Hz), spawning >1200 NPCs in seconds. `ShiftCoordinator` now accumulates time and pulls once per simulated second; regression test in `test_scenarios.gd`. - [x] Live NPCs capped at `MAX_LIVE_NPCS` (16); routine traffic is dropped at the cap and a scripted event evicts the oldest unfocused NPC. - [x] Removed the skyline backdrop planes (opaque black texture). Tracked as a follow-up in `TODO.md`. - [x] README screenshots regenerated with the fixed eye height and entrance staging. ## World and presentation follow-ups (found during the control smoke) Completed 2026-10-31 (working tree). Headless **88/88**; `src/test/smoke_session_shell.gd` and the windowed focus/radial smoke both pass. - [x] The skyline backdrop was removed: `assets/textures/ai/game_ready/skyline.png` has an opaque black background, so the planes rendered as black slabs with window dots. Re-add the backdrop only with a transparent asset or a keyed shader. - [x] Guard settles ~0.5 m above the ground after spawning on the landing. Check the capsule offset and the spawn `y`, and verify eye height while walking the steps. - [x] Steps and the landing are visual-only (no collider). The guard walks through them; add simple step colliders or restage the landing so it does not intersect the walk path. - [x] Marquee text (`GIG LINE`) is tiny from the sidewalk. Scale the Label3D up. - [x] Focus circle and name tag are small at 2–3 m. Consider a larger circle / font after a real playtest. - [x] NPC crowd cap (`MAX_LIVE_NPCS`) silently drops routine traffic at 16. Add a short despawn/lifetime for ordinary guests so the entrance does not fill up with standers. ## Session shell Completed 2026-10-31 (working tree). Headless **88/88**; `src/test/smoke_session_shell.gd` and the windowed focus/radial smoke both pass. The game boots into a live 900 s shift. `Save` declares `settings.json` and never uses it. `V` cycles venues by restarting the shift. - [x] Startup menu: pick venue, start shift, quit. Keyboard + click. Replaces `V` as the player-facing path (`V` may stay as a debug bind). - [x] Pause overlay when `Esc` releases the mouse (or a dedicated pause key): resume, restart shift, settings, quit to menu. - [x] Settings: mouse look sensitivity, persist through `Save` / `settings.json`. Load on boot. - [x] Shift review "Run another shift" returns to the venue menu, not a silent restart of the same scenario. ## Documentation drift Completed 2026-10-31 (working tree). - [x] README controls table, smoke checklist, test count, and implemented/not-yet lists refreshed for the control pass (77 tests). - [x] `PLAN.md` Tech Stack / Planned Commands still say GUT and GitHub Actions; the project uses `src/test/test_runner.gd` and `.gitea/workflows/build.yml`. - [x] MEMORY: door_staff_lines log through `GameManager`; 300 s default removed. ## Remaining PLAN verbs (radial exists) Completed 2026-10-31 (working tree). Headless **98/98**; `src/test/smoke_plan_verbs.gd` exercises all five verbs. - [x] Eye contact as a Level 1 action (PLAN / glossary). Distinct from raise-hand. Add a radial slice if it ships. - [x] "I recognize this person" is a player decision recorded as evidence, not a pre-attached artifact on every staff/guest type. Staff still auto-recognize when `override_access` is empty (`AccessLoadout`). - [x] Formal warning can activate the body-camera / security-light flag (PLAN Level 2). Overlay is currently always on; toggling should write an incident event. - [x] Blocking stations the guard between the person and the door (PLAN Level 3). Today it only sends `apply_player_action(BLOCKING)`. - [x] "Request reason for entry" as a Level 3 ask, with a short NPC reply from `dialogue_lines`. ## Crowd presentation Completed 2026-10-31 (working tree). Headless **105/105**; `src/test/smoke_crowd.gd` verifies rubbernecking and panic clearing. A 16-NPC entrance measured ~1.2 ms/frame (824 fps, vsync off), so no LOD/culling was added at the current cap. `CrowdSystem` is a mood/attention float. PLAN wants people watching, gathering, and panicking. - [x] On `aggressive_behavior` / `emergency_responder`, nearby idle NPCs stop and face the incident (rubberneck). On `crowd_panic`, they move away from the door. - [x] Profile a busy entrance with textured materials and props; add LOD or culling for distant NPCs if frame time drops. ## Emergency aftermath Completed 2026-10-31 (working tree). Headless **113/113**; `src/test/smoke_emergency.gd` covers the unjustified-call investigation, the evacuation aftermath, and the review copy. Responder outcomes (`command` / `restraint` / `evacuation` / `arrest`) are log lines. PLAN also wants evacuation, a closed street, and an investigation when the call was unjustified. - [x] `evacuation` outcome actually clears the entrance (NPCs leave, door stays locked). - [x] Unjustified emergency (low danger, no weapon) writes an investigation note on the shift review and applies the existing false-report / reputation hit. - [x] Review copy names the responder outcome, not only battery and crowd %. ## Body-camera replay Completed 2026-10-31 (working tree). Headless **120/120**; `src/test/smoke_timeline.gd` opens the timeline from the review, steps through every event, and returns to the review. - [x] Shift review: a timeline of recorded incident events (ADR-0005 already stores them). Scrub or step through; no live 3D replay required in the first version. ## NPC theater Completed 2026-10-31 (working tree). Headless **127/127**; verified visually with a staged sit/photo/perform lineup. Intents `sit` / `photo` / `film` / `perform` already pick waypoints. ADR-0010 rig (face/hair) is in the working tree. Bodies still idle-stand at those spots. - [x] Sit pose at `steps_end`. Photo: brief raise of the influencer/tourist prop. Performer stays put and plays the hat/sign. ## Audio Completed 2026-10-31 (working tree). Headless **134/134**; `src/test/smoke_audio.gd` checks the bed follows mood and the radio crackles, headless and windowed (real audio server). `AudioFeedback` is sine beeps. DONE called that "crowd ambience"; it is not. - [x] Loop a low crowd bed that rises with `CrowdSystem.mood`. - [x] Radio open/close crackle on the existing radio panel, still headless-safe (no-op without an audio server). ## Accessibility Completed 2026-10-31 (working tree). Headless **139/139**; verified visually at 1.4x font scale with the shape meter showing. - [x] Escalation colors that still read in grayscale (shape or label, not hue alone). - [x] HUD font scale in settings. ## Scenario content still missing vs PLAN lists Completed 2026-10-31 (working tree). Headless **149/149**; `src/test/smoke_scenario_content.gd` spawns the refusal event and the celebrity arrival through the real signal path. - [x] Joke Shop: guest on the reservation list without ID; borrowed/mismatched ID; guest refuses wand screening; late arrival during a performance. - [x] Studio: filming-boundary repeat (influencer ignores a posted line); celebrity-arrival crowd spike (more rubberneckers, not only one `arrive` spawn). - [x] Building: "activate body camera" and "request reason" once those verbs exist, used in at least one authored event. ## Distribution follow-up (host config) Completed 2026-10-31 (working tree). Headless **153/153**. Verified via API+host credentials (2026-10-31): - [x] Repo is **public** (`private:false`) and account has admin rights. - [x] Release `v0.2.1` now carries all five artifacts (portable binaries + NSIS installer + AppImage). - [x] **Host setting applied**: `[service] REQUIRE_SIGNIN_VIEW = false` in `/etc/gitea/app.ini` (backup written alongside), Gitea restarted. Verified anonymously: repo page 200, API 200, and a release asset downloads with the full 50.8 MB AppImage. ## Release pipeline follow-up Completed 2026-10-31 (working tree). Headless **153/153**. - [x] Fix `tools/publish_release.sh`: the `~/.git-credentials` branch never set `AUTH`, so every tag build since v0.2.2 failed to publish. - [x] Add a `github.token` fallback in `.gitea/workflows/build.yml` (the repo has no `GITEA_TOKEN` secret). - [x] Fall back to the checkout's git extraheader when no token/credentials exist (the Gitea runner does not expose `github.token`). - [x] Uploads now fail loudly and the release is verified to carry every artifact (a silent HTTP 4xx used to leave an empty release with a green job). - [x] Host disk expanded to 16G; v0.2.0 and v0.2.9 releases restored from the local artifacts. - [x] Host maintenance via `root@192.168.88.30`: apt cache cleaned and journald capped (`SystemMaxUse=300M` in `/etc/systemd/journald.conf.d/99-size.conf`); ~7.6G free. - [x] Verified end to end: tagging v0.2.12 produced a release with all five artifacts (AppImage, Linux, macOS, NSIS installer, Windows) from CI, with no repo secret. - [x] Backfill decision: v0.2.4–v0.2.8 stay superseded; their tags and source remain, and the release page points at the current build. No rebuilds planned. ## Open Polish Completed 2026-10-31 (working tree). Headless **153/153**. - [x] Keep `%` placeholders stable in `Strings.fmt` when adding keys (positional formatting). ## Screen design references (QA tooling) Completed 2026-10-31 (working tree). 30 Python tooling tests pass; the live judge run classed 3 captures as match and 3 as review (concept mockups are more stylised), with no confident mismatch. - [x] `tools/screen_designs.py generate` writes one reference design per screen (from the `docs/` descriptions) to `docs/screens/designs/`, with an offline `--draft` wireframe mode and an OpenRouter `--ai` mockup mode. - [x] `tools/screen_designs.py compare` checks `docs/screenshots/*.png` against the designs (aspect ratio, size, blank-frame, and layout/palette/detail drift). - [x] Vision-model judge for semantic screenshot comparison: `judge` sends the capture + reference design to a vision model, classes verdicts as match/review/diff with a confidence threshold, supports `--dry-run`, `--json`, `--only`, and `--model`, and reads the repo `.env` for the API key. ## Tooling (when an editor workflow exists) Completed 2026-10-31 (working tree). Headless **157/157**; the exported `.tres` venues round-trip through the resource loader. - [x] Author venues as `.tres` resources: `tools/export_venues.gd` writes `content/venues/.tres` from the factory, and a test loads them back to check nothing is lost (events, profiles, policy, phases, door lines, locations). - [x] GUT decision: **not adopted.** The custom headless runner (`src/test/test_runner.gd`) already covers 157 assertions without an editor plugin, runs in CI, and keeps the project dependency-free; adding GUT would duplicate coverage and require the editor. ## In-Game Scene Examples Completed 2026-10-31 (v0.3.0 working tree). `python3 tools/screen_designs.py list` reports 19 screens (8 scene, 11 UI); the Python tooling tests pass. - [x] Extend the `tools/screen_designs.py` catalogue with the screens that exist in code but had no reference: wand screening, Joke Shop door queue, TV Studio celebrity arrival, crowd panic, responder arrival, body-camera timeline, escalation HUD. - [x] Add a `list` subcommand so the catalogue is inspectable without generating images. - [x] Cover the new entries with `tools/test_screen_designs.py` assertions. - [x] Regenerate the deterministic draft references under `docs/screens/designs/`. (`generate` now keeps existing references unless `--force` is passed, so a draft run cannot replace an AI mockup; `--only` selects a subset.) ## Rendering Quality Completed 2026-10-31 (v0.3.0 working tree). Screenshots regenerated; `screen_designs.py compare` reports 6 good / 0 bad. - [x] Add a shared surface-material module (`src/game/materials.gd`) with roughness/metallic/uv-scale profiles per surface and a single environment tune-up (tonemap, glow, fog, shadows). - [x] Rebuild `WorldBuilder` surfaces with the material profiles and add street detail: window bands, door trim, bollards, bench, bin, curb, planter foliage. - [x] Per-phase environment values (fog, sky energy, glow) so night reads darker and the marquee glows. - [x] Improve NPC silhouette and materials: per-role clothing palette, caps/hoods/backpacks, sleeves and hands, walking leg swing. - [x] Cover materials/environment/avatar helpers with headless tests (`test_materials.gd`, `test_target_cycle.gd`). ## AI 3D Model Tooling Completed 2026-10-31 (v0.3.0 working tree). `model_pipeline.py build` + `audit` pass for all six props; `test_generated_props.gd` proves they import as scenes and that the world places them. - [x] Add `tools/model_pipeline.py`: prop catalogue, deterministic low-poly GLB builder, OpenRouter concept-image generation, palette sampling and GLB audit. - [x] Add `tools/test_model_pipeline.py` (valid GLB structure, deterministic output, audit failures, sRGB→linear conversion, role assignment). - [x] Generate the prop models into `assets/models/generated/` and import them. - [x] Load the generated props in `WorldBuilder` with a procedural fallback when the GLB is missing. ## Controls, Movement and NPC Selection Completed 2026-10-31 (v0.3.0 working tree). The windowed smoke now covers wheel cycling and right-click clearing; the headless suite passes 177/177. - [x] Record the re-evaluated control model as ADR-0011. - [x] Rank focus candidates by view angle then distance (`NpcFocus.rank_indices`) instead of insertion order. - [x] Add mouse-wheel target cycling next to `Tab`, right-click clearing focus, and a `Shift` precision walk speed. - [x] Show the nearby-candidate count and the cycle hint in the HUD. - [x] Cover ranking, cycling and walk speed with tests, then re-run the windowed smoke. ## Web Export Build Target Completed 2026-10-31 (v0.3.0). Headless **181/181**; `godot --headless --export-release "Web"` produces a 63 MB static bundle and it serves locally with HTTP 200s for html/wasm/pck. - [x] Add a `Web` preset to `export_presets.cfg` using `variant/thread_support=false` so the build runs on plain static hosting without COOP/COEP headers. - [x] Export to `build/web/` and package `build/gig-line-web-.zip` from `tools/build_releases.sh` (zip or Python fallback). - [x] Publish the web zip with the desktop artifacts (`tools/publish_release.sh`). - [x] Cover the preset, the non-threaded choice, and the release wiring in `src/test/tests/test_web_export.gd`. - [x] Document the artifact in `docs/RELEASING.md`, the README gallery/status, and `PLAN.md`. ## Prop Texture Atlas Completed 2026-10-31 (v0.3.1). Headless **182/182**, Python tooling **53/53**; screenshots regenerated and `screen_designs.py compare` still reports 6 good / 0 bad. - [x] `model_pipeline.atlas_for()` packs each prop's palette roles into one small power-of-two PNG and a matching metallic/roughness atlas (roughness in G, metallic in B), with nearest sampling so a palette cell stays a flat colour. - [x] `build_glb()` merges every part into a single primitive with per-vertex atlas UVs and one material (`baseColorTexture` + `metallicRoughnessTexture`), so a prop is one draw material instead of one per part. - [x] `audit_glb()` checks the single-material/atlas contract (one material, both textures, TEXCOORD_0 on every primitive, two images). - [x] Tests: atlas determinism and power-of-two sizing, UV range, palette override reaching the atlas, and a Godot test that each imported prop scene uses exactly one material. - [x] Removed the now-unused sRGB→linear factor helpers (colours travel in the sRGB texture). ## Complete AI Mockup Set Completed 2026-10-31 (v0.3.2). All 19 catalogue screens now have an AI reference; Python tooling **53/53**. - [x] Generated mockups for the seven entries that only had wireframes: wand screening, Joke Shop queue, TV Studio arrival, crowd panic, responder arrival, body-camera timeline, escalation HUD. - [x] `generate` loads `.env` like `judge` already did, so `--ai` works without exporting `OPENROUTER_API_KEY` by hand. - [x] Verified every reference is 1280x720, reviewed the new images visually, and confirmed `list` reports 19 designs (12 kept, 7 new). ## Staged Captures For The New Screens Completed 2026-11-01 (v0.3.3). `screen_designs.py compare` reports **9 good / 0 bad**; vision judge: wand **match** (0.90), timeline **match** (1.00), escalation **review**, 0 diff. Python tooling **53/53**, headless **182/182**. - [x] `capture_screenshots.gd` now continues past the night set: switches to The Joke Shop, stages a guest, retries the wand until it alerts (falls back to the documented keys false alarm), applies formal warning + blocking, and opens the body-camera timeline. - [x] New captures `07-wand-screening.png`, `08-escalation-hud.png`, `09-body-camera-timeline.png` added to `docs/screenshots/` and to the README gallery. - [x] Wired `ref` for `wand_screening`, `escalation_hud` and `body_camera_timeline` so `compare` and `judge` include them (9 of 19 screens now have a staged capture). - [x] Regenerated the escalation HUD mockup as a third-person over-the-shoulder night shot so the reference matches the game camera. - [x] Added `Hud.clear_log()` so staged captures drop target-cycling noise; the escalation shot keeps the real reaction lines. ## Prop Collision And Placement Completed 2026-11-01 (v0.4.0). Headless **187/187**, Python tooling **53/53**, the new `smoke_props.gd` passes windowed, and `screen_designs.py compare` still reports 9 good / 0 bad. - [x] Every generated prop now gets a fitted static collider: a cylinder for bollard, bin, and hydrant, a box for everything else. The collider is computed from the model's mesh bounds in holder-local space, so placement rotation and variant scale carry over. - [x] Added `smoke_props.gd` (windowed): the guard walks into a bench and stops instead of passing through it; a guest heading for a point beyond the same bench routes around it with navigation avoidance and never ends up inside the collider. - [x] Props publish a `NavigationObstacle3D` footprint, and `NpcAvatar` enables agent avoidance with a `velocity_computed` callback. The navmesh stays a flat walkable plane; avoidance does the steering around street furniture. - [x] Replaced the hard-coded prop calls with `WorldBuilder.PROP_PLACEMENTS`, grouped by entrance, mid-block, and curb zones, with a static `placement_problems()` validator for bounds, overlap/min-spacing, duplicates, and the door/steps/guest-lane exclusions. - [x] Deterministic variants per placement (`variant_for`): yaw jitter, subtle scale, and optional procedural clutter (bag by the bin, crate by the newspaper boxes, cone by the hydrant), so repeated props stop looking cloned. - [x] Fallback primitive props go through the same bounds fit, so a missing `.glb` cannot silently remove collision; a unit test covers the fallback body, shape, width, and navigation obstacle. - [x] Tests: placement validation and its failure modes, variant determinism/range, colliders on every placed prop (including obstacle presence), and the fallback collider path. ## Prop Pipeline Checks Completed 2026-11-01. Python tooling **56/56**, headless **188/188**, prop gallery **match** (0.90) in the vision judge, `compare` **10 good / 0 bad**. - [x] `audit_glb()` now decodes positions/UVs/indices and enforces the art budget (`MAX_TRIANGLES = 400`, `MAX_VERTICES = 600`), rejects degenerate triangles, UVs outside `[0, 1]`, a prop that floats above or sinks below the ground (`min y ~= 0`), and the single-material/atlas contract. The newspaper box sat 3 cm high; its catalogue parts moved down so it rests on y = 0. - [x] Added `capture_prop_gallery.gd`: renders all six props on a studio backdrop (two rows on a display platform) to `docs/screenshots/10-prop-gallery.png`, with a `prop_gallery` catalogue entry and AI mockup, so `compare`/`judge` cover close-range prop art. - [x] Confirmed instances share the imported atlas material (headless test) and documented the triangle/vertex budget in the pipeline docstring. - [x] README lists the gallery capture command and image; the pipeline docstring and audit docs describe the budget checks. ## Better NPC Characters Completed 2026-11-01 (v0.5.0). Headless suite **208/208**; NPC budget smoke reports 16 avatars, 534 meshes, ~6.9 ms average frame time; PLAN-verb, crowd, emergency, and timeline smokes pass. - [x] Replaced the rigid capsules with an articulated rig: hip -> thigh -> knee -> shin -> shoe and shoulder -> upper arm -> elbow -> forearm -> hand, with knee/elbow pivots driven by the walk cycle and the seated pose. - [x] Layered clothing (`layer_for`): jacket, hi-vis vest, coat, and dress over the base outfit, with role-appropriate defaults and deterministic colourways. - [x] Extended accessories and hand items: bag, umbrella, phone, camera, clipboard, cup, plus the existing cap/hood/backpack/sunglasses/lanyard. Hand items attach to the right hand socket. - [x] Face and hair variety: ears, brows, mouth, five hair silhouettes (long, bun, beard, bald, short), height range, and deterministic per-seed appearance. - [x] Added idle behaviour layers (`idle_style_for`): breathing, weight shift, look-around, phone, clipboard, and crossed arms; escalation gestures raise both hands (L3+) or point (L2), and noticed NPCs turn their head toward the guard. - [x] Walk cycle now swings hips/shoulders, bends knees and elbows, and uses a faster, longer stride when fleeing. - [x] Door queue staging: GameManager sorts waiting guests and assigns spaced `queue_slot_for` offsets, so one person is at the door and the rest wait behind without overlapping. - [x] Reaction readability: a billboarded symbol above the head (`!!` panic, `?!` watching, `!` blocked, `?` warned) updates from the reaction/escalation state. - [x] Three LOD tiers with hysteresis: full detail, simplified (props/shadows off), and a billboard impostor; `apply_lod` is tested for the swap. - [x] Re-measured the live-NPC budget with the full rig (16 avatars, 534 meshes) and added `smoke_npc_budget.gd`, which fails above the documented 50 ms frame budget. - [x] Tests: deterministic layers/hand items/hair, role layers, queue slot spacing, LOD thresholds/hysteresis, reaction labels, articulated pivots, mesh budget, and the walk-to-idle animation transition. ## Better Player Movement Completed 2026-11-01 (v0.5.0). Windowed `smoke_movement.gd` passes; headless tests cover surface lookup, constants, gamepad bindings, and settings persistence. - [x] Split acceleration and braking (`accel` 22, `decel` 30 m/s²) so stopping is snappier than starting, and fixed the inverted forward axis found while staging the movement smoke: W now walks toward the camera's forward vector instead of moonwalking. - [x] Step-up assist (0.32 m) climbs kerbs and the entrance steps, with wall sliding left to `move_and_slide`; the smoke walks the guard from the sidewalk onto the landing. - [x] Camera feel: speed-scaled head bob, a landing dip, and a precision-walk FOV change, all gated by the new "Head bob and camera motion" setting; a motion-sickness toggle persists. - [x] Precision walk shows a HUD chip and plays a short tick when it engages, in addition to the slower speed. - [x] Surface-aware footsteps (concrete/asphalt/stone) synced to stride distance, plus a landing cue; both have observable counters for tests. - [x] Gamepad support: left-stick movement, right-stick look with deadzone, A/B/X/Y face buttons, LB slow, RB wand; bindings are added at runtime and the control legend swaps to gamepad hints when a pad event is seen. - [x] Moved the remaining hard-coded gameplay keys (Q/F/G/L/B/V, Tab cycle) onto named InputMap actions, and the panel allow/deny keys onto movement actions. - [x] Key/button remapping in the settings panel: click a binding, press a key or pad button, persisted as `input_bindings` through `Save`; `InputBindings.apply` rewrites only key/button events and leaves mouse and gamepad axes alone. - [x] Added invert-Y and head-bob settings (with save/load round-trip tests) alongside the existing look sensitivity. - [x] Tests: surface lookup, step/stride/precision constants, gamepad binding presence and deadzone, remap descriptor/apply/labels, and settings persistence for camera and bindings. - [x] Added `smoke_movement.gd` (windowed) and `11-precision-walk.png` with a movement catalogue entry, AI mockup, and compare coverage. ## NPC Facing During Movement Completed 2026-11-01 (post-v0.5.0, pre-v0.6.0). Headless suite **209/209**. - [x] Root cause: `NpcAvatar` used Godot's `look_at()`, which aims the node's **-Z** axis at the target, but the procedural rig's face (eyes, brows, mouth, focus disc) and every Blender-authored successor face **+Z**. Every walking NPC therefore showed its face toward the side it was leaving. - [x] Added the pure `NpcAvatar.yaw_towards(direction)` helper (+Z forward, tested at +Z/+X/-Z and an arbitrary direction), and replaced `look_at()` in the navigation and direct-move paths plus the reaction-face turn. - [x] Corrected the head-turn offsets to use the same +Z convention (`basis.z` instead of `-basis.z`), so noticed NPCs still look at the guard with the fixed facing. - [x] Added `test_yaw_faces_the_movement_direction`; recaptured the targeting screenshot to confirm NPCs now show their back only when walking away. ## Blender Asset Library Integration Completed 2026-11-01 (v0.6.0 work). Headless **222/222**, Python **68/68**, all windowed/headless smokes pass, `compare` **11 good / 0 bad**. AO/ORM baking remains in Phase 4. - [x] Committed the audited library (8 characters, 18 gear, 20 props), generator, `.blend` sources, manifest, review sheets, and integration notes. - [x] `NpcAvatar` selects a character variant by role/seed (`npc_casual`, `npc_jacket`, `npc_coat`, `npc_vest`, `npc_dress`, `npc_hoodie`, `npc_firefighter`) with `npc_base` and the procedural rig as fallbacks; the choice is recorded in `character_model`. - [x] Only the five contract slots (`skin`, `cloth`, `pants`, `accessory`, `hair`) are tinted; fixed detail materials keep their authored colour. `npc_vest` keeps its fixed hi-vis vest, and firefighter/guard use dark uniform tints. - [x] Library gear replaces the procedural accessories and hand items at the documented `head` / `back` / `chest` / `hand` attach points (cap, hood, helmet, backpack, SCBA tank, lanyard, phone, cup, clipboard, umbrella, camera, sign, flashlight, wand, radio), with the primitive path retained as fallback. - [x] `WorldBuilder` loads the Blender replacements (`bollard`, `bench`, `trash_bin`, `planter_box`, `newspaper_box`, `fire_hydrant`, `street_lamp`, `street_tree`, `traffic_cone`) and places the new dressing (`palm_tree`, `round_planter`, `a_frame_sign`, `crowd_barrier`, `stanchion`, `security_podium`, `handrail`, `double_door`, `marquee`, `sedan`, `wall_lantern`) with colliders/avoidance and placement validation. - [x] Library emissive surfaces (lamp bulbs, marquee bulbs/sign face, wall lanterns, door LED, sedan lights) are collected and driven by `set_night_mode()`. - [x] Added a library triangle counter and 60k triangle budget check; the current set is ~27,986 triangles. NPC budget smoke: 16 avatars = 112 meshes at ~6.9 ms. - [x] Recaptured the README/catalogue screenshots (including the prop gallery from the library models) and fixed the marquee text placement. ## Environment and Lighting Lift Completed 2026-11-01 (v0.6.0). Headless **222/222**, Python **69/69**, all smokes pass, `compare` **11 good / 0 bad**. - [x] Baked per-asset AO maps for the full Blender library with `tools/bake_ao.py` (Cycles, 256×256, stored next to each GLB as `_ao.png`); Godot wires the red channel into `StandardMaterial3D.ao_texture` for character slots, gear and props. - [x] Regenerated brick, concrete and asphalt at 1024² through `asset_pipeline.py`, derived tangent normal maps with `tools/make_normal_maps.py`, and wired them into `SurfaceMaterials.textured()` while keeping the existing UV scales. - [x] Evaluated Forward+: it boots on the desktop Vulkan driver, but the renderer is a project-wide setting and the Web build must stay on GL Compatibility. Decision: keep Compatibility for v0.6.0; a desktop-only preset needs a separate project/override and is deferred. - [x] Recaptured `docs/screenshots/` and ran `compare` + `judge`; no regressions. - [x] Fixed the library marquee text/face alignment and added the library AO/triangle tests. ## v0.6.0 Release Completed 2026-11-01. `config/version` = `0.6.0`; ADR-0013 marked Accepted; CI published six artifacts (Linux, Windows, macOS, NSIS installer, AppImage, Web) for tag `v0.6.0`. - [x] Bumped the version and synchronised README/architecture/blender-assets/plan docs. - [x] Headless **222/222**, Python **69/69**, all smokes, `compare` **11 good / 0 bad**. - [x] Release verified: `https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.6.0`. ## Runtime Image Path Migration Completed 2026-11-01. Historical note: the screen-design references originally lived only in `docs/screens/designs/`; they were promoted to the runtime root `assets/images/ui/` under ADR-0014. `docs/screens/designs/` remains a reference-only snapshot; `tools/screen_designs.py` now reads and writes `assets/images/ui/`. ## Realistic Backgrounds, Textures and Scene References Completed 2026-11-01 (part of the v0.6.x realism pass). The flat box skyline was replaced by per-phase AI panoramas, the surface tiles by higher-detail v2 picks with normal maps, and the lighting/exposure was retuned against AI concept frames. Evidence: `docs/screens/realism/2026-11-01-adopt/` (`report.md`, `sheet.jpg`), `screen_designs.py compare` **11 good / 0 bad**; headless **227/227**, Python **86/86**, all smokes. ### Realistic backgrounds, textures and scene references Goal: replace the outdated flat skyline backdrop (`assets/textures/skyline.png`, black boxes with dot windows) and the flat-looking surface tiles with AI-generated, more realistic backgrounds and textures. Add a larger set of example 3D scene images as art direction, and measure the in-game result against that target with the screenshot tools. Plan: [`PLAN.md` → Visual Quality Direction → Realism pass](PLAN.md#realism-pass). **Rule for every step: never overwrite an existing image.** New outputs get new paths. When a path is taken, the tools pick the next free `-vN` suffix. Raw AI output stays on disk under `assets/textures/ai/v2/raw/` (gitignored like the other raw AI folders). Processed game-ready candidates go to `assets/textures/ai/game_ready/v2/`, a local pool where only the validated picks are tracked (ADR-0014). Scene references go to `docs/screens/concepts/`, and trial captures and reports to `docs/screens/realism//`. #### Step 1 — Tooling (`tools/scene_concepts.py`) - [x] Add a catalogue of prompts in three groups: `scene` (16:9 example 3D game frames), `backdrop` (4:1 skyline panoramas with transparent sky), and `texture` (seamless PBR surface tiles). Each entry has a name, prompt, role and target path. - [x] `generate [--group] [--only] [--variants N]`: call the OpenRouter image API through `generate_textures.generate_image` (key from the repo `.env`), save raw output, and never overwrite (`next_free_path`). Print every file written. - [x] `process`: turn raw backdrops into 2048×512 RGBA strips that tile horizontally (edge crossfade, sky made fully transparent, hard alpha for the alpha-scissor material). Turn raw textures into seamless 1024² tiles (`asset_pipeline.make_seamless`) plus a normal map (`make_normal_maps.normal_map`). Skip outputs that already exist. - [x] `audit`: run `asset_pipeline.audit_image` on the processed candidates (`backdrop` / `tile` roles) and fail on any bad result. - [x] `realism`: for each trial capture, report the Pillow detail metrics (edge density, contrast, colour spread, saturation) next to the matching concept image. Optionally ask a vision model (`google/gemini-2.5-flash-lite`) for 0–10 realism scores (lighting, materials, texture detail, backdrop, overall) with concrete fixes. Write a new JSON + Markdown report per run. - [x] Unit tests (`tools/test_scene_concepts.py`): catalogue shape, the no-overwrite path picker, backdrop/tile processing on synthetic images, metrics, and the judge payload/parse (offline, no network). #### Step 2 — Example 3D scene images - [x] Generate 3D-game-style example frames for every venue and time of day: Hollywood entrance (morning, golden hour, rainy night), boulevard skyline view (day, dusk), Joke Shop alley (evening, close-up of the door), studio stage door (night arrival), sidewalk material close-up, entrance steps and podium, crowd-control barrier line, and a responder arrival wide shot. - [x] Review the set and record the visual targets each frame sets (sky, haze, material detail, backdrop depth) in the report. #### Step 3 — Backgrounds (replace the outdated skyline) - [x] Generate skyline panoramas for day, dusk and night (lit windows): Hollywood/Los Angeles mid-rise blocks, palm silhouettes, the hills on the horizon, a transparent sky, and a straight-on view, in several variants each. - [x] Process them into `assets/textures/ai/game_ready/v2/skyline_{day,dusk,night}*.png`, seamless horizontally for the 3× UV repeat around the backdrop ring. Strips are 2048×1024 (2:1, one UV repeat of the 400×60 m ring is about 133×60 m). 12 candidates, 11 audit clean; `skyline_day-v3` misses the seam limit by 0.6. Variants 1–2 contain garbled "Hollywood" lettering, so the prompt now forbids signs. Picks: `skyline_day-v4`, `skyline_dusk-v3`, `skyline_night-v4`. #### Step 4 — Textures - [x] Generate higher-detail seamless tiles: red brick facade, granite/limestone facade (studio), sidewalk concrete pavers with joints, asphalt with aggregate, wet night asphalt, stone steps, striped awning canvas, and stucco wall. - [x] Process them into 1024² game-ready tiles plus normal maps in `assets/textures/ai/game_ready/v2/` and audit them (16 tiles + 16 normals, all good). The `awning_canvas` seam crossfade smears the stripes at the tile edge; it needs a stripe-aligned crop. #### Step 5 — In-game trial captures (no game-code change) - [x] Add `src/test/capture_realism.gd`. It boots the Hollywood venue, captures a fixed shot list (entrance day, boulevard/skyline day, boulevard night), and can swap textures at runtime from `--swap=OLD=NEW` / `--tint=` / `--normal=` user arguments (the first version used `--skyline=`/`--brick=`/… flags; `--swap` is path-based and also covers night stages). The swap finds materials by their original texture path, so baseline and candidate frames use the same camera. Writes to the `--out=` directory. - [x] Capture the baseline (current assets) and the candidate set (v2 skyline + textures), and copy them into `docs/screens/realism//{baseline,candidate}/`. #### Step 6 — Compare against the realistic target - [x] Run `scene_concepts.py realism` on baseline and candidate. Record the metric deltas and the vision scores, and note which candidate textures/backdrops win. Run `docs/screens/realism/2026-11-01-v2/` (`report-v2.md`, `sheet.jpg`) gives these mean judge scores: backdrop 2.6 → 3.6 (v2 day + night skyline) → **4.0** (v2 day + dusk skyline for the Evening phase), texture detail 1.2 → 1.6–1.75, overall 1.6 → 2.0. Single scores swing by ±1–2 between runs, so read them as trends. The `characters` score moved from 3.0 to 1.0 with identical NPCs, which shows the noise. - [x] Note the remaining realism gaps (sky/fog, lighting, building geometry, NPC fidelity) as follow-up tasks below. #### Step 6b — Follow-ups from the 2026-11-01 realism report - [x] Fix sidewalk overexposure: lowered the day sun (0.78→0.62), exposure (0.8→0.6) and ambient (0.42→0.22), and darkened the sidewalk/street albedo tints. The like-for-like ground luminance in `docs/screens/realism/2026-11-01-adopt/` went from 173/162/132 to 95/92/73 (concept targets 78/100/100). The entrance shot stays ~20 % brighter than the concept: a residual, tracked in the follow-ups below. - [x] Make the Evening/Late phases darker and lit by practical lights (marquee, lamps, windows), and add wet-asphalt roughness at night, as in the rain-night concept. Evening is now dusk (`sun 0.3→0.12`, `exposure 0.95→0.72`, dark violet sky), Late is near-dark, and `WorldBuilder.set_phase()` swaps the street to the wet asphalt map in Late. - [x] Choose the skyline per phase (day → `skyline_day`, Evening → `skyline_dusk`, Late → `skyline_night`) and stop the scene fog from bleaching it. The backdrop is now a `fog_disabled` shader, because Godot 4.3 has no `fog_enabled` on `StandardMaterial3D`. The first dusk panorama was a red floodlit skyline that clashed with the Evening sky, so a calmer prompt produced `skyline_dusk-v5` (blue-grey towers, lit windows). - [x] Raise the backdrop plane (60 m → 90 m) and use 2 UV repeats so the skyline is a band with proportions near the 2:1 panorama, not a thin strip at the horizon. - [x] Add facade depth (plinth, string courses, cornice, brick pilasters, window glazing bars) and replace the blob trees and shrubs with denser foliage meshes: `_shrub()` now builds many small flat-shaded clumps plus outer clumps that break the silhouette (`street_tree` 1472→2592 tris), with fresh AO maps for `street_tree`, `planter_box` and `round_planter`. - [x] Re-cut `awning_canvas` so its stripes align at the tile edge: `stripe_period()` measures the repeat and `align_stripes()` resamples to a whole number of periods (edge crossfade smeared the stripes before). `audit_striped_tile()` checks the wrap step against the harshest interior step instead of the flat edge-difference rule. - [x] Capture shots from the Joke Shop and the studio too: `capture_realism.gd` now runs five stages (day, evening, late, joke_shop, studio) with 9 shots and a `--stages=` subset. The venues share the Hollywood geometry, so those two compare lighting, not layout. #### Step 7 — Adopt the winners - [x] Track the validated picks in place (ADR-0014). `.gitignore` now ignores the candidate pool and un-ignores `game_ready/v2/picks/`, where `scene_concepts.py promote` copies the eight validated candidates under stable names (`skyline_{day,dusk,night}`, `brick_facade`, `sidewalk_pavers`, `asphalt`, `asphalt_wet`, `awning_canvas`) plus normal maps. - [x] Point `WorldBuilder` at the chosen v2 skylines (separate day/dusk/night textures through the new `set_phase()`), and retune the tints per phase. - [x] Switch `materials.gd` / `world_builder.gd` surface preloads to the chosen v2 tiles + normal maps, keeping the current UV scales (`Materials.asphalt()` gained a `wet` variant). Updated `test_materials.gd` (pick paths, wet vs dry) and `test_world.gd` (shader backdrop, panorama per phase, wet street). - [x] Regenerate `docs/screenshots/` with `capture_screenshots.gd` (11/11), then run `screen_designs.py compare` (**11 good / 0 bad**) and `judge` (5 match, 2 review, 4 diff — the diffs are HUD/mockup layout gaps from the "Visual alignment" section, not realism regressions; the default flash-lite judge model is currently failing with a provider 400, so the run used `--model google/gemini-2.5-flash`). `scene_concepts.py realism` on `docs/screens/realism/2026-11-01-adopt/` (3 judge repeats per shot) gives a like-for-like improvement on every sub-score: overall 0.8→1.0, lighting 0.8→1.0, materials 0.8→1.0, texture 0.8→1.0, backdrop 0.6→1.6, and the Pillow metric gap 0.444→0.348. - [x] Update `README.md` (tool usage + gallery), `MEMORY.md` and `docs/architecture.md`. ## Realism Follow-ups — Lighting, Flashlight, NPC Detail Completed 2026-11-01. The follow-ups left over after the texture/skyline adoption, split off from `TODO.md` when each one was finished. Evidence: `docs/screens/realism/2026-11-01-npc/` (`report.md`, `sheet.jpg`), the portrait capture, and the refreshed `docs/screenshots/` gallery (`screen_designs.py compare` **11 good / 0 bad**). Headless **228/228**, Python **86/86**. ### Ground exposure at the entrance - [x] Fixed the over-bright entrance ground: the morning sun now sits at 34° elevation (was 50°) with a phase `sun_yaw`, so the facade shades the near sidewalk and the props cast long shadows. Ground luminance went 95 → **83** against the concept's 78 (it was 173 before the realism pass), with the boulevard at 82 and the street at 81. Day haze warmed and thickened (`fog_color` 0.87/0.83/0.76, density 0.0034, energy 0.5) and the night glow went to 1.15. ### Flashlight readability - [x] Made the flashlight read in the dark: the cone went 30° → **14°** with a 20 m range, forward aim, `light_energy` 4.2 and `spot_attenuation`/`spot_angle_attenuation` 0.9, so it lights the door instead of washing the facade. - [x] Staged the two night captures in LATE (`elapsed_s` 760) instead of dusk in `capture_screenshots.gd`; the judge moved `06-night-flashlight` from **diff (1.00) to review (0.10)**. - [x] Rejected a fake additive beam twice (cylinder + UV ramp, then a vertical-fade `ShaderMaterial`): on GL Compatibility a first-person camera inside the cone renders it as a filled disc with a bright far ring. `Materials.light_beam()` and the mesh were removed; the visible *shaft* is parked behind the Forward+ preset in `TODO.md`. - [x] Guarded the aim with `test_flashlight_aims_a_tight_forward_spot` (lamp on the head, straight ahead, cone ≤ 20°, starts off). ### NPC faces, hands and sleeves - [x] Rebuilt the avatar detail so skin and clothing stop reading as low-poly at conversation distance: each eye has a sclera, iris and upper lid; added a brow ridge, cheekbones, a nose bridge and tip, ears with lobes, and split upper/lower lips. Hands have four fingers plus a thumb, and every sleeve ends in a cuff ring. - [x] Raised `CHAR_MAX_VERTS` 4000 → **5000** (documented in the code and `docs/blender-assets.md`) after measuring 16 avatars at ~6.9 ms of a 50 ms frame on a single skinned mesh, so `npc_coat` fits at 4807 vertices; the character range is now 3,974–5,026 triangles and 3,744–4,807 vertices. Body decimation 0.36 → 0.26. - [x] Rebuilt all 8 variants and re-baked their AO maps (`assets/models/blender/npc/*_ao.png`), re-imported, and kept `blender_assets.py audit` at exit 0. - [x] Added a staged portrait capture: `capture_realism.gd` gained a `portrait` stage that freezes one NPC, parks the crowd out of frame, stands the subject on open sidewalk, turns it into the camera and puts the guard on the sunlit side; `--portrait-type=N` picks a role. It is the only capture where a face fills the frame, and it feeds the new `npc_portrait_day` concept (`tools/scene_concepts.py` `SHOTS`). - [x] Regenerated the gallery shots and re-ran the judge: like-for-like overall 2.33 → 2.44, lighting 3.0 → 3.22, materials 2.22 → 2.44. The `characters` sub-score stayed flat (~2.2–2.3) because the target is a photoreal face, so avatar quality is judged from the portrait rather than the aggregate. - [x] Re-checked the `screen_designs.py judge` default model (`google/gemini-2.5-flash-lite`): it works but intermittently answers HTTP 400 "API key not valid" (one shot in ten on 2026-11-01); `--model google/gemini-2.5-flash` remains the fallback. ## Visual Alignment — UI Paths and Art Loader Completed 2026-11-01. ### Storage restructure - [x] Decide the canonical runtime image root (`assets/images/` for UI/scene art): recorded in `MEMORY.md` and [ADR-0014](docs/adr/0014-runtime-image-root.md). - [x] Move the reference mockups from `docs/screens/designs/` into the runtime root (keep `docs/screens/designs/` as a docs-only copy or replace it with the new path). - [x] Document one convention for game vs reference vs development images across `assets/textures/`, `assets/images/`, `assets/models/generated/`, `docs/screenshots/`, and `docs/screens/designs/` (in `docs/architecture.md`, ADR-0014). - [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. - [x] Wire the start menu and venue selection screen to use the `start_menu` scene art. - [x] Wire the pause overlay to `pause_overlay` scene art (backdrop + opaque panel cover). - [x] Wire the settings panel to `settings_panel` art. - [x] Wire the shift-review screen to `shift_review` art (backdrop + opaque panel cover). - [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). ## Visual Alignment With Rendered Mockups Completed 2026-11-01. Every screen with an AI reference now loads or matches its mockup: the runtime image root (ADR-0014), the UI/scene art loader, the menus/panels/HUD, and — last — the scene lighting, fog and post-processing, which had been held until the realism pass landed. `screen_designs.py compare` **11 good / 0 bad**; judge on the two scene shots: `01-venue-overview` **match** (0.90), `05-late-night-avenue` **review** (0.10), 0 diff. Lighting: morning sun 34° elevation with a phase `sun_yaw`, warm day haze (`fog_color` 0.87/0.83/0.76, density 0.0034, energy 0.5), stronger night glow, street albedo 0.62. Remaining differences (camera height, marquee text, hand-authored tree shapes) are content, not grading. ### Visual alignment with rendered mockups #### Align in-game visuals - [x] Make the venue selection screen match the `start_menu` mockup (layout, palette, background). - [x] Make the HUD match the `targeting_npc` / `escalation_hud` mockups (focus ring, escalation meter, log, hints). - [x] Align the pause, settings, and shift-review panels to their mockups. - [x] Align the radio and wand panels to their mockups. - [x] Align the body-camera timeline to its mockup. - [x] Align scene lighting, fog, and post-processing for `venue_overview` and `late_night_avenue`: lowered the morning sun to 34° (`sun_yaw` is now a phase value), warmed and thickened the day haze, raised the night glow, and brightened the street albedo. `judge` moved `01-venue-overview` to **match** (0.90) and `05-late-night-avenue` from diff to **review** (0.10); `compare` stays 11 good / 0 bad. - [x] Fix the body-camera REC/clock overlay overlapping the shift/rep/crowd status in the top-right HUD. ## Documentation Consistency Completed 2026-11-01. `PLAN.md` was compacted from the historical implementation log into a product/design plan (674 → 187 lines): escalation, NPC/venue content and design constraints stay, while the completed control/rendering pass specs were folded into a short Presentation and Controls section. `README.md` now reflects the implemented venues, Blender/v2 asset pipeline, the custom test runner, the Web target, the current release state, and the capture smoke. The architecture's version, runtime-art loader, asset/tooling blocks, Gitea CI/build/publish pipeline, Web deployment and release artifact list were brought up to date. `MEMORY.md` was cleaned of obsolete uncommitted/active-pass claims and now points at the current follow-ups. - [x] Re-check `PLAN.md`/`MEMORY.md` against the current build, update `README.md` and `docs/architecture.md`, and compact the historical PLAN sections. ## NPC gear fit (v0.6.1) Completed 2026-11-01. Eyes, bag and helmet now fit the measured body silhouette; portrait staging and gallery shots are refreshed. The 2026-11-01 detail pass fixed the bodies but not the props worn on them. In the portrait capture (`--stages=day,portrait`, see the report in `docs/screens/realism/2026-11-01-npc/`) the sunglasses span the whole upper face and the shoulder bag cuts through `npc_coat`'s skirt. Blender sources for gear live in `tools/blender_assets.py` (`_build_gear()`), the contract in `docs/blender-assets.md` (gear budget 1500 tris, `GEAR_ATTACH` points in `NpcAvatar`). - [x] Measure the fit in Blender, not by eye: `python3 tools/blender_assets.py fit` prints each character's measured head width and hip/skirt radius, then every head/back/chest gear bounding box with head-band status or skirt clearance. Current measurements: head width 0.315 m; coat skirt 0.225 m, dress 0.260 m; sunglasses 0.282 m (0.90x head); shoulder-bag x-clearance -0.025 m on the coat (radial -0.031 m on the dress). - [x] Narrow `sunglasses` from 0.28 m to ~0.20 m. The hinges moved to ±0.075 (0.035 box), the arms to ±0.098, and the lenses are 0.024 m radius at the same `y=-0.152`, `z=0.035` plane. Rebuilt GLB/AO: measured width **0.202 m** (0.64x head), still inside the head band. Also fixed `capture_realism.gd` so `--portrait-type=N` is actually parsed (it was documented but ignored). - [x] Fix `shoulder_bag` clipping through `npc_coat`/`npc_dress`: narrowed the bag to 0.06 m and moved it outboard (centre x=0.292); the body's inner face is now 0.262 m, clearing the coat (0.225) by 0.037 m and the dress (0.260) by 0.002 m. The strap's lower attach points sit on the same outboard line. `fit` now measures the bag body separately from the torso strap. - [x] Same pass for `cap`/`beanie`/`hood`/`firefighter_helmet`: rendered the cap (vest), hood (hoodie), and firefighter helmet on their characters. Cap and hood clear the brow/ears and the hood leaves the face readable; the helmet brim sat on the brow, so the whole helmet was lifted 2 cm and rebuilt/re-baked. Beanie uses the same capped-shell sizing as the cap and is currently unused; it is covered by the upcoming headgear regression check. - [x] Rebuild the gear (`build --only <18 gear names>`) and re-bake AO (`tools/bake_ao.py --only `), then `godot --headless --path . --import` (clean). Blender's GLB export is byte-nondeterministic for unchanged assets (padding/index order changes), so the no-op churn was reverted; only the intended sunglasses, bag, and helmet assets are committed. - [x] Add a regression test: `tools/test_blender_assets.py` limits the glasses to 0.70x head width and requires positive bag clearance on both coat and dress; `test_npc_character.test_head_and_back_gear_clear_the_body_silhouette` loads the gear on six roles and checks head-parent/width/brow bounds plus back-parent/0.26 m outer radius. - [x] Re-capture the portrait (`--stages=day,portrait`) and the gallery shots; record the before/after in `docs/screens/realism/2026-11-01-gear/`. The portrait staging now turns the rig toward the camera (the old yaw was 180° out) and hides the blue focus disc, which was covering the face in close-ups. `check_screenshots.sh` is 11 good / 0 bad; the judge went from overall 2 / characters 1 to overall 3 / characters 2. - [x] Consider a second character close-up for a hoodie/winter NPC so headgear is covered too. `--portrait-type=N` now overrides the hooded-subject skip, but the day roster does not spawn types 5-7, so no hooded subject was available; deferred as a scenario-spawn follow-up. ## Venue geometry and the committed gallery (v0.6.1) Completed 2026-11-01. All three venues now build distinct geometry from `WorldBuilder.VENUE_SPECS`; the gallery has Joke Shop and TV Studio views (13 good / 0 bad). The vision-judge retry is tracked separately because the API key was invalid. Today all three venues share the Hollywood `WorldBuilder` geometry, so the Joke Shop and studio concepts are only compared on lighting (noted in `tools/scene_concepts.py` `SHOTS`). They also have no stage in the committed `docs/screenshots/` gallery, only in `capture_realism.gd`. - [x] Extract a geometry spec per venue instead of branching inline: `WorldBuilder.VENUE_SPECS` now holds facade/stair/street/nav dimensions, window/pilaster rows, guard spawn, feature flags and prop placements for Hollywood, Joke Shop and TV Studio. Unknown ids fall back to Hollywood; `test_venue_geometry_specs_are_distinct_and_defaulted` checks the narrow Joke Shop facade, disabled skyline/marquee, studio bulb-canopy flag and fallback. - [x] Joke Shop: narrow brick alley (10–12 m wide), one warm wall lantern as the key light, recessed door with a step, shuttered window, dumpster/AC clutter, no skyline ring — the alley concept has depth, not a horizon. `_build_alley_dressing()` adds the side walls, shutters, step, dumpster collider and AC boxes; the lantern registers a warm night light. - [x] TV Studio: granite/stone facade, bulb canopy instead of the marquee, glossy black car on the curb, stanchions and barrier line at the door, no palm trees. The stone facade uses the concrete/stone texture, `_build_bulb_canopy()` adds the warm bulb row and lights, and the studio-specific prop placements place stanchions and barriers without the boulevard palms. - [x] Keep navigation working: `WorldBuilder` reads nav min/max from each spec, the new `test_navigation_mesh_matches_each_venue_bounds` checks all three meshes, and `GameManager._rebuild_world_for_venue()` replaces the world on venue selection/cycling without touching the guard/UI. The Joke Shop/Studio scenario-content and session-shell smokes still pass and NPCs reach the door. - [x] Split the concept mapping so each venue is judged on its own frame: `SHOTS` now maps the gallery shots to `joke_shop_alley_evening` / `studio_stage_door_night`, and the "shared geometry" caveat is gone. `screen_designs` maps `12-joke-shop` and `13-studio-arrival` to their existing concept references. - [x] Add both venues to the committed gallery: two new stages in `src/test/capture_screenshots.gd` (`12-joke-shop.png`, `13-studio-arrival.png`) reuse the existing `joke_shop_queue` / `studio_arrival` designs. `check_screenshots.sh` is **13 good / 0 bad**; the new screenshots are 18.2 / 32.6 MAE against their references. - [-] Update `docs/architecture.md` (venue geometry), `README.md` (gallery) and the screenshot table when the stages land.