diff --git a/DONE.md b/DONE.md index 411e0cd..5c9516e 100644 --- a/DONE.md +++ b/DONE.md @@ -1196,3 +1196,49 @@ Web deployment and release artifact list were brought up to date. `MEMORY.md` wa obsolete uncommitted/active-pass claims and now points at the current follow-ups. - [x] Re-check `PLAN.md`/`MEMORY.md` against the current build, update `README.md` and `docs/architecture.md`, and compact the historical PLAN sections. + +## NPC gear fit (v0.6.1) + +Completed 2026-11-01. Eyes, bag and helmet now fit the measured body silhouette; portrait staging and gallery shots are refreshed. + +The 2026-11-01 detail pass fixed the bodies but not the props worn on them. In the portrait +capture (`--stages=day,portrait`, see the report in `docs/screens/realism/2026-11-01-npc/`) the +sunglasses span the whole upper face and the shoulder bag cuts through `npc_coat`'s skirt. Blender sources for gear +live in `tools/blender_assets.py` (`_build_gear()`), the contract in `docs/blender-assets.md` +(gear budget 1500 tris, `GEAR_ATTACH` points in `NpcAvatar`). + +- [x] Measure the fit in Blender, not by eye: `python3 tools/blender_assets.py fit` prints each + character's measured head width and hip/skirt radius, then every head/back/chest gear + bounding box with head-band status or skirt clearance. Current measurements: head width + 0.315 m; coat skirt 0.225 m, dress 0.260 m; sunglasses 0.282 m (0.90x head); shoulder-bag + x-clearance -0.025 m on the coat (radial -0.031 m on the dress). +- [x] Narrow `sunglasses` from 0.28 m to ~0.20 m. The hinges moved to ±0.075 (0.035 box), the + arms to ±0.098, and the lenses are 0.024 m radius at the same `y=-0.152`, `z=0.035` plane. + Rebuilt GLB/AO: measured width **0.202 m** (0.64x head), still inside the head band. + Also fixed `capture_realism.gd` so `--portrait-type=N` is actually parsed (it was documented + but ignored). +- [x] Fix `shoulder_bag` clipping through `npc_coat`/`npc_dress`: narrowed the bag to 0.06 m and + moved it outboard (centre x=0.292); the body's inner face is now 0.262 m, clearing the coat + (0.225) by 0.037 m and the dress (0.260) by 0.002 m. The strap's lower attach points sit on + the same outboard line. `fit` now measures the bag body separately from the torso strap. +- [x] Same pass for `cap`/`beanie`/`hood`/`firefighter_helmet`: rendered the cap (vest), hood + (hoodie), and firefighter helmet on their characters. Cap and hood clear the brow/ears and + the hood leaves the face readable; the helmet brim sat on the brow, so the whole helmet was + lifted 2 cm and rebuilt/re-baked. Beanie uses the same capped-shell sizing as the cap and is + currently unused; it is covered by the upcoming headgear regression check. +- [x] Rebuild the gear (`build --only <18 gear names>`) and re-bake AO (`tools/bake_ao.py --only + `), then `godot --headless --path . --import` (clean). Blender's GLB export is + byte-nondeterministic for unchanged assets (padding/index order changes), so the no-op churn + was reverted; only the intended sunglasses, bag, and helmet assets are committed. +- [x] Add a regression test: `tools/test_blender_assets.py` limits the glasses to 0.70x head + width and requires positive bag clearance on both coat and dress; + `test_npc_character.test_head_and_back_gear_clear_the_body_silhouette` loads the gear on + six roles and checks head-parent/width/brow bounds plus back-parent/0.26 m outer radius. +- [x] Re-capture the portrait (`--stages=day,portrait`) and the gallery shots; record the + before/after in `docs/screens/realism/2026-11-01-gear/`. The portrait staging now turns the + rig toward the camera (the old yaw was 180° out) and hides the blue focus disc, which was + covering the face in close-ups. `check_screenshots.sh` is 11 good / 0 bad; the judge went + from overall 2 / characters 1 to overall 3 / characters 2. +- [x] Consider a second character close-up for a hoodie/winter NPC so headgear is covered too. + `--portrait-type=N` now overrides the hooded-subject skip, but the day roster does not spawn + types 5-7, so no hooded subject was available; deferred as a scenario-spawn follow-up. diff --git a/MEMORY.md b/MEMORY.md index 2f9dd58..cb27d85 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -246,6 +246,10 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o 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. +- **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 diff --git a/TODO.md b/TODO.md index d2a8154..571c15a 100644 --- a/TODO.md +++ b/TODO.md @@ -3,49 +3,6 @@ Work is grouped by theme. Each item should be small enough to complete and verify on its own; see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these plans. -## NPC gear fit (v0.6.1) - -The 2026-11-01 detail pass fixed the bodies but not the props worn on them. In the portrait -capture (`--stages=day,portrait`, see the report in `docs/screens/realism/2026-11-01-npc/`) the -sunglasses span the whole upper face and the shoulder bag cuts through `npc_coat`'s skirt. Blender sources for gear -live in `tools/blender_assets.py` (`_build_gear()`), the contract in `docs/blender-assets.md` -(gear budget 1500 tris, `GEAR_ATTACH` points in `NpcAvatar`). - -- [x] Measure the fit in Blender, not by eye: `python3 tools/blender_assets.py fit` prints each - character's measured head width and hip/skirt radius, then every head/back/chest gear - bounding box with head-band status or skirt clearance. Current measurements: head width - 0.315 m; coat skirt 0.225 m, dress 0.260 m; sunglasses 0.282 m (0.90x head); shoulder-bag - x-clearance -0.025 m on the coat (radial -0.031 m on the dress). -- [x] Narrow `sunglasses` from 0.28 m to ~0.20 m. The hinges moved to ±0.075 (0.035 box), the - arms to ±0.098, and the lenses are 0.024 m radius at the same `y=-0.152`, `z=0.035` plane. - Rebuilt GLB/AO: measured width **0.202 m** (0.64x head), still inside the head band. - Also fixed `capture_realism.gd` so `--portrait-type=N` is actually parsed (it was documented - but ignored). -- [x] Fix `shoulder_bag` clipping through `npc_coat`/`npc_dress`: narrowed the bag to 0.06 m and - moved it outboard (centre x=0.292); the body's inner face is now 0.262 m, clearing the coat - (0.225) by 0.037 m and the dress (0.260) by 0.002 m. The strap's lower attach points sit on - the same outboard line. `fit` now measures the bag body separately from the torso strap. -- [x] Same pass for `cap`/`beanie`/`hood`/`firefighter_helmet`: rendered the cap (vest), hood - (hoodie), and firefighter helmet on their characters. Cap and hood clear the brow/ears and - the hood leaves the face readable; the helmet brim sat on the brow, so the whole helmet was - lifted 2 cm and rebuilt/re-baked. Beanie uses the same capped-shell sizing as the cap and is - currently unused; it is covered by the upcoming headgear regression check. -- [x] Rebuild the gear (`build --only <18 gear names>`) and re-bake AO (`tools/bake_ao.py --only - `), then `godot --headless --path . --import` (clean). Blender's GLB export is - byte-nondeterministic for unchanged assets (padding/index order changes), so the no-op churn - was reverted; only the intended sunglasses, bag, and helmet assets are committed. -- [x] Add a regression test: `tools/test_blender_assets.py` limits the glasses to 0.70x head - width and requires positive bag clearance on both coat and dress; - `test_npc_character.test_head_and_back_gear_clear_the_body_silhouette` loads the gear on - six roles and checks head-parent/width/brow bounds plus back-parent/0.26 m outer radius. -- [x] Re-capture the portrait (`--stages=day,portrait`) and the gallery shots; record the - before/after in `docs/screens/realism/2026-11-01-gear/`. The portrait staging now turns the - rig toward the camera (the old yaw was 180° out) and hides the blue focus disc, which was - covering the face in close-ups. `check_screenshots.sh` is 11 good / 0 bad; the judge went - from overall 2 / characters 1 to overall 3 / characters 2. -- [ ] Consider a second character close-up for a hoodie/winter NPC so headgear is covered too - (`--portrait-type=5` already forces a hooded role for comparison). - ## Venue geometry and the committed gallery (v0.6.1) Today all three venues share the Hollywood `WorldBuilder` geometry, so the Joke Shop and studio @@ -96,6 +53,8 @@ path. ## Realism follow-ups (deferred) +- [ ] Capture a hooded/winter NPC close-up once a scenario spawns types 5-7 (the explicit + `--portrait-type` override is already in `capture_realism.gd`). - [ ] Human-authored tree/hedge silhouettes: the whole scene now reads well except the foliage, which is still flat-shaded clumps (`_shrub()`/`_tree()` in `tools/blender_assets.py`). Only worth doing if a higher-poly organic builder is added to the library. diff --git a/src/test/capture_realism.gd b/src/test/capture_realism.gd index 21daa13..3e84b9e 100644 --- a/src/test/capture_realism.gd +++ b/src/test/capture_realism.gd @@ -318,7 +318,12 @@ func _faces_covered(npc: Node3D) -> bool: var profile = brain.get("profile") if profile == null: return false - return int(profile.get("npc_type")) in HOODED_TYPES + var npc_type := int(profile.get("npc_type")) + # An explicit --portrait-type is a deliberate request to inspect headgear, + # including a hood; the default crowd selection still avoids covered faces. + if _portrait_type >= 0 and npc_type == _portrait_type: + return false + return npc_type in HOODED_TYPES ## Apply swaps, tints and normals to every material whose original albedo path ## matches. Called once per stage; materials are visited through the tree.