diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9519bf9..1fced3b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ Semantic Versioning.
- Character necks are fuller and shoulders slope from the neck instead of
reading as a thin post on a hanger.
+- Character eyes sit in the socket behind a thin upper lid, and the chin and
+ cheekbones are sculpted into the head instead of reading as separate spheres.
- Tourists no longer appear to carry two backpacks: the photo-pose camera bag is
hidden except while they are actually taking a picture.
- Backpack shoulder straps are now flattened bands that sit outside the chest
diff --git a/MEMORY.md b/MEMORY.md
index 1d0c8eb..db329fe 100644
--- a/MEMORY.md
+++ b/MEMORY.md
@@ -81,6 +81,11 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
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.
- **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
diff --git a/TODO.md b/TODO.md
index 8f77843..c17ee37 100644
--- a/TODO.md
+++ b/TODO.md
@@ -99,10 +99,13 @@ and equipment fit.
### Head, face and hair
-- [ ] Refine the head volume and jaw/cheek/chin proportions against the portrait concept; keep the
- face front direction unambiguous for the focus cone and head-turn.
-- [ ] Improve brow/eye-socket depth, eyelid/iris readability, nose bridge/tip, lips and ear shape
- without turning the low-poly face into a noisy mesh.
+- [x] Refine the head volume and jaw/cheek/chin proportions against the portrait concept; keep the
+ face front direction unambiguous for the focus cone and head-turn. (Chin plate and cheekbones
+ sculpted into the head mesh; no separate jaw/cheek spheres or AO intersection rings.)
+- [x] Improve brow/eye-socket depth and eyelid/iris readability without turning the low-poly face
+ into a noisy mesh. (Recessed sclera, thin upper lid; `sheet_npc_baseline_compare-face.jpg`.)
+- [ ] Refine the nose bridge/tip, lips and ear shape; keep the five tint slots and face direction
+ unchanged.
- [ ] Audit hair caps, fringes, side locks, buns and beard silhouettes for gaps, intersections and
obvious scalp seams; keep hair color in the tintable slot.
- [ ] Verify hats/hoods/sunglasses/helmets still clear the refined brow, ears and hair; update gear
diff --git a/assets/models/blender/manifest.json b/assets/models/blender/manifest.json
index cba8252..5fc897c 100644
--- a/assets/models/blender/manifest.json
+++ b/assets/models/blender/manifest.json
@@ -487,8 +487,8 @@
"accessory",
"hair"
],
- "triangles": 3984,
- "vertices": 3793
+ "triangles": 3760,
+ "vertices": 3556
},
"npc_coat": {
"bounds_godot": {
@@ -528,8 +528,8 @@
"accessory",
"hair"
],
- "triangles": 4978,
- "vertices": 4776
+ "triangles": 4754,
+ "vertices": 4574
},
"npc_dress": {
"bounds_godot": {
@@ -567,8 +567,8 @@
"accessory",
"hair"
],
- "triangles": 4400,
- "vertices": 4075
+ "triangles": 4176,
+ "vertices": 3863
},
"npc_firefighter": {
"bounds_godot": {
@@ -608,8 +608,8 @@
"accessory",
"hair"
],
- "triangles": 4848,
- "vertices": 4831
+ "triangles": 4624,
+ "vertices": 4487
},
"npc_guard": {
"bounds_godot": {
@@ -652,8 +652,8 @@
"accessory",
"hair"
],
- "triangles": 4558,
- "vertices": 4652
+ "triangles": 4334,
+ "vertices": 4456
},
"npc_hoodie": {
"bounds_godot": {
@@ -693,8 +693,8 @@
"accessory",
"hair"
],
- "triangles": 4816,
- "vertices": 4425
+ "triangles": 4592,
+ "vertices": 4187
},
"npc_jacket": {
"bounds_godot": {
@@ -734,8 +734,8 @@
"accessory",
"hair"
],
- "triangles": 4384,
- "vertices": 4130
+ "triangles": 4160,
+ "vertices": 3930
},
"npc_vest": {
"bounds_godot": {
@@ -775,8 +775,8 @@
"accessory",
"hair"
],
- "triangles": 4496,
- "vertices": 4271
+ "triangles": 4272,
+ "vertices": 4186
},
"palm_tree": {
"bounds_godot": {
diff --git a/assets/models/blender/npc/npc_casual.glb b/assets/models/blender/npc/npc_casual.glb
index d4d7cc1..637e2c7 100644
Binary files a/assets/models/blender/npc/npc_casual.glb and b/assets/models/blender/npc/npc_casual.glb differ
diff --git a/assets/models/blender/npc/npc_casual_ao.png b/assets/models/blender/npc/npc_casual_ao.png
index 95237ec..092be57 100644
Binary files a/assets/models/blender/npc/npc_casual_ao.png and b/assets/models/blender/npc/npc_casual_ao.png differ
diff --git a/assets/models/blender/npc/npc_coat.glb b/assets/models/blender/npc/npc_coat.glb
index 9d023eb..068e129 100644
Binary files a/assets/models/blender/npc/npc_coat.glb and b/assets/models/blender/npc/npc_coat.glb differ
diff --git a/assets/models/blender/npc/npc_coat_ao.png b/assets/models/blender/npc/npc_coat_ao.png
index c26cdc9..876e346 100644
Binary files a/assets/models/blender/npc/npc_coat_ao.png and b/assets/models/blender/npc/npc_coat_ao.png differ
diff --git a/assets/models/blender/npc/npc_dress.glb b/assets/models/blender/npc/npc_dress.glb
index 68396b2..54c1d60 100644
Binary files a/assets/models/blender/npc/npc_dress.glb and b/assets/models/blender/npc/npc_dress.glb differ
diff --git a/assets/models/blender/npc/npc_dress_ao.png b/assets/models/blender/npc/npc_dress_ao.png
index a70b849..5e3cb52 100644
Binary files a/assets/models/blender/npc/npc_dress_ao.png and b/assets/models/blender/npc/npc_dress_ao.png differ
diff --git a/assets/models/blender/npc/npc_firefighter.glb b/assets/models/blender/npc/npc_firefighter.glb
index 67081d3..cfac41f 100644
Binary files a/assets/models/blender/npc/npc_firefighter.glb and b/assets/models/blender/npc/npc_firefighter.glb differ
diff --git a/assets/models/blender/npc/npc_firefighter_ao.png b/assets/models/blender/npc/npc_firefighter_ao.png
index 22e6176..49f5b28 100644
Binary files a/assets/models/blender/npc/npc_firefighter_ao.png and b/assets/models/blender/npc/npc_firefighter_ao.png differ
diff --git a/assets/models/blender/npc/npc_guard.glb b/assets/models/blender/npc/npc_guard.glb
index 3959e5b..762c67d 100644
Binary files a/assets/models/blender/npc/npc_guard.glb and b/assets/models/blender/npc/npc_guard.glb differ
diff --git a/assets/models/blender/npc/npc_guard_ao.png b/assets/models/blender/npc/npc_guard_ao.png
index 8ee029c..5cac8f1 100644
Binary files a/assets/models/blender/npc/npc_guard_ao.png and b/assets/models/blender/npc/npc_guard_ao.png differ
diff --git a/assets/models/blender/npc/npc_hoodie.glb b/assets/models/blender/npc/npc_hoodie.glb
index 76d86da..5e0b34d 100644
Binary files a/assets/models/blender/npc/npc_hoodie.glb and b/assets/models/blender/npc/npc_hoodie.glb differ
diff --git a/assets/models/blender/npc/npc_hoodie_ao.png b/assets/models/blender/npc/npc_hoodie_ao.png
index c30b301..e35fcc8 100644
Binary files a/assets/models/blender/npc/npc_hoodie_ao.png and b/assets/models/blender/npc/npc_hoodie_ao.png differ
diff --git a/assets/models/blender/npc/npc_jacket.glb b/assets/models/blender/npc/npc_jacket.glb
index f0207f6..dcc6e78 100644
Binary files a/assets/models/blender/npc/npc_jacket.glb and b/assets/models/blender/npc/npc_jacket.glb differ
diff --git a/assets/models/blender/npc/npc_jacket_ao.png b/assets/models/blender/npc/npc_jacket_ao.png
index b8eb5fa..f3aa10c 100644
Binary files a/assets/models/blender/npc/npc_jacket_ao.png and b/assets/models/blender/npc/npc_jacket_ao.png differ
diff --git a/assets/models/blender/npc/npc_vest.glb b/assets/models/blender/npc/npc_vest.glb
index 7761e08..3c9db1f 100644
Binary files a/assets/models/blender/npc/npc_vest.glb and b/assets/models/blender/npc/npc_vest.glb differ
diff --git a/assets/models/blender/npc/npc_vest_ao.png b/assets/models/blender/npc/npc_vest_ao.png
index f2b948c..bf69ba4 100644
Binary files a/assets/models/blender/npc/npc_vest_ao.png and b/assets/models/blender/npc/npc_vest_ao.png differ
diff --git a/docs/blender-assets.md b/docs/blender-assets.md
index 8df6136..44b85c5 100644
--- a/docs/blender-assets.md
+++ b/docs/blender-assets.md
@@ -94,8 +94,8 @@ All eight use the `npc_base` contract, so each one can replace
`Materials.npc_slot()` tints them the same way it tints `npc_base`.
- budget ≤ 6000 triangles and ≤ 5000 vertices (glTF vertex count, after seam
splits; `CHAR_MAX_VERTS` in `tools/blender_assets.py`, raised from 4000 for
- the 2026-11-01 face/hand pass). The current range is 3,984–4,978 triangles
- and 3,793–4,831 vertices.
+ the 2026-11-01 face/hand pass). The current range is 3,760–4,754 triangles
+ and 3,556–4,574 vertices.
Construction: the torso and limbs are one continuous Skin-modifier mesh,
subdivided twice, decimated to 26 % with X symmetry, and cut at the waist, cuffs,
diff --git a/docs/blender_previews/README.md b/docs/blender_previews/README.md
index dd719ba..d0b40ff 100644
--- a/docs/blender_previews/README.md
+++ b/docs/blender_previews/README.md
@@ -46,6 +46,7 @@ Committed contact sheets and local Blender renders for the character, gear and p
|
| [`sheet_npc_baseline.jpg`](sheet_npc_baseline.jpg) |
|
| [`sheet_npc_baseline_compare-p1.jpg`](sheet_npc_baseline_compare-p1.jpg) |
|
| [`sheet_npc_baseline_compare-body.jpg`](sheet_npc_baseline_compare-body.jpg) |
+|
| [`sheet_npc_baseline_compare-face.jpg`](sheet_npc_baseline_compare-face.jpg) |
|
| [`sheet_npc_gear.jpg`](sheet_npc_gear.jpg) |
|
| [`sheet_props.jpg`](sheet_props.jpg) |
|
| [`shoulder_bag.png`](shoulder_bag.png) |
diff --git a/docs/blender_previews/sheet_npc_baseline_compare-face.jpg b/docs/blender_previews/sheet_npc_baseline_compare-face.jpg
new file mode 100644
index 0000000..04463e9
Binary files /dev/null and b/docs/blender_previews/sheet_npc_baseline_compare-face.jpg differ
diff --git a/docs/development-status.md b/docs/development-status.md
index 1a3a1fa..6aaec55 100644
--- a/docs/development-status.md
+++ b/docs/development-status.md
@@ -40,6 +40,7 @@ Implemented so far:
- Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing. The wand panel lists the guest's carried items, and multiline HUD/wand strings render real line breaks.
- NPC neck/shoulder transition (0.9.4 work): the body graph now uses a fuller neck base, a slightly tapered neck top and a lower shoulder root, giving the head/neck/shoulder line a trapezius slope instead of a thin post. The full character library was rebuilt with a final degenerate-face cleanup; the baseline comparison is `sheet_npc_baseline_compare-body.jpg`.
+- NPC face-depth pass (0.9.4 work): sclera are recessed and capped by a thin upper lid, and the separate jaw/cheek spheres are replaced by a chin plate and cheekbones sculpted into the head mesh. The full library was rebuilt; the comparison is `sheet_npc_baseline_compare-face.jpg`.
- 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 visual baseline (0.9.4 work): `src/test/capture_npc_baseline.gd` captures all eight library characters from front, three-quarter, profile, back, full-body and crowd views in a neutral studio (48 frames). The criteria, measurements and ranked gaps are in [`npc-visual-review.md`](npc-visual-review.md); the committed sheet is [`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg). No model changes were made in this baseline step.
diff --git a/docs/npc-visual-review.md b/docs/npc-visual-review.md
index 6bac82f..66e32f0 100644
--- a/docs/npc-visual-review.md
+++ b/docs/npc-visual-review.md
@@ -1,10 +1,12 @@
# NPC visual baseline and acceptance criteria
-Status: baseline captured 2026-11-02; the first P1 fixes were applied the same
-day (tourist photo-prop visibility and backpack strap geometry). The face pass
-and the remaining body/clothing work are still open. The baseline remains the
-reference for the 0.9.4 NPC visual optimization; the post-fix comparison is
-[`blender_previews/sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg).
+Status: baseline captured 2026-11-02. The first P1 fixes (tourist photo-prop
+visibility, backpack straps), the neck/shoulder transition and the face-depth
+pass are applied. Remaining body/clothing work is open; the baseline remains the
+reference for 0.9.4. Comparison sheets:
+[`sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg),
+[`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg)
+and the latest [`sheet_npc_baseline_compare-face.jpg`](blender_previews/sheet_npc_baseline_compare-face.jpg).
## What was captured
@@ -98,20 +100,19 @@ From `python3 tools/blender_assets.py fit` and the manifest:
| Character | Tris | Verts | Head width | Clothing radius | Notable gear clearances |
| --- | ---: | ---: | ---: | ---: | --- |
-| `npc_casual` | 3,984 | 3,793 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
-| `npc_jacket` | 4,384 | 4,130 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
-| `npc_coat` | 4,978 | 4,776 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
-| `npc_vest` | 4,496 | 4,271 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
-| `npc_dress` | 4,400 | 4,075 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
-| `npc_hoodie` | 4,816 | 4,425 | 0.315 m | 0.190 m (hip) | — |
-| `npc_firefighter` | 4,848 | 4,831 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
-| `npc_guard` | 4,558 | 4,652 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
+| `npc_casual` | 3,760 | 3,556 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
+| `npc_jacket` | 4,160 | 3,930 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
+| `npc_coat` | 4,754 | 4,574 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
+| `npc_vest` | 4,272 | 4,186 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
+| `npc_dress` | 4,176 | 3,863 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
+| `npc_hoodie` | 4,592 | 4,187 | 0.315 m | 0.190 m (hip) | — |
+| `npc_firefighter` | 4,624 | 4,487 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
+| `npc_guard` | 4,334 | 4,456 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
-The widest character is `npc_firefighter` at 4,848 triangles and 4,831 vertices, so
-the current headroom is 1,152 triangles and 169 vertices per character. Any face
-or hand refinement must come out of that budget or replace existing detail. The
-refit backpack measures 380 triangles / 406 vertices; the character and gear
-table above remains valid because the character meshes did not change.
+The widest character is `npc_coat` at 4,754 triangles and 4,574 vertices, so the
+current headroom is 1,246 triangles and 426 vertices per character. Any hand or
+clothing refinement must come out of that budget or replace existing detail. The
+refit backpack measures 380 triangles / 406 vertices.
## Baseline findings, ranked
@@ -124,11 +125,12 @@ table above remains valid because the character meshes did not change.
photo prop is now hidden outside `photo`/`film` poses
(`photo_prop_visible_for_pose()`), so it only appears when the character is
actually taking a picture; the after frame confirms one bag at rest.
-2. **The face is the largest portrait gap.** The eyes, nose and lips are
- readable but sit on a shallow, faceted head: the eye whites protrude, the
- eyelid/cheek transition is missing, and the mouth is a dark slot. This is
- intentionally stylized, but the head volume and eye socket are the first
- things a player sees at conversation range.
+2. **Face depth. Fixed 2026-11-02.** The sclera was recessed into the socket and
+ wrapped with a thin upper lid; the separate jaw/cheek spheres were replaced
+ by a chin plate and cheekbones sculpted into the head mesh. That removes the
+ dark intersection rings that read as a beard shadow and keeps the mouth on
+ the surface. The mouth bar and low-poly brows remain stylized, but the eye
+ socket and jaw/cheek transition now read as one head volume.
3. **Backpack straps read as rigid black bars. Refit 2026-11-02.** The straps
were thin round tubes whose chest/waist points sat inside the torso, so only
fragments showed from the front. They are now flattened bands whose front path
@@ -182,17 +184,20 @@ python3 tools/npc_baseline.py compare \
```
The comparison sheet places the before/after pair for each view side by side.
-The first run is committed as
-[`sheet_npc_baseline_compare-p1.jpg` and `sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg);
-keep the baseline sheet as the before artifact and add a new numbered/after
-sheet rather than replacing it.
+The committed runs are
+[`sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg),
+[`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg)
+and [`sheet_npc_baseline_compare-face.jpg`](blender_previews/sheet_npc_baseline_compare-face.jpg).
+Keep the baseline sheet as the before artifact and add a new comparison sheet
+rather than replacing it.
## Next implementation order
-1. Refine the head/eye socket/jaw volume without changing the face direction or
- the five tint slots.
+1. Improve the torso/hip transition and the remaining body-shape variation.
2. Add a conversation-distance hand/grip capture and fix hand geometry before
judging hand items.
3. Audit hems and footwear in walk/sit/block poses.
-4. Re-run the 48-frame baseline, the 96-frame final-context review, the 24-NPC
+4. Refine nose/lip/ear shapes only if the portrait pass still flags them after
+ the hands and clothing read cleanly.
+5. Re-run the 48-frame baseline, the 96-frame final-context review, the 24-NPC
budget and `blender_assets.py audit/fit`; publish the next comparison sheet.
diff --git a/tools/blender_assets.py b/tools/blender_assets.py
index 914c0ad..b152c32 100644
--- a/tools/blender_assets.py
+++ b/tools/blender_assets.py
@@ -728,23 +728,48 @@ def _hood(name, mat, head_center):
return _finish(_link(name, bm), mat, smooth=True, sharp_angle=60)
+def _shape_head_mesh(head):
+ """Sculpt a chin plate and cheekbones into the head sphere itself.
+
+ Separate jaw/cheek spheres leave visible intersection rings in the baked AO,
+ which read as a beard shadow. Moving the head's own front vertices keeps the
+ surface continuous while still giving the key light something to catch.
+ """
+ bm = bmesh.new()
+ bm.from_mesh(head.data)
+ for v in bm.verts:
+ x, y, z = v.co
+ if y >= 0.0:
+ continue
+ ax = abs(x)
+ # Chin/jaw plate: forward and slightly wider below the mouth.
+ jaw = (max(0.0, 1.0 - abs(z - 1.565) / 0.055)
+ * max(0.0, 1.0 - ax / 0.075))
+ v.co.y -= 0.012 * jaw
+ v.co.x *= 1.0 + 0.06 * jaw
+ # Cheekbone: a shallow, wide lift under each eye.
+ cheek = (max(0.0, 1.0 - abs(z - 1.610) / 0.040)
+ * max(0.0, 1.0 - abs(ax - 0.072) / 0.050))
+ v.co.y -= 0.006 * cheek
+ bm.normal_update()
+ bm.to_mesh(head.data)
+ bm.free()
+
+
def _face(mats, spec):
"""Head with readable eyes, brow ridge, nose, lips and ears.
At conversation distance the eye region carries the read, so each eye gets a
- white sclera, a dark iris and an upper lid, and the face gets cheekbones and
- a nose bridge instead of a single cone (realism pass detail work).
+ white sclera, a dark iris and an upper lid, and the face gets sculpted
+ cheekbones and a nose bridge instead of a single cone (realism pass detail
+ work).
"""
skin, acc, hair = mats["skin"], mats["accessory"], mats["hair"]
white = material("eye_white", textured_solid=True)
- parts = [sphere("Head", 0.155, (0, 0, 1.635), skin, seg=18, rings=12,
- scale=(0.92, 1.0, 1.12))]
- # jaw / chin and cheekbones soften the egg into a face
- parts.append(sphere("Jaw", 0.10, (0, -0.04, 1.556), skin, seg=12, rings=8,
- scale=(1.0, 1.0, 0.8)))
- for s in (-1.0, 1.0):
- parts.append(sphere("Cheek", 0.034, (0.076 * s, -0.104, 1.606), skin, seg=8,
- rings=5, scale=(1.0, 0.7, 0.8)))
+ head = sphere("Head", 0.155, (0, 0, 1.635), skin, seg=18, rings=12,
+ scale=(0.92, 1.0, 1.12))
+ _shape_head_mesh(head)
+ parts = [head]
# nose: bridge and ball (nostrils were cut to stay inside the vertex budget)
parts.append(cyl("NoseBridge", 0.016, 0.022, 0.06, (0, -0.145, 1.652), skin, seg=8,
rot=(math.radians(-72), 0, 0), smooth=True))
@@ -756,20 +781,25 @@ def _face(mats, spec):
scale=(0.42, 1.0, 1.35)))
parts.append(sphere("EarLobe", 0.018, (0.142 * s, -0.005, 1.605), skin, seg=8,
rings=5, scale=(0.45, 1.0, 0.9)))
- # eye: sclera, iris, upper lid and brow
- ex, ey, ez = 0.052 * s, -0.138, 1.664
+ # Eye: recess the sclera into the socket, then wrap it with skin lids.
+ # The old white ball sat ~1 cm proud of the head; this keeps the iris
+ # readable without the doll-like bulge.
+ ex, ey, ez = 0.052 * s, -0.134, 1.664
parts.append(sphere("EyeWhite", 0.019, (ex, ey, ez), white, seg=8, rings=5,
- scale=(1.25, 0.7, 0.85)))
- parts.append(sphere("Iris", 0.0105, (ex, ey - 0.013, ez), acc, seg=8, rings=5,
+ scale=(1.25, 0.62, 0.85)))
+ parts.append(sphere("Iris", 0.0105, (ex, ey - 0.007, ez), acc, seg=8, rings=5,
scale=(1.0, 0.55, 1.0)))
- parts.append(box("Lid", (0.038, 0.010, 0.008), (ex, ey + 0.004, ez + 0.014),
- skin, rot=(math.radians(-10), 0, 0)))
+ # One thin upper lid; no lower lid, which made the eyes read as
+ # half-closed slits at conversation distance.
+ parts.append(sphere("UpperLid", 0.020, (ex, ey - 0.006, ez + 0.014), skin,
+ seg=8, rings=4, scale=(1.20, 0.32, 0.24),
+ rot=(math.radians(-10), 0, 0)))
parts.append(box("Brow", (0.055, 0.014, 0.012), (0.052 * s, -0.143, 1.699), hair,
rot=(0, 0.14 * s, 0.0)))
# lips: upper and lower instead of a single bar
- parts.append(box("LipUpper", (0.052, 0.012, 0.008), (0, -0.145, 1.588), acc,
+ parts.append(box("LipUpper", (0.052, 0.012, 0.008), (0, -0.156, 1.588), acc,
rot=(math.radians(6), 0, 0)))
- parts.append(box("LipLower", (0.046, 0.012, 0.009), (0, -0.143, 1.578), acc))
+ parts.append(box("LipLower", (0.046, 0.012, 0.009), (0, -0.154, 1.578), acc))
if spec.get("beard"):
parts.append(sphere("Beard", 0.105, (0, -0.06, 1.55), hair, seg=12, rings=8,
scale=(1.0, 0.9, 0.75), cut_below=-0.2))