---
title: "Inverse kinematics"
description: "An animation clip stores joint angles. Play it and the character does the same thing every time…"
section: "Topics"
slug: "topics-ik"
canonical: "https://origozero.ai/docs/topics-ik"
updated: "2026-10-12T20:30:16.976414807+00:00"
---

# Inverse kinematics

Inverse kinematics is the correction. Forward kinematics goes from joint angles
to where the hand ends up; inverse kinematics goes the other way — you say where
the hand should end up, and the solver works out the angles. In practice IK
almost never replaces animation. It takes the pose a clip produced and adjusts
the parts of it the world constrains.

## The shape of it

You give a solver a **chain** (a run of bones from a root to a tip), a
**target**, and a **weight**. It rewrites the chain's rotations so the tip
reaches the target, blended against the pose that came in.

```luau
hero.component.add("IKLimb", { role = "rightarm", target = doorknobRef })
```

That is the whole idea. Everything else is about which chain, which solver, and
how much.

## Two ways to name a chain

**By bone name.** Works on any rig — humanoid, quadruped, crane, tentacle,
desk lamp. Nothing assumes a skeleton shape.

```luau
crane.component.add("IKChain", {
    rootBone = "boom_base", tipBone = "boom_tip",
    target = hookRef, solver = "ccd",
})
```

**By canonical role.** A humanoid rig carries a role map, so `"rightarm"`
resolves to whatever that particular mesh calls its upper arm. The same
configuration then works on every humanoid.

```luau
hero.component.add("IKLimb", { role = "rightarm", target = doorknobRef })
```

The role names are the engine's own vocabulary and are not always the obvious
word: an upper arm is `leftarm` and the forearm is `leftforearm`, while an upper
leg is `leftupleg` and the shin is `leftleg`. `IKLimb` accepts `leftarm`,
`rightarm`, `leftleg`, and `rightleg`, and tells you so if you name something
else.

A rig with no role map — most non-humanoids — fills none of them. That is not a
failure; it is what the bone-name form is for.

## Picking a solver

| Solver | For | Why |
|---|---|---|
| `twoBone` | arms, legs | Exact, single pass. The law of cosines answers outright. |
| `ccd` | short chains with joint limits | One bone rotates per step, so a limit is enforced there and then. |
| `fabrik` | tentacles, ropes, long spines | Solves in positions rather than rotations; converges far faster on long runs. |
| `aim` | weapons, turrets | Points an axis. An orientation, not a position. |
| `lookAt` | heads, spines | Splits one clamped turn between body and head. |

## Weight is how IK gets used

`weight` blends the solved pose against the animated one. Zero is an exact
pass-through and costs nothing — the solve is skipped. One is the full
correction.

Ramping it is the normal pattern. A hand that snaps onto a doorknob reads as a
glitch; a hand that reaches over a quarter second reads as intent. Fade in as
the character commits, fade out as they leave.

## Poles, and why a knee bends the wrong way

Two bones reaching a target have two solutions — the joint can bend either way.
A **pole** target picks one: put an entity behind the elbow or in front of the
knee and the joint bends toward it.

```luau
hero.component.add("IKLimb", {
    role = "rightarm", target = doorknobRef, pole = elbowHintRef,
})
```

Without a pole the bend plane the incoming animation produced is preserved. That
is stable and usually right, and it is why IK does not flip a joint that was
already posed sensibly. Reach for a pole when the animated bend is not the one
you want, or when the chain starts straight and has no plane to preserve.

## Feet on real ground

`IKRig.grounding` casts a ray under each foot, moves that foot's target to the
surface, tilts it to the normal within `groundingMaxSlope`, and drops the pelvis
far enough that neither leg has to over-reach.

```luau
hero.component.add("IKRig", { grounding = true, groundingMaxSlope = 45 })
```

The pelvis drop is the part people forget. Put a character on a slope and
correct only the feet, and the downhill leg stretches straight and still comes
up short. Lowering the hips is what gives it the room.

An explicit foot target wins over the grounder, which still counts that foot
toward the pelvis drop. That is how "plant this foot on the ledge, let the other
follow the terrain" stays coherent.

## Reach is finite

A limb reaches as far as its bones are long, and no further. A target beyond
that has no solution: the chain extends straight toward it and stops. This is
correct, and it is also the single most common reason a solve looks broken when
nothing is wrong.

Before debugging anything else, measure how far the target is from the chain's
root and compare that against `reach`:

```luau
local s = hero.component.get("IKLimb"):limbState()
-- s.reach            how far this chain can extend
-- s.distanceToTarget how far the tip still is FROM the target after solving
```

The two are different measurements and mixing them up is its own dead end.
`distanceToTarget` is the residual error, not the distance you compare against
`reach` — a small `distanceToTarget` means the solve arrived, and reading it as
"the target is close, so there is plenty of headroom" gets the diagnosis exactly
backwards. To compare against `reach`, measure root-to-target yourself from the
joint positions.

**`reach` is in the rig's model space**, which is frequently not world units —
the standard humanoid here reports a reach of about 61 for an arm that is 0.61 m
long. The factor usually lives on a node inside the imported body rather than on
the entity you set `localScale` on, so an entity at scale 1 can still report
model units 100× world. Convert with `bind.modelToWorld`, or simply compare
model-space quantities against each other.

## Checking a solve

A screenshot cannot tell a hand that is tracking a target from a hand that
happens to be near one. Every component answers that directly:

```luau
hero.component.get("IKLimb"):limbState()
-- { ready, status, boneCount, reach, distanceToTarget, hasPole, weight, error }
```

`status` is the field to read first when nothing seems to be happening: it says
in words what the component is doing. A body spawned from a bundle takes a few
frames to arrive, so `ready` is briefly false while `status` explains it is still
waiting — and if the engine is paused it says the wait will not clear on its own.
That is a different situation from a chain that genuinely could not resolve, and
`error` is set only in that second case. Checking `error` alone reads a nil and
concludes everything is fine.

The equivalents are `chainState()`, `lookState()`, `aimState()`, and
`rigState()`.

`lookState().clamped` deserves attention: it distinguishes "watching the target"
from "turned as far as allowed and still facing away", which look identical in a
capture.

## How it fits with animation

IK layers onto whatever animation graph is driving a body, so a clip or a
locomotion blend keeps running underneath and IK corrects the parts it owns. A
body nothing animates gets a graph built around its rest pose, so IK reaches it
the same way — and if an animator publishes its own graph later, IK moves onto
it without being told.

Because solving happens on the pose buffer inside that graph, the corrected pose
travels the same path the animation already took: skinning, bone attachments,
colliders, debug drawing. IK adds no second route to the skeleton.

## Try it

`demos/ik_showcase` has four scenes. `ik_reach` fades a hand onto a moving
sphere. `ik_grounded` walks a character over a staircase and a ramp with the
feet planting. `ik_lookat` tracks an orbiting target into the clamp and back.
`ik_chain` runs a FABRIK tail and a clamped look-at on a fox — 25 bones, no
humanoid roles at all.

Those scenes live inside the builtin library, so their `require` lines resolve
against it: a demo writes `require("modules.api.engine.lighting")`. Copied
verbatim into a scene in your own world that does not resolve, because the
search starts at `/source/`. Prefix it — `require("@builtin::modules.api.engine.lighting")`
— and it works. The same applies to any builtin path you lift out of a demo.

## Measure in play mode, not edit mode

Edit mode is paused, so a `ClipPlayer` never advances and the body sits in its
rest pose rather than the pose the clip would put it in. Any measurement taken
there describes a character standing differently from the one that will exist at
runtime.

The numbers stay internally consistent, which is what makes this costly: the
same setup can report `reachable = false` at 0.66 m in edit mode and
`reachable = true` at 0.48 m in play, purely because a crouch clip moves the
shoulder. Enter play before measuring anything, and re-measure there after any
change to the clip or the pose.

This is also why an animated pose is worth measuring against rather than
reasoning about. A crouch clip can carry the hips and shoulders a long way from
the entity's own origin — far enough that a target placed sensibly relative to
the entity sits outside the arm's reach. `ik.bones` gives the real shoulder
position; place targets relative to that.

## Checking a rig, and a solve, from outside

The `ik` toolbox answers the questions IK debugging actually asks:

```luau
tools.use("ik", "bones", "hero", "ankle")   -- bone names + world positions + role map
tools.use("ik", "reach", "hero", "rightarm", "doorknob")  -- reachable band, in world units
tools.use("ik", "state", "hero")            -- every IK component and what it is doing
```

`ik.bones` is the one to reach for first on an unfamiliar rig: it returns the
canonical role map alongside the bones, so you can stop guessing what this
particular mesh calls things. `ik.reach` reports in world units, which is the
comparison `reach` on a component does not give you.
