Phase 4 of the v0.6.0 graphics plan: - tools/bake_ao.py bakes 256² AO maps for every Blender character/gear/prop (Cycles, existing UVs) next to each GLB; Materials.apply_ao() wires the red channel into character slots, gear, and props. Compatibility has no SSAO. - Regenerated brick/concrete/asphalt at 1024² with OpenRouter, derived tangent normal maps via tools/make_normal_maps.py, and wired them through SurfaceMaterials.textured() without changing UV scales. - Evaluated Forward+: boots on desktop Vulkan, but the renderer is project-wide and Web must stay GL Compatibility; v0.6.0 keeps Compatibility (decision in MEMORY/plan). A desktop-only override is deferred. - Recaptured screenshots; compare 11 good / 0 bad. Fixed marquee text alignment. - Tests: baked AO maps exist for all 46 assets; prop/character AO material checks; headless 222/222, Python 69/69, all smokes pass. - Version 0.6.0; ADR-0013 accepted; README/architecture/blender-assets/plan and TODO/DONE updated.
51 KiB
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
.gitignorefor 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.gdthat 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 (
Ftoggles).
Core Simulation — Systems
- Implement
ShiftCoordinatorto start, advance, and end a shift. - Implement
VenueScenarioas aResourcewith locations, rules, NPC pools, and event pools. - Implement an event scheduler that emits routine traffic and occasional scripted events (
EventScheduler). - Implement
NPCControllerwith movement toward waypoints, doors, and queues (movement handled byNpcAvatar).
NPC Interaction
- Define
NpcProfileresources 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
AccessVerifierwith 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
EscalationManagerwith 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
IncidentRecorderemitting structured events (actor, behavior, action, evidence, escalation, outcome). - Implement body-camera evidence tied to incident events.
- Implement
ReputationEvaluatorfor peaceful resolutions, accuracy, complaints, damage, and entrance security. - Implement
CrowdSystemreactions 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.LATEentry (start_s700) toshift_phasesin all three venues. - Confirm the LATE-tagged
violent_emergency_eventis now pulled fromEventScheduler.pull_due. - Add a test asserting the LATE phase is reached and the event is eligible in phase.
Missing PLAN Features — Protester NPC
- Add
PROTESTERtoRules.NpcTypeandNPC_TYPE_NAMES. - Add a protester profile (id
"protester") with ARGUE/IGNORE/RECORD/CALL_SOMEONE reactions. - Add a
TYPE_COLORSentry forPROTESTERinnpc_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_SOMEONEtoRules.Reaction. - Handle
CALL_SOMEONEinNPCController._update_state_for_reaction(transitions to ARGUING). - Give influencer, trespasser, and protester profiles CALL_SOMEONE weights.
- Add a reaction log line in
GameManagerand aStringsentry. - Add a test that CALL_SOMEONE can be rolled and updates the FSM.
Missing PLAN Features — Door lock/secure
- Add a
DoorStatecore class with locked/unlocked state andattempt_force(). - Add a visible door lock indicator (green open / red locked) to the world.
- Add the
Lkey binding toggling the door lock, with HUD indication. - Make FORCE_ENTRY reactions fail while the door is locked (thwarted outcome).
- Add
Stringsentries and a headless test for the lock state machine.
Missing PLAN Features — Flashlight
- Add a
Gkey binding toggling the guard's flashlight. - Attach a
SpotLight3Dto the guard that toggles with the flashlight. - Widen NPC perception radius while lit at evening/late phases (
perceive_guardbonus). - Add
Stringsentries and a headless test for the perception effect.
Persistence
- Call
Save.save_shift_result(review)fromGameManager._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
Savehelper.
Navigation
- Add a
NavigationRegion3Dwith a hand-authored flat navmesh covering sidewalk, steps, and entrance. - Switch
NpcAvatarfrom direct movement toNavigationAgent3Dpath 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.fmtsignatures unchanged. - Update the strings tests to confirm every JSON key resolves to a real value.
Distribution — Downloadable executables
- Commit
export_presets.cfgwith Linux, Windows, and macOS desktop presets. - Add
tools/build_releases.shfor headless release exports. - Add
docs/RELEASING.mdwith template installation, build, and Gitea release steps. - Add a prominent Downloads section at the top of
README.mdlinking to the release page. - Add
tools/publish_release.sh(Gitea API release + asset upload, requiresGITEA_TOKEN).
CI/CD — Gitea Actions full build
- Add
.gitea/workflows/build.ymlwith 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 andworkflow_dispatch. - Tag builds automatically publish the Gitea release via
tools/publish_release.shusing theGITEA_TOKENsecret. - Remove the old
.github/workflows/ci.ymlto avoid double runs on hosts scanning both paths. - Document the CI flow in
docs/RELEASING.md.
Gameplay — Venue selection
- Add a
Vcontrol that cycles Hollywood Blvd → The Joke Shop → TV Studio and restarts the shift (GameManager._cycle_venue), with ahud.venue_switchedstring 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_modeswitches 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.gdnow 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 indocs/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.shproduces 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_TOKENsecret, so pushing thev0.2.0tag 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
/releasesreturn 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.ymlon tags).
🎨 AI texture generation (OpenRouter)
- Extend
tools/generate_textures.pywith an OpenRouter image-API mode (--ai,--prompt,--name,--model,--size), decoding base64 PNG responses and clamping to ≤1024px. - Verify a live
--aigeneration against a realOPENROUTER_API_KEY(generatedai/…set at 1024×1024; seedream minimum-pixel by requesting 2048 and downscaling.
🧹 Runtime hygiene + timeline/polish items
- RefCounted cycle cleanup:
FSMnow holds its owner viaWeakRef;ShiftCoordinatorisRefCounted(logic-only, no tree needs); persistence tests free theSavenode. 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_Srecharge between wand scans with awand.rechargemessage (Open Polish item).
📦 Installer packaging (Windows NSIS + Linux AppImage)
- Export presets:
binary_format/embed_pck=trueon all platforms — every binary is now self-contained (all game data in one file;.pckno longer shipped). - App icon:
assets/textures/icon.png(1024) + multi-sizeicon.ico, copied topackaging/windows/gig-line.icoandpackaging/linux/gig-line.pngvia the generator. - NSIS script
packaging/windows/gig-line.nsi(installsGig Line.exe, uninstaller, shortcuts, registry entry;makensis /DVERSION=… /DARTIFACT=…). - AppImage:
packaging/linux/gig-line.desktop,AppRun, appdata.xml, andbuild_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.ymlbuild job; publish glob already uploadsbuild/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.exebuilt,7z lshowsgig-line.exe+uninstall.exe, AppImage built (33.9 MB), releasev0.2.1published,Job succeeded.- Gotchas fixed on the way: makensis needs
-Ddefines (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).
- Gotchas fixed on the way: makensis needs
🎮 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 underassets/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_pow2guards 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@v4from 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=1to both jobs. - Confirmed in run 1090: zero
CacheServicelines, 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 fromdoor_staff_lines(empty for unknown contexts/venues). - Wire
GameManagerto 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 fromNpcTypealone. (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_itemsper 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
sidewalkandsteps_endwaypoints so film/photo intents do not walk to the entrance. - Wand shortcut is
C, notW. 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(nowC+ wheel).A/Dallow/deny only while inspect/wand is open (they step the wheel while it is open).Qremains a closed-menu polite shortcut. - Accel/decel on walk; gravity only when airborne;
floor_snap_length0.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.Tabstill 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;Label3Ddisplay_name. HUD target line uses the display name, not the node name. - Screen-center crosshair.
Radial HUD
src/game/ui/action_radial.gd: LMB orSpaceopens 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 onR.- Highlight by mouse angle, or
A/D/ arrows. Click slice orSpaceconfirms.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–6still 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
Headis the ring attach point. Groupnpc.
Input, copy, tests
- Named input map:
action_menu(Space/LMB),confirm(Space/Enter),cancel(Esc/RMB),inspect(E),wand(C).GameManagerusesis_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.pngwith transparent background (commented out opaque border line 234 intools/generate_textures.py). - Integrated backdrop into
WorldBuildervia_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.
GameManagerdid not compile:radialwas typedCanvasLayer, soradial.is_open/confirm_selected()made:=inference fail. Typed asActionRadial(the parse error would have broken the whole main scene).- Camera sat at the guard's feet:
Headhad no height offset, and the new gravity pulled the guard to the floor.Headis 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.
ShiftCoordinatornow accumulates time and pulls once per simulated second; regression test intest_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.pnghas 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
Vas the player-facing path (Vmay stay as a debug bind). - Pause overlay when
Escreleases 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.mdTech Stack / Planned Commands still say GUT and GitHub Actions; the project usessrc/test/test_runner.gdand.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_accessis 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). Oncrowd_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.
evacuationoutcome 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
arrivespawn). - 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.1now carries all five artifacts (portable binaries + NSIS installer + AppImage). - Host setting applied:
[service] REQUIRE_SIGNIN_VIEW = falsein/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-credentialsbranch never setAUTH, so every tag build since v0.2.2 failed to publish. - Add a
github.tokenfallback in.gitea/workflows/build.yml(the repo has noGITEA_TOKENsecret). - 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=300Min/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 inStrings.fmtwhen 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 generatewrites one reference design per screen (from thedocs/descriptions) todocs/screens/designs/, with an offline--draftwireframe mode and an OpenRouter--aimockup mode.tools/screen_designs.py comparechecksdocs/screenshots/*.pngagainst the designs (aspect ratio, size, blank-frame, and layout/palette/detail drift).- Vision-model judge for semantic screenshot comparison:
judgesends 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.envfor 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
.tresresources:tools/export_venues.gdwritescontent/venues/<id>.tresfrom 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.pycatalogue 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
listsubcommand so the catalogue is inspectable without generating images. - Cover the new entries with
tools/test_screen_designs.pyassertions. - Regenerate the deterministic draft references under
docs/screens/designs/. (generatenow keeps existing references unless--forceis passed, so a draft run cannot replace an AI mockup;--onlyselects 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
WorldBuildersurfaces 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
WorldBuilderwith 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 aShiftprecision 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
Webpreset toexport_presets.cfgusingvariant/thread_support=falseso the build runs on plain static hosting without COOP/COEP headers. - Export to
build/web/and packagebuild/gig-line-web-<version>.zipfromtools/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, andPLAN.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.
generateloads.envlikejudgealready did, so--aiworks without exportingOPENROUTER_API_KEYby hand.- Verified every reference is 1280x720, reviewed the new images visually, and confirmed
listreports 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.gdnow 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.pngadded todocs/screenshots/and to the README gallery. - Wired
refforwand_screening,escalation_hudandbody_camera_timelinesocompareandjudgeinclude 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
NavigationObstacle3Dfootprint, andNpcAvatarenables agent avoidance with avelocity_computedcallback. 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 staticplacement_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
.glbcannot 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) todocs/screenshots/10-prop-gallery.png, with aprop_gallerycatalogue entry and AI mockup, socompare/judgecover 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_foroffsets, 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_lodis 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 (
accel22,decel30 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_bindingsthroughSave;InputBindings.applyrewrites 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) and11-precision-walk.pngwith 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:
NpcAvatarused Godot'slook_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 replacedlook_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.zinstead 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,
.blendsources, manifest, review sheets, and integration notes. NpcAvatarselects a character variant by role/seed (npc_casual,npc_jacket,npc_coat,npc_vest,npc_dress,npc_hoodie,npc_firefighter) withnpc_baseand the procedural rig as fallbacks; the choice is recorded incharacter_model.- Only the five contract slots (
skin,cloth,pants,accessory,hair) are tinted; fixed detail materials keep their authored colour.npc_vestkeeps 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/handattach points (cap, hood, helmet, backpack, SCBA tank, lanyard, phone, cup, clipboard, umbrella, camera, sign, flashlight, wand, radio), with the primitive path retained as fallback. WorldBuilderloads 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 intoStandardMaterial3D.ao_texturefor character slots, gear and props. - Regenerated brick, concrete and asphalt at 1024² through
asset_pipeline.py, derived tangent normal maps withtools/make_normal_maps.py, and wired them intoSurfaceMaterials.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 rancompare+judge; no regressions. - Fixed the library marquee text/face alignment and added the library AO/triangle tests.