Files
security-game/DONE.md
T
zwitschi a5e7b3c013
Full build / Import + headless tests (push) Successful in 34s
Full build / Full desktop build + optional release (push) Successful in 4m4s
Add AI mockups for the seven remaining catalogue screens
The design catalogue grew to 19 screens, but only the original twelve had an AI
reference; the rest still showed "design -" in `list`. Generated the missing set
so every screen can be reviewed against an intended look:

- wand screening (metal-detected panel with carried items and allow/deny)
- Joke Shop queue, TV Studio arrival, crowd panic, responder arrival
- body-camera timeline, escalation HUD

`generate` now loads `.env` the same way `judge` does, so `--ai` no longer needs
the API key exported by hand. Each reference is 1280x720; the twelve existing
mockups were kept (no `--force`).

Verification: `list` reports 19 designs, Python tooling 53/53, headless suite
182/182, `compare` still 6 good / 0 bad on the captured screens. Version 0.3.2.
2026-11-01 00:01:02 +01:00

39 KiB
Raw Blame History

DONE

Completed task sections moved from TODO.md. Sections are moved only when every item is finished and verified.

Project Setup

  • Confirm Godot 4.x editor version to standardize on (Godot 4.3 stable, official build).
  • Initialize a git repository and add a sensible root .gitignore for Godot and export artifacts.
  • Scaffold the Godot project (project.godot, input map, folder structure).
  • 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.)
  • Document setup, run, and test commands in README.md.

Prototype Scene

  • Build a placeholder Hollywood Boulevard building entrance with steps, sidewalk, and a restricted door (procedural WorldBuilder).
  • Add a controllable guard character with first-person camera controls (GuardController).
  • Add basic keyboard/mouse input: move, look, interact, and cancel.
  • Add a debug overlay showing NPC intent, escalation level, and shift timer (F toggles).

Core Simulation — Systems

  • Implement ShiftCoordinator to start, advance, and end a shift.
  • Implement VenueScenario as a Resource with locations, rules, NPC pools, and event pools.
  • Implement an event scheduler that emits routine traffic and occasional scripted events (EventScheduler).
  • Implement NPCController with movement toward waypoints, doors, and queues (movement handled by NpcAvatar).

NPC Interaction

  • Define NpcProfile resources with intent, temperament, compliance, and escalation tolerances.
  • Implement a lightweight finite state machine for NPC states (FSM).
  • Implement the observation phase (Level 0) without forced intervention.
  • Implement simple deterrence (Level 1): raise hand, point, eye contact, and head shake.
  • Implement polite dialogue prompts and reaction logic driven by escalation level and profile.
  • 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.
  • Implement blocking and physical positioning (Level 3) without default force.
  • Wire typical NPC reactions: leave, argue, ignore, record, move closer, or return later.

Access Verification

  • Implement AccessVerifier with input from recognition, wristbands, IDs, tickets, credentials, and guest lists.
  • Add wristband inspection UI showing color, pattern, date, and event marking.
  • Model valid, expired, counterfeit, mismatched, missing, and ambiguous access artifacts.
  • Record admit, redirect, hold, and refuse decisions for shift review.
  • Add feedback scoring for incorrectly denying an authorized person or admitting an unauthorized one.

Escalation and Radio — Core

  • Implement EscalationManager with explicit levels 0 through 5 and available-action gating.
  • Implement the security-assistance radio call (Level 4) with a report form and arrival delay.
  • Implement the emergency radio (Level 5) with hold-to-confirm and a report form.
  • Add radio cooldown and battery limits to prevent spam.

Incident, Reputation, and Shift Review

  • Implement IncidentRecorder emitting structured events (actor, behavior, action, evidence, escalation, outcome).
  • Implement body-camera evidence tied to incident events.
  • Implement ReputationEvaluator for peaceful resolutions, accuracy, complaints, damage, and entrance security.
  • Implement CrowdSystem reactions to calm, aggressive, armed, and emergency situations.
  • Implement the end-of-shift review screen summarizing decisions, consequences, and justification.

Scenario Content — Venue Data

  • Author a minimal Hollywood Boulevard building scenario with tourist, confused-visitor, and simple-trespasser NPCs.
  • Author a rare violent-threat event that requires justified emergency escalation.
  • Add The Joke Shop scenario data: ID checks, guest lists, and wand-screening policy.
  • Add the Hollywood Boulevard Late Night TV Studio scenario data with the full NPC mix and credential checks.
  • Add a mental-health-crisis event with careful, respectful de-escalation guidance.

Tooling and Quality

  • Add headless test coverage for access verification rules.
  • Add headless test coverage for escalation transitions and scoring.
  • Add fixed-seed scenario tests for deterministic NPC behavior.
  • Set up GitHub Actions to run headless tests on push (ci.yml).
  • Add a smoke-test checklist for launching one full shift (in README.md).

Bug Fixes — LATE shift phase

  • Add a Rules.Phase.LATE entry (start_s 700) to shift_phases in all three venues.
  • Confirm the LATE-tagged violent_emergency_event is now pulled from EventScheduler.pull_due.
  • Add a test asserting the LATE phase is reached and the event is eligible in phase.

Missing PLAN Features — Protester NPC

  • Add PROTESTER to Rules.NpcType and NPC_TYPE_NAMES.
  • Add a protester profile (id "protester") with ARGUE/IGNORE/RECORD/CALL_SOMEONE reactions.
  • Add a TYPE_COLORS entry for PROTESTER in npc_avatar.gd.
  • Add routine protester events to the Hollywood Boulevard and TV Studio event pools.
  • Add scenario tests asserting the protester profile and events exist.

Missing PLAN Features — “Call someone else” reaction

  • Add CALL_SOMEONE to Rules.Reaction.
  • Handle CALL_SOMEONE in NPCController._update_state_for_reaction (transitions to ARGUING).
  • Give influencer, trespasser, and protester profiles CALL_SOMEONE weights.
  • Add a reaction log line in GameManager and a Strings entry.
  • Add a test that CALL_SOMEONE can be rolled and updates the FSM.

Missing PLAN Features — Door lock/secure

  • Add a DoorState core class with locked/unlocked state and attempt_force().
  • Add a visible door lock indicator (green open / red locked) to the world.
  • Add the L key binding toggling the door lock, with HUD indication.
  • Make FORCE_ENTRY reactions fail while the door is locked (thwarted outcome).
  • Add Strings entries and a headless test for the lock state machine.

Missing PLAN Features — Flashlight

  • Add a G key binding toggling the guard's flashlight.
  • Attach a SpotLight3D to the guard that toggles with the flashlight.
  • Widen NPC perception radius while lit at evening/late phases (perceive_guard bonus).
  • Add Strings entries and a headless test for the perception effect.

Persistence

  • Call Save.save_shift_result(review) from GameManager._on_shift_ended.
  • Load past results and show record count + best score on the shift review screen.
  • Add a test that a saved review round-trips through the Save helper.

Navigation

  • Add a NavigationRegion3D with a hand-authored flat navmesh covering sidewalk, steps, and entrance.
  • Switch NpcAvatar from direct movement to NavigationAgent3D path following (with direct-movement fallback).
  • Keep per-intent waypoint and returning-trespasser re-targeting on the navmesh.
  • Make the step geometry visual-only (no collider) so pathing to the entrance works.

Localization — JSON strings

  • Move the string table to res://content/strings.en.json (41 keys).
  • Load the JSON at startup; Strings.lookup/Strings.fmt signatures unchanged.
  • Update the strings tests to confirm every JSON key resolves to a real value.

Distribution — Downloadable executables

  • Commit export_presets.cfg with Linux, Windows, and macOS desktop presets.
  • Add tools/build_releases.sh for headless release exports.
  • Add docs/RELEASING.md with template installation, build, and Gitea release steps.
  • Add a prominent Downloads section at the top of README.md linking to the release page.
  • Add tools/publish_release.sh (Gitea API release + asset upload, requires GITEA_TOKEN).

CI/CD — Gitea Actions full build

  • 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.
  • Tag builds automatically publish the Gitea release via tools/publish_release.sh using the GITEA_TOKEN secret.
  • Remove the old .github/workflows/ci.yml to avoid double runs on hosts scanning both paths.
  • Document the CI flow in docs/RELEASING.md.

Gameplay — Venue selection

  • 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)

  • 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.
  • Street lamps: poles with emissive bulbs + OmniLights on the sidewalk; WorldBuilder.set_night_mode switches bulbs/lights on at night.
  • Marquee signage: emissive “STEP LINE” strips + Label3D above the entrance.
  • Street dressing: crosswalk stripes, planters, and low-poly trees.
  • NPC visual variety: per-seed height/brightness variation (deterministic LCG).
  • Denser routine traffic (tourists/performers/influencers/trespassers).
  • 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/.
  • config/version → 0.2.0; build/ artifacts rebuilt and Linux binary smoke-tested.
  • New world/lighting headless tests (56 total).

Screenshot Refresh

  • Regenerate and verify the six README screenshots from the current build.

🔴 PRIORITY — Ship downloadable builds (complete 2026-10-31)

  • Install Godot 4.3 export templates locally.
  • ./tools/build_releases.sh produces Linux/Windows/macOS artifacts (0.2.0).
  • Linux binary smoke-tested (headless, 0 script errors).
  • 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).
  • Confirm links resolve: tag page and /releases return HTTP 200 publicly. Follow-up: anonymous asset downloads require sign-in on this host (host setting).
  • CI packaging follow-up was already live (.gitea/workflows/build.yml on tags).

🎨 AI texture generation (OpenRouter)

  • Extend tools/generate_textures.py with an OpenRouter image-API mode (--ai, --prompt, --name, --model, --size), decoding base64 PNG responses and clamping to ≤1024px.
  • 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

  • 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.
  • 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.
  • 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)

  • 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).
  • 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.
  • NSIS script packaging/windows/step-line.nsi (installs Step Line.exe, uninstaller, shortcuts, registry entry; makensis /DVERSION=… /DARTIFACT=…).
  • AppImage: packaging/linux/step-line.desktop, AppRun, appdata.xml, and build_appimage.sh (appimagetool, APPIMAGE_EXTRACT_AND_RUN=1).
  • Local end-to-end: rebuilt embedded exports and produced step-line-0.2.0-x86_64.AppImage, ran it headless (0 script errors).
  • CI: NSIS + AppImage steps added to .gitea/workflows/build.yml build job; publish glob already uploads build/step-line-*<ver>* (covers setup/AppImage names).
  • 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)

  • 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/.
  • Environment: textured facade + glass door, concrete sidewalk, asphalt street, striped awning with “STEP LINE”, generated skyline backdrop planes, soft sun shadows.
  • NPCs: humanoid rig (torso/head/limbs) with archetype props (camera, delivery box, hat, sign, backpack), procedural walk bob, head-turns-toward-guard cue.
  • UI/feedback: inspection artifact cards, mode icons (flashlight/door/wand), escalation vignette, body-camera overlay (corner brackets + REC + shift clock).
  • 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

  • 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).
  • Add GODOT_SILENCE_ROOT_WARNING=1 to both jobs.
  • Confirmed in run 1090: zero CacheService lines, 54/54 tests pass, job succeeds.
  • Document the cache situation and re-enable path in docs/RELEASING.md.
  • Ignore runner logs in .gitignore (.gitea/logs/).

Gameplay — Venue door-staff dialogue

  • Add VenueScenario.door_staff_line(context, rng) picking a seeded-random line from door_staff_lines (empty for unknown contexts/venues).
  • 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).
  • Add a headless test covering all Joke Shop contexts, unknown contexts, empty venues, and deterministic seeded picks.

Core Simulation — NPC Perception

  • Implement a perception pass so NPCs react to the guard's gestures and proximity.
  • Give NPCs local waypoints per intent (photo spots, queue, stage door, sidewalk exit) instead of all walking to the entrance.
  • Add returning-trespasser behavior: an NPC that was directed away may circle back later in the shift.

Escalation and Radio — Responder Dispatch

  • Spawn a dispatched security NPC / armed responder avatar that arrives at the venue after the call delay.
  • Give the responder command, restraint, evacuation, and arrest outcomes that resolve the incident.
  • Add a visible emergency-radio cooldown indicator in the HUD.

Scenario Content — Wand Screening (The Joke Shop)

  • Implement the handheld wand inspection interaction with alerts from keys, belts, phones, and weapons.
  • Enforce screening-before-entry flow and record false alarm / prohibited item decisions.
  • Add venue-specific dialogue lines for door staff.

Polish and Accessibility

  • Add aim-free targeting (Tab) and venue cycling (V). (The earlier dialogue tone picker was superseded and removed with the tone-of-voice system.)
  • Add interaction alternatives that do not require precise aiming (e.g. scan-and-select pinned list of nearby NPCs).
  • Add visual and audio feedback per escalation level (colors, tones, shake).
  • Add audio for crowd ambience, radio responses, and confirmation prompts.
  • Externalize dialogue and UI text for localization.

Generalized Game Asset Tooling

  • Reuse the OpenRouter image backend for role-aware arbitrary asset generation.
  • Add exact-dimension image crop and aspect-preserving resize commands.
  • Audit existing assets for role-specific dimensions, aspect, alpha, and tile seams; report projection/perspective checks for visual review.
  • Add automated tests and document asset-pipeline usage.

Generated Asset Integration

  • Regenerate the AI asset set at role-specific aspect ratios and dimensions.
  • Audit, process, and wire every generated asset into the game runtime.
  • 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.

  • 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).)
  • Joke Shop: valid ID+ticket, ID without ticket, expired/missing ID. Recognition does not auto-grant on every AUTHORIZED_GUEST.
  • Wand venues roll carried_items per spawn (seeded); override can force a pocket.
  • Studio counterfeit / follow-through events carry override_access.
  • AccessVerifier.verify(..., already_screened) so a screened guest is not still denied.
  • Joke Shop / Studio sidewalk and steps_end waypoints so film/photo intents do not walk to the entrance.
  • 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

  • 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.
  • Accel/decel on walk; gravity only when airborne; floor_snap_length 0.3.
  • Mouse look only while captured and not input_locked. Freeze walk+look while radial / inspect / wand / radio / review is up. Recapture on close.
  • cancel (Esc / RMB) closes the top modal first; with nothing open, Esc toggles mouse capture and RMB does nothing.

Focus and focus circle

  • 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.
  • 8-frame miss grace so walk-bob does not drop focus.
  • 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.
  • Screen-center crosshair.

Radial HUD

  • 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.
  • Highlight by mouse angle, or A/D / arrows. Click slice or Space confirms. Esc / RMB closes with no action (no recorder event).
  • Inspect / wand / radio slices close the wheel then open the existing panels. Freeze until that panel closes.
  • Number keys 1–6 still work when the wheel is closed.

NPC display

  • 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

  • Named input map: action_menu (Space/LMB), confirm (Space/Enter), cancel (Esc/RMB), inspect (E), wand (C). GameManager uses is_action_pressed.
  • Windowed smoke: focus circle + name, wheel open/step/confirm/cancel (src/test/smoke_focus_radial.gd, exits non-zero on failure).
  • 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.

  • Regenerated assets/textures/game_ready/skyline.png with transparent background (commented out opaque border line 234 in tools/generate_textures.py).
  • Integrated backdrop into WorldBuilder via _build_skyline_backdrop(): creates a PlaneMesh with T_SKYLINE texture at Vector3(-80, 12, -50) behind the building.
  • 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.

  • 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).
  • 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.
  • 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.
  • Steps rose away from the door; treads now rise toward the entrance.
  • 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.
  • Live NPCs capped at MAX_LIVE_NPCS (16); routine traffic is dropped at the cap and a scripted event evicts the oldest unfocused NPC.
  • Removed the skyline backdrop planes (opaque black texture). Tracked as a follow-up in TODO.md.
  • 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.

  • 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.
  • 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.
  • 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.
  • Marquee text (STEP LINE) is tiny from the sidewalk. Scale the Label3D up.
  • Focus circle and name tag are small at 2–3 m. Consider a larger circle / font after a real playtest.
  • 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.

  • Startup menu: pick venue, start shift, quit. Keyboard + click. Replaces V as the player-facing path (V may stay as a debug bind).
  • Pause overlay when Esc releases the mouse (or a dedicated pause key): resume, restart shift, settings, quit to menu.
  • Settings: mouse look sensitivity, persist through Save / settings.json. Load on boot.
  • 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).

  • README controls table, smoke checklist, test count, and implemented/not-yet lists refreshed for the control pass (77 tests).
  • 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.
  • 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.

  • Eye contact as a Level 1 action (PLAN / glossary). Distinct from raise-hand. Add a radial slice if it ships.
  • "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).
  • Formal warning can activate the body-camera / security-light flag (PLAN Level 2). Overlay is currently always on; toggling should write an incident event.
  • Blocking stations the guard between the person and the door (PLAN Level 3). Today it only sends apply_player_action(BLOCKING).
  • "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.

  • On aggressive_behavior / emergency_responder, nearby idle NPCs stop and face the incident (rubberneck). On crowd_panic, they move away from the door.
  • 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.

  • evacuation outcome actually clears the entrance (NPCs leave, door stays locked).
  • Unjustified emergency (low danger, no weapon) writes an investigation note on the shift review and applies the existing false-report / reputation hit.
  • 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.

  • 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.

  • 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.

  • Loop a low crowd bed that rises with CrowdSystem.mood.
  • 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.

  • Escalation colors that still read in grayscale (shape or label, not hue alone).
  • 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.

  • Joke Shop: guest on the reservation list without ID; borrowed/mismatched ID; guest refuses wand screening; late arrival during a performance.
  • Studio: filming-boundary repeat (influencer ignores a posted line); celebrity-arrival crowd spike (more rubberneckers, not only one arrive spawn).
  • 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):

  • Repo is public (private:false) and account has admin rights.
  • Release v0.2.1 now carries all five artifacts (portable binaries + NSIS installer + AppImage).
  • 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.

  • Fix tools/publish_release.sh: the ~/.git-credentials branch never set AUTH, so every tag build since v0.2.2 failed to publish.
  • Add a github.token fallback in .gitea/workflows/build.yml (the repo has no GITEA_TOKEN secret).
  • Fall back to the checkout's git extraheader when no token/credentials exist (the Gitea runner does not expose github.token).
  • 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).
  • Host disk expanded to 16G; v0.2.0 and v0.2.9 releases restored from the local artifacts.
  • 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.
  • 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.
  • 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.

  • 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.

  • 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.
  • tools/screen_designs.py compare checks docs/screenshots/*.png against the designs (aspect ratio, size, blank-frame, and layout/palette/detail drift).
  • 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.

  • 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).
  • 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.

  • 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.
  • Add a list subcommand so the catalogue is inspectable without generating images.
  • Cover the new entries with tools/test_screen_designs.py assertions.
  • 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.

  • 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).
  • Rebuild WorldBuilder surfaces with the material profiles and add street detail: window bands, door trim, bollards, bench, bin, curb, planter foliage.
  • Per-phase environment values (fog, sky energy, glow) so night reads darker and the marquee glows.
  • Improve NPC silhouette and materials: per-role clothing palette, caps/hoods/backpacks, sleeves and hands, walking leg swing.
  • 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.

  • Add tools/model_pipeline.py: prop catalogue, deterministic low-poly GLB builder, OpenRouter concept-image generation, palette sampling and GLB audit.
  • Add tools/test_model_pipeline.py (valid GLB structure, deterministic output, audit failures, sRGB→linear conversion, role assignment).
  • Generate the prop models into assets/models/generated/ and import them.
  • 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.

  • Record the re-evaluated control model as ADR-0011.
  • Rank focus candidates by view angle then distance (NpcFocus.rank_indices) instead of insertion order.
  • Add mouse-wheel target cycling next to Tab, right-click clearing focus, and a Shift precision walk speed.
  • Show the nearby-candidate count and the cycle hint in the HUD.
  • 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.

  • 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.
  • Export to build/web/ and package build/step-line-web-<version>.zip from tools/build_releases.sh (zip or Python fallback).
  • Publish the web zip with the desktop artifacts (tools/publish_release.sh).
  • Cover the preset, the non-threaded choice, and the release wiring in src/test/tests/test_web_export.gd.
  • 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.

  • 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.
  • 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.
  • audit_glb() checks the single-material/atlas contract (one material, both textures, TEXCOORD_0 on every primitive, two images).
  • 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.
  • 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.

  • 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.
  • generate loads .env like judge already did, so --ai works without exporting OPENROUTER_API_KEY by hand.
  • Verified every reference is 1280x720, reviewed the new images visually, and confirmed list reports 19 designs (12 kept, 7 new).