simvx.core.ai.agent_node

Frame-loop driver that ticks any :class:Brain from a node’s on_update.

This is the dependency-free half of the AI driving layer: it ticks any core Brain (a hand-rolled behaviour tree, state machine, utility scorer, or the LLM brain in simvx.ai) and never imports simvx.ai or httpx. Classical brains are first-class here with zero LLM dependency.

Three primitives, smallest to largest:

  • func:

    build_ai_context - the free helper that snapshots a node + tree into an :class:AIContext for one tick.

  • class:

    BrainRunner - owns brain + blackboard + tick_interval; its

    meth:

    BrainRunner.step ticks at a cadence, passing the elapsed since the last tick (an accumulator), not the raw frame dt, so brains see correct deltas regardless of tick rate. Usable from any existing Node subclass.

  • class:

    AgentNode - the canonical batteries-included node that builds a Blackboard in on_ready, calls runner.step in on_update, and tears the brain down in on_exit_tree.

The driver calls exactly one method per tick: brain.tick(ctx). It never calls perceive / decide / act directly.

Module Contents

Classes

BrainRunner

Drive a :class:Brain at a fixed cadence from any node’s per-frame update.

AgentNode

A node that drives one :class:Brain every frame (or at tick_interval).

Functions

build_ai_context

Snapshot a live in-tree node into an :class:AIContext for one tick.

API

simvx.core.ai.agent_node.build_ai_context(node: simvx.core.node.Node, dt: float, blackboard: simvx.core.ai.blackboard.Blackboard) simvx.core.ai.brain.AIContext[source]

Snapshot a live in-tree node into an :class:AIContext for one tick.

agent is the node, world is its :class:SceneTree, and now is the tree’s accumulated sim clock. Must be called while the node is in the tree (node.tree is None before on_enter_tree); that is a programming error, raised eagerly rather than silently feeding the brain now=0.

class simvx.core.ai.agent_node.BrainRunner(brain: simvx.core.ai.brain.Brain, blackboard: simvx.core.ai.blackboard.Blackboard, *, tick_interval: float = 0.0)[source]

Drive a :class:Brain at a fixed cadence from any node’s per-frame update.

tick_interval == 0 ticks every frame (the classical authoritative cadence). tick_interval > 0 ticks every tick_interval seconds, and the dt the brain sees is the time elapsed since the previous tick (the accumulator), not the raw frame dt.

Initialization

step(node: simvx.core.node.Node, dt: float) None[source]

Advance the cadence by dt and tick the brain if it is due.

class simvx.core.ai.agent_node.AgentNode(*, brain: simvx.core.ai.brain.Brain | None = None, squad: simvx.core.ai.blackboard.Blackboard | None = None, **kwargs: Any)[source]

Bases: simvx.core.node.Node

A node that drives one :class:Brain every frame (or at tick_interval).

Set self.brain (in __init__ or before the node enters the tree). The node builds its :class:Blackboard in on_ready (optionally a child() of a squad board), creates a :class:BrainRunner, ticks it in on_update, and tears the brain down in on_exit_tree. With self.brain = None it is an inert node, so subclasses can set the brain conditionally.

Initialization

tick_interval

‘Property(…)’

on_ready() None[source]
on_update(dt: float) None[source]
on_exit_tree() None[source]
strict_errors: ClassVar[bool]

True

dev_checks: ClassVar[bool]

None

script_error_raised

‘Signal(…)’

dynamic: bool

False

visible

‘Property(…)’

update_mode

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]

None

classmethod __init_subclass__(**kwargs)
property name: str
property visible_in_tree: bool
reset_error() None
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) None
reparent(new_parent: simvx.core.node.Node)
node_at(path, default=_NO_DEFAULT)
find(target, *, direct: bool = False)
find_all(target, *, direct: bool = False)
expect(target, *, direct: bool = False)
ancestor(target)
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]
property path: str
property is_scene_root: bool
add_to_group(group: str)
remove_from_group(group: str)
is_in_group(group: str) bool
on_enter_tree() None
on_fixed_update(dt: float) None
on_draw(renderer) None
on_picked(event: simvx.core.events.InputEvent) None
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
queue_redraw() None
property render_dirty: bool
clear_children()
destroy()
property destroying: bool
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None
property app
property tree: simvx.core.scene_tree.SceneTree
property physics
property physics_2d
__getitem__(key: str)
classmethod get_properties() dict[str, simvx.core.descriptors.Property]
__repr__()