194 lines
15 KiB
Markdown
194 lines
15 KiB
Markdown
# 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 <pack>/.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
|
||
<name>`). `blender_assets.py build` writes the GLB only; the old `<name>_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 <q> --slug <block> --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.
|
||
|
||
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
|