From 78267918bfa8681b7f84657bd16d735e2c87251b Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Tue, 3 Nov 2026 16:35:19 +0100 Subject: [PATCH] Trim MEMORY.md: drop superseded handoffs and release anecdotes, fix stale counts and the sign-in contradiction --- MEMORY.md | 363 ++++++------------------------------------------------ 1 file changed, 37 insertions(+), 326 deletions(-) diff --git a/MEMORY.md b/MEMORY.md index 12ff322..fe8d327 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -131,7 +131,8 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o P0/P1 only, and must not change the `npc_base` contract, renderer or 0.10.0 candidate. The MIT Blender skill pack is now routed through `AGENTS.md` §Local Blender skills; skill advice loses to the project contract, and there is no Blender MCP here (headless flatpak scripts instead). - MEMORY is ~960 lines with dated historical entries; trimming it is a tracked maintenance task. + MEMORY was trimmed to ~800 lines on 2026-11-03 (historical handoffs and release anecdotes + dropped; gotchas kept); further trimming is tracked in `TODO.md`. - **GLB intake inspection (`blender_assets.py inspect`, 2026-11-03):** runs inside Blender, clears parents and applies object transforms, then reports tris/budget, raw and seam-split verts, Godot-space bounds (height/feet for characters), bones, materials, UVs, skinning and @@ -279,7 +280,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o `Materials.NPC_FABRIC_UV_SCALE = Vector3(4,4,1)`, and `NpcAvatar` skips the baked per-character AO on those two slots. The 256² AO was magnifying into large blotches; the fabric now reads as a fine weave. Skin/hair/gear keep baked AO. Test `test_fabric_uv_scale_avoids_the_baked_ao_pattern` - guards this. Current suite is 272/272. + guards this. Current suite is 276/276. - **NPC visual baseline (0.9.4, 2026-11-02):** `src/test/capture_npc_baseline.gd` is the neutral-studio 8-variant × 6-view baseline (front, three-quarter, profile, back, full body, crowd). It uses a fixed orbit camera, mid-grey studio backdrop, key/fill lights, no HUD/crowd/animation; @@ -295,45 +296,13 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o stylized guide in `docs/npc-proportion-audit.md`; head ~0.190 (upper 0.22) and broad-build shoulders ~0.370/hip ~0.225 are near the upper bounds, not outliers. No skeleton/mesh rescale was needed; run the report after any body change. -- **NPC neck/shoulder transition (0.9.4, 2026-11-02):** the body graph uses a fuller neck base - (1.43 m, radius 0.074/0.072), a taller/thicker neck top and a shoulder root lowered from 1.39 to - 1.37 m. A final triangulate + tiny-face delete in `build_character` removes zero-area triangles - that the new neck branch exposed in jacket/hoodie. All eight GLBs and AO maps were rebuilt; - `npc_proportions.py` still passes and `sheet_npc_baseline_compare-body.jpg` is the review image. -- **NPC face-depth pass (0.9.4, 2026-11-02):** recessed the sclera to `ey=-0.134` and added a - thin upper lid; removed the separate jaw/cheek spheres and instead sculpt front head vertices - (`_shape_head_mesh`) around a chin plate and cheekbones. This avoids dark AO intersection rings. - Moved lips forward to stay on the reshaped surface. Latest review sheet: - `sheet_npc_baseline_compare-face.jpg`; max character is now 4,754 tris / 4,574 verts. -- **NPC torso/hip transition (0.9.4, 2026-11-02):** pelvis radius 0.165→0.168, waist - 0.150→0.146, chest 0.170→0.168, upper chest 0.172→0.170. This is a subtle shirt taper, not a - skeleton change; all proportions pass and `sheet_npc_baseline_compare-torso.jpg` records it. - Max character is 4,754 tris / 4,584 verts. -- **NPC footwear pass (0.9.4, 2026-11-02):** added `ShoeToe`/`ShoeHeel` spheres to the casual shoe - and `BootToe`/`BootHeel` to responder boots; both stay above the sole/boot contact plane. Current - counts are 4,482–5,474 tris / 4,260–5,494 verts under 8,000/8,000. Sheet: - `sheet_npc_baseline_compare-shoes.jpg`. -- **NPC hand-item forward offset (0.9.4, 2026-11-02):** `NpcAvatar.HAND_ITEM_FORWARD = - Vector3(0,0,0.04)` is added in `_attach_gear()` for every `GEAR_ATTACH` hand item after the - head/chest offsets. Hand gear is authored around the wrist proxy; without this, phone, - flashlight, cup and clipboard were partly inside the palm. Fingers are still open, so true - curl/thumb contact and umbrella/sign grips stay open. Comparison: - `sheet_npc_hands_grip_compare.jpg`; regression assertions cover camera and flashlight positions. -- **NPC headgear clearance (0.9.4, 2026-11-02):** after the face-detail pass, the 96 - final-context frames confirm the cap, firefighter helmet, hood and sunglasses clear the refined - brow/eyes; `blender_assets.py fit` stays clean, so no gear was rebuilt. The broad helmet brim is - intentional turnout language, not a fit failure. -- **NPC face-detail pass (0.9.4, 2026-11-02):** Nose tip radius reduced to 0.022 with ala wings, - the two dark lip boxes became one shallow `Mouth` sphere (two blobs read as a moustache), and the - ears gained a dark `EarInner` concha. All eight rebuilt; current counts are 4,226–5,218 tris / - 4,119–5,240 verts under the 8,000/8,000 cap. Sheet: - `sheet_npc_baseline_compare-face-detail.jpg`. -- **NPC hand pass (0.9.4, 2026-11-02):** palm radius 0.046 at z .742, scale (.80,.62,.88); - fingers are `tube` paths from z .758 to .681–.694 with tip spheres, thumb is a bent tube. The - optional hand capture is `capture_npc_baseline.gd -- --view=hand` (8 frames, baseline stays 48). - `npc_firefighter` is at 5,240 verts after the face-detail pass; the library cap is 8,000 (see - the character-mesh-cap decision above), so grip/clothing detail still has headroom. Grip offsets - remain open. +- **NPC 0.9.4 visual passes (2026-11-02, consolidated):** the neck/shoulder, face-depth, + torso/hip, footwear, face-detail and hand passes all landed and are baked into + `tools/npc_pipeline.py` / `NpcAvatar`. The durable bits are the `NpcAvatar.HAND_ITEM_FORWARD = + Vector3(0,0,0.04)` offset (hand gear is authored around the wrist proxy) and the zero-area-triangle + cleanup after the neck branch (see the Blender-tooling note in Known Issues). Exact per-pass + values, review sheets and current per-variant counts live in `docs/3d-model-review.md` and the + code; the library cap is 8,000/8,000. Open: true finger curl/thumb contact and umbrella/sign grips. - **UI flow smoke (2026-11-02):** `smoke_ui_flows.gd` boots the real scene and exercises inspection allow/deny, normal vs emergency radio, Help filtering, wand allow/deny and review return-to-menu. Keep this smoke in the 0.9.6 @@ -389,7 +358,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o cover 0.8 settings/campaign/history and a 0.9.0 checkpoint. `_load_json` uses `JSON.new().parse()` so corrupt player files fall back silently instead of logging a parse error; atomic writes leave the old file intact when the temp - path cannot be written. The migration suite is 9 tests; total 272. + path cannot be written. The migration suite is 9 tests; total 276. - **0.9.6 integration (2026-11-03):** no gameplay/content changes. A clean detached worktree at `f5b8ef2` rebuilt Linux/Windows/macOS/Web/HQ without the dirty tree's ignored `tmp/`/`build/` payload; the in-repo build was 1.6–3.1 GB @@ -397,21 +366,11 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o artifacts from a clean checkout/worktree. Verification: 263/263 tests, 101/101 Python, 14 smokes, 24-NPC 6.90 ms / 168 meshes / 131,616 triangles, gallery 21/0, screen judge 21/21, realism judge overall 2.1 / gap 0.291. -- **Release v0.9.6 (2026-11-03):** commit `4bb2896` tagged `v0.9.6`; Gitea - Actions run 1296 passed checks and the full build and published seven assets - (Linux, Windows, macOS, NSIS setup, AppImage, Web, Desktop HQ). Release page: - `https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.6`. This is - the integration checkpoint before 0.10.0; do not reuse the tag. -- **Release v0.9.5 (2026-11-02):** commit `4bc6390` tagged `v0.9.5`; Gitea Actions run - 922 passed checks and the full build and published seven assets (Linux, Windows, - macOS, NSIS setup, AppImage, Web, Desktop HQ). Release page: - `https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.5`. Do not - reuse the tag; future fixes need a new patch tag. -- **Release v0.9.4 (2026-11-02):** commit `a57b86c` tagged `v0.9.4`; Gitea Actions - run 916 passed checks and the full build and published seven assets (Linux, - Windows, macOS, NSIS setup, AppImage, Web, Desktop HQ). Release page: - `https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.4`. Do not - reuse the tag; future fixes need a new patch tag. +- **Release v0.9.4/v0.9.5/v0.9.6 (2026-11-02/03):** tags `v0.9.4` (`a57b86c`), + `v0.9.5` (`4bc6390`) and `v0.9.6` (`4bb2896`, the 0.10.0 integration checkpoint) each + published seven assets (Linux, Windows, macOS, NSIS setup, AppImage, Web, Desktop HQ) with + full CI runs; release pages live under `releases/tag/`. Never reuse a tag — future fixes + need a new patch tag. Full per-version records stay in `CHANGELOG.md`. - **Hollywood landmark map (2026-11-02):** the default venue is the real El Capitan Entertainment Centre block at 6840 Hollywood Blvd, not a generic brick entrance. Its reference/mapping notes live in `docs/hollywood-boulevard-map.md`; the map-budget test @@ -484,224 +443,6 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o **Mockup copies:** 21 design references live in `assets/images/ui/`; `docs/screens/designs/` remains the reference-only copy. `tools/screen_designs.py` reads/writes the runtime root. -## Handoff — historical snapshot (2026-11-01; superseded by Current State / Plan vs code) - -- **Status (2026-11-01, after the venue-geometry pass):** v0.7.0 was released at that point. Post-v0.6.0 the - realism pass, the visual-alignment pass and the NPC face/hand pass all landed. Headless - **272/272**, Python tooling **108/108**, `tools/check_screenshots.sh` **21 good / 0 bad**, - `screen_designs.py compare` 11 good / 0 bad, `scene_concepts.py audit` all GOOD, - `blender_assets.py audit` exit 0, `smoke_npc_budget` OK (16 avatars, 6.94 ms of 50 ms). - `PLAN.md` is compact; README/architecture track the current controls, asset pipeline, CI and - release artifacts. The historical notes below remain useful for implementation gotchas; for - current tasks use `TODO.md`. - -- **Phase 2 — AI texturing (this agent, DONE 2026-11-01).** Phase 1 assets were - already in the tree (untracked) and are preserved. Generated six seamless AI - clothing albedos into `assets/textures/npc/ai/{shirt,trousers,jacket,vest, - coat,dress}.png` (512×512, `--role tile`, gpt-5-image-mini; audit 6 good). - **Gotcha:** `asset_pipeline.py generate` defaults to `GEN_SIZE=2048`, which - gpt-5-image-mini rejects (HTTP 400); pass `--request-size 1024` for 1:1 roles. - `generate --role tile` already applies `make_seamless`, so a separate - `seamless` pass is redundant. Added to `tools/npc_pipeline.py`: `prepare` - (layers AI shirt→cloth, trousers→pants albedos at 256px) and `palette` - (writes `assets/textures/npc/palette.json` via `model_pipeline.dominant_colors`). - `write_slot_textures()` now skips the albedo for slots with an AI override - (normal/ORM stay procedural). Blender `build` re-run: GLB audit ok, 1204 tris / - 2488 verts, AI albedo embedded for cloth+pants. Phase 2 completed and shipped with - the v0.6.0 asset library. - **Import concurrency:** a parallel agent is running - `timeout 300 godot --headless --editor --quit --path .` and then checks - `assets/models/generated/npc_base.glb.import` — treat that as the canonical - import. My own `godot --headless --path . --import` hung with no output past - the engine banner (two Godot instances contended on `.godot`); I killed the - orphan (PID 2482934). Do not run a second import while that `--editor --quit` - is active. - Next: Phase 3 (`NpcAvatar._load_model` + slot tinting) consumes - `palette.json` and the GLB; Phase 2's capture/judge item is blocked on that. - -- **Git hygiene + tooling (2026-11-01):** dev asset folders are now gitignored - and untracked: `assets/models/concepts/`, `assets/models/npc/` (Blender - sources), `assets/textures/npc/ai/` (raw AI fabrics), and the raw - `assets/textures/ai/*` outputs (only `assets/textures/ai/game_ready/` stays). - Kept in the repo: `assets/textures/npc/*_{albedo,normal,orm}.png` + - `palette.json`, `assets/models/generated/*` (props + npc_base.glb), and the - authored `assets/textures/*` set. `tools/asset_pipeline.py generate` gained - `--variants N` — generates N alternatives saved as `-1..N.png` (N=1 - keeps the unsuffixed path). Python tooling tests still pass (exit 0). - -- **Screenshots + docs (2026-11-01):** regenerated the eleven README captures - (`capture_screenshots.gd` + `capture_prop_gallery.gd`) — ten changed, the - prop gallery is byte-identical (props unchanged). README gained an NPC - character-model subsection, a "Confirmed game assets" section (linked - `game_ready` textures, generated models, NPC maps), and refreshed - dev-status / not-yet lines; `docs/architecture.md` Level 2 now lists - `npc_pipeline.py` + `asset_pipeline.py` and the confirmed-asset locations. - -- Repo: `/run/media/system/DATA/Documents/06-games/security-game` (also reachable at - `/var/home/zwitschi/Documents/06-games/security-game`). Godot: `~/.local/bin/godot` (4.7.2). - Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **177/177**. - Python tooling tests: `cd tools && python3 -m unittest test_asset_pipeline test_screen_designs test_model_pipeline` → **53**. - Session shell smoke: `godot --headless --path . --script res://src/test/smoke_session_shell.gd` → exit 0. - Windowed control smoke: `godot --path . --script res://src/test/smoke_focus_radial.gd` → exit 0 - (now also covers wheel cycling and right-click clearing). -- `main` is in sync with origin. **v0.2.2** tags the session-shell + world-follow-up commit; - CI builds the portable binaries, NSIS installer, and AppImage on the tag push. -- **v0.3.0 (2026-10-31, working tree):** rendering pass (`src/game/materials.gd`, phase-driven - environment), generated `.glb` props (`tools/model_pipeline.py`, `assets/models/generated/`), - aim-free selection with wheel cycling and Shift precision walk (ADR-0011/0012), and a **Web** - export target. `export_presets.cfg` gains a `Web` preset with `variant/thread_support=false` - (static hosting, no COOP/COEP); `tools/build_releases.sh` exports `build/web/` and zips it to - `build/gig-line-web-.zip`, and `publish_release.sh` uploads it. Covered by - `src/test/tests/test_web_export.gd`. -- **Prop atlas (v0.3.1):** `model_pipeline.atlas_for()` builds a per-prop power-of-two PNG with - one 8×8 cell per palette role, plus a metallic/roughness atlas (G=roughness, B=metallic). The - GLB merges all parts into one primitive with constant per-vertex UVs (cell centre) and a single - material (`baseColorTexture` + `metallicRoughnessTexture`, NEAREST sampling). Colours are sRGB in - the texture, so the old `srgb_to_linear`/`_linear_rgba` helpers were removed. `audit_glb` fails - if a prop is not exactly one material or misses TEXCOORD_0/images. -- **Mockups complete (v0.3.2, updated 0.9.3):** `docs/screens/designs/` holds AI references for - all 21 catalogue screens, and every entry now has a committed current-build capture. - `capture_screenshots.gd` covers scenes 01–13, `capture_ui_states.gd` covers UI states 14–21, - and `capture_prop_gallery.gd` covers the prop gallery. `generate` now calls `_load_env_file()`. -- **Staged captures (v0.3.3, updated 2026-11-02):** `capture_screenshots.gd` emits the scene - gallery; states 7–10 switch to The Joke Shop at elapsed 620 s, retry the wand until it alerts, - then jump to 760 s after staging for night lighting. LATE now has its own routine pool. - Shot 12 closes any stale wand/inspection modal before the venue beauty shot (the wand fallback - can leave `hud.show_wand` open without `wand_open`). The timeline needs - `recorder.close_all_open()`, because `to_dict()` only lists closed incidents. - `capture_ui_states.gd` adds 14–21; crowd panic stages a group 70 frames into the flee and - responder arrival parks the responder with the door locked. `check_screenshots.sh` writes all - captures to `docs/screenshots/` before `compare`; judge is 21/21 match. -- **Prop collision (v0.4.0):** `_add_prop_model()` fits a `StaticBody3D` from the model bounds - (cylinder for bollard/bin/hydrant, box otherwise) and adds a `NavigationObstacle3D`; NPC agents - enable avoidance and move with the safe velocity from `velocity_computed`. The navmesh stays a - flat plane, so avoidance (not a baked mesh) routes guests around props. Placements live in - `PROP_PLACEMENTS` with `placement_problems()` validation; variants come from `variant_for(index)`. - `smoke_props.gd` must be windowed because the guard reads captured-mouse input. -- **Realism pass (2026-11-01, completed + adopted):** `tools/scene_concepts.py` - (`list`/`generate`/`process`/`promote`/`audit`/`realism`) never overwrites an image - (`next_free_path` adds `-vN`; `process` skips existing unless `--new`; `promote` keeps an - existing pick). Raw AI output: `assets/textures/ai/v2/raw/` (gitignored). Candidate pool: - `assets/textures/ai/game_ready/v2/` (gitignored) and the **tracked runtime assets** are - `.../v2/picks/{skyline_day,skyline_dusk,skyline_night,brick_facade,sidewalk_pavers,asphalt, - asphalt_wet,awning_canvas}.png` (+ `_normal`). Concepts `docs/screens/concepts/` (committed, - `.gdignore`, targets in its README). Runs `docs/screens/realism//` (PNG captures - gitignored; `report.{json,md}` + `sheet.jpg` committed). - `src/test/capture_realism.gd` runs five stages (day 0 s / evening 620 s / late 760 s / - joke_shop 700 s / studio 700 s; `--stages=` subset) and swaps textures by the material's - *original* albedo path. **Gotcha:** in the SceneTree script, do not overwrite `_phase` after - `_begin_stage()` sets `done`, or the run loops forever between stage/shoot (`_queue` empty). - **Gotcha:** `elapsed_s = 620` is *Evening* (dusk), 700+ is Late. Godot 4.7 - `StandardMaterial3D` has no `fog_enabled`; the skyline is a `ShaderMaterial` with - `render_mode unshaded, cull_disabled, fog_disabled` + `ALPHA_SCISSOR_THRESHOLD` so the - panorama's own haze shows. Backdrop plane is 90 m tall with 2 UV repeats (~200×90 m per - repeat, near the 2:1 panorama aspect). Legacy `assets/textures/skyline.png` is no longer - loaded but stays on disk for the `test_world` asset check. - **Phase look:** day sun 0.62 / exposure 0.6 / ambient 0.22; evening sun 0.12 / exposure - 0.72 / dark violet sky; late exposure 0.95 / ambient 0.10 with the wet asphalt street and a - bright skyline tint (1.5) so the night windows read. Stripes: `stripe_period()` (detrended - autocorrelation) + `align_stripes()` (resample to whole periods) replace the edge crossfade - for `awning_canvas`; `audit_striped_tile()` compares the wrap step with the harshest interior - step. - **Judge:** the default `google/gemini-2.5-flash-lite` mostly works but intermittently answers - HTTP 400 "API key not valid" (one shot of ten on 2026-11-01); re-run or pass - `--model google/gemini-2.5-flash` when it does. Scores are noisy (±1–2), so - `realism --repeats 3` averages and `summarize()` reports a like-for-like mean over the shots - every set captured. Final: overall 0.8→1.0, backdrop 0.6→1.6, metric gap 0.444→0.348. - `screen_designs.py judge` gained one retry for the same transient 400. - **Character portrait capture (2026-11-01):** `capture_realism.gd` has a `portrait` stage - (`npc_portrait` -> concept `npc_portrait_day`) because crowd shots cannot judge avatars. It - freezes one NPC, parks the rest of the crowd 140 m away (a passer-by otherwise blocks the - frame), moves the subject to an open sidewalk patch, turns it into the camera and stands the - guard on the sunlit side. Needed because the NPC FSM re-writes `rotation.y` every physics tick - (so the yaw must be set after `set_physics_process(false)`), the venue's morning sun leaves a - face in shadow from most angles, and `visible = false` does not keep the crowd out of frame. - `--portrait-type=N` forces a role; hooded roles (trespasser/violent/crisis) are skipped. - **Flashlight (2026-11-01, final):** a *tight* `SpotLight3D` on `Head`: `spot_range` 20, - `spot_angle` 14, `light_energy` 4.2, `spot_attenuation` 0.9, forward `rotation_degrees = 0`, - starts invisible. A fake additive light shaft was tried twice (cylinder with a UV ramp, then a - vertical-fade `ShaderMaterial`) and **rejected both times**: on GL Compatibility a first-person - camera inside the cone renders it as a filled disc and the far ring shows, so - `Materials.light_beam()` and the beam mesh are gone. Regression guard: - `test_flashlight_aims_a_tight_forward_spot` in `src/test/tests/test_door_and_flashlight.gd` - (lamp on the head, aims straight, cone ≤ 20°, starts off). Night captures are staged in LATE - (`elapsed_s` 760), not dusk. - **NPC detail pass (2026-11-01):** `_face()` now builds sclera + iris + upper lid per eye, brow - ridge, cheekbones, nose bridge/tip, ears with lobes and split upper/lower lips; the new - `eye_white` material is a *fixed-colour* slot, so `_apply_outfit_materials()` leaves it white. - `_hands_and_shoes()` has four fingers plus a thumb and `_clothing()` adds `Cuff` tori. - Budget work at the time: `CHAR_MAX_VERTS` 5000 (the library cap is now 8000; see the - character-mesh-cap decision), body decimation 0.26, upper lids are - boxes rather than spheres, and knuckle spheres/nostrils were cut to stay inside it. - **Orientation debugging:** to check a GLB's front axis, compare the mean Z of the `eye_white` - surface (face) and of the `rubber` sole verts (toes) — all 8 avatars are +Z, so any - "they walk backwards" symptom is a yaw bug, not an export flip. -- **Blender asset library (v0.6.0, integrated 2026-11-01):** - `tools/blender_assets.py` (`build`/`render` in Blender; `audit`/`sheet`/`list` stdlib) writes - `assets/models/blender/{npc,npc_gear,props}/*.glb`, `manifest.json` (integration source of - truth), sources in `assets/models/blender_src/` (`.gdignore`d), and review sheets - `docs/blender_previews/sheet_*.jpg` (per-asset PNG renders are gitignored). 8 characters, 18 - gear, 20 props; notes in `docs/blender-assets.md`. Characters are drop-in for `npc_base` - (same mesh node name, bones at the same rest positions, five tint slots); the `npc_base` - fallback stayed under its frozen 6000/4000 budget because of body decimation (0.26) + - `smart_project` at 80°: glTF vertex - count includes UV-seam splits, so extra beveled detail quickly breaks the limit. The 2026-11-01 - face/hand pass raised `CHAR_MAX_VERTS` to 5000 (tris 3,974–5,026) after measuring 16 avatars at - ~6.9 ms of a 50 ms frame; on 2026-11-02 the library cap became 8000/8000 while - `npc_pipeline.MAX_VERTICES`/`MAX_TRIANGLES` (base mesh) stayed frozen at 4000/6000. - **Gotchas:** `bisect_plane` and thin bevels leave zero-area triangles (Godot warns "non-finite - normal in LOD generation"); `_clean()` + a weld after bisect fix it, and the audit rejects them. - Fixed-colour detail materials carry a 4×4 texture because `_apply_outfit_materials()` sets - `albedo_color = WHITE` on unknown slots. Cycles CPU render ≈ 3 s/asset; Godot import of the - library is clean. - **Integration:** `NpcAvatar.character_model_for()` selects a variant by role/seed; only the - five contract slots are tinted (detail materials untouched; `npc_vest` keeps its fixed hivis - vest, firefighter/guard get dark uniform tints). Gear is attached at head/back/chest/hand and - falls back to primitives; `npc_hoodie` skips the extra hood gear. `WorldBuilder` prefers - `assets/models/blender/props/` for the six replacements and the new street dressing, fits the - same colliders/obstacles, and collects library emissives for `set_night_mode()`. - `LIBRARY_TRIANGLE_BUDGET` = 60k (current ~27,986). Library props are excluded from - `PROP_PLACEMENTS` only when they need dedicated slots (door/marquee/sedan/handrail/lantern). - **Gear fit (v0.6.1):** `fit` measures head width (0.315 m), skirt/hip radius and gear bounds; - sunglasses are 0.202 m, the bag's body inner face is 0.262 m (coat 0.225, dress 0.260), and the - helmet is lifted 2 cm above the brow. `test_npc_character` guards head/back gear in Godot. - Blender's GLB export is byte-nondeterministic for unchanged assets (padding/index order), so do - not commit a full no-op rebuild; rebuild only the assets whose geometry changed. -- **Venue geometry (v0.6.1+, Joke Shop updated 2026-11-02):** `WorldBuilder.VENUE_SPECS` is the per-venue table; `WorldBuilder.new(root, id)` / `set_venue()` select it, unknown ids fall back to `hollywood_building`. Dimensions, window/pilaster rows, stairs, street, nav bounds, guard spawn, feature flags and prop placements are read from the spec. `GameManager.world_root` holds only the world; `_rebuild_world_for_venue()` frees/recreates it on venue selection/cycling so guard/UI survive. Joke Shop now uses `architecture: "comedy_store"` with `_build_comedy_store_building()`, `SunsetHillside`, `SunsetNeighbours` and `SunsetAcrossStreet`; the old `_build_alley_dressing()` is gated off. TV Studio keeps the stone facade, bulb canopy, car paint and barrier placements. Gallery shot 12 now maps to `joke_shop_entrance_night`; shot 13 stays `studio_stage_door_night`. -- **Screen-design judge (2026-11-01):** `screen_designs.py judge` could not run because `OPENROUTER_API_KEY` returned HTTP 400 "API key not valid". `check_screenshots.sh` still passed 13/13; the judge retry is a TODO follow-up. -- **Portrait capture:** `capture_realism.gd` faces the **Rig** toward the camera (using the - inverse look direction) and hides the blue `FocusCircle`; the old code used the wrong yaw and - left the focus disc over the face. An explicit `--portrait-type=N` now overrides the hooded - skip, but the day roster spawns none of types 5-7, so a hooded close-up is deferred. -- **v0.6.0 environment pass:** `tools/bake_ao.py` bakes 256² AO maps per library asset - (`_ao.png`, red channel) and `Materials.apply_ao()`/`SurfaceMaterials` wire them in; - `tools/make_normal_maps.py` derives tangent normals for the 1024² brick/concrete/asphalt - textures. Forward+ boots on desktop but is rejected for v0.6.0 (project-wide renderer, Web - must stay GL Compatibility). Headless 222/222, Python 69/69. -- **Prop pipeline (done):** audit enforces 400 tris / 600 verts, ground contact, UV range, no - degenerate tris, one material. `capture_prop_gallery.gd` writes `10-prop-gallery.png` (3x2 studio - line-up, back row on a platform) and the `prop_gallery` catalogue entry compares it. -- **NPC character pass (v0.5.0):** Rig is articulated (`Rig/Hip* -> Knee`, `Shoulder -> Elbow -> - Hand`); `outfit_for` adds layer/hair_style/hand_item; `_idle_layer` drives phone/clipboard/crossed - and escalation gestures; `_reaction_tag` shows `!!`, `?!`, `!`, `?`; `apply_lod` swaps to a - billboard impostor at 26 m (hysteresis 1.5 m); GameManager `_refresh_queue` assigns queue offsets. - Perf: 16 avatars = 534 meshes, ~6.9 ms headless. -- **Player movement (v0.5.0):** **W was inverted** (`get_axis(forward, back)` is negative for W and - the code multiplied it directly); now `forward * -input_dir.y`. Step-up uses test_move + a raised - copy (0.32 m). Guard emits footstep/landed/precision_changed; GameManager forwards them to - AudioFeedback and the HUD. Gamepad actions are added at runtime by - `GuardController._ensure_gamepad_bindings`; remaps persist via `Save` `input_bindings` and are - applied by `InputBindings.apply` (key/button events only). Windowed `smoke_movement.gd` guards it. -- **Scheduled TODO pass complete:** only the optional Future ideas remain (live replay, extra - venues, voice audio, image-to-mesh). Grounding facts: the - NPC rig is rigid capsules with a whole-limb swing, and `guard_controller.gd` uses one `accel` - value. Each task should get its own windowed smoke or capture where it changes feel/looks. -- **Backlog complete:** every planned TODO section is done and moved to `DONE.md`; `TODO.md` - now only lists optional future ideas (live 3D replay, NPC LOD, more venues, real audio, prop - atlas, AI mockups for the new catalogue entries, image-to-mesh props). GUT was consciously not - adopted (custom runner covers it without an editor plugin). -- Do not re-open `override_access`. Do not bind wand to `W`. Do not remove the radio easter egg. - ## UI / Presentation Decisions - Runtime UI/scene art is loaded through `src/game/ui/art.gd` (`assets/images/ui/` then `scenes/`); missing files return null so panels keep procedural fallbacks. @@ -895,43 +636,11 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o - Host sign-in follow-up is **done**: `REQUIRE_SIGNIN_VIEW = false` in `/etc/gitea/app.ini` (backup `app.ini.bak-*` alongside), Gitea restarted; anonymous repo page/API return 200 and release assets download in full. Reverse by setting it back to `true` and restarting. -- **Release publishing was broken since v0.2.2 (fixed 2026-10-31):** `publish_release.sh` - only built `AUTH` in the `GITEA_TOKEN` branch; the `~/.git-credentials` branch set `CRED` - and then used unbound `${AUTH[@]}` under `set -u`, so every API call failed and the tag - job ended "could not create or find release". The repo also has **no** `GITEA_TOKEN` - secret (`/actions/secrets` is empty) and the runner has no git credentials. Fixes: the - script sets `AUTH=(-u "$CRED")`; the workflow falls back to `github.token` - (`secrets.GITEA_TOKEN || github.token`) — but `github.token` is **not** provided by this - Gitea/act runner, so the env dump showed no token and the v0.2.10 tag build failed again. - Final fix: `publish_release.sh` falls back to the **local git extraheader** that - `actions/checkout` writes (`git config --local --get-regexp '^http\..*\.extraheader$'`), - which carries the runner's basic-auth credentials for the same server. Verified locally by - adding that extraheader and publishing v0.2.10 (all five artifacts). **v0.2.9 and v0.2.10 - are published**; v0.2.4–v0.2.8 tags have no artifacts. Verify the CI path on the next tag. -- **CI publishing verified (2026-10-31):** tagging v0.2.12 produced a release with all five - artifacts automatically (no repo secret), confirming the checkout-extraheader fallback. -- **Host access (2026-10-31):** `root@192.168.88.30` now works from this workstation (key auth), - host name `gitea`, Gitea 28.2.0. Maintenance applied: `apt-get clean`, journald capped to - `SystemMaxUse=300M` (`/etc/systemd/journald.conf.d/99-size.conf`, journal ~133M), disk at - ~7.6G free of 16G. -- **Host disk resolved (2026-10-31):** the Gitea VM root filesystem was expanded from 7.8G to - 16G (`/dev/mapper/pve-vm--500--disk--0`, now ~7.8G free). The pruned **v0.2.0** and **v0.2.9** - releases were restored from the local `build/` artifacts and verified (4 and 5 assets). - `git.allucanget.biz` runs on this host as user `zwitschi`; root@192.168.88.30 is **not** - reachable from this workstation (publickey denied; password auth closes the banner), so host - maintenance must go through a `zwitschi` login with sudo. -- **Host disk full (2026-10-31, historical):** the v0.2.11 tag build created release id 27 and logged the - uploads, but they failed with `HTTP 500 Attachments.Save: write … no space left on device` - (`/dev/mapper/pve-vm--500--disk--0`, 7.8G, 100%, 0 free). `git push` then failed too - ("unpacker error"). Unblocked by deleting the superseded **v0.2.0** and **v0.2.9** releases - via the API (~565 MB of assets), then publishing v0.2.11 locally with the fixed script - (verified 5 assets). Remaining ~150 MB is tight — the next release needs host cleanup on - 192.168.88.30: `sudo journalctl --vacuum-size=100M` (journal 837 MB), `sudo apt-get clean` - (91 MB), clear `/var/lib/gitea/data/attachments/tmp`. SSH works as `zwitschi`; `sudo -n` - needs a password. **Releases now:** v0.2.11, v0.2.10, v0.2.1 (5 assets each); v0.2.0 and - v0.2.9 entries were pruned. PLAN.md + ADRs 0007–0010 still describe the intended UX. -- **Release v0.2.1 is LIVE** (2026-10-31): CI run 1110 built the NSIS installer + AppImage - and published — installers/AppImage are on the releases page. Releasing = bump version → tag → push. +- **Release publishing (fixed 2026-10-31):** `publish_release.sh` falls back to the local git + extraheader that `actions/checkout` writes (`git config --local --get-regexp + '^http\..*\.extraheader$'`) because this Gitea/act runner provides no `GITEA_TOKEN`. Always + build release artifacts from a clean checkout/worktree (the dirty tree's ignored `tmp/`/`build/` + payload blew artifact size to 1.6–3.1 GB once). Per-version records live in `CHANGELOG.md`. - NSIS gotchas (validated in a local ubuntu:24.04 docker repro): makensis uses `-D` defines (never `/D`), resolves relative `-DICON`/`-DARTIFACT` against the **script directory** so CI passes `$PWD`-absolute paths; installed exe must be space-free (`gig-line.exe`); ICO must be @@ -939,19 +648,8 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o cannot load). - appimagetool runs system `appstreamcli` if present (strict, exit 3 on warnings) — project metadata now passes `--pedantic`. -- Distribution facts (verified via Gitea API + host creds): repo is **public**, account is - admin; `v0.2.1` has all 5 assets; the installers were initially missing from the release - because `publish_release.sh` uploaded only portables — now fixed (uploads installer + - AppImage; falls back to `~/.git-credentials` when no `GITEA_TOKEN`). -- Host sign-in: instance `[service] REQUIRE_SIGNIN_VIEW=true` blocks anonymous browse/download - (not a repo/API setting). `~/.git-credentials` holds the Gitea creds (basic-auth usable); - `.env` only holds `OPENROUTER_API_KEY`. -- **Release v0.2.0 was LIVE**: pushing the `v0.2.0` tag made Gitea Actions run the build job - and publish automatically (host has the `GITEA_TOKEN` secret). Tag page and `/releases` are - public (HTTP 200). Caveat: anonymous **asset downloads** redirect to login on this host - (host setting — tracked in TODO). Future releases = bump `config/version`, `git tag vX.Y.Z`, - push. No local token needed. -- Core simulation, three scenarios, v0.2.1 installers/AppImage, first-person prototype (inspect/wand/radio, shift review, Tab targeting, V venue cycle) are on local `main`. Tagged v0.2.1 was 57 tests; this working tree is **74/74**. +- `~/.git-credentials` holds the Gitea creds (basic-auth usable); `.env` only holds + `OPENROUTER_API_KEY`. - NPC avatars now walk to per-intent waypoints (`VenueScenario.waypoint_for_intent`): enter/trespass/deliver/arrive → entrance, photo/film/perform/wander/roam → sidewalk, sit → steps_end. `NpcAvatar.setup` takes an explicit `door_position`. - Returning-trespasser behavior: `NpcProfile.return_probability` (trespasser 0.6, mental-crisis 0.4, others 0). `NPCController._maybe_schedule_return()` moves a directed-away NPC to `LOITERING_DISTANT`; `tick_return(delta)` fires after 20-60s and transitions back to `APPROACH`. `NpcAvatar._advance_fsm` re-targets the door on return. - Perception pass: `NPCController.perceive_guard(distance)` returns `{noticed, band, reaction}` with bands `unaware` (>6m), `aware` (2-6m), `near` (<2m). Near + temperament ≥0.6 → ARGUE, else COMPLY. `GameManager._perception_pass()` feeds real guard distance each physics tick and logs first-time notice. @@ -1035,6 +733,19 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o ## Known Issues / Notes +- Capture/pose harness gotchas: scripts that read captured-mouse input must run windowed + (`smoke_props.gd`); the NPC FSM re-writes `rotation.y` every physics tick, so pose/portrait + captures set the yaw after `set_physics_process(false)`; and `visible = false` does not keep the + crowd out of a portrait frame (park the rest of the crowd away instead). +- Blender tooling: `bisect_plane` and thin bevels leave zero-area triangles (Godot warns + "non-finite normal in LOD generation"); `_clean()` + a weld after bisect fix them and the audit + rejects them. +- Image models have per-model size floors/caps (Seedream needs 1920+, gpt-5-image-mini caps + lower); `generate_image` applies model-specific sizing, and concept generation defaults to + `--request-size 1536`. Do not hand-set a single size for every model. +- Frozen decisions: do not re-open `override_access`. Do not bind wand to `W`. Do not remove the + radio easter egg. + - Reference cycles are fixed: `FSM` holds its owner via `WeakRef`, `ShiftCoordinator` is `RefCounted`, and persistence tests free the `Save` node — the suite exits with no "resources still in use" warnings (was ~13). If a future subsystem introduces a cycle, @@ -1049,7 +760,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o - Venue `door_staff_lines` are logged through `GameManager` (inspect / wand / refuse). They are not a separate dialogue UI. - Access loadout is implemented; do not re-fix `override_access` as if it were still unread. - `Save.SETTINGS_FILE` is declared and unused; settings persistence goes through `Save.load_settings()` / `save_settings()`. -- Headless suite is **272/272**; Python tooling is **108/108**; screenshot compare is **21 good / 0 bad** and the automatic judge is **21/21 match, 0 diff**. Remaining visual follow-ups are in `TODO.md`. +- Headless suite is **276/276**; Python tooling is **143/143**; screenshot compare is **21 good / 0 bad** and the automatic judge is **21/21 match, 0 diff**. Remaining visual follow-ups are in `TODO.md`. - Typed spawn signal extra args: handlers must accept the 4th `event: Dictionary` or Godot will not call them (`test_shift_coordinator_runs_a_shift` failed until `_on_spawn` took four args). - GDScript inference trap that broke the main scene: a `var` typed as a base class (`radial: CanvasLayer`) exposes script members as `Variant`, so `var x := radial.is_open` is a