Files
security-game/MEMORY.md
T
zwitschi c8875a3dfa
Full build / Import + headless tests (push) Successful in 44s
Full build / Full desktop build + optional release (push) Skipped
Turn the NPC visual review into the 0.9.9 roster signoff page with committed sheets
2026-11-06 14:05:49 +01:00

24 KiB
Raw Blame History

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 (resolved 2026-11-05): Godot's set_bone_pose_rotation offsets from the bone rest, so the correct swing around a world axis is rest * Quaternion(rest.inverse() * axis, angle). For the old 180° rest this reduces to the legacy rest * Quaternion(axis, angle), so the 13-core fallback is unchanged (commit 8b1bf73).

  • Full roster swap (2026-11-05, owner): at the 0.9.9 face gate the owner chose option (a), full swap. All twelve public character names are baked from the three CC0 Quaternius sources (casual, hoodie, worker); variety comes from the five tint slots, the outfit rules and the gear library. The procedural library + MakeHuman head stay in the tree as the tested fallback (build --source procedural), not in the shipped roster. Silhouette variety is the known regression — three meshes for twelve roles — and the modular part library is the 0.9.10 fix.

  • 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 (working recipe, tmp/npc-research/bake_armdown_v5.py): pose-rotate UpperArm.L/R −90° local X → read the pose matrices → apply the Armature modifier → reset the pose to identity → set each arm bone's rest matrix from its pose matrix → re-add the modifier → export export_apply=False. Missing the pose reset double-applies the arm-down (mesh ±0.53 instead of ±0.23). armature_apply in Blender 5.2 is broken (deforms the mesh but does not bake the rest). The canonical base must be join-only (build_base_minimal.py: no remove_doubles weld, no scale/ground transform, keep the Armature modifiers) — the weld and the scale/ground transform corrupt the skin weights (arm poses to ±0.51 instead of −0.23).
  • Quaternius NpcAvatar wiring (commits 2a6f29f, cd285b3): RIG_BONES + NpcAvatar.bone_name(joint, side) hold the only side mapping — the Quaternius labels L/R opposite the project, so proxy side 0 (-x) drives UpperLeg.R/UpperArm.R while side 1 drives the .L bones. The arm/elbow swing axis is Vector3.LEFT (legs stay RIGHT). The hand proxy is global_wrist − global_elbow (subtracting the parent-relative elbow.position misplaces handheld gear). Old 13-core names resolve through the same table.
  • Bone posing must write the global pose (NpcAvatar._swing_bone): reset_bone_pose() then set_bone_global_pose(Transform3D(Basis(swing * global_rot), current_origin)), conjugated with get_bone_global_rest(). Both rejected alternatives fail: the rest-relative set_bone_pose_rotation(rest * Quaternion(rest⁻¹·axis, angle)) swings about the parent-relative rest (the glTF importer already folded the axis conversion into it) and splayed the Quaternius arms sideways; the plain rest⁻¹ * swing * rest conjugation flips the limbs 180°. Verified by measuring get_bone_global_pose() after the call — posed bone axis matches the proxy pivot at dot 1.000 on the Quaternius and the procedural rigs render unchanged. Two caveats: children need the parent's swing passed in (the write is absolute), and set_bone_global_pose() is a no-op outside the scene tree, so the headless runner (tests run in _initialize()) cannot assert on it.
  • The Quaternius source UV map is degenerate — every loop at (0,0), flat-colour materials, no atlas. The AO/texture bake silently produces a 100 % black map, which tints every AO'd slot (skin, hair, accessory) black in Godot: a black leg is the symptom. build_base_minimal.py now Smart-UV-unwraps and repacks the layout (texel density 0 → 73–74 px/m on 256²), and the base pipeline is two explicit steps with distinct files: build_base_minimal.py (quaternius/Casual_Hoodie.glb → quaternius_tpose.glb, joins without welding, renames the materials to the five tint slots, unwraps) then bake_armdown_v5.py (quaternius_tpose.glb → quaternius_base.glb + assets/models/blender_src/npc_casual.blend). Re-running v5 on its own output double-bakes the rest.
  • Hand gear must be self-contained: the camera carried a 0.42 m neck-strap loop that floats when the item is parented to the wrist. Hand items get a short wrist strap, not a neck strap. The backpack straps were authored for the wider procedural chest and floated ~5 cm proud of the Quaternius torso; the chest points now sit at x 0.12 / Godot z 0.15, which fits both.
  • 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.

Full roster (0.9.9, commits a6ccd86, 04d211b, b3acf37): all twelve characters run on the Quaternius bases (4,458–6,424 tris, 62 joints, 73–74 px/m texel density), built by the shipped tools/blender_assets.py two-pass pipeline (_quaternius_tpose_glb → _bake_quaternius_rest). Gates: audit 12/12, inspect clean, fit OK, 24-NPC budget 160,608 tris / 0.84 ms, Godot 278/278, Python 167/167. The owner visual signoff page is docs/npc-visual-review.md. Release packaging was also fixed the same day: excluding tmp/, build/, docs/, tools/, src/test/ and .agents/ from the export presets cut the Linux binary from 426 MB to 273 MB, and tools/build_releases.sh now writes a SHA256SUMS file.

Quaternius pilot (2026-11-05, commits cd285b3, fb3e75a, 90f189a): npc_casual ships as the Quaternius base. It renders arm-down, carries the five tint slots, plays the Idle/Walk/Sit pose code on the 62-joint rig, and the procedural rigs are visually unchanged. Verified: inspect (no issues), audit OK (6,206 tris / 12,988 verts, 62 bones), fit head 0.201 m, texel density 73–74 px/m, npc_proportions measured, 24-NPC budget smoke OK (168 meshes, 162,304 tris, 0.82 ms avg frame). The face/silhouette gate sheet and the three options are in docs/screens/npc-face-gate/; the owner decision is the only thing left before the 0.9.9 rollout.

Test counts as of 2026-11-05: Python 167/167, Godot 278/278, check_docs OK.