Animation
Common mistakes with animation in Unreal through MifBridge: Animation Blueprints, animation assets, IK Rig and retargeting, UMG widget animation, pose reads and cloth. Read it when an animation call says ok and nothing moves.
Most entries here share one shape. The call succeeded, the value reads back, and the engine reads a different field or needs a second step. The fix is usually a typed endpoint instead of a raw property write, or a measurement taken another way.
Animation Blueprints
State machines and transition rules have dotted ids
State machines, states and transition rule graphs are not in an Animation Blueprint's top-level graph arrays. They hang off nodes. list_graphs, list_nodes and find_nodes reach them, and each nested graph gets a dotted graphId:
/Game/Anims/ABP_Vehicle.ABP_Vehicle::AnimGraph.Locomotion.Idle
A top-level graph keeps exactly its own name. A bare leaf name such as Idle is accepted when it is unambiguous. When two state machines both hold a state called Idle, the call is refused and the refusal lists the full paths. Pick one of those paths instead of guessing.
A transition's rule graph is named Transition by the engine, and that name is unique only inside its own transition node. The id therefore names the transition, and a second transition between the same two states adds an ordinal:
/Game/Anims/ABP_Vehicle.ABP_Vehicle::AnimGraph.Locomotion.Transition(Idle->Walk)
/Game/Anims/ABP_Vehicle.ABP_Vehicle::AnimGraph.Locomotion.Transition(Idle->Walk)#2
describe_anim_blueprint gives each transition's ruleGraphId. Copy it from there. For node-specific settings such as blend times and thresholds, use get_property or list_object_properties on the node.
A cooked Animation Blueprint has no graph to read
A cooked Animation Blueprint's AnimGraph is baked into runtime node data, not stored as graphs. The graph endpoints cannot show it. See Cooked projects for what cooking removes.
Sequences, montages and blend spaces are not Blueprints
No graph endpoint can see an animation sequence, montage, blend space or composite. Use the two read endpoints built for them:
list_animationsreads the asset registry only and loads nothing. It reportstruncated, so a cap never looks like a complete list.describe_animationtakes anassetPathand works for every type. It returnsskeleton,playLength,rateScale,notifies[]andcurves[]. For a montage it addssections[](withnextSection, which is what makes a montage loop or chain) andslots[]. For a blend space it addsblendAxes[]andsamples[].
IK Rig and retargeting
A retarget root write that verifies can still do nothing (UE 5.8)
{"ok": true, "changed": true, "verified": true}
That is the answer to set_property on an IK Rig's retarget root on 5.8, and the rig does not retarget. Every word is true: the value was written and reads back. The engine split the field in 5.8. RootBone now means the bone that carries root motion at the origin, and PelvisBone is what the retargeter reads where 5.3 to 5.7 read the root. The old field still exists, still writes and still reads. Nothing warns.
The tell is two readers disagreeing: get_property on RetargetDefinition.RootBone returns 'pelvis' while list_ik_rig reports retargetRoot as 'None' with the note "this rig has no retarget root".
Use set_ik_rig_retarget_root. It goes through the engine's controller, so it follows the split on every engine. The general rule: when an endpoint exists for a structure, prefer it to set_property on the structure's members.
Solver names changed at UE 5.6 and no old name survives
Before 5.6 an IK Rig solver is addressed by class name. From 5.6 it is addressed by struct path. The names do not carry over:
| UE 5.3 class name | UE 5.6 and later struct path |
|---|---|
IKRig_BodyMover |
/Script/IKRig.IKRigBodyMoverSolver |
IKRigFBIKSolver |
/Script/IKRig.IKRigFullBodyIKSolver |
IKRig_LimbSolver |
/Script/IKRig.IKRigLimbSolver |
IKRig_PoleSolver |
/Script/IKRig.IKRigPoleSolver |
IKRig_SetTransform |
/Script/IKRig.IKRigSetTransform |
Do not guess by pattern. The 5.3 full-body solver is IKRigFBIKSolver, with no underscore, unlike its neighbors. The example in the engine's own header comment, /Script/IKRig.FullBodyIKSolver, does not exist in any engine from 5.3 to 5.8.
A wrong name in the engine's own call logs only a warning and leaves the rig with an empty solver stack. An IK Rig with no solver evaluates to the input pose, which looks like "my goals are being ignored". add_ik_solver checks the name against the live solver registry first and answers ok:false with the correct spelling. list_ik_solver_types lists what the engine you are connected to accepts. Trust that list, because the other engine's ids will not resolve.
UMG widget animation fails silently in three ways
Widget animation is a different system from skeletal animation, and each of these reports success.
Times are in ticks, not seconds or frames. A widget animation stores times at a tick resolution, typically 60000 per second. A key at 0.95 seconds is not at 0.95 and not at frame 19:
0.95 seconds -> 57000 ticks (0.95 * 60000)
Send seconds or display frames to a raw tick field and every key lands somewhere else while every call answers ok:true. list_widget_animations and set_widget_animation_keys report times in both units on purpose, so a bad conversion shows in one of them. The playback range end is exclusive: a 1.5 second animation ends at tick 90001.
A binding is two things. A widget binding needs a possessable in the animation's movie scene and an entry mapping its id to the widget's name. With only one half, the animation exists, compiles and plays, and moves nothing. Removal needs both halves too. The typed endpoints handle both.
The engine's own bind function ends the editor when called without a preview widget. The root widget case is refused rather than approximated, because it genuinely depends on the Designer's preview widget.
A false zero to watch for when reading back: the movie scene's own track list holds only root tracks. A track bound to a widget hangs off the binding. Counting only root tracks reports trackCount: 0 for an animation with a working keyed track.
Poses and physics
An off-screen skeletal mesh never leaves its reference pose
A skeletal mesh actor placed out of the Play In Editor camera's view, simulating physics, with IsSimulatingPhysics true, kept the same bone transforms for three seconds on 5.8.2. Its pose tick is skipped when it is not rendered, so the physics bodies' motion never reaches the bones. Set VisibilityBasedAnimTickOption to AlwaysTickPoseAndRefreshBones on the component before Play In Editor.
Cloth simulates in PIE and on screen only, and MaxDistance is not a hard limit
A skeletal mesh with legacy clothing produces no simulation data in the editor world. In Play In Editor it produced none either when it stood 30 m to the side of the player, but did in front of the camera. read_component_pose with its cloth option says so when there is nothing to read.
Cloth particles also move past their painted limit. In one measurement, a fixed particle (MaxDistance 0) moved 0.0005 cm, but 36 to 42 of 957 particles went past their own limit, the worst 11.2 cm past a limit of 0.56 cm. A small MaxDistance does not pin a vertex. Paint 0 to pin one.
A pose read follows the editor's tick
After set_sequencer_time, get_sequencer_state shows the new frame, while read_component_pose can keep returning the old frame's bones, because the editor world did not tick the component. Ask for the pose of a specific moment with read_component_pose and its sequence and frame parameters: it evaluates the sequence itself instead of reading the last computed pose. See Common mistakes with the world for the Sequencer entries.
A Pose Search schema whose only skeleton has a role finalizes to nothing
Give a Pose Search schema's one skeleton a role such as Rider (Skeletons[0].Role, 5.4 and later) and the default Trajectory and Pose channels, which read the default role, finalize to nothing. FinalizedChannels is empty and SchemaCardinality is 0. Nothing says so, and create_asset warns. A database's search index is derived data the editor builds after an edit, so create_pose_search_database reports it as null instead of guessing.