diff --git a/docs/gallery/assets/vertex-weight-limit-hero.webp b/docs/gallery/assets/vertex-weight-limit-hero.webp
index 8050191..fbd5b3e 100644
Binary files a/docs/gallery/assets/vertex-weight-limit-hero.webp and b/docs/gallery/assets/vertex-weight-limit-hero.webp differ
diff --git a/docs/gallery/contact-sheets/game-pipeline-audit-contact-sheet.webp b/docs/gallery/contact-sheets/game-pipeline-audit-contact-sheet.webp
index e728ddf..576fc13 100644
Binary files a/docs/gallery/contact-sheets/game-pipeline-audit-contact-sheet.webp and b/docs/gallery/contact-sheets/game-pipeline-audit-contact-sheet.webp differ
diff --git a/docs/gallery/vertex-weight-limit/index.html b/docs/gallery/vertex-weight-limit/index.html
index c76cfe6..bb3469b 100644
--- a/docs/gallery/vertex-weight-limit/index.html
+++ b/docs/gallery/vertex-weight-limit/index.html
@@ -183,7 +183,7 @@
-A runnable example that rigs a mech arm — bolted pedestal and shoulder fairing, a shoulder hub and clevis-ended upper arm, the elbow hinge pin inside a ribbed flex bellows, a long plated forearm, wrist bellows and collar, and a three-finger gripper — with deliberately rich five-bone weight bumps in the bellows, then enforces the game-engine maximum of four bone influences per vertex through the data API, following mesh-editing-and-bmesh and building on the linear-blend-skinning precedent of armature-bend.
+A runnable example that rigs a mech arm — bolted pedestal and shoulder fairing, an upper arm ending in clevis cheeks, the elbow hinge pin capped through a ribbed flex bellows, a long plated forearm, wrist bellows and collar, and a three-finger gripper — with deliberately rich five-bone weight bumps in the bellows, then enforces the game-engine maximum of four bone influences per vertex through the data API, following mesh-editing-and-bmesh and building on the linear-blend-skinning precedent of armature-bend.
Pipeline arc: modeling/LOD in lod-decimate-chain, weighting here, export in gltf-export-roundtrip.
What it witnesses: the skinning constraint every game engine enforces and AI-generated rigging code most often violates silently.
1. The limit is a data-API operation, not a context operator. Instead of bpy.ops.object.vertex_group_limit_total, the example reads each vertex's groups, keeps the top four by weight, VertexGroup.removes the rest, and renormalizes the survivors. Dropping without renormalizing leaves sums at 0.986 — a mesh that shrinks toward the origin under load (the check's measured failure, 1.438e-02 off unit sum). 2. The armature modifier is still exactly linear blend skinning after the limit: every depsgraph-evaluated vertex equals Σ wᵢ · (pose.matrix @ bone.matrix_local.inverted()) @ rest, with the weights read back from the mesh's own deform layer (v.groups) — the weights on the mesh are the contract, not the weights you meant to write. Measured lbs_err = 3.0e-07. 3. Pruning must not damage the pose. Evaluated positions before and after the limit are held within 0.05 (measured 3.0e-03), the pedestal mount stays exactly pinned (Root is unposed), and the pre-limit authoring really carries five influences in the boots — otherwise the witness would be vacuous.
@@ -248,7 +248,7 @@ Source
BONE_SPANS = {"Root": (-0.35, 0.10), "Shoulder": (0.10, 1.35),
"Elbow": (1.35, 2.55), "Wrist": (2.55, 2.90), "Claw": (2.90, 3.35)}
BONE_CENTERS = {b: (s[0] + s[1]) / 2 for b, s in BONE_SPANS.items()}
-POSE_DEG = {"Shoulder": -8.0, "Elbow": -35.0, "Wrist": -18.0, "Claw": 12.0}
+POSE_DEG = {"Shoulder": -8.0, "Elbow": -35.0, "Wrist": -14.0, "Claw": 6.0}
# armor palette slots
GUNMETAL, ORANGE, RUBBER, ACCENT = 0, 1, 2, 3
@@ -332,25 +332,24 @@ Source
cone_part(bm, 0.05, 0.04, 0.06,
(0.36 * math.cos(a), 0.36 * math.sin(a), 0.03),
(0, 0, 0), GUNMETAL, part_of, "Root")
- # shoulder hub poking through the fairing (rigid on Shoulder)
- cone_part(bm, 0.19, 0.19, 0.56, (0.0, 0.0, 0.62), (90, 0, 0),
- GUNMETAL, part_of, "Shoulder")
- # upper arm shell, slimmer than the old barrel (rigid on Shoulder)
- tag(lathe_part(bm, [(0.45, 0.19), (0.60, 0.23), (0.80, 0.24),
- (1.00, 0.23), (1.15, 0.21)], ORANGE), "Shoulder")
+ # upper arm shell rooted deep inside the fairing — no gap at the
+ # shoulder when the joint articulates (rigid on Shoulder)
+ tag(lathe_part(bm, [(0.10, 0.14), (0.30, 0.19), (0.45, 0.19),
+ (0.60, 0.23), (0.80, 0.24), (1.00, 0.23),
+ (1.15, 0.21)], ORANGE), "Shoulder")
# clevis cheeks the hinge pin rides in (rigid on Shoulder)
for sy in (-1, 1):
box_part(bm, (0.12, 0.05, 0.42), (0.0, sy * 0.165, 1.24),
(0, 0, 0), ORANGE, part_of, "Shoulder")
# elbow flex bellows: the five-influence zone the limit prunes
- tag(lathe_part(bm, [(1.15, 0.17), (1.20, 0.20), (1.26, 0.16),
- (1.33, 0.20), (1.40, 0.16), (1.47, 0.20),
- (1.55, 0.17)], RUBBER), "FLEX")
+ tag(lathe_part(bm, [(1.15, 0.21), (1.20, 0.19), (1.26, 0.21),
+ (1.33, 0.19), (1.40, 0.21), (1.47, 0.19),
+ (1.55, 0.19)], RUBBER), "FLEX")
# the hinge pin through the cheeks, capped both ends (rides Elbow)
cone_part(bm, 0.15, 0.15, 0.46, (0.0, 0.0, 1.35), (90, 0, 0),
GUNMETAL, part_of, "Elbow")
for sy in (-1, 1):
- cone_part(bm, 0.18, 0.18, 0.05, (0.0, sy * 0.225, 1.35),
+ cone_part(bm, 0.17, 0.17, 0.05, (0.0, sy * 0.215, 1.35),
(90, 0, 0), GUNMETAL, part_of, "Elbow")
# bright seal ring on the bellows middle — the accent marking the
# primary pruned-weight zone
@@ -363,19 +362,19 @@ Source
box_part(bm, (0.10, 0.05, 0.60), (0.0, 0.235, 2.02),
(0, 0, 0), ORANGE, part_of, "Elbow")
# wrist flex bellows (second blend zone) + collar
- tag(lathe_part(bm, [(2.45, 0.16), (2.51, 0.19), (2.57, 0.15),
- (2.64, 0.18), (2.70, 0.15), (2.75, 0.16)],
+ tag(lathe_part(bm, [(2.45, 0.18), (2.51, 0.20), (2.57, 0.16),
+ (2.64, 0.19), (2.70, 0.16), (2.75, 0.17)],
RUBBER), "FLEX")
tag(lathe_part(bm, [(2.75, 0.16), (2.85, 0.175), (2.90, 0.16)],
GUNMETAL), "Wrist")
# palm block (rigid on Wrist)
- box_part(bm, (0.26, 0.20, 0.14), (0.0, 0.0, 2.99),
+ box_part(bm, (0.30, 0.22, 0.24), (0.0, 0.0, 2.98),
(0, 0, 0), GUNMETAL, part_of, "Wrist")
# three two-segment fingers, splayed (rigid on Claw)
for a_deg in (90.0, 210.0, 330.0):
a = math.radians(a_deg)
- for pos, tilt, size in (((0.09, 3.11), 12.0, (0.075, 0.11, 0.22)),
- ((0.15, 3.28), 26.0, (0.06, 0.095, 0.18))):
+ for pos, tilt, size in (((0.08, 3.08), 12.0, (0.075, 0.11, 0.22)),
+ ((0.14, 3.26), 26.0, (0.06, 0.095, 0.18))):
off, z = pos
m = (mathutils.Matrix.Translation((0.0, 0.0, z))
@ mathutils.Matrix.Rotation(a, 4, 'Z')
diff --git a/examples/vertex-weight-limit/README.md b/examples/vertex-weight-limit/README.md
index 11d61d9..51d5463 100644
--- a/examples/vertex-weight-limit/README.md
+++ b/examples/vertex-weight-limit/README.md
@@ -1,10 +1,10 @@
# Vertex Weight Limit
A runnable example that rigs a mech arm — bolted pedestal and shoulder
-fairing, a shoulder hub and clevis-ended upper arm, the elbow hinge pin inside
-a ribbed flex bellows, a long plated forearm, wrist bellows and collar, and a
-three-finger gripper — with deliberately rich five-bone weight bumps in the
-bellows, then enforces the game-engine
+fairing, an upper arm ending in clevis cheeks, the elbow hinge pin capped
+through a ribbed flex bellows, a long plated forearm, wrist bellows and
+collar, and a three-finger gripper — with deliberately rich five-bone weight
+bumps in the bellows, then enforces the game-engine
**maximum of four bone influences per vertex** through the data API, following
[`mesh-editing-and-bmesh`](../../skills/mesh-editing-and-bmesh/SKILL.md) and
building on the linear-blend-skinning precedent of
diff --git a/examples/vertex-weight-limit/preview.webp b/examples/vertex-weight-limit/preview.webp
index fec640c..69a5733 100644
Binary files a/examples/vertex-weight-limit/preview.webp and b/examples/vertex-weight-limit/preview.webp differ
diff --git a/examples/vertex-weight-limit/vertex_weight_limit.py b/examples/vertex-weight-limit/vertex_weight_limit.py
index 73389af..12aa5bb 100644
--- a/examples/vertex-weight-limit/vertex_weight_limit.py
+++ b/examples/vertex-weight-limit/vertex_weight_limit.py
@@ -42,7 +42,7 @@
BONE_SPANS = {"Root": (-0.35, 0.10), "Shoulder": (0.10, 1.35),
"Elbow": (1.35, 2.55), "Wrist": (2.55, 2.90), "Claw": (2.90, 3.35)}
BONE_CENTERS = {b: (s[0] + s[1]) / 2 for b, s in BONE_SPANS.items()}
-POSE_DEG = {"Shoulder": -8.0, "Elbow": -35.0, "Wrist": -18.0, "Claw": 12.0}
+POSE_DEG = {"Shoulder": -8.0, "Elbow": -35.0, "Wrist": -14.0, "Claw": 6.0}
# armor palette slots
GUNMETAL, ORANGE, RUBBER, ACCENT = 0, 1, 2, 3
@@ -126,25 +126,24 @@ def tag(rings_verts, bone):
cone_part(bm, 0.05, 0.04, 0.06,
(0.36 * math.cos(a), 0.36 * math.sin(a), 0.03),
(0, 0, 0), GUNMETAL, part_of, "Root")
- # shoulder hub poking through the fairing (rigid on Shoulder)
- cone_part(bm, 0.19, 0.19, 0.56, (0.0, 0.0, 0.62), (90, 0, 0),
- GUNMETAL, part_of, "Shoulder")
- # upper arm shell, slimmer than the old barrel (rigid on Shoulder)
- tag(lathe_part(bm, [(0.45, 0.19), (0.60, 0.23), (0.80, 0.24),
- (1.00, 0.23), (1.15, 0.21)], ORANGE), "Shoulder")
+ # upper arm shell rooted deep inside the fairing — no gap at the
+ # shoulder when the joint articulates (rigid on Shoulder)
+ tag(lathe_part(bm, [(0.10, 0.14), (0.30, 0.19), (0.45, 0.19),
+ (0.60, 0.23), (0.80, 0.24), (1.00, 0.23),
+ (1.15, 0.21)], ORANGE), "Shoulder")
# clevis cheeks the hinge pin rides in (rigid on Shoulder)
for sy in (-1, 1):
box_part(bm, (0.12, 0.05, 0.42), (0.0, sy * 0.165, 1.24),
(0, 0, 0), ORANGE, part_of, "Shoulder")
# elbow flex bellows: the five-influence zone the limit prunes
- tag(lathe_part(bm, [(1.15, 0.17), (1.20, 0.20), (1.26, 0.16),
- (1.33, 0.20), (1.40, 0.16), (1.47, 0.20),
- (1.55, 0.17)], RUBBER), "FLEX")
+ tag(lathe_part(bm, [(1.15, 0.21), (1.20, 0.19), (1.26, 0.21),
+ (1.33, 0.19), (1.40, 0.21), (1.47, 0.19),
+ (1.55, 0.19)], RUBBER), "FLEX")
# the hinge pin through the cheeks, capped both ends (rides Elbow)
cone_part(bm, 0.15, 0.15, 0.46, (0.0, 0.0, 1.35), (90, 0, 0),
GUNMETAL, part_of, "Elbow")
for sy in (-1, 1):
- cone_part(bm, 0.18, 0.18, 0.05, (0.0, sy * 0.225, 1.35),
+ cone_part(bm, 0.17, 0.17, 0.05, (0.0, sy * 0.215, 1.35),
(90, 0, 0), GUNMETAL, part_of, "Elbow")
# bright seal ring on the bellows middle — the accent marking the
# primary pruned-weight zone
@@ -157,19 +156,19 @@ def tag(rings_verts, bone):
box_part(bm, (0.10, 0.05, 0.60), (0.0, 0.235, 2.02),
(0, 0, 0), ORANGE, part_of, "Elbow")
# wrist flex bellows (second blend zone) + collar
- tag(lathe_part(bm, [(2.45, 0.16), (2.51, 0.19), (2.57, 0.15),
- (2.64, 0.18), (2.70, 0.15), (2.75, 0.16)],
+ tag(lathe_part(bm, [(2.45, 0.18), (2.51, 0.20), (2.57, 0.16),
+ (2.64, 0.19), (2.70, 0.16), (2.75, 0.17)],
RUBBER), "FLEX")
tag(lathe_part(bm, [(2.75, 0.16), (2.85, 0.175), (2.90, 0.16)],
GUNMETAL), "Wrist")
# palm block (rigid on Wrist)
- box_part(bm, (0.26, 0.20, 0.14), (0.0, 0.0, 2.99),
+ box_part(bm, (0.30, 0.22, 0.24), (0.0, 0.0, 2.98),
(0, 0, 0), GUNMETAL, part_of, "Wrist")
# three two-segment fingers, splayed (rigid on Claw)
for a_deg in (90.0, 210.0, 330.0):
a = math.radians(a_deg)
- for pos, tilt, size in (((0.09, 3.11), 12.0, (0.075, 0.11, 0.22)),
- ((0.15, 3.28), 26.0, (0.06, 0.095, 0.18))):
+ for pos, tilt, size in (((0.08, 3.08), 12.0, (0.075, 0.11, 0.22)),
+ ((0.14, 3.26), 26.0, (0.06, 0.095, 0.18))):
off, z = pos
m = (mathutils.Matrix.Translation((0.0, 0.0, z))
@ mathutils.Matrix.Rotation(a, 4, 'Z')