Hanging orc gear on sockets with a build-time clip check
Weapons and armour split off the orc model onto named sockets in Godot, a Blender check that fails builds when gear sinks in, and gems that glow out of step.
I took the orc’s sword and armour off its model and hung them on named sockets, in three steps of one rework. A Blender build now fails if a held weapon enters the hand or a worn piece sinks more than 5 cm into the body in any animation. The gems on the gear glow on their own time. All of it is still a trial: monsters in the game wear the old joined models. The work is done by AI coding agents that I direct.
Why was the sword’s hilt running through the orc’s fist?
Until now an orc was 20 separate models, 5 themes by 4 states, each with its weapon and armour baked into one mesh. I asked for the pipeline to be reworked around sockets, so a monster is a body plus shared gear instead of hundreds of baked variants.
Step 1 split the fantasy peak orc’s sword off as a small model of its own. The game hangs it on the right hand bone with a BoneAttachment3D. The new option --apart <socket> leaves a socket’s parts out of the body and writes the socket’s bone and place into the body’s manifest. Compared at 9 moments in each of the 5 animations, the socketed sword sat 0.02 mm from the joined one.
Then I watched the film and saw the hilt running through the hand. The joined orc had the same fault, which is why that comparison passed: a measurement against a reference only finds differences from the reference.
The cross-guard sat at a fixed 0.105 m along the haft, inside a fist 0.19 m wide. The kit’s hilt proportions were fixed numbers and it had no way to say how wide a fist is.

The fix went into the kit. kit.fist_span(hand) gives how much of a haft a closed fist covers, claws and thumb counted. A helper gives a weapon the two ends of the fist with 12 mm clear, and each part of a hilt is placed at the larger of its own proportion and the fist’s end plus its size.
Then I had the check written, and run on the old hilt to watch it fail. clip_check.py runs inside the build, because it needs the separate parts and the poses, which the finished file has neither of. It tests every socketed part against every body piece and every other socket’s parts, both ways round, in the rest pose and every 4th frame of all five animations, about 60 poses. A held thing may touch nothing, to 4 mm. Contact that is meant, like the gripped haft, is marked on the part. A failure prints the part, the bone, the piece it hit, the animation, the time and the depth.
On the old hilt it found 9 failures, the worst the guard 43 mm into the wrist. On the fitted hilt, 0. It also caught two more weapons: the meat theme’s bone cleaver (12 to 13 mm into the fist) and the eldritch theme’s void blade (heel 18 to 20 mm in). Both were refitted, and 12 of the 20 orc variants were rebuilt.
The check cannot see a thin sheet passing between sample points, anything between sampled frames, cloth, or dead variants. It also cannot tell me how a grip looks, so each build draws the grip from five sides for a person to look at.
A dead orc’s sword stood on its point beside the body. Fallen pieces are tumbled 70 to 110 degrees about a random level axis and set down on their lowest point, which suits a helm and stands a long blade on its tip. A held thing that falls is now turned to lie along the ground.
How can any orc hold any weapon?
Step 2 made weapons models of their own, built with no creature loaded and no creature in the name. The weapon’s origin is the grip, +Y runs along the haft and +Z toward the knuckles. The new root model, gen_weapon.py, is nothing but a fist. Each theme’s weapon dressing hangs on it and is exported alone: 12 files, 4 weapons by 3 living states. A state changes surface and light only, never shape, so one clip check covers all three.
My first attempt built the weapon upright, and the six-sided grips came out turned, with corners about 1 cm off the baked orcs. I kept a “standard fist” written down as numbers, the orc’s, and built the weapon in its attitude. The result came out 0.02 mm from the baked weapons.
The game sizes a weapon to its holder by comparing fists. The same arithmetic is written twice, in GDScript for the game and in Python for the pipeline, and both read one JSON file:
ratio = holder_fist_width / weapon_fist_width
scale = clamp(ratio * multiplier, smallest, largest) # one number, every axis
slide the weapon along its haft until the middle of its fist is on the middle of the holder's
then nudge [through the palm, along the haft, toward the knuckles]
then turn [about the palm's axis, about the haft, about the knuckles' axis]
Defaults are a multiplier of 1.0, smallest 0.4 and largest 2.5. The file has levels for creatures, weapons and single pairings, and a value comes from the most specific level that gives it. used lists the pairings the game may make.
I tried scaling two ways on a runt’s longsword at 1.5: every axis, and length alone with the haft kept as thick as the fist. Both grips were clean, but length alone read as a different, narrower sword with a stretched pommel. One number on every axis stayed.
The second holder is the orc’s generator drawn at 0.72, 1.47 m against 2.04 m, with the orc’s skeleton and clip names. It stays a test fixture until a real smaller creature replaces it.
Every pairing the data lists is put in the hand at its fit and run through every clip. Held parts may enter the hand by 4 mm. The result goes into the body’s manifest with the fit it was checked at, so MonsterSockets.problem() can tell a pairing that passed from one that was never checked, or was re-sized since. All 8 pairings, 2 holders by 4 weapons, pass. The steampunk machine cleaver put a pipe 5 mm into the fingers and is left uninstalled, because the fix belongs in its shape.
Moving the breastplate to the spine
Step 3 did the same for armour. Eight pieces are one model, SM_gear_<creature>_<theme>_<state>.glb, one mesh a socket, sharing one material and one 256 px texture. The body is built bare. What a worn-down state has lost or lets hang loose is a list in the body’s data: missing for sockets whose piece the state takes away, and loose for a transform in the piece’s own space. A smaller orc wears the same file scaled, 0.72 for the runt.
Dents are dropped from gear kept apart, because a dent is shape and the three states’ gear is one shape.
I built two ways of drawing it in the game and measured them. Gear.HUNG gives each piece its own BoneAttachment3D. Gear.MERGED joins all pieces into one skinned mesh, with every point weighted to its socket’s one bone. Draw calls with shadows, joined, hung and merged:
| Joined | Hung | Merged | |
|---|---|---|---|
| One orc | 6 | 39 | 18 |
| Twenty of one variant | 100 | 142 | 208 |
| Twenty of three states | 100 | 208 | 223 |
Merging is cheaper for one monster. For a pack it loses, because twenty copies of one still piece share a draw call and a skinned mesh is drawn once a body. Step 4, wear as a material, is for mixed packs sharing their gear’s draw calls.
Making the build fail meant choosing a depth. The measured depths ran from 2 to 25 cm with no gap to put a line in, so the line stayed at the 5 cm the check already listed by. The breastplate was the worst, 18 to 25 cm under the belly in the attack, because it rode the chest bone while the body folded beneath it.

I moved the breast and pack sockets to the spine bone and dropped the breast socket 10 cm. The head stays up and the jaw shut through the strike. In the peak state the breastplate’s worst depth went from 17.7 to 3.2 cm. On the fantasy orc, pairs over 5 cm went from 25, 27 and 20 (peak, declining, decayed) to 0.
Those fits are shared by every theme, so all 20 joined orcs were rebuilt, including an attack I had approved. A tasset lying on the loincloth read as 9 cm inside it, so open sheets, the loincloth and belt, are no longer tested against.
Four of the five themes fail the new check and are installed anyway. I decided not to fix them: the old models are to be redone on the new head and body kits and kept only for comparison.
Giving every gem its own time
Twenty orcs with the same sword cost 108 draw calls when the gems shared one material, but their gems pulsed in unison. I chose a shader with a per-instance offset over a few shared materials out of step, even if it was more effort.
Godot 4 does not let a script read the shader a StandardMaterial3D generates, so GearGlow carries a hand-written one, with the standard material’s albedo, roughness, metallic, specular and emission copied in. One ShaderMaterial serves every monster holding a model in a state. Three numbers set the level, computed in the vertex function:
glow_turnon the material: the game clock’s seconds times the glow’s rate. A stopped clock stops it.glow_offset, an instance uniform: how many turns ahead this holder is.glow_rate, an instance uniform: from 0.875 to 1.125 in whole 64ths.
MonsterGlow sets both uniforms once with set_instance_shader_parameter, derived from the node’s instance id.
float at = glow_turn * glow_rate + glow_offset;
float breath = 0.5 - 0.5 * cos(fract(at) * TAU);
float wander = 0.65 * drift(at, 17u) + 0.35 * drift(at * 2.0, 31u);
// peak: 1.0 - depth * breath
// declining: 1.0 - depth * (0.3 * breath + 0.7 * smoothstep(0.5, 0.8, wander))
// decayed: 1.0 - depth * (1.0 - 0.8 * smoothstep(0.4, 0.85, wander))
drift is one-dimensional value noise: an integer hash of each whole step, eased with a smoothstep. The same hash is written in GDScript so a test can compare the level a gem should be at.
My first take kept the old flicker, a hard dip on a dice roll, and moved it into the shader. Gems were out of step and draw calls unchanged, but my reaction to the film was that they looked like a flashlight switching on and off. The second take gave gear shapes of its own, every one eased: a slow breath at peak, slower with soft fades when declining, mostly dim with faint pulses when decayed. Peak needs a depth of 0.5 to be seen, because a peak gem at energy 1.6 saturates to white and a 6% breath, which is what the eyes do, is invisible. The second film looked great to me.
Draw calls stayed at 108 for twenty orcs. No shape changes more than 1.83% of its level in a frame.
Next
Step 4 makes wear a material, so a mixed pack shares its gear’s draw calls. Still to fix are the steampunk machine cleaver’s pipe and a fit for bodies with proportions other than the orc’s.