Builtin Library
README.md
animation Module
Public Luau surface over the __animation Internal FFI namespace — the engine's
animation observation, and ragdolls. Auto-injected as _G.animation via the
prelude.
Observing what is animating
animation.observe() answers "what is this engine posing right now, and why is
a body not moving" in one read. It reports the ENGINE's own measurement of the
pose landing on each armature, which is what makes it different from reading a
ClipPlayer's fields back: those carry what content asked for, and playing is
the value whoever attached the component wrote there. Every body report carries
both — the measured pose beside the clips the animator declared — so the two
are compared rather than confused.
animation.observe() -> AnimationObservation— the whole surface in one call:{ sampledAtMs, frame, bodies, animatingCount, riggedBodyCount, motionWindowMs }.animation.bodies() -> { AnimationBody }— every body the engine holds animation state for.animation.animating() -> { AnimationBody }— the ones whose pose is changing.animation.body(entityId) -> AnimationBody?— one body. Accepts a character root and answers for the skinned body under it.animation.whyStill(entityId) -> (reason, detail)— the one cause a body is not moving.animation.clips(entityId) -> { AnimationClip }— the clips contributing to its pose, with playheads.animation.coverage(entityId) -> (matched, total)— how many of the body's bones the clips driving it reach.
The verdict, and where it comes from
body.animating is read off body.pose, which the engine measures where a pose
lands on the armature:
pose.applies— poses that reached the bones.pose.revision— poses whose values differed from the one before. A body being animated advances this every frame.pose.sinceChangedMs— how long ago the values last differed. UndermotionWindowMs, the body is animating.pose.bonesWrittenofpose.bonesRequested— how much of the pose found an armature entity to land on.
body.reason names why a still body is still, from a closed set: deactivated,
noRiggedSkeleton, noGraph, clipUnreadable, noOutputNode, retargetPending,
retargetMatchedNoRoles, stopped, finished, paused, poseNotApplied,
poseUnchanged.
Retarget coverage
clip.matched of clip.total is what the engine computed when the clip was
bound to the body's rig. A humanoid clip on a humanoid rig reads 41 of 50; a
clip from an unrelated rig reads 0 of 50 while its playhead advances and the
body stands in its rest pose. clip.unmatched names the bones left at rest, up
to twelve of them.
Declaring a custom animator
The shipped animators — ClipPlayer, the character controller's locomotion, the
animation toolbox — all drive @builtin::systems.anim.AnimGraph, whose
:tick publishes through AnimGraph:publish. An animator that poses a body its
own way calls animation.declare(entityId, facts) once per frame it runs, and
animation.forget(entityId) when it releases the body.
Reading it as a file
The same document is served at /zero/runtime/animation, with one entry per
body under /zero/runtime/animation/bodies/<entity>. Both consumers read the
observation the engine publishes each frame, so they carry the same fields.
Ragdolls
animation.ragdoll(id, opts?) carries a skinned body's skeleton on physics
bodies: a death, a knockout, a hit reaction, a stunt fall, a crash-test dummy,
one limp limb. id is the skinned entity or any entity above it. A humanoid
gets bodies sized per body part and anatomical joints; any other rig gets a
capsule along every bone and ball joints.
animation.ragdoll(id)/animation.ragdoll(id, false): ragdoll the body, or hand it back to its animation. The first call builds the ragdoll on the next frame.animation.ragdoll(id, { limp = { "leftarm" } }): only those bones and the bones below them go limp; the rest keep animating.animation.ragdoll(id, { mass = 75, bones = { head = { mass = 5, swing = 30 } } }): describe it: a total mass, and per boneshape,radius,length,size,mass,joint,swing,twist,hinge,strength,damping. On a humanoid a bone is named by its skeleton name or its humanoid role.animation.ragdollState(id) -> RagdollState?: every bone body with the entity it answers to, its shape, mass, pose, whether it is simulated, and its joint, plusproblems.animation.ragdollBone(id, bone) -> string?: the entity a bone body answers to. EveryPhysicscall takes it: joints, impulses, velocity,bodyState,contacts.
The physics guide's "Ragdolls" section (guides { path: "topics/physics" })
has the whole surface with examples.