🔄 Finite State Machine#
#bs.fsm:help
A powerful Finite State Machine (FSM) system for managing complex state-based behaviors in Minecraft.
FSMs are without a doubt the most commonly used technology in game AI programming today. They are conceptually simple, efficient, easily extensible, and yet powerful enough to handle a wide variety of situations.
—Daniel D. Fu & Ryan Houlette
The FSM module provides a comprehensive system for creating, managing, and executing finite state machines. It allows you to define states, transitions, and behaviors in a declarative way, making complex state management simple and maintainable.
📦 Templates#
A state machine is described by a template: a plain NBT compound that you store wherever you want, with vanilla commands. Since a template is just data, you can write it once on load, or build and edit it at runtime, and you choose how to namespace it.
Template
initial: Name of the initial state (must exist in states array).
on_cancel: Command to run when the machine is cancelled (optional).
states: Array of state definitions.
State
name: Unique name for the state.
on_tick: Command to run every tick while in this state (optional).
on_enter: Command to run when entering this state (optional).
on_exit: Command to run when exiting this state (optional).
final: Whether this state is a final state (optional, default: false). Validation metadata only, never read at runtime: a machine stops as soon as it enters a state with no outgoing transition, whether or not it is flagged. Validation requires a final state to have no transition, so on a validated template the two coincide.
transitions: Array of transition definitions (optional).
Transition
name: Name of the transition (optional, required on a manual transition: it is the signal name that fires it).
condition: Transition condition. One of the following:
Manual transition: the literal
"manual", triggered by the#bs.fsm:emitfeature.Predicate-based transition.
type: Must be “predicate”.
wait: Predicate to check to trigger the transition.
Command-based transition.
type: Must be “command”.
wait: Command to check to trigger the transition.
Time-based transition.
type: Must be “delay”.
wait: Number of ticks after entering the state before the transition fires (0 acts as 1).
to: Name of the target state (must exist in states array).
Example: store a light template, then validate it:
# Store the template wherever you want
data modify storage my_pack:fsm light set value { \
initial: "off", \
states: [ \
{ \
name: "off", \
on_enter: "setblock ~ ~ ~ minecraft:redstone_lamp", \
transitions: [{ name: "turn_on", condition: "manual", to: "on" }] \
}, \
{ \
name: "on", \
on_enter: "setblock ~ ~ ~ minecraft:redstone_lamp[lit=true]", \
final: true \
} \
] \
}
# Check it once, before running it
function #bs.fsm:validate { uses: "my_pack:fsm light" }
Since the template is yours, you delete it like any other data:
data remove storage my_pack:fsm light
🔧 Functions#
You can find below all functions available in this module.
Validate#
- #bs.fsm:validate
Check that a template describes a valid state machine, and report every problem found through the log module.
- Inputs:
Function macro:
Arguments
uses: Storage source of the template, as
<namespace>:<storage> <path>.
- Outputs:
Return: Success (1) if the template is valid, failure (0) otherwise.
Example: validate a template:
function #bs.fsm:validate { uses: "my_pack:fsm light" }
Running machines are never validated: this is up to you. Validate a static template once on load, and a dynamic one every time you are done editing it.
Credits: theogiraudet
Init#
- #bs.fsm:init
Run a new state machine from a template, in the global context. The global context doesn’t keep the context (
as,at, …) in which it is executed. Therefore, such a context should directly be specified in different callback commands.- Inputs:
Function macro:
Arguments
name: Unique name of the machine, used to address it later.
uses: Storage source of the template, as
<namespace>:<storage> <path>.
- Outputs:
Return: Success (1) if the machine was started, failure (0) otherwise.
State: The machine runs globally, starting in its initial state.
Example: run a light machine:
function #bs.fsm:init { name: "main_light", uses: "my_pack:fsm light" }
# The light machine is now running globally and has entered its initial state
Credits: theogiraudet
- #bs.fsm:init_as
Run new state machines from a template, bound to the executing entities. The commands and predicates of the machine are executed as and at the entity it is bound to. If the entity leaves the world while the machine runs, whether it is killed or its chunk is unloaded, the module automatically stops its tick commands and transitions evaluation. The machine itself stays stored on the entity, so an entity coming back does not resume it and still holds its name: cancel it as that entity to free the name again.
- Inputs:
Execution
as <entities>: Entities to bind. The entities must not be players.Function macro:
Arguments
name: Unique name of the machine for this entity, used to address it later.
uses: Storage source of the template, as
<namespace>:<storage> <path>.
- Outputs:
Return: Success (1) if the machine was started, failure (0) otherwise.
State: The machines run on the executing entities, starting in their initial state.
Example: run a light machine bound to an entity:
execute as @n[type=zombie] run function #bs.fsm:init_as { name: "entity_light", uses: "my_pack:fsm light" }
# The light machine is now running on this zombie and has entered its initial state
Credits: theogiraudet
Emit#
- #bs.fsm:emit
Emit a signal to a running machine. The signal takes the manual transition of the current state bearing that name, if there is one.
- Inputs:
Execution
as <entity>: Entity the machine is bound to, for a local machine.Function macro:
Arguments
name: Name of the machine to emit the signal to.
signal: Name of the signal to emit.
bind: Binding of the machine.
“global”: The machine runs in the global context.
“local”: The machine runs on the current execution context.
- Outputs:
Return: Success (1) if the signal triggered a transition, failure (0) otherwise.
State: The machine moves to the target state of the transition, running the
on_exitof the current state then theon_enterof the new one.
Example: emit a signal to a machine:
# Emit a signal to a global machine
function #bs.fsm:emit { name: "main_light", signal: "turn_on", bind: "global" }
# Emit a signal to a machine bound to an entity
execute as @n[type=zombie] run function #bs.fsm:emit { name: "entity_light", signal: "turn_on", bind: "local" }
Emitting a signal the current state does not listen to is not an error: the machine simply stays where it is and the function fails. Only a machine that is not running at all is reported through the log module.
Credits: theogiraudet
Cancel#
- #bs.fsm:cancel
Cancel and stop a running machine.
- Inputs:
Execution
as <entity>: Entity the machine is bound to, for a local machine.Function macro:
Arguments
name: Name of the machine to cancel.
bind: Binding of the machine.
“global”: The machine runs in the global context.
“local”: The machine runs on the current execution context.
- Outputs:
Return: Success (1) if the machine was cancelled successfully, failure (0) otherwise.
State: The machine is stopped and cleaned up: it stops ticking, stops listening to transitions, and its name becomes available again. If its template has an on_cancel command, it is run.
Example: cancel a door machine:
# Cancel the door machine
function #bs.fsm:cancel { name: "main_door", bind: "global" }
# The door machine is now stopped
The on_cancel command runs once the machine has been unregistered, so it cannot read the machine back, but it may start a new one under the same name.
Cancelling does not run the on_exit command of the current state.
Credits: theogiraudet
❓ What is a FSM?#
A Finite State Machine (FSM) is a conceptual model used to describe how a system behaves in response to events. It defines a limited set of possible states that the system can be in at any given moment. The system starts in an initial state and, when something happens, such as receiving an input or a signal, it may change its state following predefined rules. These changes are called transitions, and each one depends on the current state and the event received.
What makes FSMs powerful is their simplicity and clarity. By reducing a system’s behavior to a set of states and transitions, we can describe even complex logic in a very structured and predictable way. At any point in time, the system is in exactly one state, and the logic for moving between states is well defined. This helps avoid ambiguity and makes it easier to understand how the system reacts to different situations.
In Minecraft, Finite State Machines can be particularly useful to manage tree dialog, boss phases, or any system state. Outside Minecraft, Finite State Machines are widely used in many fields because they provide a clean way to manage systems that have different modes or stages. In software development, they are useful for designing user interfaces, game character behavior, communication protocols, and more. In hardware and control systems, they are often used to manage sequences of operations or reactions to sensor inputs. Overall, FSMs are a fundamental tool for modeling reactive systems in a way that is both rigorous and easy to reason about.
💡 Example in Minecraft#
stateDiagram-v2
[*] --> Idle
Idle --> Alert : if player detected
Alert --> Attack : after 5s AND player still detected
Alert --> Idle : after 5s AND player gone
Attack --> Searching : if player lost
Searching --> Attack : if player found
Searching --> Idle : after 10s AND player not found
Attack --> Idle : if player defeated
This finite state machine controls the behavior of a custom mob in Minecraft: a sentinel that guards a specific area. It begins in the Idle state, where it stays mostly still, occasionally performing small ambient animations. When a player enters its detection radius, as determined by a custom command or predicate, the FSM transitions to the Alert state. In the Alert state, the sentinel visually or audibly signals that it has detected an intruder. This state is time-based, lasting about five seconds. If the player is still present when this period ends, the sentinel moves to the Attack state. During Attack, the mob actively pursues and attacks the player. If the player escapes or is no longer detectable, the FSM transitions to the Searching state. There, the sentinel wanders the area near the last known location of the intruder for a set amount of time. If it finds the player again during this search, it returns to Attack. Otherwise, if the timer runs out without detecting anyone, it returns to the Idle state and resumes its guard duty. If the player is defeated, the FSM transitions to the Idle state and resumes its guard duty.
📋 Validation Rules#
The FSM system enforces several validation rules to ensure proper operation:
Initiality#
The FSM must have an
initialstate specifiedThe initial state must exist in the states array
Unicity#
All state names must be unique within the FSM
Acceptability#
The FSM must have at least one final state
Final states are states marked with
final: true
Reachability#
Every state must be able to reach a final state
This is determined by walking the transition graph backwards from the final states
Transition Validation#
All transition target states must exist in the states array
Transition conditions must be valid according to their type
Manual transitions must have a name, since that name is the signal firing them
🔄 State Lifecycle#
Each state in an FSM follows a specific lifecycle:
Enter: The
on_enterfunction is called when entering the stateTick: The
on_tickfunction is called every tick while in the stateTransition evaluation: When a transition condition is met, the state transitions Transitions are evaluated once per tick, starting the tick after the state was entered, so a machine never chains several automatic transitions within a single tick
Exit: The
on_exitfunction is called when leaving the state
A machine ends when it enters a state with no outgoing transition, which is what a final state is.
Its on_enter still runs, then the machine is unregistered: it stops ticking, stops listening to transitions, and its name becomes available again.
A machine that has not reached such a state runs until you stop it with #bs.fsm:cancel.
⚡ Transition Types#
The FSM system supports several types of transitions:
Manual#
Triggered by #bs.fsm:emit, which fires the manual transition of the current state named after the emitted signal.
Useful for player interactions or external events.
Predicate#
Triggered when a predicate returns true. Useful for conditional logic.
Command#
Triggered when a command succeeds. Useful for complex conditions.
Delay#
Triggered wait ticks after entering the state, whichever way it was entered.
Useful for timed behaviors.
⚠️ Best Practices#
Keep states focused: Each state should represent a single, well-defined behavior
Use meaningful names: State and transition names should clearly describe their purpose
Handle edge cases: Always consider what happens when transitions fail
Clean up resources: Use the on_exit functions to clean up state-specific resources
Define a cancel command: Use a
on_cancelcommand to clean up resources when the FSM is cancelled