Move the gap analysis and risks into PLAN.md, version the character track (0.9.9 rig correction, 0.9.10 roster) and split 0.9.11-0.9.12 polish and verification out of the old 0.10.0 milestone. Add roadmaps to 0.10.0, 0.11.0 and 1.0.0, record the Quaternius face-fidelity gate and the rest-relative pose finding in MEMORY.md, and move the completed 0.9.7 and pose-harness sections to DONE.md.
254 lines
20 KiB
Markdown
254 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`.
|
||
- **AI-assisted asset review loop (2026-11-04):** head/face shape decisions use a concept +
|
||
judge loop — generate a target concept (`generate_textures.generate_image`, Seedream) and use
|
||
`tools/describe_image.py` as the vision judge, iterating until the judge's specific fixes
|
||
converge. Review pages: `docs/screens/head-review/README.md`, `docs/3d-model-review.md`
|
||
§AI-driven head rework.
|
||
- **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.
|
||
- **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-05):** all characters use 24,000 tris / 24,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. The raise funds the Quaternius base bake and
|
||
the 12-variant clothing detail. Measure the 24-NPC/Web budget after
|
||
any geometry change; never silently lower the cap without a new decision.
|
||
- **Quaternius is the NPC base (2026-11-05, owner):** ADR-0017 supersedes the generator-first
|
||
sourcing decision. The CC0 Quaternius Ultimate Modular character pack is the primary NPC mesh
|
||
and animation source. Stage 1 bakes a proportion-warped, 13-core-contract `npc_base` so
|
||
`NpcAvatar`, gear and animations keep working; the 62-joint/24-clip source rig is authoring
|
||
input, not a runtime dependency. If the warp fails the silhouette gate, Stage 2 needs a new ADR
|
||
for a 62-joint runtime, not a hybrid. The MakeHuman head and procedural `npc_base` remain
|
||
tested fallbacks until the Quaternius rollout ships. Quaternius source: CC0, attribution in
|
||
`CREDITS.md`; the three researched GLBs and licence notes are kept in
|
||
`tmp/npc-research/quaternius/`. MPFB/MakeHuman body work is closed; do not add new work there.
|
||
- **Quaternius bake keeps source detail (2026-11-05, owner):** during the contract bake (§2),
|
||
keep the Quaternius mesh as close to source as possible — no unnecessary decimation or LOD
|
||
reduction. The canonical base is only 3,199 verts / 6,206 tris, far under the 24,000 cap, so
|
||
there is no budget reason to decimate before the silhouette gate. Do not reuse the old
|
||
7,000-triangle or MPFB decimation targets.
|
||
- **Quaternius runtime rig (2026-11-05, owner, ADR-0018 option 2):** the Stage 1 proportion warp
|
||
failed the silhouette gate (arm ×1.63 / leg ×0.74 / torso ×1.61; vision judge: paddle hands,
|
||
lost forearms, stretched shins). The owner chose to **adopt the Quaternius 62-joint rig at
|
||
runtime** instead of warping: `NpcAvatar` maps the Quaternius joint names, gear attach points
|
||
are re-derived from the Quaternius skeleton (hip ≈0.87, shoulder ≈0.148/1.39, wrist ≈0.99,
|
||
knee ≈0.52), the mesh keeps source LOD (arm-down rest pose baked), and Idle/Walk play directly
|
||
on the 62-joint rig (no retarget).
|
||
- **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`.
|
||
|
||
- **Release roadmap (2026-11-05, owner):** `0.9.9` Quaternius pilot base (rig/export fix,
|
||
rest-relative pose math, owner face/silhouette gate), `0.9.10` 12-role roster, `0.9.11`
|
||
scheduled venue/prop/lighting polish, `0.9.12` performance/Web/accessibility verification,
|
||
`0.10.0` public-readiness minor, then `0.10.1`–`0.10.3` fixes, `0.11.0` replay + fourth venue +
|
||
localization foundation, `0.12.0` signed/storefront builds + save v2, `0.13.x` content complete
|
||
and release candidates, `1.0.0` stable. The full gap analysis and roadmap live in `PLAN.md`; the
|
||
character track is versioned now, so a blocked rig cannot block the release. Planning only —
|
||
none of these versions is built yet.
|
||
- **Quaternius face/readability gate (2026-11-05):** the base is 6.2k tris total and replaces a
|
||
library with a sculpted realistic face, so the 0.9.9 gate is a product decision, not just an
|
||
art check. Owner picks full swap / crowd-only / extra head detail. Do not lower character
|
||
fidelity silently.
|
||
- **Pose composition is rest-relative (2026-11-05, hypothesis to verify at 0.9.9):** Godot's
|
||
`set_bone_pose_rotation` offsets from the bone rest, so `rest * Quaternion(axis, angle)` only
|
||
equals the target when the rest is ±identity (the old 180° rig). The candidate fix is
|
||
`rest.inverse() * target`, proven with measured global bone transforms.
|
||
- **One character source, one fallback:** no new NPC source without an ADR. MPFB body work stays
|
||
closed, the Stage-1 proportion warp is not reopened, and the procedural `npc_base` + MakeHuman
|
||
head stay tested until the Quaternius base passes its gate.
|
||
|
||
## 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.
|
||
- Quaternius arm-down bake: pose-rotate `UpperArm.L/R` −90° local X → apply Armature modifier →
|
||
`armature_apply` → re-add modifier bakes the rest, but `export_apply=True` writes wrong inverse
|
||
bind matrices (Godot renders A-pose even though the rest is arm-down). The runtime `_pose_bone`
|
||
`rest * Quaternion(axis, angle)` only cancels when `rest² = identity` (old rig's 180° rest); the
|
||
Quaternius ~76° rest does not, so the pose formula must be re-derived for a non-180° rest.
|
||
Godot's bone pose rotation is relative to the bone rest, not absolute: treat
|
||
`rest.inverse() * target` as the candidate and verify with measured global bone transforms.
|
||
- 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)
|
||
- Head/part pipeline: `python3 tools/npc_parts.py build|extract|assemble|inspect|sheet|compare`
|
||
(Blender subcommands run under flatpak). Head sources: `build_character(head="face"|"proto"|"mh")`.
|
||
- 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.
|
||
- Quaternius scope: full 12-role swap, crowd-only, or extra head detail (owner decision at 0.9.9).
|
||
- Localization target languages beyond English (0.11.0/0.12.0).
|
||
- macOS notarization approach for broad distribution (0.12.0).
|
||
- Fourth-venue choice for 0.11.0 (candidate: TCL Chinese Theatre forecourt).
|
||
|
||
## Current State
|
||
|
||
v0.9.8 is published. The build covers the three venues, the shift loop, tutorial/campaign/
|
||
checkpoints, the 0.9.3–0.9.8 polish/texture/integration/character passes and the Blender +
|
||
realism asset pipeline (see `DONE.md` and `CHANGELOG.md`). The 12-character procedural library
|
||
(realistic face + four crowd variants) and the Comedy Store map rebuild are complete.
|
||
|
||
**Planning pivot (2026-11-05, owner):** the character and map work was re-sequenced into
|
||
patches (`0.9.9`–`0.9.12`) with `0.10.0` as the public-readiness minor. The gap analysis and the
|
||
three release roadmaps now live in `PLAN.md`; `TODO.md` is the executable task list. ADR-0017
|
||
Stage 1 (proportion warp) is dead; ADR-0018 option 2 (62-joint runtime) is accepted. The
|
||
`quaternius_base.glb`, joint map, slot map and `NpcAvatar` name mapping exist, but the arm-down
|
||
skin export and the pose composition are still wrong — no Quaternius character has rendered
|
||
correctly in Godot yet, and no character GLB has been rebuilt since the pivot.
|
||
|
||
Test counts as of 2026-11-05: Python 167/167 (last run), Godot 277/277 (last run before the
|
||
docs-only pivot), `check_docs` OK.
|
||
|
||
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
|