Skip to main content
Euca’s in-engine AI drives NPCs, and it is classical and deterministic — not a language model. There are two layers: a simple steering goal you set over HTTP (/ai/set attaches an AiGoal; the ai_system turns it into a desired Velocity every tick), and a behavior-tree library (euca-ai) you author in Rust. This page sets a chase goal and reads the velocity the tick produces; every command is real and runnable against a local server, with output captured from one.
“AI” here is never an LLM. The agents that drive Euca are external clients (your code or model, over the API). This page is about the NPC AI that lives inside the world. Fully headless.

Set a chase goal and step it

Spawn a chaser and a target at a distance (chasing a target at your own position gives a degenerate zero/NaN direction — keep them apart):
Attach a chase goal to the chaser, pointing at the target, with a speed. Read it back before stepping — the goal is set, but no velocity has been computed yet:
The desired velocity is computed by the ai_system, which runs on a tick. Step once, then read again:
The chaser now has a velocity of [3.0, 0.0, 0.0] — speed 3.0, aimed straight at the target on the +x axis. patrol reads back as "patrol (3 waypoints)"; idle as "idle".

Behavior and gotchas

The things you only find out by running it:
  • The goal sets immediately; the velocity computes on a tick. /ai/set attaches the AiGoal at once (ai reflects it right away), but velocity stays [0,0,0] until the next /step runs the ai_system. Set, then step, then read the velocity.
  • /ai/set accepts only idle, chase, and patrol over HTTP — flee (and any other string) falls through to idle. The response message echoes whatever you sent, so it looks accepted, but the stored goal is idle:
    AiBehavior::Flee exists in the engine; it just has no HTTP path. Trust the ai field on read-back, not the message.
  • ai_system computes velocity, not position. It writes a desired Velocity toward the goal. Turning that velocity into movement needs a physics/character-controller step on the entity; a bare spawned unit holds the velocity but won’t necessarily advance its transform. Spawn with a physics_body (or run the relevant movement system) if you want the unit to actually travel.
  • Setting AI on a missing entity returns ok:false. {"entity_id":999,...}{"ok":false,"message":"Entity 999 not found"}. Nothing is changed.

Behavior trees (euca-ai)

For richer NPC logic, the euca-ai crate is a full behavior-tree library, authored in Rust:
  • CompositesSequence, Selector, Parallel { RequireAll | RequireOne }
  • DecoratorsInverter, RepeatN, RepeatUntilFail, Cooldown, Guard
  • LeavesAction (MoveTo, Wait, SetBlackboard, Log, Custom) and Condition (HasKey, Compare, InRange, IsAlive, Custom)
  • Blackboard — a per-entity typed key-value store; BtBuilder is a fluent construction DSL.
behavior_tree_system ticks every BehaviorTreeExecutor. Behavior trees have no HTTP surface — they’re authored in Rust today; the only AI you can drive over HTTP is the /ai/set steering goal above.

Endpoints

Status

  • Shipped — Steering goals (idle / chase / patrol over HTTP; flee exists in-engine but has no HTTP path), and the behavior-tree library (composites, decorators, leaves, blackboard, builder). No stubs.

AI endpoint

The /ai/set endpoint with its request and response schema.