Move hand gear forward into the palm
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped

This commit is contained in:
zwitschi committed 2026-11-02 19:33:42 +01:00
1 parent 7150bd7673
commit 58cf12fd1d
9 files changed
+45 -15

No files matched your search

+2
View File
@@ -21,6 +21,8 @@ Semantic Versioning.
separate thumb instead of flat bars. separate thumb instead of flat bars.
- Character noses gained ala wings and a smaller bridge/tip, the mouth is a - Character noses gained ala wings and a smaller bridge/tip, the mouth is a
rounded slot, and the ears have a darker inner concha. rounded slot, and the ears have a darker inner concha.
- Hand-held items now sit forward in the open palm instead of being partly
buried inside the hand.
### Fixed ### Fixed
+6
View File
@@ -96,6 +96,12 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
0.150→0.146, chest 0.170→0.168, upper chest 0.172→0.170. This is a subtle shirt taper, not a 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. skeleton change; all proportions pass and `sheet_npc_baseline_compare-torso.jpg` records it.
Max character is 4,754 tris / 4,584 verts. Max character is 4,754 tris / 4,584 verts.
- **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 - **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 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 brow/eyes; `blender_assets.py fit` stays clean, so no gear was rebuilt. The broad helmet brim is
+5 -3
View File
@@ -126,9 +126,11 @@ and equipment fit.
- [x] Refine hands and fingers enough to read at conversation distance without exceeding the - [x] Refine hands and fingers enough to read at conversation distance without exceeding the
character triangle budget. (Wider palm, two-segment bent fingers with rounded tips, separate character triangle budget. (Wider palm, two-segment bent fingers with rounded tips, separate
thumb; optional `--view=hand` capture and `sheet_npc_hands_compare.jpg`.) thumb; optional `--view=hand` capture and `sheet_npc_hands_compare.jpg`.)
- [ ] Fix grip alignment for phone, cup, clipboard, camera, sign, flashlight, wand and radio - [x] Move hand gear forward out of the wrist/palm so phone, cup, clipboard, camera, flashlight,
against the new palm/finger geometry. (Several props still occlude the palm or float beside wand and radio are no longer buried inside the hand. (`HAND_ITEM_FORWARD`, 4 cm; new capture
it in the hand capture.) sheet `sheet_npc_hands_grip_compare.jpg` and regression assertions.)
- [ ] Add finger curl/thumb contact for the hand items and judge the umbrella/sign grips against the
new palm/finger geometry.
- [ ] Audit shoes/boots for silhouette, grounding and contact with the floor; preserve the - [ ] Audit shoes/boots for silhouette, grounding and contact with the floor; preserve the
responder and firefighter footwear language. responder and firefighter footwear language.
- [ ] Improve clothing layer transitions at collars, cuffs, plackets, waistbands, pockets, badges - [ ] Improve clothing layer transitions at collars, cuffs, plackets, waistbands, pockets, badges
+1
View File
@@ -50,6 +50,7 @@ Committed contact sheets and local Blender renders for the character, gear and p
| <img src="sheet_npc_baseline_compare-torso.jpg" width="280" alt="sheet_npc_baseline_compare-torso.jpg"> | [`sheet_npc_baseline_compare-torso.jpg`](sheet_npc_baseline_compare-torso.jpg) | | <img src="sheet_npc_baseline_compare-torso.jpg" width="280" alt="sheet_npc_baseline_compare-torso.jpg"> | [`sheet_npc_baseline_compare-torso.jpg`](sheet_npc_baseline_compare-torso.jpg) |
| <img src="sheet_npc_baseline_compare-face-detail.jpg" width="280" alt="sheet_npc_baseline_compare-face-detail.jpg"> | [`sheet_npc_baseline_compare-face-detail.jpg`](sheet_npc_baseline_compare-face-detail.jpg) | | <img src="sheet_npc_baseline_compare-face-detail.jpg" width="280" alt="sheet_npc_baseline_compare-face-detail.jpg"> | [`sheet_npc_baseline_compare-face-detail.jpg`](sheet_npc_baseline_compare-face-detail.jpg) |
| <img src="sheet_npc_hands_compare.jpg" width="280" alt="sheet_npc_hands_compare.jpg"> | [`sheet_npc_hands_compare.jpg`](sheet_npc_hands_compare.jpg) | | <img src="sheet_npc_hands_compare.jpg" width="280" alt="sheet_npc_hands_compare.jpg"> | [`sheet_npc_hands_compare.jpg`](sheet_npc_hands_compare.jpg) |
| <img src="sheet_npc_hands_grip_compare.jpg" width="280" alt="sheet_npc_hands_grip_compare.jpg"> | [`sheet_npc_hands_grip_compare.jpg`](sheet_npc_hands_grip_compare.jpg) |
| <img src="sheet_npc_gear.jpg" width="280" alt="sheet_npc_gear.jpg"> | [`sheet_npc_gear.jpg`](sheet_npc_gear.jpg) | | <img src="sheet_npc_gear.jpg" width="280" alt="sheet_npc_gear.jpg"> | [`sheet_npc_gear.jpg`](sheet_npc_gear.jpg) |
| <img src="sheet_props.jpg" width="280" alt="sheet_props.jpg"> | [`sheet_props.jpg`](sheet_props.jpg) | | <img src="sheet_props.jpg" width="280" alt="sheet_props.jpg"> | [`sheet_props.jpg`](sheet_props.jpg) |
| <img src="shoulder_bag.png" width="280" alt="shoulder_bag.png"> | [`shoulder_bag.png`](shoulder_bag.png) | | <img src="shoulder_bag.png" width="280" alt="shoulder_bag.png"> | [`shoulder_bag.png`](shoulder_bag.png) |
Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

+1
View File
@@ -45,6 +45,7 @@ Implemented so far:
- NPC mesh budget (0.9.4 work): the eight library variants now have an 8,000-triangle / 8,000-glTF-vertex cap (up from 6,000/5,000); the `npc_base` fallback remains 6,000/4,000. Current characters measure 4,226–5,218 triangles and 4,119–5,240 vertices. - NPC mesh budget (0.9.4 work): the eight library variants now have an 8,000-triangle / 8,000-glTF-vertex cap (up from 6,000/5,000); the `npc_base` fallback remains 6,000/4,000. Current characters measure 4,226–5,218 triangles and 4,119–5,240 vertices.
- NPC face-detail pass (0.9.4 work): ala wings and a smaller nose bridge/tip, one rounded mouth slot, and a darker ear concha; all eight rebuilt. The comparison is `sheet_npc_baseline_compare-face-detail.jpg`. - NPC face-detail pass (0.9.4 work): ala wings and a smaller nose bridge/tip, one rounded mouth slot, and a darker ear concha; all eight rebuilt. The comparison is `sheet_npc_baseline_compare-face-detail.jpg`.
- NPC headgear clearance (0.9.4 work): the 96-frame final-context review after the face-detail pass confirms the cap, firefighter helmet, hood and sunglasses clear the refined brow and eyes; no gear rebuild was needed. - NPC headgear clearance (0.9.4 work): the 96-frame final-context review after the face-detail pass confirms the cap, firefighter helmet, hood and sunglasses clear the refined brow and eyes; no gear rebuild was needed.
- NPC hand-item grip (0.9.4 work): runtime `HAND_ITEM_FORWARD` moves every hand gear 4 cm forward from the wrist proxy, so phone, cup, clipboard, camera, flashlight, wand and radio sit in the open palm instead of partly inside it. Finger curl/thumb contact remains open; the comparison is `sheet_npc_hands_grip_compare.jpg`.
- NPC hand pass (0.9.4 work): wider, shallower palms; four two-segment bent fingers with rounded tips and a separate bent thumb; optional `--view=hand` capture at 0.80 m. The hand comparison is `sheet_npc_hands_compare.jpg`; item grip offsets remain open. - NPC hand pass (0.9.4 work): wider, shallower palms; four two-segment bent fingers with rounded tips and a separate bent thumb; optional `--view=hand` capture at 0.80 m. The hand comparison is `sheet_npc_hands_compare.jpg`; item grip offsets remain open.
- NPC proportion audit (0.9.4 work): `tools/npc_proportions.py` measures all eight GLBs against a stylized height/head/shoulder/hip/torso/leg/arm/foot guide. All variants pass; the head and broad-build silhouettes are the nearest upper bounds. Results are in [`npc-proportion-audit.md`](npc-proportion-audit.md). - NPC proportion audit (0.9.4 work): `tools/npc_proportions.py` measures all eight GLBs against a stylized height/head/shoulder/hip/torso/leg/arm/foot guide. All variants pass; the head and broad-build silhouettes are the nearest upper bounds. Results are in [`npc-proportion-audit.md`](npc-proportion-audit.md).
- NPC P1 portrait fixes (0.9.4 work): tourists now hide their photo-pose camera bag outside `photo`/`film` poses, removing the duplicate-backpack silhouette; the backpack gear was rebuilt with flattened, forward chest straps and a fresh AO map. The before/after sheet is [`blender_previews/sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg); the face and remaining clothing/hand work stay open. - NPC P1 portrait fixes (0.9.4 work): tourists now hide their photo-pose camera bag outside `photo`/`film` poses, removing the duplicate-backpack silhouette; the backpack gear was rebuilt with flattened, forward chest straps and a fresh AO map. The before/after sheet is [`blender_previews/sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg); the face and remaining clothing/hand work stay open.
+16 -11
View File
@@ -9,8 +9,9 @@ Comparison sheets:
[`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg) [`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg)
the latest [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg), the latest [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg),
the face-detail pass [`sheet_npc_baseline_compare-face-detail.jpg`](blender_previews/sheet_npc_baseline_compare-face-detail.jpg), the face-detail pass [`sheet_npc_baseline_compare-face-detail.jpg`](blender_previews/sheet_npc_baseline_compare-face-detail.jpg),
and the conversation-distance hand pass the conversation-distance hand pass
[`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg). [`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg), and the
forward-grip pass [`sheet_npc_hands_grip_compare.jpg`](blender_previews/sheet_npc_hands_grip_compare.jpg).
## What was captured ## What was captured
@@ -156,12 +157,14 @@ The refit backpack measures 380 triangles / 406 vertices.
### P2 — fix after the first P1 pass ### P2 — fix after the first P1 pass
4. **Hands: first pass fixed 2026-11-02; grip alignment still open.** The palm 4. **Hands: geometry and forward offset fixed 2026-11-02; finger curl still
is wider and shallower, and each finger is now a two-segment bent tube with a open.** The palm is wider and shallower, and each finger is now a
rounded tip and a separate bent thumb. The hand capture shows a readable two-segment bent tube with a rounded tip and a separate bent thumb. Hand gear
four-finger hand instead of flat bars. The camera, phone, cup, clipboard and is authored around the wrist proxy, so the runtime now moves every hand item
sign still need their attach offsets checked: several props occlude the palm `HAND_ITEM_FORWARD` (4 cm) into the open palm; the phone, flashlight, camera,
or read as floating beside it. cup and clipboard are no longer buried inside the hand. The fingers are still
open, so a true wrap/thumb contact and the umbrella/sign grips remain to be
judged.
5. **Clothing detail is tile-driven and transitional seams are visible.** The 5. **Clothing detail is tile-driven and transitional seams are visible.** The
fabric pattern repeats strongly on the casual, coat and dress variants, and fabric pattern repeats strongly on the casual, coat and dress variants, and
collar/cuff/waist edges are abrupt. This is more visible than the shoe/hem collar/cuff/waist edges are abrupt. This is more visible than the shoe/hem
@@ -210,14 +213,16 @@ The committed runs are
and [`sheet_npc_baseline_compare-face.jpg`](blender_previews/sheet_npc_baseline_compare-face.jpg). and [`sheet_npc_baseline_compare-face.jpg`](blender_previews/sheet_npc_baseline_compare-face.jpg).
The transition pass is [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg). The transition pass is [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg).
The face-detail pass is [`sheet_npc_baseline_compare-face-detail.jpg`](blender_previews/sheet_npc_baseline_compare-face-detail.jpg). The face-detail pass is [`sheet_npc_baseline_compare-face-detail.jpg`](blender_previews/sheet_npc_baseline_compare-face-detail.jpg).
The hand pass is [`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg). The hand pass is [`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg);
the forward-grip pass is
[`sheet_npc_hands_grip_compare.jpg`](blender_previews/sheet_npc_hands_grip_compare.jpg).
Keep the baseline sheet as the before artifact and add a new comparison sheet Keep the baseline sheet as the before artifact and add a new comparison sheet
rather than replacing it. rather than replacing it.
## Next implementation order ## Next implementation order
1. Fix hand-item grip offsets (phone, cup, clipboard, camera, sign, flashlight, 1. Add finger curl/thumb contact for the hand items and judge the umbrella/sign
wand and radio) against the new palm/finger geometry. grips; the forward offset is already in place.
2. Add the remaining body-shape variation between variants and re-measure the 2. Add the remaining body-shape variation between variants and re-measure the
LOD/impostor silhouettes. LOD/impostor silhouettes.
3. Audit hems and footwear in walk/sit/block poses. 3. Audit hems and footwear in walk/sit/block poses.
+7
View File
@@ -179,6 +179,11 @@ const GEAR_OFFSETS := {
"lanyard": Vector3(0, 0, 0.04), "lanyard": Vector3(0, 0, 0.04),
"firefighter_helmet": Vector3(0, 0.03, 0), "firefighter_helmet": Vector3(0, 0.03, 0),
} }
## Hand gear is authored around the wrist proxy. The palm sits ~4 cm in front
## of that origin, so without this offset the first phone/flashlight versions
## were partly buried inside the hand. Keep the grip axis and move the whole
## item forward into the open palm.
const HAND_ITEM_FORWARD := Vector3(0, 0, 0.04)
const GEAR_ATTACH := { const GEAR_ATTACH := {
"cap": "head", "beanie": "head", "hood": "head", "sunglasses": "head", "cap": "head", "beanie": "head", "hood": "head", "sunglasses": "head",
"firefighter_helmet": "head", "firefighter_helmet": "head",
@@ -746,6 +751,8 @@ func _attach_gear(gear_name: String) -> bool:
return false return false
if GEAR_OFFSETS.has(gear_name): if GEAR_OFFSETS.has(gear_name):
node.position += GEAR_OFFSETS[gear_name] node.position += GEAR_OFFSETS[gear_name]
if str(GEAR_ATTACH[gear_name]) == "hand":
node.position += HAND_ITEM_FORWARD
_detail_nodes.append(node) _detail_nodes.append(node)
var ao := Materials.load_ao(BLENDER_GEAR_DIR + gear_name + "_ao.png") var ao := Materials.load_ao(BLENDER_GEAR_DIR + gear_name + "_ao.png")
for child in node.find_children("*", "MeshInstance3D", true, false): for child in node.find_children("*", "MeshInstance3D", true, false):
+7 -1
View File
@@ -203,6 +203,9 @@ func test_library_gear_attaches_to_the_contract_points() -> bool:
var b := TestUtils.check(camera != null and camera.get_parent() is Node3D var b := TestUtils.check(camera != null and camera.get_parent() is Node3D
and camera.get_parent().name == "Hand", and camera.get_parent().name == "Hand",
"hand gear attaches under the hand proxy") "hand gear attaches under the hand proxy")
var f := TestUtils.check(camera != null
and camera.position.z >= NpcAvatar.HAND_ITEM_FORWARD.z - 0.001,
"hand gear sits forward of the wrist proxy instead of inside the palm")
tourist.free() tourist.free()
var responder := _avatar(Rules.NpcType.RESPONDER, 12) var responder := _avatar(Rules.NpcType.RESPONDER, 12)
var helmet := responder.find_child("Gear_firefighter_helmet", true, false) as Node3D var helmet := responder.find_child("Gear_firefighter_helmet", true, false) as Node3D
@@ -213,10 +216,13 @@ func test_library_gear_attaches_to_the_contract_points() -> bool:
"firefighter helmet attaches to Rig/Head") "firefighter helmet attaches to Rig/Head")
var d := TestUtils.check(tank != null and flashlight != null, var d := TestUtils.check(tank != null and flashlight != null,
"responder gets SCBA tank and flashlight") "responder gets SCBA tank and flashlight")
var g := TestUtils.check(flashlight != null
and flashlight.position.z >= NpcAvatar.HAND_ITEM_FORWARD.z - 0.001,
"flashlight uses the same forward grip offset")
var e2 := TestUtils.check(helmet != null and helmet.position.y > 0.02, var e2 := TestUtils.check(helmet != null and helmet.position.y > 0.02,
"the firefighter helmet clears the brow") "the firefighter helmet clears the brow")
responder.free() responder.free()
return a and b and c and d and e and e2 return a and b and c and d and e and e2 and f and g
## Horizontal bounds of a gear GLB in its attach-node space. The head/back ## Horizontal bounds of a gear GLB in its attach-node space. The head/back
## contract is measured against the Blender fit report (head width 0.315 m, ## contract is measured against the Blender fit report (head width 0.315 m,