s&box/field-guide
the symptom, in your words

"Ragdoll a scripted-rig NPC with pure engine physics"

✓ verified on engine 26.07lane: Rigging & animationposted
▸ SYMPTOM

You want an NPC to crumple / faint / get hit without authoring collapse animation clips. Per-bone C# posing looks tempting and then explodes (see setbonetransform-silently-noop). Limbs float disconnected while the torso ragdolls (see bone-name-dot-to-underscore).

▸ CAUSE

Ragdoll for a scripted-rig character is engine physics, not clip authoring and not SetBoneTransform.

You put a PhysicsShapeList + PhysicsJointList in the vmdl (capsule per major bone + joints). Sandbox.ModelPhysics builds one physics body per bone, simulates them, and writes results back onto the SkinnedModelRenderer's bones.

▸ FIX

1. Author physics nodes in the vmdl

Copy schema from citizen prefabs:

snippet
addons/citizen/Assets/models/citizen/prefabs/citizen_physics{shape,joint}list.vmdl_prefab

Two nodes under RootNode children:

  • PhysicsShapeList → children = capsules (PhysicsShapeCapsule)
  • PhysicsJointList → children = joints

Capsule fields: parent_bone, surface_prop="flesh", collision_tags="solid", radius, point0, point1 (capsule axis in the bone's local frame). For bones that run down local +X: point0 ≈ [r/2,0,0]point1 ≈ [len−r/2,0,0].

Joints name bodies by bone name via parent_body / child_body:

  • PhysicsJointConical (ballsocket) — spine/neck/head/shoulder/hip
    (enable_swing_limit / swing_limit, twist limits, friction)
  • PhysicsJointRevolute (hinge) — elbows & knees
    (enable_limit / min_angle / max_angle)

anchor_origin = pivot in the parent body's local frame = child bone head ≈ [parent_len, 0, 0].

Every joint's parent and child must have a shape or the body floats disconnected — give the neck a body; it bridges chest→head.

Units: physics shape coords are in the same unit as mesh+bones BEFORE ModelModifier_ScaleAndMirror. For a meters→cm FBX lane that means centimetres; the vmdl's ModelModifier_ScaleAndMirror 0.3937 scales physics with mesh+anims. Citizen proof: radius 10.0 = 10 cm pelvis capsule alongside the same 0.3937 modifier. Do not pre-scale capsules to inches.

Bone names in physics KV3: sanitize Blender .L/.R_L/_R — see bone-name-dot-to-underscore.

2. Toggle at runtime

snippet
var physics = Components.GetOrCreate<ModelPhysics>();
physics.Renderer = skinned;          // SkinnedModelRenderer
physics.Model = skinned.Model;       // same physics-bearing vmdl
physics.Enabled = true;
physics.MotionEnabled = true;

// shove
foreach (var body in physics.PhysicsGroup.Bodies)
    body.ApplyImpulse(impulse);

// stand back up
physics.Destroy(); // bone control returns to Sequence next frame
// snap root to pelvis body rest (Bodies[0].Position), typically x/y only

Members ModelPhysics.{Renderer,Model,MotionEnabled,Enabled,PhysicsGroup} and PhysicsBody.ApplyImpulse / Position resolve under dotnet build. Note: the type may not enumerate via reflection GetTypes() (native registration) — trust the compiler.

Radii as a fraction of each bone's length (+ abs clamps) scale with proportions better than hardcoded human dims. Generous swing/twist limits with identity anchor_angles can read as a crumple while you tune axes. (needs verification) for your exact bone-length table — copy citizen, then tune.

▸ WHY IT WORKS

ModelPhysics is the engine's bind between ModelDoc physics bodies and a skinned pose. Once capsules/joints exist on the compiled model, enabling the component hands bone control to the physics world and writes simulated transforms back each tick — which is exactly what a ragdoll is. Collapse clips become unnecessary; SetBoneTransform never enters the picture.

Verified on engine 26.07 — seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?