Files
security-game/DONE.md
T
zwitschi bb40a98537
Full build / Full desktop build + optional release (push) Successful in 4m2s
Full build / Import + headless tests (push) Successful in 31s
Give street props colliders, zoned placements and variants
Props were decoration: the guard and guests walked straight through benches,
bins, bollards and planters. They now block movement and steer guests around.

Collision
- _add_prop_model() fits a StaticBody3D from the model's mesh bounds in
  holder-local space: a cylinder for bollard/bin/hydrant, a box otherwise, so
  placement rotation and variant scale carry over.
- Fallback primitives go through the same fit, so a missing .glb cannot silently
  remove collision.
- Each prop also publishes a NavigationObstacle3D; NpcAvatar enables agent
  avoidance and moves with the safe velocity from velocity_computed. The navmesh
  stays a flat walkable plane and avoidance does the steering.

Placement
- PROP_PLACEMENTS groups furniture 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.
- variant_for(index) gives deterministic yaw jitter, subtle scale, and optional
  clutter (bag by the bin, crate by the newspaper boxes, cone by the hydrant).

Verification
- New windowed smoke_props.gd: guard walks into a bench and stops; a guest
  headed beyond the bench routes around it and never enters the collider.
- Headless 182 -> 187: placement validation and its failure modes, variant
  determinism/range, collider + obstacle on every placed prop, fallback collider.
- Other smokes (PLAN verbs, crowd, emergency, timeline) still pass; Python
  tooling 53/53; screenshots recaptured; compare 9 good / 0 bad.
- Version 0.4.0. TODO: collision/placement section moved to DONE; prop pipeline
  checks remain.
2026-11-01 10:16:53 +01:00

737 lines
42 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 “STEP 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/step-line.ico` and `packaging/linux/step-line.png` via the generator.
- [x] NSIS script `packaging/windows/step-line.nsi` (installs `Step Line.exe`, uninstaller,
shortcuts, registry entry; `makensis /DVERSION=… /DARTIFACT=…`).
- [x] AppImage: `packaging/linux/step-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 `step-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/step-line-*<ver>*` (covers setup/AppImage names).
- [x] CI run 1110 verified end-to-end (v0.2.1): NSIS installer `step-line-setup-0.2.1.exe`
built, `7z l` shows `step-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
(`step-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 “STEP 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 (`STEP 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/step-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.