# 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 preinstalled `godot-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 (not `class_name` globals) so scripts compile in headless `--script` runs 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. `ShiftCoordinator` owns an `EventBus` instance (`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. - Prototype shift duration defaults to 300 s for testing; production targets 10-15 minutes per PLAN.md. - RNG: NPC reactions and event scheduling are seedable for deterministic tests. ## Current State - Next playable pass (planned, not coded): first-person stay, but focus becomes a camera cone + head ring, and NPC verbs move onto a round pie menu (LMB/`Space` open, click or A/D/arrows then `Space` confirm, `Esc`/RMB cancel). Emergency stays on `R`. Wand leaves `W` (conflicts with walk). Host sign-in follow-up is deferred. Details in PLAN.md "Player Control, Focus, and Action Menu". - **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 `-D` defines (never `/D`), resolves relative `-DICON`/`-DARTIFACT` against the **script directory** so CI passes `$PWD`-absolute paths; installed exe must be space-free (`step-line.exe`); ICO must be classic BMP entries (ImageMagick `icon:auto-resize=48,32,16`; PIL emits PNG-compressed ICOs NSIS cannot load). - appimagetool runs system `appstreamcli` if 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.1` has all 5 assets; the installers were initially missing from the release because `publish_release.sh` uploaded only portables — now fixed (uploads installer + AppImage; falls back to `~/.git-credentials` when no `GITEA_TOKEN`). - Host sign-in: instance `[service] REQUIRE_SIGNIN_VIEW=true` blocks anonymous browse/download (not a repo/API setting). `~/.git-credentials` holds the Gitea creds (basic-auth usable); `.env` only holds `OPENROUTER_API_KEY`. - **Release v0.2.0 was LIVE**: pushing the `v0.2.0` tag made Gitea Actions run the build job and publish automatically (host has the `GITEA_TOKEN` secret). Tag page and `/releases` are public (HTTP 200). Caveat: anonymous **asset downloads** redirect to login on this host (host setting — tracked in TODO). Future releases = bump `config/version`, `git tag vX.Y.Z`, push. No local token needed. - Core simulation, three scenarios, headless tests (**57 passing**), and a first-person prototype with interaction HUD, access inspection, radio (assist + emergency hold-to-confirm), debug overlay, and shift review are implemented and green. - 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.setup` takes an explicit `door_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 to `LOITERING_DISTANT`; `tick_return(delta)` fires after 20-60s and transitions back to `APPROACH`. `NpcAvatar._advance_fsm` re-targets the door on return. - Perception pass: `NPCController.perceive_guard(distance)` returns `{noticed, band, reaction}` with bands `unaware` (>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: `RadioSystem` tracks `pending_calls` and decrements `arrival_delay_s` in `tick`; emits `responder_arrived` when a call's delay elapses. `RadioSystem.responder_outcome(call)` maps danger/weapons → command/restraint/evacuation/arrest. `GameManager._on_responder_arrived` spawns a `RESPONDER` avatar at the entrance and logs the outcome. HUD shows an emergency-radio cooldown label via `_update_radio_cooldown`. - Wand screening (Joke Shop): `WandScreener` (new `src/core/wand_screener.gd`) classifies carried items as prohibited (`weapon/knife/gun/blade`) vs false-alarm (`keys/belt/phone/coins/watch/buckle`) vs clear. `NpcProfile.carried_items` holds guest items. `GameManager` binds `W` to `_toggle_wand_screening` and `A`/`D` to `decide_wand`; `decide_access` blocks admission at wand venues until `controller.screened` is true. `VenueScenario.door_staff_lines` holds venue-specific door-staff dialogue (Joke Shop has id/ticket/wand/refusal/prohibited lines). - 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 on `NPCController.roll_reaction/apply_player_action` no longer exist; `NpcProfile.dialogue_lines` is a flat `Array[String]`; `GameManager` uses `_apply_interaction(level, action_name)`; the `T` key is gone. HUD `tone_label` removed and `hud.tone_prefix` string deleted from JSON. - Polish/accessibility: venue cycling (`V` cycles `VENUE_ORDER` and restarts the shift), aim-free targeting (`Tab` cycles nearby NPCs within `NEARBY_RADIUS`), color-coded escalation indicator (`Hud.set_escalation`), procedural audio (`AudioFeedback` node generates sine tones for confirm/radio/escalation/deny), and a centralized `Strings` table (`src/core/strings.gd`, `lookup`/`fmt`) backed by `content/strings.en.json`. NOTE: `Strings.get` was renamed to `Strings.lookup` because `get` collides with `Object.get`. - Newly added: LATE shift phase (start 700s) with a reachability test; PROTESTER archetype + events + colors; CALL_SOMEONE reaction; `DoorState` core class + `L` binding + visual indicator; flashlight (`G`, SpotLight3D, perception bonus at evening/late); persistence wired (`Save.save_shift_result` on shift end, history + best score on the review screen); `NavigationRegion3D` flat navmesh + `NavigationAgent3D` NPC paths (steps now visual-only); strings moved to `res://content/strings.en.json` (41 keys, loader keeps `lookup`/`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 to `user://screenshot.png`); `godot --path . -s res://src/test/capture_screenshots.gd` (staged README gallery shots to `user://screenshots/`, copied to `docs/screenshots/`). - Release build verified locally: export templates for 4.3 installed at `~/.local/share/godot/export_templates/4.3.stable/`; `./tools/build_releases.sh` produced Linux/Windows/macOS artifacts for 0.1.0 in `build/` and the Linux binary passed a headless smoke test. Publishing to Gitea still needs a token (see `tools/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 and `workflow_dispatch`). A repo secret `GITEA_TOKEN` is required for tag releases. The old `.github/workflows/ci.yml` was removed so hosts scanning both paths do not double-run. - Venue cycling: `V` switches 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.yml` no longer uses `actions/cache` (the runner can't reach the GitHub cache service; import is fast). `GODOT_SILENCE_ROOT_WARNING=1` set 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.py` produces all authored PNGs under `assets/textures/` (≤1024px, pow2); regenerate after changing the script, then run `godot --headless --import` before tests. 57 headless tests guard the pipeline. - General image pipeline: `python3 tools/asset_pipeline.py` reuses the OpenRouter image backend for role-sized generation and provides exact crop/resize plus role-aware audits. Audit checks dimensions, aspect, required alpha, and tile-edge continuity; perspective/projection stays a manual visual check. Python tools require Pillow from `requirements-tools.txt`. - AI texture mode: `python3 tools/generate_textures.py --ai` generates textures via the OpenRouter image API (`POST https://openrouter.ai/api/v1/images`, model `bytedance-seed/seedream-4.5` by default, base64 PNG in `data[].b64_json`). Requires `OPENROUTER_API_KEY` (a gitignored `.env` in the repo root holds it locally; source it with `set -a && . ./.env && set +a`). `--prompt`/`--name`/`--model`/`--size` override the default batch. Output is downscaled to ≤1024px. NOTE: seedream rejects sizes below 3,686,400 px (1920×1920), so the script requests `GEN_SIZE=2048` and downscales — do not lower `GEN_SIZE` below 1920. Verified end-to-end 2026-10-31 (3 textures generated). `AI_TEXTURES` now covers all 15 game textures (world surfaces, awning, skyline, vignette, 4 HUD icons, 5 access cards) with a `transparent` flag per entry. NOTE: seedream-4.5 ignores `background: transparent` and returns fully opaque images (alpha=255), so the `ai/` icons/vignette/skyline are opaque — they are NOT drop-in replacements for the authored transparent PNGs. Use a transparency-capable model (or post-process) if alpha is needed. - 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.recharge` message). - NOTE: collaborators occasionally add uncommitted work to this shared tree (e.g. the radio easter egg); check `git status` before committing and leave foreign changes untouched unless asked. ## Known Issues / Notes - Reference cycles are fixed: `FSM` holds its owner via `WeakRef`, `ShiftCoordinator` is `RefCounted`, and persistence tests free the `Save` node — 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: `GameManager` uses the scenario's `shift_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=true` in `project.godot` (universal build demands ETC2 ASTC enabled). - Joke Shop `door_staff_lines` authored but not yet surfaced in the prototype UI (tracked in TODO). ## 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 TODO Gaps (evaluated 2026-10-31, all resolved) All previously identified gaps are now implemented and covered by tests/smoke items: PROTESTER archetype, door lock/secure, flashlight, CALL_SOMEONE reaction, LATE phase fix, shift-result persistence, NavigationAgent3D movement, and JSON-backed localization. Remaining backlog lives in `TODO.md`.