the symptom, in your words

"Citizen held-item pose map: HoldType, aim, and IK hand targets"

✓ verified on engine 26.07.15lane: Rigging & animationposted

▸ SYMPTOM

You set different HoldType values trying to move a character's held-item arm position, but the arm stays locked in the same pose regardless of holdtype. Or the arm unexpectedly tracks the camera up/down when looking around.

▸ CAUSE

Three independent systems control the citizen's arm, and they interact in a specific priority order (verified against CitizenAnimationHelper.cs in sbox-public):

  1. HoldType (enum: None, Pistol, Rifle, Shotgun, HoldItem, Punch, Swing, RPG, Physgun) → sets "holdtype" animgraph param. This selects the arm pose + finger-curl blend only. There is no separate finger/grip float param — the fist comes from the holdtype:

    • Pistol = grip-sized curl (a thin item sits loose between fingers)
    • Swing = one-handed closed fist wrapping a handle
    • HoldItem = flat open palms
    • Punch = both fists (raises the off hand too)
  2. AimAngle"aim_body_pitch" + "aim_body_yaw" (localised via inverse world rotation). The held arm only rises/falls with look pitch through aim_body_pitch. If your held arm tracks the camera up/down, you're feeding it a pitched look direction — flatten or clamp it to horizontal.

  3. IkLeftHand / IkRightHand (GameObject targets) → each OnUpdate, calls SetIk("hand_left"/"hand_right", go.WorldTransform). This OWNS the hand position — it is applied AFTER the holdtype pose evaluates, so the arm reaches the IK target regardless of what holdtype would set.

The common trap: the arm stays locked because an IK target is active and overriding the holdtype. Iterating through holdtypes won't move the arm — only the fingers change.

▸ FIX

To place a held-item arm, move the IK target, not the holdtype:

snippet
// HoldType controls only the fist/finger pose
AnimHelper.HoldType = CitizenAnimationHelper.HoldTypes.Swing;

// The IK target controls WHERE the hand goes
IkTarget.WorldPosition = desiredHandPosition;

// Leave IK rotation as identity to keep model-grip
// offset dials valid (hand frame unchanged, model just translates)

To prevent the arm from tracking the camera vertically, feed a horizontal look direction:

snippet
// Flatten the look direction — no camera pitch
AnimHelper.WithLook( WorldRotation.Forward );

Additional params:

  • Handedness (Both, Right, Left) → "holdtype_handedness"
  • AimBodyWeight / AimHeadWeight / AimEyesWeight"aim_body/head/eyes_weight" (default 0)

Note: SetIk is animgraph-gated — it's inert on a direct-sequence rig. The citizen uses a full AnimGraph rig, so IK works.

▸ WHY IT WORKS

The animgraph evaluates holdtype first (setting the base arm pose and finger curl), then applies IK targets on top (overriding the arm position to reach the target). This means IK always wins for position, while holdtype only controls the grip shape. Understanding this priority — holdtype for fingers, IK for position, aim for camera-driven tilt — prevents the common debugging loop of cycling holdtypes to fix what's actually an IK problem.

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

Want to know when new guides or fixes drop? Join the community to help build this out. Report gotchas, flag outdated fixes, or just lurk.

Join the Discord