diff --git a/CHANGELOG.md b/CHANGELOG.md
index 848343f..a6a9c6c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,8 @@ Semantic Versioning.
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.
+- Shoes and responder boots now have rounded toes and heels instead of reading
+ as flat blocks.
### Fixed
diff --git a/MEMORY.md b/MEMORY.md
index 1272df4..3994e03 100644
--- a/MEMORY.md
+++ b/MEMORY.md
@@ -96,6 +96,10 @@ 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
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,
diff --git a/TODO.md b/TODO.md
index efeb192..d3860fe 100644
--- a/TODO.md
+++ b/TODO.md
@@ -131,8 +131,9 @@ and equipment fit.
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
- responder and firefighter footwear language.
+- [x] Audit shoes/boots for silhouette, grounding and contact with the floor; preserve the
+ responder and firefighter footwear language. (Rounded toe/heel caps; boots keep the black
+ rubber language; all eight rebuilt, grounding and proportions still pass.)
- [ ] Improve clothing layer transitions at collars, cuffs, plackets, waistbands, pockets, badges
and reflective bands; remove obvious z-fighting or floating detail.
- [ ] Add believable fabric folds/wrinkles at elbows, knees, waist and hems where the extra
diff --git a/assets/models/blender/manifest.json b/assets/models/blender/manifest.json
index d2c64a7..641856a 100644
--- a/assets/models/blender/manifest.json
+++ b/assets/models/blender/manifest.json
@@ -453,7 +453,7 @@
"max": [
0.321,
1.824,
- 0.181
+ 0.189
],
"min": [
-0.321,
@@ -487,15 +487,15 @@
"accessory",
"hair"
],
- "triangles": 4226,
- "vertices": 4119
+ "triangles": 4482,
+ "vertices": 4260
},
"npc_coat": {
"bounds_godot": {
"max": [
0.332,
1.824,
- 0.181
+ 0.189
],
"min": [
-0.332,
@@ -528,8 +528,8 @@
"accessory",
"hair"
],
- "triangles": 5218,
- "vertices": 4975
+ "triangles": 5474,
+ "vertices": 5264
},
"npc_dress": {
"bounds_godot": {
@@ -567,8 +567,8 @@
"accessory",
"hair"
],
- "triangles": 4644,
- "vertices": 4439
+ "triangles": 4900,
+ "vertices": 4719
},
"npc_firefighter": {
"bounds_godot": {
@@ -608,15 +608,15 @@
"accessory",
"hair"
],
- "triangles": 5088,
- "vertices": 5240
+ "triangles": 5344,
+ "vertices": 5494
},
"npc_guard": {
"bounds_godot": {
"max": [
0.361,
1.819,
- 0.181
+ 0.189
],
"min": [
-0.361,
@@ -652,15 +652,15 @@
"accessory",
"hair"
],
- "triangles": 4790,
- "vertices": 4865
+ "triangles": 5046,
+ "vertices": 5096
},
"npc_hoodie": {
"bounds_godot": {
"max": [
0.326,
1.856,
- 0.181
+ 0.189
],
"min": [
-0.326,
@@ -693,15 +693,15 @@
"accessory",
"hair"
],
- "triangles": 5048,
- "vertices": 4712
+ "triangles": 5304,
+ "vertices": 4950
},
"npc_jacket": {
"bounds_godot": {
"max": [
0.332,
1.824,
- 0.181
+ 0.189
],
"min": [
-0.332,
@@ -734,15 +734,15 @@
"accessory",
"hair"
],
- "triangles": 4616,
- "vertices": 4447
+ "triangles": 4872,
+ "vertices": 4661
},
"npc_vest": {
"bounds_godot": {
"max": [
0.321,
1.819,
- 0.181
+ 0.189
],
"min": [
-0.321,
@@ -775,8 +775,8 @@
"accessory",
"hair"
],
- "triangles": 4724,
- "vertices": 4713
+ "triangles": 4980,
+ "vertices": 4879
},
"palm_tree": {
"bounds_godot": {
diff --git a/assets/models/blender/npc/npc_casual.glb b/assets/models/blender/npc/npc_casual.glb
index 3c43af3..712de73 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 facf84c..86302ed 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 a41bb12..a5d6378 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 0b8eb0a..09d3147 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 d720baf..2dcfa5c 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 bb37989..a947abc 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 bfc1d09..ce47f67 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 dc3ca95..846460d 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 ea28796..ef11298 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 abfd458..fb81e73 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 a9a90e8..53a8685 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 352b73c..1e88e4e 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 0a4617e..494c68c 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 dec4699..f491002 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 858cfed..c83f4a0 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 453b3b1..7d98956 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 8f8c991..0607ac2 100644
--- a/docs/blender-assets.md
+++ b/docs/blender-assets.md
@@ -96,7 +96,7 @@ All eight use the `npc_base` contract, so each one can replace
variants (vertex count after seam splits; `CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in
`tools/blender_assets.py`, raised from 6,000/5,000 on 2026-11-02). The frozen
`npc_base` fallback keeps its original 6,000/4,000 contract. The current range
- is 4,226–5,218 triangles and 4,119–5,240 vertices, leaving room for the
+ is 4,482–5,474 triangles and 4,260–5,494 vertices, leaving room for the
remaining grip and clothing detail.
Construction: the torso and limbs are one continuous Skin-modifier mesh,
diff --git a/docs/blender_previews/README.md b/docs/blender_previews/README.md
index 4daf974..98d3423 100644
--- a/docs/blender_previews/README.md
+++ b/docs/blender_previews/README.md
@@ -51,6 +51,7 @@ Committed contact sheets and local Blender renders for the character, gear and p
|
| [`sheet_npc_baseline_compare-face-detail.jpg`](sheet_npc_baseline_compare-face-detail.jpg) |
|
| [`sheet_npc_hands_compare.jpg`](sheet_npc_hands_compare.jpg) |
|
| [`sheet_npc_hands_grip_compare.jpg`](sheet_npc_hands_grip_compare.jpg) |
+|
| [`sheet_npc_baseline_compare-shoes.jpg`](sheet_npc_baseline_compare-shoes.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-shoes.jpg b/docs/blender_previews/sheet_npc_baseline_compare-shoes.jpg
new file mode 100644
index 0000000..73bc1b5
Binary files /dev/null and b/docs/blender_previews/sheet_npc_baseline_compare-shoes.jpg differ
diff --git a/docs/development-status.md b/docs/development-status.md
index 189226d..f250967 100644
--- a/docs/development-status.md
+++ b/docs/development-status.md
@@ -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 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 footwear pass (0.9.4 work): casual shoes gained rounded toe and heel caps; responder/firefighter boots gained matching toe/heel volumes while retaining the black rubber boot language. All eight rebuilt; comparison `sheet_npc_baseline_compare-shoes.jpg`.
- 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 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).
diff --git a/docs/npc-visual-review.md b/docs/npc-visual-review.md
index d7a3a2b..685fbba 100644
--- a/docs/npc-visual-review.md
+++ b/docs/npc-visual-review.md
@@ -117,17 +117,17 @@ From `python3 tools/blender_assets.py fit` and the manifest:
| Character | Tris | Verts | Head width | Clothing radius | Notable gear clearances |
| --- | ---: | ---: | ---: | ---: | --- |
-| `npc_casual` | 4,226 | 4,119 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
-| `npc_jacket` | 4,616 | 4,447 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
-| `npc_coat` | 5,218 | 4,975 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
-| `npc_vest` | 4,724 | 4,713 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
-| `npc_dress` | 4,644 | 4,439 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
-| `npc_hoodie` | 5,048 | 4,712 | 0.315 m | 0.190 m (hip) | — |
-| `npc_firefighter` | 5,088 | 5,240 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
-| `npc_guard` | 4,790 | 4,865 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
+| `npc_casual` | 4,482 | 4,260 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
+| `npc_jacket` | 4,872 | 4,661 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
+| `npc_coat` | 5,474 | 5,264 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
+| `npc_vest` | 4,980 | 4,879 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
+| `npc_dress` | 4,900 | 4,719 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
+| `npc_hoodie` | 5,304 | 4,950 | 0.315 m | 0.190 m (hip) | — |
+| `npc_firefighter` | 5,344 | 5,494 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
+| `npc_guard` | 5,046 | 5,096 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
-`npc_coat` is widest at 5,218 triangles (2,782 below the 8,000 cap);
-`npc_firefighter` is closest on vertices at 5,240 (2,760 below the 8,000 cap).
+`npc_coat` is widest at 5,474 triangles (2,526 below the 8,000 cap);
+`npc_firefighter` is closest on vertices at 5,494 (2,506 below the 8,000 cap).
The refit backpack measures 380 triangles / 406 vertices.
## Baseline findings, ranked
@@ -169,9 +169,10 @@ The refit backpack measures 380 triangles / 406 vertices.
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
details in the current portraits.
-6. **Shoes are simple blocks.** They are grounded, but the toe/heel silhouette
- is minimal; the responder and firefighter footwear should remain distinct
- after any refinement.
+6. **Shoes: fixed 2026-11-02.** Casual shoes gained rounded toe and heel caps;
+ responder/firefighter boots gained matching toe/heel volumes while keeping the
+ taller black rubber boot language. Both stay above the sole contact plane, and
+ the full-body comparison is `sheet_npc_baseline_compare-shoes.jpg`.
7. **Headgear scale and clearance. Checked 2026-11-02.** The cap and helmet
width ratios are 1.067 and 1.651; those are expected for headgear. Final-context
portraits confirm the cap, firefighter helmet, hood and sunglasses clear the
@@ -225,6 +226,6 @@ rather than replacing it.
grips; the forward offset is already in place.
2. Add the remaining body-shape variation between variants and re-measure the
LOD/impostor silhouettes.
-3. Audit hems and footwear in walk/sit/block poses.
+3. Audit hems in walk/sit/block poses; footwear silhouette is now in the baseline.
4. 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 be391cc..9bd543f 100644
--- a/tools/blender_assets.py
+++ b/tools/blender_assets.py
@@ -845,6 +845,12 @@ def _hands_and_shoes(mats, spec):
seg=6, rings=4))
parts.append(box("Shoe", (0.10, 0.25, 0.075), (0.12 * s, -0.045, 0.04), shoe,
bevel=0.028, segs=1))
+ # Round the toe and heel so the low-poly shoe reads as footwear rather
+ # than a box; both stay above the rubber sole contact plane.
+ parts.append(sphere("ShoeToe", 0.045, (0.12 * s, -0.155, 0.045), shoe,
+ seg=8, rings=5, scale=(1.05, 0.80, 0.55)))
+ parts.append(sphere("ShoeHeel", 0.034, (0.12 * s, 0.070, 0.045), shoe,
+ seg=8, rings=5, scale=(1.0, 0.55, 0.55)))
parts.append(box("Sole", (0.106, 0.262, 0.02), (0.12 * s, -0.045, 0.01),
material("rubber", textured_solid=True)))
return parts
@@ -916,6 +922,12 @@ def _clothing(spec, mats):
rot=(0, 0, 0), seg=14, minor_seg=4))
parts.append(box("Boot", (0.12, 0.27, 0.22), (0.12 * s, -0.04, 0.11),
mats["rubber"], bevel=0.03, segs=2))
+ parts.append(sphere("BootToe", 0.055, (0.12 * s, -0.155, 0.075),
+ mats["rubber"], seg=8, rings=5,
+ scale=(1.0, 0.80, 0.55)))
+ parts.append(sphere("BootHeel", 0.045, (0.12 * s, 0.080, 0.055),
+ mats["rubber"], seg=8, rings=5,
+ scale=(1.0, 0.55, 0.50)))
if layer == "guard":
belt = torus("Belt", 0.168, 0.022, (0, 0, 0.86), mats["leather"], seg=24,
minor_seg=4, scale=(1.0, 0.72, 1.4))