Package a Forward+ preset with volumetric fog, SDFGI and a volumetric flashlight for a separate Linux/Windows Desktop HQ zip. Gate the effects on a real RenderingDevice, tune LATE fog, and adapt local light falloff for Godot 4.7. Record the Compatibility/Forward+ A/B captures and metrics, add the artifact to the build/publish scripts, and keep the default Compatibility/Web path unchanged. Headless 231/231, Python 87/87; exported HQ binary boots with Vulkan.
1314 lines
81 KiB
Markdown
1314 lines
81 KiB
Markdown
# 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-*<ver>*` (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/<id>.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-<version>.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 `<name>_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/<run>/`.
|
||
|
||
#### 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/<run>/{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
|
||
<same>`), 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-<version>.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.
|