# 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. ## Forward+ desktop preset and a volumetric flashlight (v0.7.1) Completed 2026-11-01. The optional Desktop HQ build uses Forward+ with volumetric fog, SDFGI and a volumetric flashlight; Compatibility/Web/headless remain the default. The entrance A/B improved the metric gap 1.568 → 1.187. The vision-judge retry remains tracked separately because the API key was invalid. The Compatibility renderer has no volumetrics, so the flashlight is a tight pool of light with no visible shaft. A second desktop build can render it properly without touching the CI/default path. - [x] Write the preset as an `override.cfg` (Godot reads it beside the binary/project) that sets `rendering/renderer/rendering_method="forward_plus"`, enables `volumetric_fog_*` and SDFGI, and keeps the Compatibility build as the default (`project.godot` unchanged). The preset lives in `packaging/desktop_hq/`; `local lights adapted for Godot 4.7 falloff` too. - [x] Gate the effect in code: only give the flashlight a volumetric component when the renderer is Forward+ (`RenderingServer.get_rendering_device() != null`), so Web/CI builds are untouched and the existing headless tests keep passing. Environment volumetric fog/SDFGI use the same gate; tests assert the headless lamp is non-volumetric. - [x] Tune `light_volumetric_fog_energy`/`fog_density` for the LATE phase and re-check the entrance so the shaft does not wash the facade again. LATE density is 0.020 and the lamp volumetric energy is 3.0; the entrance read improves without bleaching the brick. - [x] A/B the two renderers with `capture_realism.gd` (`--stages=late --flashlight`, two `--out` dirs) and record the diff in `docs/screens/realism/2026-11-01-forward-plus/`: entrance metric gap 1.568 → 1.187, two-shot mean 0.907 → 0.723. Judge scores are blocked by the invalid API key and tracked with the other judge retry. - [x] Add the preset to `tools/build_releases.sh` and `docs/RELEASING.md` as an optional "desktop HQ" artifact, and note the runtime requirement (Vulkan driver) in `README.md`. `gig-line-desktop-hq-.zip` packages the Linux/Windows binaries, override.cfg and README; `publish_release.sh` uploads it when present. - [x] Test: a headless run must still boot when Forward+ is unavailable (the preset must degrade to Compatibility instead of failing to start). Verified with the preset in the project root: `godot --headless --path . --quit-after 5` exits 0 and starts the game; an exported HQ binary also boots with Vulkan Forward+ under Xvfb. ## Vision judge reliability (v0.7.2) Completed 2026-11-01. The judge now discovers the cheapest current OpenRouter image+JSON model (falling back through known candidates), reports the model used, and uses the valid key. Full screen-design judge: 13/13 match, 0 diff. The judge failed with an invalid-key HTTP 400 even though the `.env` key was later valid, and a hard-coded model can disappear or be unavailable. Make the judge discover current OpenRouter vision models, prefer cheap/structured-output-capable ones, and fall back when a provider rejects a request. - [x] Add `tools/openrouter_judge.py`: query `/api/v1/models`, filter image-input models with `response_format` support, rank by estimated token/image cost, and keep a small known-good fallback list. - [x] Let both `screen_designs.py judge` and `scene_concepts.py realism --judge` use `--model auto` (the new default), retry a transient provider error once, and move to the next model on model/provider failures. An invalid key fails with exit 2. - [x] Print/report the selected model(s) and fallback order; the judge JSON records the selected model and the selected-parameter cost is inferred from the public catalogue. - [x] Add `tools/test_openrouter_judge.py` with a fake catalogue and mocked calls for ranking, explicit/env override, invalid-key stop, transient retry and provider fallback. - [x] Re-run the full screen-design judge and the Forward+ A/B judge with the valid `OPENROUTER_API_KEY`: screen designs are **13/13 match, 0 diff**; the Forward+ report records the selected model and ~$0.006 spend. - [x] Update `docs/asset-tooling.md`, `MEMORY.md`, `README.md` and `CHANGELOG.md`; the public catalogue picks the cheapest image+JSON model (`z-ai/glm-5.3-flash` at run time). ## Milestone 0.8.0 — First hour and content depth Completed 2026-11-01. Tutorial, campaign/checkpoint resume, content validation, contextual dialogue, policy/debrief, difficulty/help and the expanded smoke matrix are in place. Goal: a new player can learn the game inside the game, play a short campaign across the three venues, and see enough authored variety that a second shift does not feel like a replay. ### 0.8.1 First-shift tutorial - [x] Write the tutorial beat sheet: menu → movement/look → focus and target cycling → action wheel → access inspection → polite direction → formal warning/body camera → radio → shift review. Eight beats and a safe compliant visitor are specified in [`docs/tutorial.md`](docs/tutorial.md). - [x] Add a tutorial state machine independent of the normal shift FSM. `Tutorial` in `src/game/tutorial.gd` owns the eight beats, accepts only the current beat's action, supports skip/restart and has deterministic headless tests. GameManager/UI wiring starts with the contextual-prompt task. - [x] Add contextual prompts that advance on the performed action, not on a timer. The non-blocking `TutorialOverlay` card updates from `Tutorial.beat_changed`; it is mouse-transparent, skippable with F1 and does not repeat after completion/skip. - [x] Stage a safe encounter with one compliant visitor; routine/scripted spawns are suppressed while the tutorial is active. The tutorial uses the real wheel, inspection, warning/body camera and radio panels. - [x] Persist tutorial completion/skip in `settings.json` and add a "Replay tutorial" action in Settings that clears both flags and restarts the Hollywood shift. - [x] Add localized tutorial strings (`tutorial.*` keys) and caption-ready prompt hooks in the overlay/state table. - [x] Add headless tutorial-state tests and `smoke_tutorial.gd`, covering prompts, the safe visitor, completion, settings persistence, replay and skip. Tutorial complete (2026-11-01): the first-shift flow is wired end to end; normal scripted test/capture runs bypass it unless `--tutorial` is passed. ### 0.8.2 Campaign and session shell - [x] Define campaign data separately from `VenueScenario`: `src/core/campaign.gd` owns venue order, shift number, completion, difficulty and the next venue. - [x] Add a campaign-select/continue screen showing the next shift/venue and previous grade; the existing venue buttons remain free-play/debug-compatible. - [x] Add a "Continue shift" path that resumes the paused checkpoint, including RNG, elapsed time, NPC/incident state, systems and door state. - [x] Add campaign status to the shift review with a Continue action for the next venue and a return-to-menu path; the review keeps retry and timeline controls. - [x] Persist campaign state (`campaign.json`) and add a confirmed Reset campaign action in Settings. - [x] Add Campaign unit tests plus `smoke_campaign.gd` covering resume, shift advancement, review continue and help. ### 0.8.3 Venue content matrix - [x] Define the per-event content matrix in the authored event dictionaries (phase, kind, NPC type, intent, access override, hint/recovery path and weight). - [x] Add `ScenarioValidator`: unique IDs, existing profiles, valid phases/kinds, intent presence, minimum routine/scripted/recovery counts and phase coverage; all three venues pass. - [x] Each venue now has at least 12 events and 6 routine variants across the phase matrix; recovery events were added for all three venues. - [x] Add recent-scripted suppression and phase-aware weights in `EventScheduler`; routine traffic remains repeatable, rare threats keep their authored probability. - [x] Add one recovery event per venue (returning authorized guest, manager check) on top of the existing crowd-calming/reputation feedback. - [x] Add `EventScheduler.preview()` and wire it into the F debug overlay as the next eligible event list. - [x] Add content-matrix/no-repeat/spawn-multiplier tests; phase gating and authored-event reachability remain covered by the scheduler and scenario-content tests. ### 0.8.4 NPC dialogue and negotiation - [x] Add `NpcProfile.context_lines` for reason/refusal/de-escalation/recognition/group lines, with deterministic `line_for()` selection and safe fallbacks. - [x] Expand authored profile context lines and keep selection seed-deterministic; no runtime LLM dependency. - [x] Wire Reason, polite/warning/blocking outcomes, recognition and reaction logs to context lines, with comply/leave vs argue/record differences visible in the HUD log. - [x] Reuse the existing crowd watch/flee reactions for group response rather than adding overlapping NPC chatter; nearby avatars react to aggression, responders and panic. - [x] Add deterministic context-line tests; authored strings remain in the existing localization table, with missing context falling back safely. - [x] Add distinct refusal/de-escalation/recognition lines for tourist and confused-visitor profiles and keep the existing venue-specific dialogue lines. ### 0.8.5 Objective, policy and debrief layer - [x] Show the venue `policy_summary` in Help and in the shift review. - [x] Log a one-line phase objective when the shift phase changes; no quest log is added. - [x] The review already lists incidents, reputation modifiers and responder/investigation outcomes; policy and campaign-next context were added to make the judgment actionable. - [x] The shift review explains the grade, policy, highlights and campaign next step; the tutorial supplies the first-shift guidance. - [x] Existing incident/review tests cover evidence and missing/unknown outcome handling; the policy field is sourced directly from the validated scenario. ### 0.8.6 Save, resume and settings - [x] Add separate `settings.json`, `campaign.json`, `shift_results.json` and versioned `shift_state.json` (schema version 1); the checkpoint deliberately omits tutorial UI state and scheduler event objects, restoring only RNG/system/NPC state. - [x] Write saves through a temporary file and atomic rename; malformed JSON loads as defaults and a failed write leaves the previous file intact. - [x] Settings merge over defaults so missing/older keys migrate in place; campaign and shift-state readers accept missing optional fields with safe defaults. - [x] Mid-shift checkpoint restores elapsed time, scheduler RNG, incident/evidence state, reputation, crowd, radio, NPC positions/access state, guard pose, flashlight/body camera and door lock; `smoke_campaign.gd` resumes and reaches the review. - [x] Add coordinator/incident snapshot round-trip tests and the campaign/checkpoint smoke; existing settings/shift-history persistence tests remain green. ### 0.8.7 Difficulty, pacing and help - [x] Add easy/normal/hard presets adjusting spawn multiplier, NPC compliance tolerance and report-credibility strictness; escalation and emergency rules are unchanged. - [x] Expose difficulty in Settings; normal is the default and the values are documented in code. - [x] Add the standalone `HelpOverlay` covering controls, focus/wheel, access, body camera, radio and review, with the current venue policy. - [x] Help has a filter field and builds remappable-action rows from `InputBindings` at open time. - [x] Debug/event details stay behind F; normal HUD keeps the tutorial and phase objective concise. - [x] Settings defaults and persistence tests cover difficulty; help is generated from every `InputBindings.REMAPPABLE` action. ### 0.8.8 Verification and exit criteria - [x] Add headless tests for tutorial progression, campaign state, content validation, dialogue, coordinator/incident snapshot round-trips and settings/difficulty defaults. - [x] Add `smoke_tutorial.gd` and `smoke_campaign.gd` covering tutorial completion/replay/skip, resumed shift, campaign venue transition and Help. - [x] Run the automated first-hour path (tutorial + campaign/resume smoke) end to end; the external five-person playtest is tracked in the 0.9.5 release-candidate milestone. - [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-1.0 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-1.0 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.