Files
security-game/MEMORY.md
T

244 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`.
- **MakeHuman head accepted (2026-11-04, owner):** the basic human head is a pass.
`assets/models/blender_src/makehuman_head.glb` is the CC0 MakeHuman-derived head
(12,642 tris / 7,690 split verts). The automated vision gate still records REJECT on
the eye/lid region (globe depth, lid overlap, iris at three-quarter) as deferred
polish, not a blocker. The assembled path (`build_character(head="mh")`) adds a
skull-conforming hair cap derived from the closed head shell, hides the scalp under
that cap, and narrows the neck/shoulder body. Default head source stays `face` until
the owner reviews the hair/body and the 12-variant rollout starts.
- **MakeHuman hair/body prototype (2026-11-04):** `npc_casual --head mh` is
14,753 tris / 12,822 raw / 12,823 seam-split verts / 14 bones / 9 materials,
`inspect` clean. All 12 variants were counted at 14,693–15,987 tris. Hair uses flat
shading normals because the old hair normal map caused dark tangent patches on the
new cap. Scalp faces fully covered by the hair cap are deleted (saves ~950 tris);
the accepted face geometry is untouched. The procedural body was narrowed at the
neck/shoulders (shoulder root z 1.345, chest 0.158, neck 0.060/0.054) and its skin
decimate dropped to 0.21. The local renders are in
`tmp/npc-parts/prototype_mh/renders/`.
- **MPFB full-body prototype (2026-11-04):** the current body candidate is the local MPFB 2.0.18
pipeline at `tmp/npc-research/build_mpfb_npc.py` (dev-only; no `.mhclo` clothing installed).
It creates a macro-driven human, bakes targets, adds the `game_engine` rig, captures arm
points/weights, removes the rig, applies the helper MASK, scales to 1.81 m, retargets the A-pose
arms to the contract wrist, cuts the neck at 1.505, decimates, bisects the clothing boundaries,
paints the five slots, adds a short neck flare, project hands, rounded shoes and cuffs, the
accepted head and the 14-bone contract armature. Current output: **15,213 tris / 13,391 glTF
verts / 13,398 seam-split verts / 1.810 m / 14 bones / 10 materials**; `inspect` clean and feet
grounded. The neck is a radial cut of the decimated column bridged by a four-ring, 20-segment
lathe (lower rings painted cloth, reads as a crew neck); the ankles are hidden by a bevelled
0.12 m shoe shell, 0.13 × 0.32 m sole and a 0.08 m pant cuff. A light smoothing pass on the closed
thigh/hip region softens the decimated silhouette. Earlier torus collar, cloth cylinder collar,
skin neck-blend sphere and destructive neck `smooth_vert` pass failed: the shells read as a
brace, shelf or donut, and vertex smoothing tore the open collar boundary into a dark hole. An
untucked shirt-hem band at the hips also failed: it read as a hanging white flap at the crotch.
Remaining polish: V-shaped shirt hem at the crotch, skin-tight shirt, angular thigh facets; a
proper shirt shell is the fix. The prototype uses flat slot colours because the one-shot MPFB UV
unwrap does not match the per-slot NPC atlases. Renders:
`tmp/npc-parts/mpfb_proto/renders66/`; body-only: `body_only.glb`.
- **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/venues.md#hollywood-boulevard` 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/release-qa.md#balance-acceptance` 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/asset-tooling.md#optional-interactive-blender-mcp`. 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.
- MakeHuman head/hair gotchas: the imported head is broad and flat, so a separate hair
ellipsoid floats or leaves a bald patch; derive the cap from the closed head shell and
boolean-cut the hairline. Do not reuse the skull's UVs with the old `hair_normal.png`
(dark tangent patches) — hair skips the normal map. Generate the cap before deleting
the hidden scalp, or the cap inherits the hole. The flatpak sandbox cannot read `/tmp`;
helper scripts must live under `$PWD`.
- MPFB gotchas: call `TargetService.bake_targets(body)` before any decimate (shape-key
modifier error); add the built-in rig and capture arm weights before applying the MASK
modifier, or `build_basemesh_position_info` divides by zero on an emptied bone group;
decimate before bisecting the clothing loops, or the decimator collapses the bisect
into a sawtooth. Clear custom split normals after joining (imported MPFB normals leave
flat-shaded thigh facets). Imported GLBs may include helper `Cube`/`Icosphere` meshes at
±1.0, so select `npc_base` explicitly. Delete the MPFB hands by hand/finger vertex-group
weights, not only by `|x|`, or leftover fingers become pants-coloured fins.
- 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 162/162, `check_docs` OK.
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->