Software · Controls

Behavior Tree Lab

Watch a behavior tree drive a pretend robot, one tick at a time. The tree is drawn by Arborist, the same viewer Arbor uses for its live view, so the colours mean exactly what they mean on the real robot. Break things on purpose, then edit the XML and run your own tree.

The tree

t = 0.00 s · tick 0 · tree IDLE
Loading the Arborist viewer…

Click a node to see what it does. Press + on a SubTree to draw it inline.

Field

Blue half and neutral zone, 2026 game, simplified
our robot path it is following other paths in this tree fuel

Live log

Every status change. Click one to find its node.

Tree XML

BehaviorTree.CPP v4, the same files Arbor opens
Nodes this robot knows (and their ports)

"Download for Arbor" saves a .zip in Arbor's project layout: bt_project.json, nodes.xml (the node list, with port types and enum values) and trees/. Unzip it and open the folder in Arbor. Your edits stay in this page only.

Things to try

  1. One node stays orange forever. On Drive, aim, shoot, tick "Camera can't see the target" and run it. RetryUntilSuccessful keeps asking forever. That is the docs' "wrap it in a Timeout" case: compare with Pickup and score.
  2. All green, nothing scored. Run the Real auto, then run it again with "Intake jams". The tree looks the same, but the robot shoots only the 8 fuel it started with. A tree only knows what its nodes check.
  3. Stuck behind a robot. Tick "Another robot blocks the path". FollowTrajectory fails after 3 s and the whole Sequence fails. In the XML, wrap that FollowTrajectory in <ForceSuccess> and the auto carries on.
  4. ParallelDeadline. In the Real auto, watch DriveP1. When the path finishes, IntakeOnEvent is halted if it is still waiting. Then try a middle_wait_msec of 0.
  5. Reactive vs. plain. Change a Sequence to ReactiveSequence and step through it. Every tick starts again from the first child, and a child that already succeeded is reset when a later one runs. Watch Drive path start over and over: that is why ReactiveSequence is for conditions, not for steps.
  6. Typos are errors. Change msec to msc, or AIM to AIMM. The lab refuses to run it and marks the node, the way the robot refuses to load the auto.

The pretend robot

A tiny super state machine, written the way our RobotContainer is. Every condition is in handleStateTransition(); updateLogic() only asks subsystems for states.

handleStateTransition():
  switch (wanted):
    AIM   -> system = aimed ? AIM : AIMING
    SHOOT -> system = ready ? SHOOT_FEED
                            : SHOOT_WAIT
    else  -> system = wanted

updateLogic():
  switch (system):
    AIMING, AIM -> shooter AIMING
    SHOOT_WAIT  -> shooter SHOOT
    SHOOT_FEED  -> shooter SHOOT, intake SQUEEZE
    INTAKE      -> intake INTAKE
    AUTO        -> nothing (the auto
                   sets subsystems)

Aiming takes 0.8 s and needs the camera, except at the spot right in front of the hub. The flywheel takes 0.6 s to spin up. The robot is never in SHOOT itself, so IsSystemState SHOOT never succeeds. That is why ShootFor uses a Sleep. The AIMING step state is made up for this lab.