Skip to content

The brain system

Behavior-tree AI for NPCs, evaluated every tick via the objective system.

Overview

A brain is a tree of Brain nodes attached to an agent's __BRAIN__ inventory slot. The root is always a Select node — it runs each child in order and stops at the first success. Children can be plain labels (Simple nodes), or composite Sequence / Select nodes built from structured dicts.

Each tick, brains_run_all iterates all agents that have a __BRAIN__ entry and calls brain.run(). A Simple node runs its MAST label synchronously in a temporary task; the label result (BT_SUCCESS / BT_FAIL) propagates up the tree.

brain_add is the main entry point. Call it once per behavior you want to add; the root Select node grows with each call. Use structured dicts for composite nodes:

  • {"SEL_name": [child1, child2]} — Select composite
  • {"SEQ_name": [child1, child2]} — Sequence composite

Within a brain label you have access to BRAIN, BRAIN_AGENT, and BRAIN_AGENT_ID task variables.

Note

The brain system piggybacks on the objective tick. Call brain_schedule() (or brain_add(), which calls it automatically) to register the tick handler.

Quick example

== setup ==
    brain_add(enemy_id, patrol_label)
    brain_add(enemy_id, attack_label)
    ->END

== patrol_label ==
    ///test
    if get_pos(BRAIN_AGENT_ID).distance(target_pos) > 500: BT_FAIL
    BT_SUCCESS

== attack_label ==
    target(BRAIN_AGENT_ID, player_id)
    BT_SUCCESS
from sbs_utils.procedural.brain import brain_add, brain_clear

# Simple behavior list — Select runs them in order
brain_add(enemy_id, patrol_label)
brain_add(enemy_id, attack_label)

# Nested composite example
brain_add(enemy_id, {
    "SEL_combat": [attack_label, evade_label]
})

# Remove the brain entirely
brain_clear(enemy_id)

Behavior tree result values

Return Meaning
BT_SUCCESS This node succeeded
BT_FAIL This node failed
OK_IDLE Still running (not yet resolved)

API

Manage all brains

brain_add(agent_id_or_set, label, data=None, client_id=0, parent=None)

Add a behaviour-tree node to one or more agents.

Creates or extends the agent's brain tree. The root is a Select node (runs children in order, stops at first success). Labels can be plain label references, strings, or structured dicts/lists for nested trees.

Structured dict forms: - {"label": my_label, "data": {...}} — simple node with data - {"SEL_name": [child1, child2]} — Select composite node - {"SEQ_name": [child1, child2]} — Sequence composite node

A list of labels adds multiple sibling nodes under the parent.

Parameters:

Name Type Description Default
agent_id_or_set

Agent ID, object, or set/list of either.

required
label label | str | dict | list

Behaviour node(s) to add.

required
data dict

Variables passed when the label runs. Defaults to None.

None
client_id int

Client context for GUI-task resolution. Defaults to 0 (server).

0
parent Brain | None

Parent node to attach to. Defaults to None (attaches to the agent's root Select node).

None
Example

brain_add(ENEMY_ID, patrol_label) brain_add(ENEMY_ID, {"SEL_combat": [attack_label, evade_label]})

brain_add_parent(parent, agent, label, data=None, client_id=0)

Add one or more brain nodes as children of an existing brain node.

Handles plain labels, strings, lists (multiple siblings), and structured dicts ({"SEL_name": [...]} or {"SEQ_name": [...]}) recursively.

Parameters:

Name Type Description Default
parent Brain

Parent brain node to attach children to.

required
agent int

Agent ID owning the brain.

required
label label | str | list | dict

Brain node specification.

required
data dict

Variables passed to child tasks. Defaults to None.

None
client_id int

Client context for GUI-task resolution. Defaults to 0 (server).

0

brain_clear(agent_id_or_set)

Remove the behaviour-tree brain from one or more agents.

Clears the __BRAIN__ inventory key so the agent's brain stops running on the next tick. Does not explicitly stop any sub-tasks already started by brain labels.

Parameters:

Name Type Description Default
agent_id_or_set

Agent ID, object, or set/list of either.

required
Example

brain_clear(ENEMY_ID)

brain_pause(agent_id_or_set, paused=True)

Pause (or resume) one or more agents' brains without removing them.

A paused brain is skipped by brains_run_all until resumed - used when an object is parked on the standby list so its brain doesn't act on a non-simulated object. The brain tree is preserved (unlike brain_clear).

Parameters:

Name Type Description Default
agent_id_or_set

Agent ID, object, or set/list of either.

required
paused bool

True to pause, False to resume. Defaults True.

True

brain_resume(agent_id_or_set)

Resume one or more agents' brains (see brain_pause).

brain_schedule()

Schedule the brain tick task via the objective system.

brains_is_stalled()

True if the re-entrancy guard is stuck on (no brain will ever run again).

Registered with the reset ledger so a restart soak reports it instead of leaving a silently AI-less mission.

brains_reset()

Drop cross-mission brain scheduler state (called by reset_mission_state).

brains_run_all(tick_task, pass_seconds=None)

Run agent brains for the current tick.

Iterates agents with a __BRAIN__ inventory entry and calls brain.run(). Re-entrant calls are suppressed with a guard flag; agents whose Agent.get returns None (or that are paused) are skipped.

pass_seconds controls batching:

  • None (default) - run all brains this call (original behavior; kept for existing callers/tests).
  • a number - run only a rolling slice this call, sized so a full pass over every brain completes in about pass_seconds of sim time. This spreads a large fleet's brains across ticks instead of one batch, so no single tick spikes. Each brain still runs about once per pass_seconds, preserving the prior cadence and total cost.

Parameters:

Name Type Description Default
tick_task

The tick task or event that triggered this run.

required
pass_seconds

If set, spread a full pass over ~this many seconds.

None