Files
security-game/DONE.md
T
zwitschi 79270795a8 Finish the image-root task and untrack Blender sources
- ignore assets/models/blender_src/*.blend (keep the .gdignore marker tracked) and
  remove the 46 editable .blend files from the index; they remain on disk and are
  regenerated by tools/blender_assets.py
- move the completed storage/docs/code-path/art-loader/verification, mouse-fix,
  and pop-up sections to DONE.md; TODO now holds only the open lighting item,
  realism follow-ups, and optional ideas
- docs changes only; runtime code unchanged
2026-11-01 16:49:43 +01:00

55 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 “GIG 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/gig-line.ico and packaging/linux/gig-line.png via the generator.
  • NSIS script packaging/windows/gig-line.nsi (installs Gig Line.exe, uninstaller, shortcuts, registry entry; makensis /DVERSION=… /DARTIFACT=…).
  • AppImage: packaging/linux/gig-line.desktop, AppRun, appdata.xml, and build_appimage.sh (appimagetool, APPIMAGE_EXTRACT_AND_RUN=1).
  • Local end-to-end: rebuilt embedded exports and produced gig-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/gig-line-*<ver>* (covers setup/AppImage names).
  • 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)

  • 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 “GIG 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 (GIG 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/gig-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).

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.

  • 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.
  • New captures 07-wand-screening.png, 08-escalation-hud.png, 09-body-camera-timeline.png added to docs/screenshots/ and to the README gallery.
  • 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).
  • Regenerated the escalation HUD mockup as a third-person over-the-shoulder night shot so the reference matches the game camera.
  • 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.

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

  • 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.
  • 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.
  • Confirmed instances share the imported atlas material (headless test) and documented the triangle/vertex budget in the pipeline docstring.
  • 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.

  • 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.
  • Layered clothing (layer_for): jacket, hi-vis vest, coat, and dress over the base outfit, with role-appropriate defaults and deterministic colourways.
  • 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.
  • Face and hair variety: ears, brows, mouth, five hair silhouettes (long, bun, beard, bald, short), height range, and deterministic per-seed appearance.
  • 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.
  • Walk cycle now swings hips/shoulders, bends knees and elbows, and uses a faster, longer stride when fleeing.
  • 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.
  • Reaction readability: a billboarded symbol above the head (!! panic, ?! watching, ! blocked, ? warned) updates from the reaction/escalation state.
  • Three LOD tiers with hysteresis: full detail, simplified (props/shadows off), and a billboard impostor; apply_lod is tested for the swap.
  • 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.
  • 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.

  • 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.
  • 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.
  • 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.
  • Precision walk shows a HUD chip and plays a short tick when it engages, in addition to the slower speed.
  • Surface-aware footsteps (concrete/asphalt/stone) synced to stride distance, plus a landing cue; both have observable counters for tests.
  • 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.
  • 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.
  • 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.
  • Added invert-Y and head-bob settings (with save/load round-trip tests) alongside the existing look sensitivity.
  • Tests: surface lookup, step/stride/precision constants, gamepad binding presence and deadzone, remap descriptor/apply/labels, and settings persistence for camera and bindings.
  • 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.

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

  • Committed the audited library (8 characters, 18 gear, 20 props), generator, .blend sources, manifest, review sheets, and integration notes.
  • 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.
  • 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.
  • 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.
  • 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.
  • Library emissive surfaces (lamp bulbs, marquee bulbs/sign face, wall lanterns, door LED, sedan lights) are collected and driven by set_night_mode().
  • 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.
  • 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.

  • 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.
  • 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.
  • 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.
  • Recaptured docs/screenshots/ and ran compare + judge; no regressions.
  • 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.

  • Bumped the version and synchronised README/architecture/blender-assets/plan docs.
  • Headless 222/222, Python 69/69, all smokes, compare 11 good / 0 bad.
  • 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/.

Visual Alignment — UI Paths and Art Loader

Completed 2026-11-01.

Storage restructure

  • Decide the canonical runtime image root (assets/images/ for UI/scene art): recorded in MEMORY.md and ADR-0014.
  • 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).
  • 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).
  • Re-run godot --headless --path . --import to regenerate .import sidecars after any move.
  • 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

  • Update README.md paths (screen-designs section, confirmed-assets links, the assets/images/scenes resize example).
  • 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).
  • Update tools/screen_designs.py docstrings and mark the old paths in DONE.md as historical.

Code path updates

  • Point tools/screen_designs.py generate at the new design output path and keep compare/judge reading the right locations.
  • Update GDScript preload() paths if game textures move (no runtime material/model textures moved; UI art added under assets/images/).
  • Update capture output directories if captures relocate (none did; QA captures stay in docs/screenshots/).

Load proposed interfaces + scene art from disk

  • 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.
  • Wire the start menu and venue selection screen to use the start_menu scene art.
  • Wire the pause overlay to pause_overlay scene art (backdrop + opaque panel cover).
  • Wire the settings panel to settings_panel art.
  • Wire the shift-review screen to shift_review art (backdrop + opaque panel cover).
  • Wire the wand popup to wand_screening art.
  • Style the radial menu and finish the remaining HUD chrome against their reference images.

Verify against the mockups

  • Regenerate the staged captures and run screen_designs.py compare (11 good, 0 bad; verified in a clean HEAD worktree).
  • Run screen_designs.py judge and clear every diff verdict (7 match, 4 review, 0 diff).
  • Add the capture + compare step as a repeatable smoke so drift is caught early (tools/check_screenshots.sh).

Fix mouse movement bug

  • 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

  • Improve the visual appearance of in-game pop-ups (start/pause/settings/review/radio/wand/inspection art, buttons, palette, icons, text).