# Limboai

> Use when using the LimboAI addon — behavior trees and hierarchical state machines (C++ GDExtension) with a visual editor, BTTask subclassing, and a blackboard

- **Type:** Skill
- **Install:** `agentstack add skill-jame581-godotprompter-limboai`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [jame581](https://agentstack.voostack.com/s/jame581)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [jame581](https://github.com/jame581)
- **Source:** https://github.com/jame581/GodotPrompter/tree/master/skills/limboai

## Install

```sh
agentstack add skill-jame581-godotprompter-limboai
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# LimboAI

> **Related skills:** **ai-navigation** for movement the tasks drive, **state-machine** for core-engine FSM (when you don't need an addon), **godot-brainstorming** for choosing an AI approach.

> **Addon:** LimboAI · version `v1.7.1` · Godot 4.6+ · MIT · source: https://github.com/limbonaut/limboai · written in C++ (GDExtension; engine-module build also available). GDExtension exposes GDScript; **C# requires the module build** (not GDExtension in v1.7.1).

---

## 1. When to use LimboAI

| Approach | Best for |
|---|---|
| Core-engine FSM (`state-machine` skill) | Simple agents,  void:
    # BTPlayer starts executing automatically (active = true by default).
    # Connect to updated(status) to react when the tree finishes.
    bt_player.updated.connect(_on_bt_updated)

func _on_bt_updated(status: int) -> void:
    if status == BT.SUCCESS:
        bt_player.restart()  # loop the tree
```

### C#

```csharp
// EnemyAI.cs
using Godot;

public partial class EnemyAI : CharacterBody2D
{
    [Export] private BTPlayer _btPlayer;

    public override void _Ready()
    {
        _btPlayer.Updated += OnBtUpdated;
    }

    private void OnBtUpdated(int status)
    {
        if (status == (int)BT.Status.Success)
            _btPlayer.Restart();
    }
}
```

`BTPlayer.UpdateMode` controls when the tree ticks: `IDLE` (every `_process`), `PHYSICS` (every `_physics_process`, default), or `MANUAL` (call `bt_player.update(delta)` yourself).

---

## 4. Custom tasks

Subclass `BTAction` (multi-tick work) or `BTCondition` (immediate check). Annotate with `@tool` so `_generate_name()` and `_get_configuration_warnings()` work in the editor. Place scripts under `res://ai/tasks/`; subfolders become task categories.

### GDScript

```gdscript
@tool
extends BTAction
## Moves the agent toward a blackboard position each tick.

@export var target_pos_var: StringName = &"target_pos"
@export var speed: float = 200.0

func _generate_name() -> String:
    return "MoveToward %s" % LimboUtility.decorate_var(target_pos_var)

func _setup() -> void:
    pass  # one-time init; agent and blackboard are available here

func _enter() -> void:
    pass  # called when task transitions from non-RUNNING → RUNNING

func _tick(delta: float) -> Status:
    var target: Vector2 = blackboard.get_var(target_pos_var, Vector2.ZERO)
    if agent.global_position.distance_to(target)  void:
    pass  # cleanup after SUCCESS or FAILURE
```

```gdscript
@tool
extends BTCondition
## Returns SUCCESS if the agent is within range of a target node.

@export var target_var: StringName = &"target"
@export var distance_max: float = 150.0

var _max_sq: float

func _setup() -> void:
    _max_sq = distance_max * distance_max

func _tick(_delta: float) -> Status:
    var target: Node2D = blackboard.get_var(target_var, null)
    if not is_instance_valid(target):
        return FAILURE
    var in_range := agent.global_position.distance_squared_to(
        target.global_position) 
        $"MoveToward {LimboUtility.DecorateVar(TargetPosVar)}";

    public override void _Setup() { }

    public override void _Enter() { }

    public override Status _Tick(double delta)
    {
        var target = (Vector2)Blackboard.GetVar(TargetPosVar, Vector2.Zero);
        var body = (CharacterBody2D)Agent;
        if (body.GlobalPosition.DistanceTo(target)  _maxSq = DistanceMax * DistanceMax;

    public override Status _Tick(double delta)
    {
        var target = Blackboard.GetVar(TargetVar, default(Variant)).As();
        if (!GodotObject.IsInstanceValid(target))
            return Status.Failure;
        var agent2D = (Node2D)Agent;
        bool inRange = agent2D.GlobalPosition.DistanceSquaredTo(
            target.GlobalPosition)  Status:
    # Read with a default; use no type annotation for object vars
    # to avoid errors if the stored instance was freed.
    var speed: float = blackboard.get_var(speed_var, 100.0)
    var obj = blackboard.get_var(target_var, null)
    if not is_instance_valid(obj):
        return FAILURE

    # Write back
    blackboard.set_var(speed_var, speed * 1.1)
    return RUNNING
```

### C#

```csharp
// C# has no generic GetVar — cast the returned Variant.
using Godot;

[Tool]
public partial class SampleTask : BTAction
{
    [Export] public StringName SpeedVar { get; set; } = "speed";
    [Export] public StringName TargetVar { get; set; } = "target";

    public override Status _Tick(double delta)
    {
        float speed = (float)Blackboard.GetVar(SpeedVar, 100f);
        var obj = Blackboard.GetVar(TargetVar, default(Variant)).As();
        if (!GodotObject.IsInstanceValid(obj))
            return Status.Failure;

        Blackboard.SetVar(SpeedVar, speed * 1.1f);
        return Status.Running;
    }
}
```

Useful `Blackboard` methods: `has_var(name)`, `erase_var(name)`, `list_vars()`, `get_vars_as_dict()`, `bind_var_to_property(name, obj, prop)`, `link_var(name, target_bb, target_name)`, `print_state()` (debug).

`BlackboardPlan` defines the variable schema (types, defaults, hints) and is edited in the Inspector on `BTPlayer` or `LimboHSM`. Call `blackboard_plan.create_blackboard(scene_root)` to create a scoped `Blackboard` at runtime.

---

## 6. Hierarchical state machine (LimboHSM)

`LimboHSM` is a `LimboState` node that manages child `LimboState` nodes. Transitions fire when a state calls `dispatch(event)`. See [references/hsm.md](references/hsm.md) for advanced patterns (any-state transitions, `BTState`, nested HSMs, guards).

### GDScript — scene-tree setup

```gdscript
# Character.gd — scene tree: Character → LimboHSM → IdleState, MoveState
extends CharacterBody2D

@onready var hsm: LimboHSM = $LimboHSM
@onready var idle: LimboState = $LimboHSM/IdleState
@onready var move: LimboState = $LimboHSM/MoveState

func _ready() -> void:
    hsm.add_transition(idle, move, idle.EVENT_FINISHED)
    hsm.add_transition(move, idle, move.EVENT_FINISHED)
    hsm.initialize(self)
    hsm.set_active(true)
```

### C# — scene-tree setup

```csharp
// Character.cs
using Godot;

public partial class Character : CharacterBody2D
{
    [Export] private LimboHSM _hsm;
    [Export] private LimboState _idle;
    [Export] private LimboState _move;

    public override void _Ready()
    {
        _hsm.AddTransition(_idle, _move, _idle.EventFinished);
        _hsm.AddTransition(_move, _idle, _move.EventFinished);
        _hsm.Initialize(this);
        _hsm.SetActive(true);
    }
}
```

### GDScript — state script

```gdscript
# IdleState.gd
extends LimboState

func _setup() -> void:
    pass  # agent and blackboard available; runs once during hsm.initialize()

func _enter() -> void:
    agent.get_node("AnimationPlayer").play("idle")

func _exit() -> void:
    pass

func _update(delta: float) -> void:
    if Input.get_vector(&"ui_left", &"ui_right", &"ui_up", &"ui_down").length() > 0.1:
        dispatch(EVENT_FINISHED)
```

### C# — state script

```csharp
// IdleState.cs
using Godot;

public partial class IdleState : LimboState
{
    public override void _Setup() { }

    public override void _Enter()
    {
        Agent.GetNode("AnimationPlayer").Play("idle");
    }

    public override void _Exit() { }

    public override void _Update(double delta)
    {
        if (Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down").Length() > 0.1f)
            Dispatch(EventFinished);
    }
}
```

---

## Implementation checklist

- [ ] `BTPlayer` added as a child of the agent; `behavior_tree` resource assigned
- [ ] Custom tasks annotated with `@tool` (GDScript) or `[Tool]` (C#) for editor display
- [ ] Every `_tick` returns `SUCCESS`, `FAILURE`, or `RUNNING` — never `void`/`null`
- [ ] Blackboard keys documented as exported `StringName` properties (suffix `_var`)
- [ ] C# Blackboard reads cast the `Variant` explicitly (`(float)Blackboard.GetVar(...)`)
- [ ] C# uses module build (not GDExtension) for C# support
- [ ] `BTPlayer.updated` signal used (not deprecated `behavior_tree_finished`)
- [ ] HSM: all `LimboState` nodes wired with `add_transition` before `initialize()`
- [ ] HSM: `set_active(true)` called after `initialize()`
- [ ] HSM transitions are exhaustive — every reachable state has an exit path
- [ ] `BBParam` inspector binding: use module build; GDExtension lacks the param editor UI

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [jame581](https://github.com/jame581)
- **Source:** [jame581/GodotPrompter](https://github.com/jame581/GodotPrompter)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-jame581-godotprompter-limboai
- Seller: https://agentstack.voostack.com/s/jame581
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
