> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eucaengine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Scripting (Lua)

> Attach a sandboxed Lua script to an entity so it runs every tick — and understand why a bare headless host returns an error until your project or host registers a ScriptEngine.

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](/concepts/forks). The one mental model to hold: **`/script/load` only works on a host
that registered a `ScriptEngine` resource** — a bare [headless host](/quickstart) 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:

```bash theme={null}
curl -s -X POST http://localhost:3917/spawn -H 'Content-Type: application/json' -d '{"health":100}'
# {"entity_id":1,"entity_generation":0}

curl -s -X POST http://localhost:3917/script/load \
  -H 'Content-Type: application/json' \
  -d '{"entity_id":1,"path":"scripts/spin.lua"}'
# {"ok":false,"message":"No ScriptEngine resource in world"}
```

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](/systems/templates-and-prefabs) and [input bindings](/systems/input)). 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`:

```bash theme={null}
curl -s http://localhost:3917/script/list
# Stock server: {"count":0,"entities":[]}
# With a ScriptEngine: {"count":1,"entities":[{"entity_id":1,"script_name":"spin.lua","update_fn":"update","enabled":true}]}
```

The script itself defines an `update(id)` function called once per tick for the attached entity:

```lua theme={null}
-- scripts/spin.lua
function update(id)
  local x, y, z = euca.get_position(id)
  euca.set_position(id, x, y + euca.delta_time(), z)
end
```

## 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](/concepts/forks)
  reuses the same VM instead of cloning it.

## Endpoints

| Method | Path           | Description                                                                                      |
| ------ | -------------- | ------------------------------------------------------------------------------------------------ |
| `POST` | `/script/load` | Load a `.lua` file and attach a `ScriptComponent` to an entity (needs a `ScriptEngine` resource) |
| `GET`  | `/script/list` | List entities with attached scripts — name, update function, and enabled flag                    |

To enable these end to end, register a `ScriptEngine` on your server build — see
[Hosting & deployment](/systems/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.

<Card title="Script endpoints" icon="scroll" href="/api-reference/introduction" horizontal>
  Load and list endpoints, with request and response schemas.
</Card>
