Session shell - Startup venue menu (src/game/ui/start_menu.gd): keyboard + mouse, replaces the live-shift boot and V as the player-facing venue path (V stays a debug bind). - Pause overlay on Esc (src/game/ui/pause_overlay.gd): resume, restart shift, settings, quit to menu. - Settings panel (src/game/ui/settings_panel.gd): mouse look sensitivity and HUD font scale, persisted via Save into user://settings.json and loaded on boot. - Shift review "Run another shift" now returns to the venue menu. - New smoke: src/test/smoke_session_shell.gd (menu -> shift -> pause -> settings -> menu), passes headless and windowed. World and presentation - Skyline backdrop restored with a transparent asset and an alpha-scissor unshaded material; fixes the opaque black slab from the earlier attempt and the unimported texture path. - Steps and landing are a StaticBody3D with per-tread colliders, so the guard and NPCs walk up instead of clipping through. - Marquee Label3D scaled up (72 -> 160) so STEP LINE reads from the sidewalk. - Focus torus and name tag enlarged for 2-3 m readability. - Routine traffic despawns after an idle lifetime (NpcAvatar.ephemeral), so the 16-NPC cap no longer fills with standers; authored events stay for the shift. Docs and tests - PLAN.md test/CI section corrected to the custom headless runner and Gitea Actions. - README controls table, smoke checklist, test count, and status lists refreshed. - TODO/DONE/MEMORY updated; completed sections moved to DONE.md. - Headless suite 77 -> 88 passing (settings round-trip, step colliders, skyline alpha, session-shell UI behavior, traffic lifetime).
19 KiB
19 KiB
MEMORY
Session memory for durable decisions, state, and gotchas. Keep concise, remove obsolete entries.
Decisions
- Engine: Godot 4.3 stable (official editor build,
~/.local/bin/godot). The system's preinstalledgodot-runner(4.7.2, Fedora) cannot load projects from arbitrary paths because it was compiled without path overrides; use the official editor binary instead. - Language: GDScript. All cross-file references use explicit
preload()constants (notclass_nameglobals) so scripts compile in headless--scriptruns without the editor's class cache. - Test framework deviation: the project uses a custom minimal headless test runner (
src/test/test_runner.gd) instead of GUT, because GUT needs the editor plugin. CI runs it with--headless. - Eventing: no autoload signal bus.
ShiftCoordinatorowns anEventBusinstance (src/core/event_bus.gd) passed to subsystems; subsystems emit only when a bus is assigned. Autoloads at parse time are unreliable under--script. - Static signals are not available in Godot 4.3 GDScript ("Expected func or var after static") — do not use them.
- GDScript lambdas capture local variables by value; mutating a captured local inside a lambda does not affect the outer variable. Use member functions for accumulators in tests.
- Shift length is the scenario's
shift_duration_s(900 s in all three venues). Tests and the screenshot tool override elapsed time; they do not change the default. - RNG: NPC reactions and event scheduling are seedable for deterministic tests.
Handoff (read this first)
- Repo:
/run/media/system/DATA/Documents/06-games/security-game. Godot:~/.local/bin/godot(4.3.stable). Tests:godot --headless --path . --script res://src/test/test_runner.gd→ 88/88. Session shell smoke:godot --headless --path . --script res://src/test/smoke_session_shell.gd→ exit 0. Windowed control smoke:godot --path . --script res://src/test/smoke_focus_radial.gd→ exit 0. mainis ahead of origin, all committed locally, not pushed. The working tree additionally holds the session-shell + world-follow-up batch (start menu, pause overlay, settings persistence, step colliders, skyline alpha, marquee/ring scale, traffic despawn, README/PLAN/TODO refresh).- Next work, in order: (1) remaining PLAN verbs in
TODO.md(eye contact, recognize decision, warning body-cam flag, blocking station, request reason); (2) crowd presentation (rubberneck + evacuation); (3)git add -A+ commit, push / release when the user asks. - Do not re-open
override_access. Do not bind wand toW. Do not remove the radio easter egg.
Current State
- Access artifacts:
src/core/access_loadout.gdbuilds from eventoverride_access+ venue policy. Spawn signal is now(profile, intent, npc_id, event). Recognition is not auto-attached to every guest.AccessVerifier.verify(..., already_screened=false)so Joke Shop scoring usescontroller.screened. Joke Shop/Studio gainedsidewalk/steps_endwaypoints. Wand venues rollcarried_itemsper spawn (seeded). Tests insrc/test/tests/test_access_loadout.gd. - Wand shortcut is
C, notW(Wis walk).radio_emergencyinput map no longer includes W.hud.controls/hud.screen_firstupdated. - Control pass (in tree, uncommitted, smoke-tested): cone focus via
src/game/npc_focus.gd, accel/decel, gravity only airborne,input_lockedfreeze, Esc no longer on the guard.NpcAvatarin groupnpc; neck/hair/eyes/shoes; torus head ring +Label3Dname;set_focus_visual.src/game/ui/action_radial.gddoughnut (LMB/Space open, A/D/arrows, Space/click confirm, Esc/RMB cancel). Crosshair CanvasLayer. Emergency stillRonly. Number keys 1–6 when the wheel is closed. - Named input actions now exist and are used:
action_menu,confirm,cancel(Esc + RMB),inspect(E),wand(C). The old unusedinteractaction was removed. - Runtime fixes from the smoke:
Headis +1.6 above the guard root (root sits on the floor); guard spawns in front of the door facing the street; building volume moved to z ∈ [-8,-4] so the door, awning, and marquee are visible; step treads rise toward the door;ShiftCoordinatorpulls the event scheduler once per simulated second (was every physics frame → >1200 NPCs in seconds);GameManager.MAX_LIVE_NPCS = 16caps live avatars. - Session shell (2026-10-31, uncommitted): the game now boots into
StartMenu(src/game/ui/start_menu.gd, layer 20) instead of a live shift.GameManager._on_venue_selected()builds the scenario and calls_start_shift();_shift_runninggates input and_refresh_freeze(). Esc with nothing open now pauses (PauseOverlay, layer 18) → resume / restart / settings / quit-to-menu.SettingsPanel(layer 22) writeslook_sensitivity+hud_font_scalethroughSave.save_settings()→user://settings.json;Save.load_settings()merges overSave.DEFAULT_SETTINGSon boot andGameManager._apply_settings()pushes them into the guard/HUD. Shift review "Run another shift" now calls_show_start_menu()(not a silent restart).Vstays a debug venue bind. Smoke:src/test/smoke_session_shell.gd(menu → shift → pause → settings → menu). - World follow-ups closed (2026-10-31): skyline backdrop restored with
T_SKYLINE(assets/textures/skyline.png, regenerated RGBA with a transparent sky) and anTRANSPARENCY_ALPHA_SCISSORunshaded material (_skyline_mat); steps + landing are aStaticBody3Dwith per-treadBoxShape3Dcolliders; marqueefont_size160 / outline 24; focus torus 0.26–0.34 + name tag 48px;NpcAvatar.ephemeral/lifetime_sdespawns routine traffic (authored events stay). Guard spawn height verified on the landing (guard_y=0.84). - Asset gotcha:
assets/textures/game_ready/is not imported (no.import). Use the importedassets/textures/skyline.pngpath. Untracked duplicate folder should not be referenced. - Host sign-in follow-up remains deferred. PLAN.md + ADRs 0007–0010 still describe the intended UX.
- Release v0.2.1 is LIVE (2026-10-31): CI run 1110 built the NSIS installer + AppImage and published — installers/AppImage are on the releases page. Releasing = bump version → tag → push.
- NSIS gotchas (validated in a local ubuntu:24.04 docker repro): makensis uses
-Ddefines (never/D), resolves relative-DICON/-DARTIFACTagainst the script directory so CI passes$PWD-absolute paths; installed exe must be space-free (step-line.exe); ICO must be classic BMP entries (ImageMagickicon:auto-resize=48,32,16; PIL emits PNG-compressed ICOs NSIS cannot load). - appimagetool runs system
appstreamcliif present (strict, exit 3 on warnings) — project metadata now passes--pedantic. - Distribution facts (verified via Gitea API + host creds): repo is public, account is
admin;
v0.2.1has all 5 assets; the installers were initially missing from the release becausepublish_release.shuploaded only portables — now fixed (uploads installer + AppImage; falls back to~/.git-credentialswhen noGITEA_TOKEN). - Host sign-in: instance
[service] REQUIRE_SIGNIN_VIEW=trueblocks anonymous browse/download (not a repo/API setting).~/.git-credentialsholds the Gitea creds (basic-auth usable);.envonly holdsOPENROUTER_API_KEY. - Release v0.2.0 was LIVE: pushing the
v0.2.0tag made Gitea Actions run the build job and publish automatically (host has theGITEA_TOKENsecret). Tag page and/releasesare public (HTTP 200). Caveat: anonymous asset downloads redirect to login on this host (host setting — tracked in TODO). Future releases = bumpconfig/version,git tag vX.Y.Z, push. No local token needed. - Core simulation, three scenarios, v0.2.1 installers/AppImage, first-person prototype (inspect/wand/radio, shift review, Tab targeting, V venue cycle) are on local
main. Tagged v0.2.1 was 57 tests; this working tree is 74/74. - NPC avatars now walk to per-intent waypoints (
VenueScenario.waypoint_for_intent): enter/trespass/deliver/arrive → entrance, photo/film/perform/wander/roam → sidewalk, sit → steps_end.NpcAvatar.setuptakes an explicitdoor_position. - Returning-trespasser behavior:
NpcProfile.return_probability(trespasser 0.6, mental-crisis 0.4, others 0).NPCController._maybe_schedule_return()moves a directed-away NPC toLOITERING_DISTANT;tick_return(delta)fires after 20-60s and transitions back toAPPROACH.NpcAvatar._advance_fsmre-targets the door on return. - Perception pass:
NPCController.perceive_guard(distance)returns{noticed, band, reaction}with bandsunaware(>6m),aware(2-6m),near(<2m). Near + temperament ≥0.6 → ARGUE, else COMPLY.GameManager._perception_pass()feeds real guard distance each physics tick and logs first-time notice. - Responder dispatch:
RadioSystemtrackspending_callsand decrementsarrival_delay_sintick; emitsresponder_arrivedwhen a call's delay elapses.RadioSystem.responder_outcome(call)maps danger/weapons → command/restraint/evacuation/arrest.GameManager._on_responder_arrivedspawns aRESPONDERavatar at the entrance and logs the outcome. HUD shows an emergency-radio cooldown label via_update_radio_cooldown. - Wand screening (Joke Shop):
WandScreenerclassifies carried items as prohibited vs false-alarm vs clear.NpcProfile.carried_itemsis filled at spawn on wand venues.GameManagerbindsCto_toggle_wand_screeningandA/Dtodecide_wandwhile inspect/wand is open.decide_accessstill blocks admission untilcontroller.screenedis true, andverifynow takes that flag. Door-staff lines log through GameManager. - Design change (2026-10-31): the selectable dialogue tone-of-voice system was removed.
Rules.Tone/TONE_NAMES,NpcProfile.tone_modifiers, and the tone param onNPCController.roll_reaction/apply_player_actionno longer exist;NpcProfile.dialogue_linesis a flatArray[String];GameManageruses_apply_interaction(level, action_name); theTkey is gone. HUDtone_labelremoved andhud.tone_prefixstring deleted from JSON. - Polish/accessibility: venue cycling (
VcyclesVENUE_ORDERand restarts the shift), aim-free targeting (Tabcycles nearby NPCs withinNEARBY_RADIUS), color-coded escalation indicator (Hud.set_escalation), procedural audio (AudioFeedbacknode generates sine tones for confirm/radio/escalation/deny), and a centralizedStringstable (src/core/strings.gd,lookup/fmt) backed bycontent/strings.en.json. NOTE:Strings.getwas renamed toStrings.lookupbecausegetcollides withObject.get. - Newly added: LATE shift phase (start 700s) with a reachability test; PROTESTER archetype + events + colors; CALL_SOMEONE reaction;
DoorStatecore class +Lbinding + visual indicator; flashlight (G, SpotLight3D, perception bonus at evening/late); persistence wired (Save.save_shift_resulton shift end, history + best score on the review screen);NavigationRegion3Dflat navmesh +NavigationAgent3DNPC paths (steps now visual-only); strings moved tores://content/strings.en.json(41 keys, loader keepslookup/fmt);export_presets.cfg(Linux/Windows/macOS) +tools/build_releases.sh+docs/RELEASING.md; README Downloads section. - Run tests:
godot --headless --path . --script res://src/test/test_runner.gd - Boot check:
godot --headless --path . --quit-after 120 - Screenshot tools:
godot --path . -s res://src/test/take_screenshot.gd(single shot touser://screenshot.png);godot --path . -s res://src/test/capture_screenshots.gd(staged README gallery shots touser://screenshots/, copied todocs/screenshots/). - Release build verified locally: export templates for 4.3 installed at
~/.local/share/godot/export_templates/4.3.stable/;./tools/build_releases.shproduced Linux/Windows/macOS artifacts for 0.1.0 inbuild/and the Linux binary passed a headless smoke test. Publishing to Gitea still needs a token (seetools/publish_release.sh,docs/RELEASING.md). - CI: the repo now carries a Gitea Actions workflow at
.gitea/workflows/build.yml(checks on push/PR; full export + artifact upload + optional release publish on tags andworkflow_dispatch). A repo secretGITEA_TOKENis required for tag releases. The old.github/workflows/ci.ymlwas removed so hosts scanning both paths do not double-run. - Venue cycling:
Vswitches Hollywood Blvd → The Joke Shop → TV Studio and restarts the shift. - Venue door-staff dialogue:
VenueScenario.door_staff_line(context, rng)and GameManager logging (id/ticket/wand-check, refusal, prohibited-item) — Joke Shop flavor. - CI cache:
.gitea/workflows/build.ymlno longer usesactions/cache(the runner can't reach the GitHub cache service; import is fast).GODOT_SILENCE_ROOT_WARNING=1set on jobs. Logs live in.gitea/logs/after each run. - Visual polish (v0.2.0): phase lighting (GameManager.PHASE_LIGHTING + ProceduralSkyMaterial), street lamps + night mode in WorldBuilder.set_night_mode, marquee Label3D, crosswalk/planters/ trees, NPC per-seed variation. capture_screenshots.gd shoots day + fast-forwarded LATE night (6 shots in docs/screenshots/). Version bumped to 0.2.0; binaries rebuilt.
- Assets pipeline:
python3 tools/generate_textures.pyproduces all authored PNGs underassets/textures/(≤1024px, pow2); regenerate after changing the script, then rungodot --headless --importbefore tests. 57 headless tests guard the pipeline. - General image pipeline:
python3 tools/asset_pipeline.pydefaults toopenai/gpt-5-image-mini, requests supported 1:1/3:2 sources, then fits each role's target. It provides crop, resize, tile edge blending, alpha-glyph recoloring, and role-aware audits. Screenshot auditing checks 1280x720 minimum, 16:9 aspect, and contrast; visual composition remains a manual check. Python tools require Pillow fromrequirements-tools.txt. - AI texture mode:
python3 tools/generate_textures.py --aigenerates textures via the OpenRouter image API (POST https://openrouter.ai/api/v1/images, modelbytedance-seed/seedream-4.5by default, base64 PNG indata[].b64_json). RequiresOPENROUTER_API_KEY(a gitignored.envin the repo root holds it locally; source it withset -a && . ./.env && set +a).--prompt/--name/--model/--sizeoverride the default batch. Output is downscaled to ≤1024px. NOTE: seedream rejects sizes below 3,686,400 px (1920×1920), so the script requestsGEN_SIZE=2048and downscales — do not lowerGEN_SIZEbelow 1920. Verified end-to-end 2026-10-31 (3 textures generated). Legacy--airemains on Seedream 4.5 and still ignoresbackground: transparent; do not use it for alpha-dependent assets. Gemini 3.1 Flash Image advertised broad ratios but the current provider rejected authentication. The validated role-aware model is GPT-5 Image Mini. - Runtime assets: all 15 loaded world/HUD/card textures now live in
assets/textures/ai/game_ready/; each passes role audit. Generated tile edges are blended, icon alpha masks are recolored white where needed, and the vignette uses the existing deterministic generator because the API returned an empty transparent image. The sixdocs/screenshots/captures were regenerated after wiring and pass screenshot audit. - Detailed graphics: textured facade/awning/skyline, humanoid NPC rigs + props + walk bob +
attention head-turn, inspection artifact cards, mode icons, escalation vignette, body-cam
overlay (REC + shift clock). Screenshot set regenerated (
docs/screenshots/, day + night). - Polish: wand scan cooldown (
WAND_COOLDOWN_S+wand.rechargemessage). - NOTE: collaborators occasionally add uncommitted work to this shared tree (e.g. the radio
easter egg); check
git statusbefore committing and leave foreign changes untouched unless asked.
Known Issues / Notes
- Reference cycles are fixed:
FSMholds its owner viaWeakRef,ShiftCoordinatorisRefCounted, and persistence tests free theSavenode — the suite exits with no "resources still in use" warnings (was ~13). If a future subsystem introduces a cycle, repeat the WeakRef pattern. - Shift length:
GameManageruses the scenario'sshift_duration_s(900s ≈ 10-15 min); tests and the screenshot tool override elapsed time directly. - Headless (dummy renderer) logs "Parameter m is null" from
mesh_get_surface_count; it does not occur with a real GL driver and does not affect logic. - Steps are visual-only (no collider) so NPCs can path to the entrance; the guard spawns on the landing so it never needs to climb.
- Persistence tests write into the real
user://shift_results.json(harmless local pollution of the demo history). - macOS export required
rendering/textures/vram_compression/import_etc2_astc=trueinproject.godot(universal build demands ETC2 ASTC enabled). - Joke Shop
door_staff_linesare logged throughGameManager(inspect / wand / refuse). They are not a separate dialogue UI. - Access loadout is implemented in the working tree (see Current State); do not re-fix
override_accessas if it were still unread. Save.SETTINGS_FILEis declared and unused; no mouse-sensitivity or pause menu yet.- Headless suite is 77/77; the windowed focus/radial smoke passes. Remaining visual follow-ups are at the top of
TODO.md. - Typed spawn signal extra args: handlers must accept the 4th
event: Dictionaryor Godot will not call them (test_shift_coordinator_runs_a_shiftfailed until_on_spawntook four args). - GDScript inference trap that broke the main scene: a
vartyped as a base class (radial: CanvasLayer) exposes script members asVariant, sovar x := radial.is_openis a parse error. Type it as the script type (ActionRadial) or annotate the local explicitly.--check-only --scriptcannot see theSaveautoload; use a real boot (--quit-after 180) to verify a full-scene compile. - Do not snapshot right after changing a transform in the same frame:
capture_screenshots.gdneeds a state/frame gap or the capture uses the old camera orientation. EventSchedulerprobabilities are per simulated second, not per call. Never poll it per frame.- Screenshot staging lives in
src/test/capture_screenshots.gd(guard at z≈10.5 facing the door).docs/screenshots/copies are manual: copy fromuser://screenshots/.
Open Questions
- Whether the responder avatar should be playable/controllable or purely scripted.
- Localization target languages beyond English.
- macOS notarization approach for broad distribution.
Plan vs code (re-evaluated)
On local main (6 commits not on origin): v0.2.1 packages, ADRs 0007–0010, asset pipeline wired to
assets/textures/ai/game_ready/, screenshots refreshed.
Uncommitted: AccessLoadout, control pass (cone, ring, radial, NPC face, named input actions),
and boot/spawn fixes. Headless 77/77; windowed smoke passes. Access-artifact and control-pass
sections moved to DONE.md.
Still in TODO.md: world follow-ups (guard float, step colliders, skyline asset, marquee size);
session shell; leftover PLAN verbs; crowd rubberneck; emergency aftermath; body-cam replay;
sit/photo/film poses; real crowd audio; Joke Shop/Studio extra events; PLAN.md says GUT/GitHub.