Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when using the Beehave addon — pure-GDScript behavior trees with composites, decorators, leaves, a blackboard, and a visual runtime debugger
.claude/skills/jame581-beehave/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 16% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 83% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 42% | 0% |
| case-07 | ✗→✓ | ▲ Improved | -22% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 246% | 0% |
> Related skills: ai-navigation for the movement leaves drive, state-machine for core-engine FSM, limboai for a heavier C++ BT+HSM alternative, godot-brainstorming for choosing an AI approach.
> Addon: Beehave · version v2.9.2 · Godot 4.1+ · MIT · source: https://github.com/bitbrain/beehave · written in GDScript (no official C# API — this skill is GDScript-only by design).
| Approach | Best for | |---|---| | Core-engine FSM (state-machine skill) | Simple agents, < 5 states, no addon | | Beehave (GDScript addon) | Lightweight BT, GDScript-only projects, fast iteration | | LimboAI | BT and HSM together, visual editor, C++ performance, C# support (module build) |
Choose Beehave when your project is GDScript-only, you want a behavior tree without a custom engine build, and you value a simple node-in-scene-tree authoring workflow. Beehave trees live entirely in the scene tree — every composite, decorator, and leaf is a regular Node child. For a heavier C++/C# solution with HSM integration, use the limboai skill instead. For plain state machines without a BT, use the built-in state-machine skill.
C# note: Beehave has no official C# API (zero .cs files in addons/beehave/). From C# you can call the GDScript API via Godot cross-language interop (GetNode<Node>(...).Call("tick", actor, blackboard)), but Beehave provides no typed C# classes.
Or copy the addons/beehave/ folder from the GitHub release into res://addons/beehave/.
Two autoloads are registered: BeehaveGlobalMetrics and BeehaveGlobalDebugger.
script_templates/ from the addon into the project root for leaf scaffolding templates.A Beehave tree is built from three kinds of nodes, all placed as regular scene-tree children:
| Role | Node | Behavior | |---|---|---| | Tree root | BeehaveTree | Ticks the child every frame (or physics/manual); extends Node (not BeehaveNode) | | Composites | SequenceComposite, SelectorComposite, SimpleParallelComposite, … | Flow control — AND / OR / parallel logic | | Decorators | InverterDecorator, CooldownDecorator, RepeaterDecorator, … | Wrap one child to modify its result | | Leaves | ActionLeaf, ConditionLeaf subclasses | Your custom game logic |
| Class | Logic | |---|---| | SequenceComposite | AND — all children must succeed; fails on first failure | | SequenceReactiveComposite | AND — re-evaluates from first child every tick while running | | SelectorComposite | OR — succeeds on first success; fails if all fail | | SelectorReactiveComposite | OR — re-evaluates from first child every tick while running | | SimpleParallelComposite | Runs two children simultaneously; result follows primary (child 0) | | SequenceRandomComposite | Shuffled AND — executes children in random order | | SelectorRandomComposite | Shuffled OR — tries children in random order |
| Class | Effect | |---|---| | InverterDecorator | Flips SUCCESS ↔ FAILURE; passes RUNNING through | | AlwaysSucceedDecorator | Forces SUCCESS; passes RUNNING through | | AlwaysFailDecorator | Forces FAILURE; passes RUNNING through | | RepeaterDecorator | Re-runs child until it succeeds repetitions times | | LimiterDecorator | Caps child to max_count running ticks, then FAILURE | | CooldownDecorator | Blocks re-execution for wait_time seconds after child finishes | | TimeLimiterDecorator | Gives child wait_time seconds; interrupts if still running | | DelayDecorator | Waits wait_time seconds before first executing child | | UntilFailDecorator | Loops child until it returns FAILURE, then returns SUCCESS |
gdscript# Scene tree: # Enemy (CharacterBody2D) # BeehaveTree ← tick_rate = 1, process_thread = PHYSICS # SelectorComposite # SequenceComposite ← "attack if in range" # IsInRangeCondition # AttackAction # PatrolAction ← fallback # BeehaveTree exports: # @export var enabled: bool = true # @export var tick_rate: int = 1 (1 = every frame; 3 = every 3 frames) # @export var process_thread: ProcessThread = PHYSICS # @export var blackboard: Blackboard (auto-created if not set) # @export_node_path var actor_node_path (defaults to parent node) # Access the tree from code if you need manual control: @onready var bt: BeehaveTree = $BeehaveTree func _ready() -> void: # Reduce tick cost: evaluate AI every 3 physics frames bt.tick_rate = 3 # Default process_thread is PHYSICS — switch to IDLE if actor uses _process bt.process_thread = BeehaveTree.ProcessThread.IDLE
> tick_rate note: tick_rate = 1 evaluates every frame; tick_rate = 3 every 3 frames. Increase for distant/background NPCs to save CPU. Default process thread is PHYSICS — if the actor script uses _process instead of _physics_process, set process_thread = IDLE to keep them in sync.
Leaves hold your game logic. Subclass ActionLeaf for multi-tick work or ConditionLeaf for single-frame checks, then override tick(actor, blackboard).
gdscript# IsInRangeCondition.gd class_name IsInRangeCondition extends ConditionLeaf @export var detection_range: float = 150.0 func tick(actor: Node, blackboard: Blackboard) -> int: # Beehave types `actor` as Node; cast to your concrete type for 2D members. var body := actor as Node2D var target: Node2D = blackboard.get_value("target") if body == null or not is_instance_valid(target): return FAILURE var in_range := body.global_position.distance_to(target.global_position) <= detection_range return SUCCESS if in_range else FAILURE
gdscript# AttackAction.gd class_name AttackAction extends ActionLeaf @export var attack_duration: float = 0.5 func tick(actor: Node, blackboard: Blackboard) -> int: var elapsed: float = blackboard.get_value("attack_elapsed", 0.0) elapsed += get_physics_process_delta_time() if elapsed >= attack_duration: blackboard.erase_value("attack_elapsed") # `actor` is typed Node; guard game-specific methods (or cast to your actor type). if actor.has_method("play_attack_animation"): actor.call("play_attack_animation") return SUCCESS blackboard.set_value("attack_elapsed", elapsed) return RUNNING func after_run(actor: Node, blackboard: Blackboard) -> void: # Clean up any per-run state when the tree interrupts this action blackboard.erase_value("attack_elapsed")
Return codes (defined on BeehaveNode):
SUCCESS — action complete / condition met.FAILURE — action failed / condition not met; parent composite decides what to do next.RUNNING — action needs more frames; tree will call tick() again next frame (ActionLeaf only — ConditionLeaf should never return RUNNING).Optional overrides:
before_run(actor, blackboard) — called once before the first tick of a run.after_run(actor, blackboard) — called when the child finishes (SUCCESS/FAILURE) or is interrupted.interrupt(actor, blackboard) — called when the tree interrupts a running node.The Blackboard node is a shared key/value store passed to every tick() call. BeehaveTree auto-creates an internal one if you don't assign an external Blackboard node.
gdscript# Share one Blackboard across multiple BeehaveTrees on the same actor. # Assign the same exported Blackboard node to each tree in the Inspector. # Read / write from any leaf's tick(): func tick(actor: Node, blackboard: Blackboard) -> int: # Write blackboard.set_value("target", actor.get_nearest_enemy()) # Read with default var speed: float = blackboard.get_value("move_speed", 200.0) # Conditional check if blackboard.has_value("stunned"): return FAILURE # Erase (sets key to null; has_value returns false after erase) blackboard.erase_value("temp_flag") return SUCCESS
> Named namespaces: every method accepts an optional blackboard_name: String parameter (default "default"). Use this to keep separate namespaces on one Blackboard node without name collisions (e.g., per-enemy state vs. shared world state).
> Built-in expression leaves: BlackboardSetAction, BlackboardEraseAction, BlackboardHasCondition, and BlackboardCompareCondition let you manipulate the Blackboard entirely via Inspector exports (no GDScript required). Expressions run via Godot's Expression.execute([], blackboard) — so you can call get_value("key") directly in the expression string.
Beehave ships an EditorDebuggerPlugin that adds a 🐝 Beehave tab to the bottom editor panel while your game is running:
To track per-tree CPU cost in the Performance panel, set custom_monitor = true on the BeehaveTree node. This registers beehave [microseconds]/process_time_<actor_name>-<id> as a Performance monitor.
For a walkthrough of writing custom decorators and conditions, see references/custom-nodes.md.
addons/beehave/ copied into project; plugin enabled in Project Settings → PluginsBeehaveTree added as a child of the actor; actor_node_path set (or left blank to default to parent)process_thread matches actor's loop: PHYSICS for _physics_process, IDLE for _processtick_rate tuned — increase for background NPCs (e.g., 3) to reduce per-frame costtick() override returns SUCCESS, FAILURE, or RUNNING — never void/nullConditionLeaf subclasses never return RUNNINGBlackboard, not stored on the leaf node itself (leaf nodes are shared)after_run or interrupt cleans up any Blackboard keys the action wroteBlackboard node exported and shared when multiple BeehaveTree nodes need the same data| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 20,072 | 6,386 | -68% | 1 | 1 | 0% | 3,573 | 4,161 | +16% | 0 | 0 | — |
case-02 | fail→pass | 14,620 | 10,705 | -27% | 1 | 1 | 0% | 2,663 | 4,860 | +83% | 0 | 0 | — |
case-03 | fail→pass | 20,630 | 9,723 | -53% | 1 | 1 | 0% | 3,154 | 4,467 | +42% | 0 | 0 | — |
case-04 | pass→pass | 14,543 | 11,055 | -24% | 1 | 1 | 0% | 2,731 | 4,876 | +79% | 0 | 0 | — |
case-05 | pass→pass | 17,011 | 13,513 | -21% | 1 | 1 | 0% | 2,978 | 5,348 | +80% | 0 | 0 | — |
case-06 | pass→pass | 14,877 | 9,743 | -35% | 1 | 1 | 0% | 2,560 | 4,572 | +79% | 0 | 0 | — |
case-07 | fail→pass | 26,923 | 4,187 | -84% | 1 | 1 | 0% | 4,566 | 3,558 | -22% | 0 | 0 | — |
case-08 | fail→pass | 6,341 | 2,750 | -57% | 1 | 1 | 0% | 955 | 3,300 | +246% | 0 | 0 | — |
case-09 | pass→pass | 5,560 | 2,031 | -63% | 1 | 1 | 0% | 922 | 3,194 | +246% | 0 | 0 | — |
case-10 | pass→pass | 5,343 | 2,646 | -50% | 1 | 1 | 0% | 850 | 3,332 | +292% | 0 | 0 | — |
case-11 | pass→pass | 5,265 | 3,084 | -41% | 1 | 1 | 0% | 912 | 3,389 | +272% | 0 | 0 | — |
case-12 | pass→pass | 12,582 | 8,348 | -34% | 1 | 1 | 0% | 2,149 | 4,312 | +101% | 0 | 0 | — |
case-13 | pass→pass | 14,738 | 6,744 | -54% | 1 | 1 | 0% | 2,319 | 3,980 | +72% | 0 | 0 | — |
case-22 | pass→pass | 11,704 | 5,184 | -56% | 1 | 1 | 0% | 1,885 | 3,698 | +96% | 0 | 0 | — |
case-14 | fail→pass | 11,262 | 2,978 | -74% | 1 | 1 | 0% | 1,863 | 3,363 | +81% | 0 | 0 | — |
case-15 | pass→pass | 9,543 | 2,359 | -75% | 1 | 1 | 0% | 1,503 | 3,231 | +115% | 0 | 0 | — |
case-16 | fail→pass | 17,368 | 5,250 | -70% | 1 | 1 | 0% | 3,128 | 3,763 | +20% | 0 | 0 | — |
case-17 | pass→pass | 6,142 | 3,305 | -46% | 1 | 1 | 0% | 1,006 | 3,438 | +242% | 0 | 0 | — |
case-18 | fail→pass | 14,703 | 3,598 | -76% | 1 | 1 | 0% | 2,388 | 3,449 | +44% | 0 | 0 | — |
case-19 | pass→pass | 4,853 | 3,635 | -25% | 1 | 1 | 0% | 730 | 3,396 | +365% | 0 | 0 | — |
case-20 | fail→pass | 5,309 | 3,300 | -38% | 1 | 1 | 0% | 829 | 3,369 | +306% | 0 | 0 | — |
case-21 | fail→pass | 3,411 | 1,729 | -49% | 1 | 1 | 0% | 479 | 3,074 | +542% | 0 | 0 | — |
case-23 | fail→pass | 18,528 | 4,367 | -76% | 1 | 1 | 0% | 3,056 | 3,509 | +15% | 0 | 0 | — |
case-24 | fail→pass | 11,988 | 2,673 | -78% | 1 | 1 | 0% | 1,796 | 3,273 | +82% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +50 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.