# MEMORY Session memory for durable decisions, gotchas, and commands. Implementation history lives in `DONE.md`, user-facing changes in `CHANGELOG.md`, the live task list in `TODO.md`, and the canonical implemented-features list in `docs/development-status.md`. ## Decisions - **Local-only reference override (2026-11-03, owner):** development-only photo research ignores the licence gate. Downloads live in `tmp/research/` (gitignored): DDG image search, street-view frames, neighbouring buildings — never committed, never shipped. Promotion into `docs/screens/refs/` always passes the sidecar licence + privacy gate and `CREDITS.md`. - **Project agent skills (2026-11-03):** two tracked skills live in `.agents/skills/`: `gig-line-blender-asset-contract` and `gig-line-release-qa`. They are guidance only, add no runtime dependency and are referenced from `tools/README.md`. The third-party `blender-skills` pack remains gitignored. - **TV Studio shares the Hollywood location (owner):** the Late Night TV Studio is the same El Capitan Entertainment Centre block at 6840 Hollywood Blvd (which houses *Jimmy Kimmel Live!*), restaged for a late-night arrival. `VENUE_SPECS["tv_studio"]` is a 30.0 m variant of the 29.6 m Hollywood facade. One reference board serves both venues; never create a separate TV Studio board. - **Jimmy Kimmel Live = starting point:** the Hollywood first shift and the TV Studio arrival both begin at the El Capitan block. Stated in `docs/hollywood-boulevard-map.md` and `docs/gameplay.md`. - **Street-view limitation:** Google Street View panoramas need a Google API key or manual browser capture, so the local reference sets use DDG street-level photos instead. - **Image-recitation filter:** Gemini image models reject well-known branded facades (Comedy Store wall) as generation anchors (`IMAGE_RECITATION`). Workaround: anchor on a less-branded frame of the same block, or generate text-only when the reference adds no geometry. - **OpenRouter image-to-image:** `/v1/images` is text-to-image only; the working path is `POST /v1/chat/completions` with a `content` array carrying a base64 `image_url`. Response images arrive as `message.images[].image_url.url` (fallback: a data URI inside the message text). Implemented in `generate_textures.generate_image(reference_path=...)` and `scene_concepts.py generate/anchor`; provenance sidecars are `.refgen.json`. - **Facade/venue material picks (2026-11-03):** the Masonic/El Capitan facade uses the photo-anchored `limestone_ashlar` field (warm tint `Color(0.85, 0.81, 0.74)`) with `stone_steps` for trim; the Joke Shop uses `dark_stucco`; the marquee canopy/sign box stay `black_metal` by design (fictional "GIG LINE" marquee, not the red El Capitan Theatre marquee next door). The TV Studio reuses `masonic_facade`. Committed Comedy Store refs were removed in favour of the local-only `tmp/research/comedy-store/` set; the three-up board's photo panel is a historical public-domain frame. - **Godot 4.7.2 `--import` gotcha:** with a stale `.godot/` cache, new image files never import (no `.import` created) and `preload()` of the new texture fails every dependent script with the misleading `Could not resolve external class member "spec"`. Fix: import in a scratch copy with `.godot/` removed, then copy back the `.png.import` and `.godot/imported/*.ctex/.md5`. - **Blender skills pack (local aid):** MIT `blender-skills` at `.agents/skills/blender-skills/` (94 skills + shared `references/`), gitignored, kept out of Godot's scanner by `.agents/.gdignore`. Keep the pack flat when updating (`cp -a /.claude/skills/. ...`). Pi gates project `.agents/skills` behind project trust. - **NPC model sourcing (ADR-0015):** CC0 acceptable for shipped assets; CC-BY only with attribution in `CREDITS.md`; no service assets with redistribution restrictions. The procedural generator is the primary path. Quaternius and MakeHuman were evaluated as references only, not shipped bases (Quaternius: 62-bone T-pose rig mismatch; MakeHuman: needs retopology + custom rig + clothing). - **Gaussian splatting (unversioned):** offline/development-only until an ADR proves licence, privacy, Compatibility/Web cost and mesh collision. Accepted photo channels: team captures, permissioned user/volunteer photos, clearly licensed downloads; every set needs a provenance/consent sidecar. No runtime splat dependency in 0.10.0. - **0.10.0 balance protocol:** `docs/balance-acceptance.md` defines targets/denominators for the 36 venue/difficulty/phase cells, seeds 1–100, and access/emergency error rates. Gotcha: `_read_npc_truth()` is type-derived, not behaviour-derived; the emergency-fairness review must account for that oracle mismatch. - **Blender MCP is optional (2026-11-04):** setup and safe part workflow are in `docs/blender-mcp.md`. The headless flatpak scripts remain authoritative; MCP is never a build/CI/test/runtime dependency, and anything MCP touched must pass the CLI audit/fit gates. - **Character mesh cap (2026-11-04):** all characters use 16,000 tris / 16,000 glTF vertices (`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in `tools/blender_assets.py`; `npc_pipeline.MAX_TRIANGLES`/ `MAX_VERTICES`). The required 13 core bones stay, optional extra bones are allowed, and the low-poly `npc_base` fallback remains the shipped fallback. Measure the 24-NPC/Web budget after any geometry change; never silently lower the cap without a new decision. - **Engine baseline:** Godot 4.7.2 stable (official editor `~/.local/bin/godot`). The system's `godot-runner` cannot load arbitrary project paths (compiled without path overrides); use the official editor binary. - **GDScript/architecture:** cross-file references use explicit `preload()` constants (not `class_name`) so scripts compile in headless `--script` runs; the test runner is a custom minimal harness (not GUT, which needs the editor plugin); no autoload signal bus — `ShiftCoordinator` owns the `EventBus` instance passed to subsystems. - **Skinned NPCs (v0.6.0):** `NpcAvatar._build_skinned_body()` loads `npc_base.glb`; procedural `_build_body()` is the fallback. Bone pose rotation is the **absolute local orientation**, not an offset — compose with `get_bone_rest(i).basis` or the body snaps to a broken pose. Five tint slots via `SurfaceMaterials.npc_slot()`; keep `_rig.scale` uniform on the skinned path. - **NPC facing:** the avatar's visual front is **+Z**; Godot `look_at()` aims **-Z** (made NPCs walk backwards). Use `NpcAvatar.yaw_towards(dir)` and set `rotation.y`; never `look_at()` on avatars. Head-turn offsets use `+basis.z` for the same reason. - **Release blockers (still open):** macOS unsigned, AI image provider terms/commercial rights not cleared, storefront/rating approval pending, external playtests and full hardware matrix pending. Recorded in `CREDITS.md`, `PRIVACY.md`, `docs/accessibility.md`, `docs/known-limitations.md`. ## Gotchas / Known Issues - **Two character builders — do not confuse them.** `tools/blender_assets.py` is the real builder for the **eight library characters, 18 gear pieces and props** (it has the 0.9.4 face/hand sculpting in `_face()`/`_hands_and_shoes()`); `tools/npc_pipeline.py` builds **only** `assets/models/generated/npc_base.glb`, the frozen plain-primitive fallback. Reading `npc_pipeline.py` to judge the library's face/hand detail is wrong. - **Pose capture fixed (2026-11-04):** the harness was missing `set_physics_process(false)`/ `set_process(false)` on the avatar, so the FSM (`_advance_fsm` + `_move_toward_target`) ran every tick, reset `arrived`/velocity and made `theater_pose()` revert to idle/rest. Disabling both fixes it (sit vs idle diff 5.63, walk vs idle 2.29). The earlier "moving the `Rig` child does not render" probe was a red herring — it set the position inside `_shoot()` in the same frame as `root.get_texture().get_image()`, so it never rendered a frame. Lesson: pose/portrait harnesses must freeze the avatar's process/physics before driving `_animate()`. - Capture/pose harness gotchas: scripts that read captured-mouse input must run windowed (`smoke_props.gd`); the NPC FSM rewrites `rotation.y` every physics tick, so pose/portrait captures set the yaw after `set_physics_process(false)`; `visible=false` does not keep the crowd out of a portrait frame (park the rest of the crowd away instead). - Blender: `bisect_plane` and thin bevels leave zero-area triangles (Godot "non-finite normal in LOD generation"); `_clean()` + a weld after bisect fixes them and the audit rejects them. - Image models have per-model size floors/caps (Seedream needs 1920+; `GEN_SIZE=2048`, do not lower; gpt-5-image-mini caps lower). `generate_image` applies model-specific sizing; concept generation defaults to `--request-size 1536`. - Local photo research tooling: DDG rate-limits by IP (HTTP 202) — the `refcollect.py` token bucket (0.5 req/s, burst 3) plus retry/backoff handles it; some DDG frames are AVIF mislabeled `.png`/`.jpg` (convert with ffmpeg; `refcollect.py sheet` skips unreadable). Commons API needs a descriptive User-Agent and rate-limits thumbnail downloads (fetch all metadata in one `titles=A|B|C` query). - Frozen decisions: do not re-open `override_access`; do not bind wand to `W`; do not remove the radio easter egg. - GDScript: no static signals; lambdas capture locals **by value** (mutating a captured local does not affect the outer variable — use member functions for accumulators). - Reference cycles: `FSM` holds its owner via `WeakRef`, `ShiftCoordinator` is `RefCounted`; if a new subsystem introduces a cycle, repeat the WeakRef pattern. - Headless (dummy renderer) logs `Parameter m is null` from `mesh_get_surface_count` — benign, does not occur with a real GL driver. - macOS export requires `rendering/textures/vram_compression/import_etc2_astc=true` (universal build demands ETC2 ASTC). - Typed spawn signal: handlers must accept the 4th `event: Dictionary` arg or Godot will not call them. - GDScript inference trap: a `var` typed as a base class (`radial: CanvasLayer`) exposes script members as `Variant`; type it as the script type (`ActionRadial`). `--check-only --script` cannot see the `Save` autoload — use a real boot (`--quit-after 180`) to verify full-scene compile. - Do not snapshot right after changing a transform in the same frame — `capture_screenshots.gd` needs a state/frame gap or the capture uses the old camera orientation. - `EventScheduler` probabilities are per simulated second, not per call; never poll it per frame. - Light falloff: `omni_attenuation = 0.0` is **constant**, not physical, falloff in Godot 4.7 — all local lights use `1.0`. - glTF `baseColorFactor` is LINEAR — writing sRGB makes props pastel; `srgb_to_linear()` is applied in `build_glb`. A new/changed `.glb` needs `godot --headless --import` before `load()` sees it. - **Rebuilding a character GLB also requires re-baking its AO** (`tools/bake_ao.py -- --only `). `blender_assets.py build` writes the GLB only; the old `_ao.png` then maps onto the new geometry and produces large dark cheek/jaw blotches. Bake the AO, re-import, and refresh the stale `.godot` cache (scratch-copy import) before judging a face/body change. - `Save.SETTINGS_FILE` is declared and unused; settings persist through `load_settings()`/`save_settings()`. - Crowd reactions emit `event_bus.crowd_changed`; `GameManager._connect_crowd()` must reconnect after every `coordinator.setup()` (same class of bug as the radio-dispatch reconnect). - Packaging: NSIS uses `-D` defines (never `/D`) and resolves relative defines against the script directory (pass `$PWD`-absolute paths); installed exe must be space-free; ICO must be classic BMP entries (PIL emits PNG-compressed ICOs NSIS can't load). appimagetool runs system `appstreamcli` — pass `--pedantic`. - Credentials: `~/.git-credentials` holds the Gitea creds; `.env` holds only `OPENROUTER_API_KEY`. - `assets/textures/game_ready/` is not imported (no `.import`) — use the imported `assets/textures/skyline.png` path. - Godot 4.7 writes `*.gd.uid` files — kept out of `git status` via `.git/info/exclude`. - Collaborators occasionally leave uncommitted work in the shared tree; check `git status` before committing and leave foreign changes untouched. ## Commands - Tests: `godot --headless --path . --script res://src/test/test_runner.gd` - Boot check: `godot --headless --path . --quit-after 120` - Single screenshot: `godot --path . -s res://src/test/take_screenshot.gd` - Staged gallery: `tools/check_screenshots.sh` (re-run the whole script, never a partial capture) - Blender build: `flatpak run org.blender.Blender --background --python tools/npc_pipeline.py -- build` (flatpak cwd is `/app/blender` and cannot read `/tmp` — use absolute repo paths; `.gdignore` markers keep Godot from importing `.blend`/`tmp`) - Textures: `python3 tools/generate_textures.py` (then `godot --headless --import`); AI mode `python3 tools/generate_textures.py --ai`; general pipeline `python3 tools/asset_pipeline.py` - Character checks: `python3 tools/blender_assets.py inspect|audit|fit`, `python3 tools/npc_proportions.py` - Docs: `python3 tools/check_docs.py` (every tracked image must be linked from its folder README) - Reference research: `python3 tools/refcollect.py search --slug --limit N` (local-only), `tools/refboard.py` (Commons), `tools/three_up.py compose/rows`, `tools/scene_concepts.py generate/anchor/realism` ## Open Questions - Whether the responder avatar should be playable/controllable or purely scripted. - Localization target languages beyond English. - macOS notarization approach for broad distribution. ## Current State The playable build covers the three venues, tutorial/campaign/checkpoints, the 0.9.3–0.9.6 polish/texture/integration passes and the Blender + realism asset pipeline (see `DONE.md` and `CHANGELOG.md`). The 12-character library (realistic face + four crowd variants) and the Comedy Store map rebuild are complete. **Priority (2026-11-04, owner):** 1) the **Blender NPC model overhaul** (head/face/hands, local part pipeline), 2) finish the MakeHuman character + the 3D model and map improvements, 3) ship it all together in the **0.10.0 minor**. The 0.9.7/0.9.8 character patches are published; `TODO.md` is the priority source. Test counts as of 2026-11-04: Godot 277/277, Python 160/160, `check_docs` OK.