FreeMinecraftModels Animations
This page documents what FreeMinecraftModels actually does with the animation data inside a .bbmodel or .fmmodel file: which names are special, how keyframes are baked, which interpolation and loop modes are honored, and how IK is driven. It is deliberately conservative — everything here is visible in the import and runtime pipeline.
For bone naming rules and the rest of the import contract, see Model Authoring Notes.
The Five State Animations
FreeMinecraftModels binds exactly five lowercase animation names to automatic runtime states. Everything else in the model is a custom animation.
| Animation name | Loops | When it plays |
|---|---|---|
spawn | no | Once, when the model is created. Falls through to idle when it ends |
idle | yes | While the underlying entity's velocity is at or below 0.08 |
walk | yes | While the underlying entity's velocity is above 0.08 |
attack | no | When triggered; returns to idle when it ends |
death | no | On removeWithDeathAnimation() |
Rules that follow from how the state machine is built:
- The starting state is
spawnif the model has one, otherwiseidle. A model with neither has no current state, so nothing animates until something is played explicitly. - Only animations that exist in the model get a state. A model with a
walkbut noidlenever transitions back out ofwalkon its own. - The idle/walk switch reads the underlying entity's velocity, so it is really a
DynamicEntityfeature. Static entities and player disguises have no underlying entity for this purpose and simply stay inidle; a prop's backing armor stand does not move, so props stay inidletoo. All three drive their real animations through scripts, the API, or (for disguises) FMM's own disguise controller.
jump is enum-only hereJUMP exists in the AnimationStateType enum, and the walk state does request a jump transition when the entity leaves the ground — but no jump state is ever registered, so that request resolves to nothing. An animation named jump is not dead: it just behaves like any other custom animation and has to be triggered manually. Player disguises are the exception — they use a separate controller where jump is wired up.
Player Disguises Use A Different Set
A player disguise does not run the state machine above. It has its own per-tick controller with five reserved names — attack, jump, sneak, walk, idle — evaluated in that priority order, and it warns on the console if the model has no idle. See Player Disguises for the full table and the one-shot timing caveat.
Custom Animations
Any animation whose name is not one of the five above can still be played by name:
modeledEntity.playAnimation("open", /* blend */ true, /* loop */ false);
modeledEntity.stopCurrentAnimations();
boolean exists = modeledEntity.hasAnimation("open");
context.prop:play_animation("open", true, false)
context.prop:stop_animation()
blenddoes not cross-fade.truequeues the animation to start after the current state's tick completes;falseinterrupts and switches immediately.loopapplies to custom animations only. A built-in state uses its own loop setting regardless of what you pass.- When a non-looping custom animation finishes, the entity returns to the last committed built-in state (whatever it was doing before), or
idleif there was none. playAnimationreturnsfalsewhen the name matches neither a registered state nor an animation in the model.stopCurrentAnimations()transitions toidlewhen the model has one; otherwise it exits the current state and leaves the model with no active animation.- While a custom animation is running, a request for
attack,attack_melee, orattack_rangedis swallowed so a scripted sequence is not interrupted by routine combat.
Timing And Duration
- Blockbench stores animation length in seconds. FMM converts it with
ceil(seconds x 20), so duration is always a whole number of ticks and short animations round up rather than down. - Every animation is baked into a flat per-tick frame array at import time. Playback is an array lookup per tick, not live interpolation.
- Looping animations index with
counter % duration; non-looping ones clamp to the last frame and then stop rendering further changes. - Keyframe times keep their fractional tick position (
20 x time, not rounded), so a keyframe at 0.37 s lands between ticks and is interpolated correctly instead of being snapped or dropped. The final keyframe of an animation is preserved rather than being cut off by rounding. - If two keyframes on the same channel land on exactly the same time, the later one in file order wins.
- A keyframe with a non-finite time aborts that track and produces a single
Malformed animation timeline for model ...warning per animation, while the model's other animations keep converting.
Zero-Length Animations Are Valid
An animation whose length is 0 (or negative) is treated as an intentional static pose — a common authoring choice for furniture and other props that need a named "no animation" entry. It is skipped silently with no warning, and it contributes no frames.
Loop Modes
Blockbench's loop setting is read directly from the animation:
| Blockbench loop mode | Java runtime | Bedrock export |
|---|---|---|
loop | repeats indefinitely | "loop": true |
once | plays through and stops | "loop": false |
hold | plays through and holds the last frame | "loop": "hold_on_last_frame" |
Interpolation Types
Each keyframe carries its own interpolation type, and the segment leading into a keyframe uses that keyframe's type. Four are supported:
| Blockbench type | Behavior in FMM |
|---|---|
linear | Straight linear interpolation |
catmullrom | Smoothed interpolation (ease in/out) |
bezier | Approximated with fixed 0.42 / 0.58 control points — FMM does not read per-keyframe bezier handles |
step | Snaps to the previous value until the next keyframe |
Anything outside that set fails to parse and is reported as a malformed timeline.
Animated Channels
Three channels are baked per bone: rotation, position, and scale. Notes that matter when authoring:
- Position values are divided by 16 (Blockbench pixels to blocks).
- Rotation values are converted to radians.
- Blockbench
format_version5 and newer flips the sign of X and Y rotation and of X position. FMM compensates automatically based on the declared format version, so do not hand-correct for it — but do not mix a v5 declaration with v4-shaped data either. - A bone with no keyframes on a channel keeps its rest value for that channel; a bone with no frames at all for a given tick is reset to rotation
0,0,0, translation0,0,0, scale1,1,1. - Keyframe data points may be written as strings in the
.bbmodel. FMM parses them as plain numbers — an empty string becomes1for scale and0otherwise, and anything unparseable logsFailed to parse supposed number value ...and becomes0. Molang expressions are not evaluated. - Only the first data point of a keyframe is read, so Blockbench's separate pre/post values on a step keyframe collapse to one.
What Is Not Animated
- The
hitboxbone. Animation tracks targeting it are skipped outright. - Blockbench effect tracks (sound, particle, and timeline-instruction animators). Any animator whose type is not
boneornull_objectis ignored, so FMM will not fire sounds or particles from an animation timeline. Drive those from a Lua script or from your own plugin instead. - Bones that could not be resolved by name. A track pointing at a missing bone logs
Failed to get bone <name> from model <model>!and is skipped.
Inverse Kinematics (IK)
Blockbench null objects act as IK controllers. FMM solves chains at runtime with FABRIK (Forward And Backward Reaching Inverse Kinematics), capped at 10 iterations with a 0.001 tolerance.
How it fits together:
- A null object with both
ik_source(chain root bone) andik_target(end bone or locator) defines a chain. The chain is discovered by walking up the hierarchy from the target to the source. - Only the null object's position keyframes are read. They become a per-frame goal offset relative to the controller's rest position; rotation and scale tracks on a null object are ignored.
- Each tick the goal offset for the current frame is applied and the chain is solved. On a frame with no IK data, the chain's IK rotations are cleared instead.
lock_ik_target_rotationon the null object is read from the model.
Chains that cannot be resolved are skipped with a named console warning — see Model Authoring Notes for the exact messages and the authoring constraints.
Bedrock Export
Each converted model also writes a Bedrock animation file at animations/<model_id>.animation.json inside the generated bundle:
- Animation identifiers are
animation.fmm.<model_id>.<animation_name>, with names sanitized for Bedrock. animation_lengthis the duration in seconds, floored at0.05so a one-tick animation is still valid.- Loop mode is mapped as shown in the Loop Modes table.
- A model with no animations still gets a single no-op
idleentry so the Bedrock entity definition stays valid. - Only visual bones are exported.
hitbox, the auto-generatedfmm_nametag_bone_*nametag bones, andm_mount-point bones are excluded from the geometry and therefore from the animation bones block. Thetag_bone you author is not excluded — it is exported like any other bone (usually as an empty, cube-less one); only its generated nametag counterpart is filtered out. - One animation controller is generated per animation, switched by an entity property, which is how FMM plays a specific animation on a Bedrock client.
See Resource Pack Output for where the bundle lands on disk.
Playing Animations From Other Systems
| Caller | Entry point |
|---|---|
| Plugin (Java) | ModeledEntity#playAnimation(String, boolean blend, boolean loop) / #stopCurrentAnimations() / #hasAnimation(String) |
| Prop Lua script | context.prop:play_animation(name, blend, loop) / context.prop:stop_animation() |
| Any Lua entity table | entity.model:play_animation(name, blend, loop) / entity.model:stop_animations() (available when entity.is_modeled is true) |
See the API & Developer Guide and Lua: Prop & Item API for the surrounding surfaces.
Troubleshooting
My animation never plays automatically.
Only spawn, idle, walk, attack, and death fire on their own. Everything else needs an explicit playAnimation / play_animation call.
My model does nothing at all.
It probably has neither a spawn nor an idle animation, so no state is entered on creation. Add an idle.
My animation plays but nothing moves. Check the animation length. A zero-length animation is treated as a static pose and is skipped silently by design.
Rotations are mirrored.
Check meta.format_version in the .bbmodel. FMM flips X/Y rotation sign for format version 5 and newer; a file that declares one version but carries the other version's data will come out mirrored.
Console says Malformed animation timeline for model ....
One track in that animation could not be read or interpolated. The warning fires once per animation and names the bone or IK controller involved; the model's other animations still convert.
Sounds and particles in my Blockbench timeline do nothing. Effect tracks are not imported. Trigger them from a Lua script or your own plugin.