Files
zwitschi a196a35342
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
Give Hollywood and the TV Studio the same queue story and validate every venue's placements
2026-11-06 20:55:52 +01:00

34 KiB
Raw Permalink 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/project/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. Durable evidence: docs/assets/3d-model-review.md §AI-driven head rework; the retired head-review page was removed in the 2026-11-06 docs restructure.

  • 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/design/venues.md#hollywood-boulevard and docs/design/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/process/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/assets/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) and the mesh keeps source LOD (arm-down rest pose baked). The 24 upstream clips are not played: _animate() drives the proxies and _sync_skeleton() writes the bone poses, which is what the FSM already needed for sitting, hand-raising and head turns. Playing the clips would replace that state machine, not supplement it, so all 24 stay unused and the clips are authoring reference only.

  • 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/design/accessibility.md, docs/design/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): see _swing_bone() in the gotchas — the rest-relative set_bone_pose_rotation form was abandoned; the global-pose write replaced it.

  • 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.

  • Emissive materials can carry a per-material night energy. set_night_mode used to write one multiplier into every _marquee_emissive entry, which flattens any gradient. It now reads mat.get_meta("night_energy", 2.0), so a multi-strip panel can keep its top-to-bottom ratio (the event banners: 2.6 → 1.4).

  • placement_problems() was only gated for the Joke Shop. Its test validated one venue's prop list, so the TV Studio carried six violations (props inside the steps/door exclusion and off the sidewalk) unnoticed. test_every_venue_placement_clears_the_sidewalk_and_the_approach now runs the validator on all three venues. When adding a venue or a prop, that test is the gate.

  • Venue lights must keep omni_attenuation == 1.0 — test_world.test_venue_lights_use_physical_falloff enforces physical falloff on every Omni/Spot in all three venues, so a "pool" cannot come from sharpening the curve. Use omni_range + energy instead: 10 m / energy 8 gives a brighter pool and a darker gap than the old 16 m / 2.2.

  • Tune lights against a measurement, not a look (2026-11-06): the shipped flashlight changed the late-night frame by 0.1/255 and nobody noticed. capture_realism.gd --stages=late --flashlight renders the same frame with the lamp on and off; mean luminance of the aimed region and the near pavement is now the acceptance number. Current: energy 10, range 45 m, cone 24°, tilted 5° down → 13.5 → 29.7 aimed, 3.7 → 11.7 near ground.

  • Docs live in category folders (2026-11-06): docs/project/ (architecture, development status), docs/design/ (gameplay, venues, ui-copy, accessibility, known-limitations), docs/assets/ (asset tooling, blender assets, npc sourcing, 3d-model-review, makehuman head), docs/process/ (getting started, release QA, releasing), plus docs/adr/, docs/screens/, docs/screenshots/. Write to the new paths; docs/*.md at the root no longer exists apart from README.md.

  • Foliage is leaf cards, not sphere stacks (2026-11-06): _shrub() is one low-poly inner mass plus 18–53 two-triangle cards scattered on the canopy shell by a jittered Fibonacci sequence, each rotated onto the canopy normal and tilted off it. No alpha texture, so the flat-colour library and the Compatibility renderer are unaffected — and it is ~35 % cheaper than the sphere stack it replaced. If a shrub ever reads as a rock again, the cards were probably rotated to the normal without the random tilt (spread).

  • Roster is eleven outfits over twelve roles (2026-11-06): hoodie, Casual, Worker, Suit, Adventurer, Beach, Casual_2, Farmer, King, Punk (Swat is intaken but unused — it faces Godot −Z and every yaw attempt broke the skin: node rotation turns the skin with it, object rotation collapses it, and data-space rotation after transform_apply still loses the head). Per-family slot maps live in QUATERNIUS_SOURCES; a family that cannot paint a slot (Farmer has no hair) gets patch_slots, which buries a 4 cm triangle so the material survives the export and the contract holds.

  • FBX modular parts need five fixes before they are a base (2026-11-06): (1) every FBX file carries its own armature — keep one and repoint the meshes; (2) FBX adds _end leaf bones and a neutral_bone root, and Godot refuses the GLB entirely (resource_format_binary / missing .scn) until they are stripped to the 62 joints; (3) bake object transforms before export or the glTF node transforms change the rest pose and the head renders several times too large; (4) the pack reuses one fabric material for jacket and trousers — copy it per leg mesh (*_legs) so the slot map can send it to pants; (5) imported parts share seams, so dissolve and drop zero-area triangles or the audit fails.

  • tmp/ needs a .gdignore. Godot scans the project tree, so a downloaded asset pack under tmp/quaternius-intake/ gets imported (it flooded the import log and broke --import for the rest of the project). An empty tmp/.gdignore stops it; capture output there is unaffected.

  • Modular part intake is a one-time manual download (2026-11-06, owner): the upstream pack is a Google Drive folder, so the owner downloads it once into the gitignored tmp/quaternius-intake/ and tools/quaternius_parts.py does the rest (inventory → extract → verify). A part must be one skinned mesh on the 62-joint rig; a GLB exported without an armature has no JOINTS_0 and validates as bone-less, so extract keeps the armature and build drops it again at join time.

  • Hair variety is built in-project (2026-11-06): the upstream modular pack is a Google Drive folder, so it cannot be fetched reproducibly; QUATERNIUS_HAIR_PARTS reshapes the one hair shell each base ships (short identity, buzz squash, long stretch, bald removal) and the role's existing CHARACTERS[...]["hair"] picks it. bald is restricted to bases whose skin closes the skull. Outfit variety still needs the pack or new garments.

  • No hats except the firefighter helmet (2026-11-06, owner): the iconography is not worth the cost — a cap or hood on this head hides the face, and every hat needed a lift+scale hack to clear the 0.228 m hair shell. ACCESSORIES maps no role to cap/hood, the guard/vest per-character caps are gone, the performer cone was removed, and the primitive cap/hood fallbacks were deleted with their GEAR_OFFSETS/GEAR_SCALE compensation. firefighter_helmet is the only head item. Do not re-add head gear without an owner decision and a fit measurement.

  • Mouths are authored, not upstream (2026-11-06): none of the three Quaternius heads has a mouth material, so the faces read as brow-and-eye masks. _add_quaternius_mouth() places a small lip block on the measured skin surface (front-most skin vertex in the 1.600–1.632 m band, ±35 mm of centre) at 1.616 m. One rule covers all three bases; it runs before the slot map, so match the material as _base_name(m.name).lower() == "skin".

  • 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

  • Blender material-list edits corrupt polygons silently. Two separate bugs, same root: obj.data.materials.clear() resets every polygon to slot 0, so the per-face indices must be captured before the clear and reassigned after; and IDMaterials.pop(index=) repoints the polygons that used the slot, which once painted a whole face with the eye material. The safe pattern is _purge_unused_materials(): read the per-face material names, rebuild the slot list from the names, then write the indices back by name.
  • A zero-user material still owns its name. Removing a slot leaves the datablock behind, so a donor material renamed to the freed name lands as Eye.001 and every m.name == "Eye" rule silently misses it. Purge the datablocks (bpy.data.materials.remove(m) when m.users == 0) or match on _base_name().
  • A duplicated detail face has to be offset along its own normal. Copying the eye quad into a pupil and nudging it in world -Y vanished: the eye plane is tilted, so the "front" nudge went backwards and the pupil sat inside the sclera. Offsetting along face.normal * 0.0032 fixed it. Also scale the depth axis when shrinking a copy, or it flattens onto the parent plane.
  • Blender node colours are linear; Godot tonemaps them. A mouth lip at 0.16 linear rendered as salmon/mauve. Divide by roughly 3–4 versus the intended sRGB value: the hair uses 0.027 linear, the lip 0.055/0.018/0.02.
  • The modular Quaternius Worker has no torso under its vest and no crown under its hard hat. Deleting either material leaves real holes (that is what shipped in the first 0.9.9 build: guard and firefighter showed the inside of the head and torso). Remap instead of deleting; the only geometry the build removes is the Worker brow bar, which is replaced by a donor part.
  • Head gear authored for the 0.315 m procedural head is wrong on the 0.20 m Quaternius head. Gear is authored around the head centre (1.62) while the attach node is the Head bone (1.587), and the Quaternius centre is 1.69 — a 7 cm error put every cap brim across the brows, which I spent several passes misreading as a mesh hole (the band was pure black and no material had faces there). HEAD_GEAR_LIFT = 0.103 fixes the common case. With the hats removed this only affects the firefighter helmet.
  • 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.9 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 … 9fd4824): all twelve characters run on the Quaternius bases (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). The owner signed the roster off with follow-ups and rejected three review rows; the Worker body defects (holes, unibrow, missing eyes) and the head-gear seating are fixed, every hat except the firefighter helmet is removed, and every head has an authored mouth. Gates: audit 12/12, inspect clean, fit OK, 24-NPC budget 174,976 tris / 0.85 ms, Godot 278/278, Python 167/167. The owner accepted the roster on 2026-11-06 and the review page plus its sheets were then retired; durable evidence lives in docs/assets/3d-model-review.md and docs/assets/blender-assets.md. Release packaging was 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 writes a SHA256SUMS file.

Open after the swap: hand-held gear and role props are still authored for the old body (wrist y 0.78 → 1.017, arm shorter), so the clipboard and umbrella are oversized; the eyes have no whites; the arms graze the torso on the worker base; roster variety is three meshes for twelve roles. The prop/gear fit pass is queued in TODO.md under 0.9.10.

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 the face-gate decision (owner chose the full swap).

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

Docs layout (2026-11-06): docs/ is grouped into project/, design/, assets/ and process/; adr/, screens/, screenshots/ and blender_previews/ are unchanged. docs/README.md is the index and every category folder has a README (required by tools/check_docs.py). The retired docs/screens/head-review/ page and its sheets were deleted; the MakeHuman fallback head is documented in docs/assets/makehuman-head-review.md.