253 lines
20 KiB
Markdown
253 lines
20 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`.
|
||
- **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,317 tris / 13,690 glTF
|
||
verts / 13,702 seam-split verts / 1.810 m / 14 bones / 10 materials**; `inspect` clean and feet
|
||
grounded. The torso is inflated radially by 12% through z 0.72–1.40 to loosen the shirt. 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.11 m shoe
|
||
shell, an inset 0.111 × 0.286 m sole, a tongue and a 0.08 m pant cuff. Sleeve cuffs use the
|
||
`cloth` slot and the `accessory` slot is carried by the sole. 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.
|
||
Protruding flat sole plates read as skates/bricks and were replaced by an inset sole under a
|
||
rounded shell; laces were dropped for triangle budget. The neck skin rule needed a `z > 1.35`
|
||
guard: without it the `radius < 0.08` test painted skin at the crotch. Lowering the hem to
|
||
z 0.80 (unitard), tilting it up at the front (high-cut swimwear) and a 13% hem flare (peplum)
|
||
were all rejected; the remaining crotch V is the dark hollow under a body-painted shirt. A quick
|
||
open, flared solidified tube (0.150→0.196 m, z 1.05→0.80, 24 segments, ~288 tris) also failed:
|
||
it read as a hanging apron/tabard. The real fix is a shaped shirt shell with armholes and a
|
||
curved hem, not a tube. Remaining polish: crotch V, partly loosened shirt,
|
||
angular thigh facets. 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/renders84/`; 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 -->
|