Skip to main content
Euca lets you attach per-entity behavior in Lua (euca-script, via mlua): a ScriptComponent names a .lua file whose update function runs each tick through script_tick_system, reading and writing the world through a small euca API. The script runs in a sandbox with an instruction budget, and the VM is shared across forks. The one mental model to hold: /script/load only works on a host that registered a ScriptEngine resource — a bare headless host doesn’t, so this page shows the real error you’ll hit and exactly what a scripting host changes.

Attach a script to an entity

Spawn an entity, then point /script/load at a .lua file and the entity to run it on:
That No ScriptEngine resource in world is the real response from a bare headless host — and it’s the expected one. The handler looks up an Arc<Mutex<ScriptEngine>> resource and bails before it ever touches the file, because a bare host never registers one (the same “register the resource on your host” pattern as templates and input bindings). The entity exists; nothing was attached. On a server build that does register a ScriptEngine, the same call loads the file, derives the script name (spin.lua), inserts a ScriptComponent, and returns {"ok":true,"message":"Script 'spin.lua' attached to entity 1"}. The attached entity then shows up in /script/list:
The script itself defines an update(id) function called once per tick for the attached entity:

Behavior and gotchas

The things you only find out by running it:
  • A bare headless host has no ScriptEngine, so /script/load always returns {"ok":false,"message":"No ScriptEngine resource in world"}. This is not a misconfiguration — Lua is a host-side capability you opt into by registering the engine in your project or your own host build. /script/list returns {"count":0,"entities":[]} there for the same reason.
  • Missing entities are reported before the engine check fails differently: /script/load with an unknown entity_id returns {"ok":false,"message":"Entity 999 not found"}. A real entity plus no engine is what produces the No ScriptEngine message.
  • Scripts are sandboxed with an instruction budget (default 100,000). Dangerous globals are removed and each update call is capped, so a runaway loop can’t hang the tick.
  • The euca global is the ECS bridge: spawn(), despawn(id), get_position(id) / set_position(id,x,y,z), get_health(id) / set_health(id,v), delta_time(), plus an event API on("event", fn) / emit("event", …).
  • Hot reload is built in — a file watcher reloads changed scripts at runtime.
  • The VM is fork-safe. It’s shared as Arc<Mutex<ScriptEngine>>, so a forked world reuses the same VM instead of cloning it.

Endpoints

To enable these end to end, register a ScriptEngine on your server build — see Hosting & deployment for which subsystems need host-side resources.

Status

  • Shipped — sandboxed Lua VM, instruction budget, ECS bridge, per-entity scripts, hot reload, and fork-safe sharing.
  • Caveat — a bare headless host does not register a ScriptEngine, so /script/load returns No ScriptEngine resource in world until you wire one into your project or host.

Script endpoints

Load and list endpoints, with request and response schemas.