---
title: "entity"
description: "The entity namespace — the engine's Luau API reference for entity."
section: "API Reference"
slug: "api-entity"
canonical: "https://origozero.ai/docs/api-entity"
updated: "2026-09-05T23:13:46.404875540+00:00"
tags: ["api", "reference"]
---

# entity

The `entity` namespace — 58 functions.

## entity/proxy/component/addSynced {#entity-proxy-component-addsynced}

```lua
entity(id).component.addSynced(type, data?) -> table | nil
```

Add a component that replicates to all peers. Same as add() — including returning the new component's live public proxy — but forces sync registration even without a sync {} block.

**Parameters**

- `type` `AssetRef` — Component identity
- `data` `table` _(optional)_ — Initial property values for the component

**Returns** `table | nil` — The new component's live public proxy; nil when deferred or skipped

```lua
local health = entity(id).component.addSynced('Health', { hp = 100 })
```

## entity/proxy/component/pending {#entity-proxy-component-pending}

```lua
entity(id).component.pending(type?) -> { {type: string, instance: string?, instanceId: string, fields: { {name: string, spec: string, value: string} }} }
```

The components on this entity whose awake() is held back waiting for an asset field to register, and for each one the field, its declared spec, and the identity string that did not resolve. A component in this list is attached and idle: it renders nothing and its methods return nothing until the asset arrives. Empty when every component on the entity has run. Ask here when a component appears to have done nothing — the answer separates an asset still arriving from an identity that names no asset.

**Parameters**

- `type` `AssetRef` _(optional)_ — Component identity to narrow the answer to

**Returns** `table` — Array of { type, instance?, instanceId, fields } records; empty when nothing is waiting

```lua
local waiting = entity(id).component.pending('Terrain')
if #waiting > 0 then print(waiting[1].fields[1].name, waiting[1].fields[1].value) end
```

## globals/entity/__batchReadIds {#globals-entity-batchreadids}

```lua
entity.__batchReadIds()
```

Read a component field across many entities by id, in one crossing.

## globals/entity/__batchReadToBuffer {#globals-entity-batchreadtobuffer}

```lua
entity.__batchReadToBuffer()
```

Read a component field into a typed substrate buffer.

## globals/entity/__batchWriteBound {#globals-entity-batchwritebound}

```lua
entity.__batchWriteBound()
```

Write a component field across a binding handle, in one crossing.

## globals/entity/__batchWriteFromBuffer {#globals-entity-batchwritefrombuffer}

```lua
entity.__batchWriteFromBuffer()
```

Write a component field from a typed substrate buffer.

## globals/entity/__batchWriteIds {#globals-entity-batchwriteids}

```lua
entity.__batchWriteIds()
```

Write a component field across many entities by id, in one crossing.

## globals/entity/batchAddComponent {#globals-entity-batchaddcomponent}

```lua
entity.batchAddComponent(targets: { string | entityRef }, type_name: string, data: table?) -> number
```

Add the same component type to many entities in one call. Returns the
count of entities the component was added to — an entity already
carrying an unnamed instance of the same type is skipped rather than
double-added.

**Parameters**

- `targets` `{ string | entityRef }` — Array of entity ids or entity proxies (e.g. the return of
`entity.batchSpawn` or `entity.findAll`).
- `type_name` `string` — Component type to add to every entity.
- `data` `table` _(optional)_ — Init data table, applied identically to every entity — the same
shape the second arg to `entity(id).component.add(type, data)` takes.

**Returns** `number` — How many entities had the component added.

```lua
local n = entity.batchAddComponent(ids, "Debris", { lifetime = 5 })
```

## globals/entity/batchDespawn {#globals-entity-batchdespawn}

```lua
entity.batchDespawn(targets: { string | entityRef }) -> number
```

Despawn many entities in one call. Locked or unresolvable entities
are skipped. Returns the count queued for despawn.

**Parameters**

- `targets` `{ string | entityRef }` — Array of entity ids, entity proxies, or display names (e.g.
the return of `entity.batchSpawn` / `entity.findAll`).

**Returns** `number` — Count of entities queued for despawn.

```lua
local n = entity.batchDespawn(ids)
```

## globals/entity/batchProxy {#globals-entity-batchproxy}

```lua
entity.batchProxy(targets: { string | entityRef }) -> { entityRef? }
```

Resolve an array of entity ids to proxies in one call. Each output
slot is the standard `entity(id)` proxy; ids missing from the frame
cache surface as nil at that index. Use when iterating over a snapshot
of entities so per-id lookups don't dominate the hot path.

**Parameters**

- `targets` `{ string | entityRef }` — Array of entity ids or entity proxies.

**Returns** `{ entityRef? }` — Array of proxies (nil for missing ids).

```lua
local proxies = entity.batchProxy(ids)
```

## globals/entity/batchRead {#globals-entity-batchread}

```lua
entity.batchRead(target: { string | entityRef } | binding, component: string?, field: string?, sink: buffer?) -> { any? } | number
```

Read a component-field across many entities in one call.
Polymorphic on the shape of `target` and `sink`:
- `entity.batchRead(ids)` / `(ids, comp)` / `(ids, comp, field)` —
returns one value per entity (a whole snapshot, one component table,
or one field value). Missing entities/components/fields surface as
nil at that slot.
- `entity.batchRead(binding, comp, field, buffer)` — reads each
entity's field directly into a typed CPU substrate buffer
(`substrate.createBuffer({type="vec3"})`, etc.) with no per-entity
Lua table allocation. Returns the count of successful reads.
`target` accepts an entity-id array or a `ecs.bindEntities(ids)`
handle. Buffer sinks require a binding — the typed kernel is
binding-only.

**Parameters**

- `target` `{ string | entityRef } | binding` — Array of entity ids or entity proxies, or a binding handle
from `ecs.bindEntities(ids)`.
- `component` `string` _(optional)_ — Component type name (e.g. "Transform").
- `field` `string` _(optional)_ — Field name (e.g. "position").
- `sink` `buffer` _(optional)_ — Typed CPU buffer from `substrate.createBuffer({...})` to memcpy
field values into. Required when `target` is a binding.

**Returns** `{ any? } | number` — Per-entity values when reading into Lua tables; count of reads when reading into a buffer.

```lua
local snapshot = entity.batchRead(ids)
local positions = entity.batchRead(ids, "Transform", "position")
```

## globals/entity/batchReadToBuffer {#globals-entity-batchreadtobuffer}

```lua
entity.batchReadToBuffer(binding: number, component: string, field: string, buffer: number) -> number
```

FFI primitive backing `entity.batchRead(binding, ..., buffer)`.
Prefer the unified `entity.batchRead`, which auto-dispatches by
argument shape. Reads each entity's component field directly into a
typed CPU substrate buffer, with no per-entity Lua table allocation.
After the call, read the buffer via `buf:read(0, count*stride)`.

**Parameters**

- `binding` `number` — Binding id from `ecs.bindEntities(ids).id`.
- `component` `string` — Component type name.
- `field` `string` — Field name to read.
- `buffer` `number` — Destination buffer id (must be the matching type).

**Returns** `number` — Count of successful reads.

```lua
entity.batchReadToBuffer(binding.id, "Transform", "position", buf.id)
```

## globals/entity/batchSpawn {#globals-entity-batchspawn}

```lua
entity.batchSpawn(count: number, name_prefix: string?) -> { string }
```

Spawn `count` entities in one call. Returns an array of the new
entity ids in spawn order. Each entity is given a display name of
`<name_prefix><i>` (or `entity<i>` if the prefix is omitted). Prefer this
over looping `entity.spawn` when creating large entity counts.

**Parameters**

- `count` `number` — How many entities to spawn (capped at 1,000,000).
- `name_prefix` `string` _(optional)_ — Display-name prefix appended with the 1-based index.
Defaults to "entity".

**Returns** `{ string }` — Array of newly-spawned entity ids.

```lua
local ids = entity.batchSpawn(100, "grass_")
```

## globals/entity/batchWrite {#globals-entity-batchwrite}

```lua
entity.batchWrite(target: { string | entityRef } | binding, component: string, field: string, source: { any? } | buffer) -> number
```

Write a single component-field across many entities in one call.
Polymorphic on the shape of `target` and `source`:
- `entity.batchWrite(ids, comp, field, values)` — per-call entity-id
resolution; `values` is an array the same length as `ids` (nil slots
are skipped). Use for one-shot writes.
- `entity.batchWrite(binding, comp, field, values)` — binding handle
from `ecs.bindEntities(ids)`; skips per-call id resolution. Use for
per-frame writes against a stable entity set.
- `entity.batchWrite(binding, comp, field, buffer)` — typed CPU buffer
source (`substrate.createBuffer({type="vec3"})`, etc.), with no
per-entity table allocation.
Returns the count of successful writes. Buffer sources require a
binding — the typed kernel is binding-only.

**Parameters**

- `target` `{ string | entityRef } | binding` — Array of entity ids or entity proxies, or a binding handle
from `ecs.bindEntities(ids)`.
- `component` `string` — Component type name.
- `field` `string` — Field name to write.
- `source` `{ any? } | buffer` — Per-entity values array (nil entries are skipped), or a typed
CPU buffer from `substrate.createBuffer({...})`. A buffer source
requires a binding `target`.

**Returns** `number` — Count of successful writes.

```lua
entity.batchWrite(ids, "Transform", "position", positions)
```

## globals/entity/batchWriteBound {#globals-entity-batchwritebound}

```lua
entity.batchWriteBound(binding: number, component: string, field: string, values: { any? }) -> number
```

FFI primitive backing `entity.batchWrite(binding, ...)` with a
per-entity values table. Prefer the unified `entity.batchWrite`, which
auto-dispatches by argument shape; this entry stays for power users /
debug code that wants to skip dispatch overhead.

**Parameters**

- `binding` `number` — Binding id from `ecs.bindEntities(ids).id`.
- `component` `string` — Component type name.
- `field` `string` — Field name to write.
- `values` `{ any? }` — Per-entity source values (nil = skip). Length must match the
binding's entity count.

**Returns** `number` — Count of successful writes.

```lua
entity.batchWriteBound(binding.id, "Transform", "position", values)
```

## globals/entity/batchWriteFromBuffer {#globals-entity-batchwritefrombuffer}

```lua
entity.batchWriteFromBuffer(binding: number, component: string, field: string, buffer: number) -> number
```

FFI primitive backing `entity.batchWrite(binding, ..., buffer)`.
Prefer the unified `entity.batchWrite`, which auto-dispatches by
argument shape. Caller fills a typed substrate buffer
(`substrate.createBuffer({type="vec3"})`) once via `buf:write(...)`,
then this memcpys 12 (vec3) or 16 (quat) bytes per entity into the
component field. Buffer count and binding count should match — a
mismatch processes the smaller of the two.

**Parameters**

- `binding` `number` — Binding id from `ecs.bindEntities(ids).id`.
- `component` `string` — Component type name.
- `field` `string` — Field name to write.
- `buffer` `number` — Buffer id from `substrate.createBuffer({type="vec3", len=N}).id`.

**Returns** `number` — Count of successful writes.

```lua
entity.batchWriteFromBuffer(binding.id, "Transform", "position", buf.id)
```

## globals/entity/capture {#globals-entity-capture}

```lua
entity.capture(builder: () -> ()) -> ({ string }, any?, { string })
```

Run `builder` inside an entity capture scope and return the entity ids
it minted, in creation order, the error it raised (if any), and the ids
among them that a component the builder attached minted in its own
lifecycle. Every id minted while the builder runs is recorded — through
`entity.spawn`, `entity.spawnSynced`, `entity.batchSpawn`, and
`entity.instantiate` alike. Scopes nest: an id minted inside an inner
capture is recorded by that capture AND every enclosing one — the
innermost capture answers, so a nested build shapes its own entities,
not the ones around it. A builder that raises still returns its ids, so
the caller can despawn what a failed build left behind; the scope closes
either way and never outlives this call. While the builder runs, an
operation whose result cannot be composed into a record is refused
rather than applied, and so is any operation aimed at an entity the
builder did not mint — a builder that returned while something it did
was refused comes back with an error naming every refusal.

**Parameters**

- `builder` `() -> ()` — Function run inside the scope; the entities it creates are
what comes back.

**Returns** `({ string }, any?, { string })` — Entity ids minted while the builder ran, in creation order; the error it raised (or the refusals it hit), or nil; and the ids a component the builder attached minted in its own lifecycle.

```lua
local ids, err, reproduced = entity.capture(function() entity.spawn("chair") end)
```

## globals/entity/despawn {#globals-entity-despawn}

```lua
entity.despawn(target: string | entityRef)
```

Despawn an entity and all its components. Pass an id string or an
entity proxy to despawn that ONE entity. Pass a name to despawn EVERY
entity with that name — names are not unique, so a name argument
despawns all matches, not one arbitrary match. A despawned id becomes
invalid after this call. Raises if no entity matches; for a bulk name
despawn, locked entities are skipped with a logged summary and only
raise if every match is locked.

**Parameters**

- `target` `string | entityRef` — Entity id, name, or entity proxy. A name despawns all entities
sharing that name.

```lua
entity.despawn(id)
entity.despawn("Enemy") -- despawns every entity named "Enemy"
```

## globals/entity/duplicate {#globals-entity-duplicate}

```lua
entity.duplicate(sourceId: string | entityRef, name: string?, opts: table?) -> string?
```

Duplicate an entity with all its components (transform, script
components, attributes, visuals, material) and its descendants. Returns
the new entity's id, or nil when `sourceId` names no live entity.
Descendants marked temporary are left out of the copy: they are
scaffolding whatever spawned them re-creates, so a component that
regenerates its own children rebuilds them on the copy rather than the
copy carrying a second set. `includeTemporary` copies them too, for the
hierarchy that IS the temporary thing.

**Parameters**

- `sourceId` `string | entityRef` — Entity id or entity proxy of the source entity to clone.
- `name` `string` _(optional)_ — Display name for the copy (defaults to source name + " (copy)").
- `opts` `table` _(optional)_ — `{ includeTemporary?: boolean, name?: string }` — `name` is the
same field the `name` argument sets, and wins when both are given.

**Returns** `string?` — The new entity's id, or nil when the source is not live.

```lua
local copyId = entity.duplicate(id); if copyId then entity(copyId).position = { 1, 0, 0 } end
local copyId = entity.duplicate(id, "Turret", { includeTemporary = true })
```

## globals/entity/exists {#globals-entity-exists}

```lua
entity.exists(idOrProxy: string | entityRef) -> boolean
```

Check whether an entity currently exists in the scene. Accepts an
entity-id string or an entity proxy, matched by entity id — so it agrees
exactly with `entity(id)`. A name is a different kind of identifier: a
string that misses as an id but names a live entity raises rather than
answering false, since false there is indistinguishable from absence.
Check by name with `entity.find(name) ~= nil`.

**Parameters**

- `idOrProxy` `string | entityRef` — Entity id or an entity proxy.

**Returns** `boolean` — true if the entity exists.

```lua
if entity.exists(id) then ... end
```

## globals/entity/find {#globals-entity-find}

```lua
entity.find(nameOrGlob: string) -> entityRef?
```

Find the first entity matching `nameOrGlob`. A plain string matches
an exact id or Name component; a string containing `*` (any run of
characters) or `?` (any single character) matches Names as a glob, so
`entity.find("enemy_*")` is the first entity whose name starts with
`enemy_`. A glob addresses Names only, never ids. Same-frame pending
spawns are searched too, and anything queued for despawn in the same
frame is skipped. Names are NOT unique — use `entity.findAll` when every
match matters.

**Parameters**

- `nameOrGlob` `string` — Exact entity Name or id, or a `*` / `?` glob over Names.

**Returns** `entityRef?` — First matching entity proxy, or nil.

```lua
local e = entity.find("enemy_*")
```

## globals/entity/findAll {#globals-entity-findall}

```lua
entity.findAll(nameOrGlob: string?) -> { entityRef }
```

Enumerate entity proxies. With a `nameOrGlob` argument, returns every
entity whose Name component or id matches (names are not unique): a
plain string matches exactly, while a `*` / `?` glob matches Names. With
no argument, returns every entity in the current snapshot —
`findAll("")` is the exact-match filter for the empty name, which
normally matches nothing. Same-frame pending spawns are included and
same-frame despawns filtered out. Elements are entity proxies, not id
strings — for ids, wrap the result: `entity.ids(entity.findAll(...))`.

**Parameters**

- `nameOrGlob` `string` _(optional)_ — Exact entity Name or id to filter by, or a `*` / `?` glob
over Names. Omit to enumerate every entity.

**Returns** `{ entityRef }` — Array of entity proxies, possibly empty.

```lua
for _, e in entity.findAll("enemy_*") do e:despawn() end
```

## globals/entity/getChildren {#globals-entity-getchildren}

```lua
entity.getChildren(id: string | entityRef) -> { entityRef }
```

Get an array of the direct children as entity proxies. Each element
carries `.name`, `.id`, `.position`, `.component`, and the rest of the
per-entity surface — the same shape `entity.findAll` returns.

**Parameters**

- `id` `string | entityRef` — Entity id or entity proxy.

**Returns** `{ entityRef }` — Array of child entity proxies, possibly empty.

```lua
for _, c in entity.getChildren(id) do c.internal = true end
```

## globals/entity/getDescendants {#globals-entity-getdescendants}

```lua
entity.getDescendants(id: string | entityRef) -> { entityRef }
```

Get every descendant (children, grandchildren, and deeper) of the
given entity as entity proxies in breadth-first order, excluding the
entity itself. Resolves the whole subtree in one linear pass over the
entity set, so a large subtree costs proportionally to the entity count
rather than to the subtree size times the entity count.

**Parameters**

- `id` `string | entityRef` — Entity id or entity proxy.

**Returns** `{ entityRef }` — Array of descendant entity proxies, possibly empty.

```lua
local all = entity.getDescendants(id)
```

## globals/entity/getParent {#globals-entity-getparent}

```lua
entity.getParent(id: string | entityRef) -> entityRef?
```

Get the parent entity proxy, or nil if the entity is a root entity.
The returned proxy carries `.name`, `.id`, `.position`, `.component`,
and the rest of the per-entity surface — the same shape `entity.find`
returns.

**Parameters**

- `id` `string | entityRef` — Entity id or entity proxy.

**Returns** `entityRef?` — Parent entity proxy, or nil.

```lua
local p = entity.getParent(id)
```

## globals/entity/hierarchy {#globals-entity-hierarchy}

```lua
entity.hierarchy()
```

Hierarchy helpers reached as `entity.hierarchy.<member>`.

## globals/entity/ids {#globals-entity-ids}

```lua
entity.ids(value: any) -> { string }
```

Coerce an entity, a list of entities, or nil into a list of entity id
strings. Accepts id strings and entityRefs in any mix, so the
entity-answering calls and a stored list of ids both feed it. A single
entity becomes a one-element list and nil becomes an empty list. Order is
preserved and duplicates are kept.

**Parameters**

- `value` `any` — An entity, a list of entities, or nil.

**Returns** `{ string }` — Array of entity id strings.

```lua
local ids = entity.ids(root.getChildren())
local ids = entity.ids(entity.findAll())
```

## globals/entity/instantiate {#globals-entity-instantiate}

```lua
entity.instantiate(handle: number, count: number, fn: ((number) -> EntityInstantiateOverrides?)?) -> { string }
```

Spawn `count` instances of a template registered with
`entity.template`. Each instance gets a fresh entity id; the optional
`fn(i)` callback runs per instance (`i` in 1..=count) and may return an
overrides table. Override keys: `name`, `position`, `rotation`, `scale`,
`parent`, `temporary` / `active` / `internal`, `attributes`, `components`
(script components, merged over the template body's data for that type
— a type the template lacks is added fresh), and `ecs` (native
components, merged the same way). Each override supersedes the
template's shared config for that instance. The whole batch crosses in
one call and lands as a single deferred mutation the engine expands
into bulk work — per-instance cost drops from a full round trip to one
callback plus one mutation. Inside `queue()` the batch is deferred onto
the cross-frame ring; outside, it lands in the next frame's drain.
Returns the array of newly-minted entity ids in spawn order.

**Parameters**

- `handle` `number` — Template handle from `entity.template`.
- `count` `number` — Number of instances to spawn (capped at 1,000,000).
- `fn` `((number) -> EntityInstantiateOverrides?)` _(optional)_ — Per-instance override callback `(i) -> table?`.

**Returns** `{ string }` — Array of newly-spawned entity ids in spawn order.

```lua
local ids = entity.instantiate(h, 50, function(i) return { position = { i, 0, 0 } } end)
```

## globals/entity/proxies {#globals-entity-proxies}

```lua
entity.proxies(value: any) -> { entityRef }
```

Coerce an entity, a list of entities, or nil into a list of entityRefs.

**Parameters**

- `value` `any` — An entity, a list of entities, or nil.

**Returns** `{ entityRef }` — Array of entityRefs.

## globals/entity/refs {#globals-entity-refs}

```lua
entity.refs(value: any) -> { entityRef }
```

Coerce an entity, a list of entities, or nil into a list of entityRefs —
the form carrying `.position`, `.component`, and the rest of the per-entity
surface. Accepts id strings and entityRefs in any mix. A single entity
becomes a one-element list and nil becomes an empty list. An id whose
entity has despawned raises, naming its position.

**Parameters**

- `value` `any` — An entity, a list of entities, or nil.

**Returns** `{ entityRef }` — Array of entityRefs.

```lua
for _, e in entity.refs(savedIds) do e.position = { 0, 1, 0 } end
```

## globals/entity/spawn {#globals-entity-spawn}

```lua
entity.spawn(nameOrOpts: (string | SpawnOpts)?, opts: SpawnOpts?) -> entityRef
```

Spawn a new entity and return its PROXY (the same value entity(id)
yields) — act on it immediately (`entity.spawn(name).component.add(...)`,
`.localPosition = ...`) with no second `entity(id)` round trip. The proxy
still exposes `.id` for the rare site that needs the raw string. An
entity with no components carries only a Transform and is invisible;
pass `components` to give it the components that make it visible in the
same call — `entity.spawn { name = "crate", components = { Model = {
model = "cube" } } }` — or add them afterwards through the returned
proxy. Mirrors `entity.find` / `entity.findAll`, which also return
proxies. The options table can be passed on its own with the name inside
it — `entity.spawn { name = "turret", position = { 1, 2, 3 } }` is the
same call as `entity.spawn("turret", { position = { 1, 2, 3 } })`.

**Parameters**

- `nameOrOpts` `(string | SpawnOpts)` _(optional)_ — Display name for the entity, or the options table itself.
- `opts` `SpawnOpts` _(optional)_ — Options: `components` = component types to attach to the new
entity, keyed by type name with each value the component's init table
(attached in sorted type order; a failing add raises), `internal` = take
the entity out of the default entity listings (it still renders —
`entity(id):hide()` stops the draw), `parent` = parent entity id or
proxy, `temporary` = skip this entity (and descendants) from
scene/world saves, `position` / `rotation` / `scale` = place the
entity's Transform at spawn, `id` = restore a previously-assigned entity
id (scene_loader use; leave unset for a normal spawn). An unrecognised
key is rejected loudly.

**Returns** `entityRef` — Proxy for the new entity (carries `.id`, `.component`, transform properties, etc.).

```lua
local e = entity.spawn("crate", { components = { Model = { model = "cube" } } })
local e = entity.spawn { name = "turret", position = { 1, 2, 3 } }
```

## globals/entity/spawnSynced {#globals-entity-spawnsynced}

```lua
entity.spawnSynced(nameOrOpts: (string | SpawnOpts)?, opts: SpawnOpts?) -> entityRef
```

Spawn an entity already flagged multiplayer-synced at the root — the
explicit form of `entity.spawn` for SHARED, host-authoritative content.
The entity's existence broadcasts to every peer; joiners receive it from
the relay snapshot instead of spawning their own copy. Use this (NOT
`entity.spawn`) for anything that must be the SAME object on all
clients: enemies, pickups, projectiles, dynamic world props. Call it
ONLY where exactly one client runs the code — a scene's `onHostLoad`
(host-only) phase, or behind `multiplayer.isHost()`. Calling it in
all-client code makes every client spawn+sync its own copy — the
double-spawn "explosion". Equivalent to
`entity.spawn(name, { synced = true })`; identical in every other
respect.

**Parameters**

- `nameOrOpts` `(string | SpawnOpts)` _(optional)_ — Display name for the entity, or the options table itself.
- `opts` `SpawnOpts` _(optional)_ — Same options as `entity.spawn` (`synced` is already implied).

**Returns** `entityRef` — Proxy for the new synced entity.

```lua
if multiplayer.isHost() then entity.spawnSynced("Goblin") end
```

## globals/entity/template {#globals-entity-template}

```lua
entity.template(def: EntityTemplateDef) -> number
```

Construct a reusable spawn template. Captures a shared entity config
ONCE and returns a stable handle for `entity.instantiate(handle, count,
fn?)` — one call per batch instead of one per entity. `def` keys:
`components` (script components, `{ [type] = init-data }`), `ecs`
(array of native `ecs.X{...}` components), `temporary` (instances skip
scene/world saves), `active` (spawn state), `internal` (instances are
taken out of the default entity listings; they still render),
`attributes` (`{ key = value }` applied to every instance). Every value
is a shared default; a per-instance `entity.instantiate` override
supersedes it. The template body is captured by value — later edits to
the source table do not affect templates already created.

**Parameters**

- `def` `EntityTemplateDef` — Template definition: `components` / `ecs` / `temporary` /
`active` / `internal` / `attributes`. Per-instance `name` / `position` /
`rotation` / `scale` / `parent` and any override go through the
`instantiate` callback.

**Returns** `number` — Stable template handle for `entity.instantiate`.

```lua
local h = entity.template({ components = { Model = { model = "cube" } } })
```

## globals/entity/tree {#globals-entity-tree}

```lua
entity.tree(opts: { [string]: any }?) -> { [string]: any }
```

A windowed, lean view over the scene's entity tree, in one crossing.
Rows carry id, name, parentId, depth, childCount, active, sceneLayer and
componentNames — names only, never component values — so the call costs
the rows it answers with rather than the size of the scene. Entities group
under scene layers, per-layer roots and children name-sorted; internal
entities and their subtrees stay out. `expanded` names the ids whose
children unfold, and a collapsed node still reports its `childCount`;
`filter` keeps the rows whose name or id contains the needle plus every
ancestor on a path to one, auto-unfolded, with the actual matches flagged
`matched`. `offset` / `limit` window the flattened rows, `layer` scopes the
window and its `total` to one layer while `layers` still reports every
layer's row count, and `revision` echoes
`getEntitiesRevision("structure")`, which moves only on structural change.

**Parameters**

- `opts` `{ [string]: any }` _(optional)_ — `{ layer?, expanded?, filter?, offset?, limit? }`.

**Returns** `{ [string]: any }` — `{ rows, total, layers, revision }`.

```lua
local view = entity.tree({ filter = "crate", limit = 50 })
```

## modules/entity/README {#modules-entity-readme}

```lua
require("@builtin/modules/api/engine/entity") -- entity (also available as global 'entity')
```

Entity spawn / despawn / query / hierarchy surface. Calling the table itself — `entity(idOrProxy)` — resolves an id or proxy to its live entityRef proxy. Public Luau surface over the `__entity` Internal FFI namespace, composed with the entityRef proxy metatable, the hierarchy swap helper, id/proxy coercion, and the polymorphic batch read/write dispatch.

Usage: local entity = require("@builtin/modules/api/engine/entity")
Also available as global: entity

## modules/entity/batchAddComponent {#modules-entity-batchaddcomponent}

```lua
batchAddComponent(targets: { string | entityRef }, type_name: string, data: table?): number
```

Add the same component type to many entities in one call. Returns the
count of entities the component was added to — an entity already
carrying an unnamed instance of the same type is skipped rather than
double-added.

**Parameters**

- `targets` `{ string | entityRef }` — Array of entity ids or entity proxies (e.g. the return of
`entity.batchSpawn` or `entity.findAll`).
- `type_name` `string` — Component type to add to every entity.
- `data` `table?` _(optional)_ — Init data table, applied identically to every entity — the same
shape the second arg to `entity(id).component.add(type, data)` takes.

```lua
local n = entity.batchAddComponent(ids, "Debris", { lifetime = 5 })
```

## modules/entity/batchDespawn {#modules-entity-batchdespawn}

```lua
batchDespawn(targets: { string | entityRef }): number
```

Despawn many entities in one call. Locked or unresolvable entities
are skipped. Returns the count queued for despawn.

**Parameters**

- `targets` `{ string | entityRef }` — Array of entity ids, entity proxies, or display names (e.g.
the return of `entity.batchSpawn` / `entity.findAll`).

```lua
local n = entity.batchDespawn(ids)
```

## modules/entity/batchProxy {#modules-entity-batchproxy}

```lua
batchProxy(targets: { string | entityRef }): { entityRef? }
```

Resolve an array of entity ids to proxies in one call. Each output
slot is the standard `entity(id)` proxy; ids missing from the frame
cache surface as nil at that index. Use when iterating over a snapshot
of entities so per-id lookups don't dominate the hot path.

**Parameters**

- `targets` `{ string | entityRef }` — Array of entity ids or entity proxies.

```lua
local proxies = entity.batchProxy(ids)
```

## modules/entity/batchRead {#modules-entity-batchread}

```lua
batchRead(target: { string | entityRef } | binding, component: string?, field: string?, sink: buffer?): { any? } | number
```

Read a component-field across many entities in one call.
Polymorphic on the shape of `target` and `sink`:
- `entity.batchRead(ids)` / `(ids, comp)` / `(ids, comp, field)` —
returns one value per entity (a whole snapshot, one component table,
or one field value). Missing entities/components/fields surface as
nil at that slot.
- `entity.batchRead(binding, comp, field, buffer)` — reads each
entity's field directly into a typed CPU substrate buffer
(`substrate.createBuffer({type="vec3"})`, etc.) with no per-entity
Lua table allocation. Returns the count of successful reads.
`target` accepts an entity-id array or a `ecs.bindEntities(ids)`
handle. Buffer sinks require a binding — the typed kernel is
binding-only.

**Parameters**

- `target` `{ string | entityRef } | binding` — Array of entity ids or entity proxies, or a binding handle
from `ecs.bindEntities(ids)`.
- `component` `string?` _(optional)_ — Component type name (e.g. "Transform").
- `field` `string?` _(optional)_ — Field name (e.g. "position").
- `sink` `buffer?` _(optional)_ — Typed CPU buffer from `substrate.createBuffer({...})` to memcpy
field values into. Required when `target` is a binding.

```lua
local snapshot = entity.batchRead(ids)
local positions = entity.batchRead(ids, "Transform", "position")
```

## modules/entity/batchReadToBuffer {#modules-entity-batchreadtobuffer}

```lua
batchReadToBuffer(binding: number, component: string, field: string, buffer: number): number
```

FFI primitive backing `entity.batchRead(binding, ..., buffer)`.
Prefer the unified `entity.batchRead`, which auto-dispatches by
argument shape. Reads each entity's component field directly into a
typed CPU substrate buffer, with no per-entity Lua table allocation.
After the call, read the buffer via `buf:read(0, count*stride)`.

**Parameters**

- `binding` `number` — Binding id from `ecs.bindEntities(ids).id`.
- `component` `string` — Component type name.
- `field` `string` — Field name to read.
- `buffer` `number` — Destination buffer id (must be the matching type).

```lua
entity.batchReadToBuffer(binding.id, "Transform", "position", buf.id)
```

## modules/entity/batchSpawn {#modules-entity-batchspawn}

```lua
batchSpawn(count: number, name_prefix: string?): { string }
```

Spawn `count` entities in one call. Returns an array of the new
entity ids in spawn order. Each entity is given a display name of
`<name_prefix><i>` (or `entity<i>` if the prefix is omitted). Prefer this
over looping `entity.spawn` when creating large entity counts.

**Parameters**

- `count` `number` — How many entities to spawn (capped at 1,000,000).
- `name_prefix` `string?` _(optional)_ — Display-name prefix appended with the 1-based index.
Defaults to "entity".

```lua
local ids = entity.batchSpawn(100, "grass_")
```

## modules/entity/batchWrite {#modules-entity-batchwrite}

```lua
batchWrite(target: { string | entityRef } | binding, component: string, field: string, source: { any? } | buffer): number
```

Write a single component-field across many entities in one call.
Polymorphic on the shape of `target` and `source`:
- `entity.batchWrite(ids, comp, field, values)` — per-call entity-id
resolution; `values` is an array the same length as `ids` (nil slots
are skipped). Use for one-shot writes.
- `entity.batchWrite(binding, comp, field, values)` — binding handle
from `ecs.bindEntities(ids)`; skips per-call id resolution. Use for
per-frame writes against a stable entity set.
- `entity.batchWrite(binding, comp, field, buffer)` — typed CPU buffer
source (`substrate.createBuffer({type="vec3"})`, etc.), with no
per-entity table allocation.
Returns the count of successful writes. Buffer sources require a
binding — the typed kernel is binding-only.

**Parameters**

- `target` `{ string | entityRef } | binding` — Array of entity ids or entity proxies, or a binding handle
from `ecs.bindEntities(ids)`.
- `component` `string` — Component type name.
- `field` `string` — Field name to write.
- `source` `{ any? } | buffer` — Per-entity values array (nil entries are skipped), or a typed
CPU buffer from `substrate.createBuffer({...})`. A buffer source
requires a binding `target`.

```lua
entity.batchWrite(ids, "Transform", "position", positions)
```

## modules/entity/batchWriteBound {#modules-entity-batchwritebound}

```lua
batchWriteBound(binding: number, component: string, field: string, values: { any? }): number
```

FFI primitive backing `entity.batchWrite(binding, ...)` with a
per-entity values table. Prefer the unified `entity.batchWrite`, which
auto-dispatches by argument shape; this entry stays for power users /
debug code that wants to skip dispatch overhead.

**Parameters**

- `binding` `number` — Binding id from `ecs.bindEntities(ids).id`.
- `component` `string` — Component type name.
- `field` `string` — Field name to write.
- `values` `{ any? }` — Per-entity source values (nil = skip). Length must match the
binding's entity count.

```lua
entity.batchWriteBound(binding.id, "Transform", "position", values)
```

## modules/entity/batchWriteFromBuffer {#modules-entity-batchwritefrombuffer}

```lua
batchWriteFromBuffer(binding: number, component: string, field: string, buffer: number): number
```

FFI primitive backing `entity.batchWrite(binding, ..., buffer)`.
Prefer the unified `entity.batchWrite`, which auto-dispatches by
argument shape. Caller fills a typed substrate buffer
(`substrate.createBuffer({type="vec3"})`) once via `buf:write(...)`,
then this memcpys 12 (vec3) or 16 (quat) bytes per entity into the
component field. Buffer count and binding count should match — a
mismatch processes the smaller of the two.

**Parameters**

- `binding` `number` — Binding id from `ecs.bindEntities(ids).id`.
- `component` `string` — Component type name.
- `field` `string` — Field name to write.
- `buffer` `number` — Buffer id from `substrate.createBuffer({type="vec3", len=N}).id`.

```lua
entity.batchWriteFromBuffer(binding.id, "Transform", "position", buf.id)
```

## modules/entity/capture {#modules-entity-capture}

```lua
capture(builder: () -> ()): ({ string }, any?, { string })
```

Run `builder` inside an entity capture scope and return the entity ids
it minted, in creation order, the error it raised (if any), and the ids
among them that a component the builder attached minted in its own
lifecycle. Every id minted while the builder runs is recorded — through
`entity.spawn`, `entity.spawnSynced`, `entity.batchSpawn`, and
`entity.instantiate` alike. Scopes nest: an id minted inside an inner
capture is recorded by that capture AND every enclosing one — the
innermost capture answers, so a nested build shapes its own entities,
not the ones around it. A builder that raises still returns its ids, so
the caller can despawn what a failed build left behind; the scope closes
either way and never outlives this call. While the builder runs, an
operation whose result cannot be composed into a record is refused
rather than applied, and so is any operation aimed at an entity the
builder did not mint — a builder that returned while something it did
was refused comes back with an error naming every refusal.

**Parameters**

- `builder` `() -> ()` — Function run inside the scope; the entities it creates are
what comes back.

```lua
local ids, err, reproduced = entity.capture(function() entity.spawn("chair") end)
```

## modules/entity/despawn {#modules-entity-despawn}

```lua
despawn(target: string | entityRef)
```

Despawn an entity and all its components. Pass an id string or an
entity proxy to despawn that ONE entity. Pass a name to despawn EVERY
entity with that name — names are not unique, so a name argument
despawns all matches, not one arbitrary match. A despawned id becomes
invalid after this call. Raises if no entity matches; for a bulk name
despawn, locked entities are skipped with a logged summary and only
raise if every match is locked.

**Parameters**

- `target` `string | entityRef` — Entity id, name, or entity proxy. A name despawns all entities
sharing that name.

```lua
entity.despawn(id)
entity.despawn("Enemy") -- despawns every entity named "Enemy"
```

## modules/entity/duplicate {#modules-entity-duplicate}

```lua
duplicate(sourceId: string | entityRef, name: string?, opts: table?): string?
```

Duplicate an entity with all its components (transform, script
components, attributes, visuals, material) and its descendants. Returns
the new entity's id, or nil when `sourceId` names no live entity.
Descendants marked temporary are left out of the copy: they are
scaffolding whatever spawned them re-creates, so a component that
regenerates its own children rebuilds them on the copy rather than the
copy carrying a second set. `includeTemporary` copies them too, for the
hierarchy that IS the temporary thing.

**Parameters**

- `sourceId` `string | entityRef` — Entity id or entity proxy of the source entity to clone.
- `name` `string?` _(optional)_ — Display name for the copy (defaults to source name + " (copy)").
- `opts` `table?` _(optional)_ — `{ includeTemporary?: boolean, name?: string }` — `name` is the
same field the `name` argument sets, and wins when both are given.

```lua
local copyId = entity.duplicate(id); if copyId then entity(copyId).position = { 1, 0, 0 } end
local copyId = entity.duplicate(id, "Turret", { includeTemporary = true })
```

## modules/entity/exists {#modules-entity-exists}

```lua
exists(idOrProxy: string | entityRef): boolean
```

Check whether an entity currently exists in the scene. Accepts an
entity-id string or an entity proxy, matched by entity id — so it agrees
exactly with `entity(id)`. A name is a different kind of identifier: a
string that misses as an id but names a live entity raises rather than
answering false, since false there is indistinguishable from absence.
Check by name with `entity.find(name) ~= nil`.

**Parameters**

- `idOrProxy` `string | entityRef` — Entity id or an entity proxy.

```lua
if entity.exists(id) then ... end
```

## modules/entity/find {#modules-entity-find}

```lua
find(nameOrGlob: string): entityRef?
```

Find the first entity matching `nameOrGlob`. A plain string matches
an exact id or Name component; a string containing `*` (any run of
characters) or `?` (any single character) matches Names as a glob, so
`entity.find("enemy_*")` is the first entity whose name starts with
`enemy_`. A glob addresses Names only, never ids. Same-frame pending
spawns are searched too, and anything queued for despawn in the same
frame is skipped. Names are NOT unique — use `entity.findAll` when every
match matters.

**Parameters**

- `nameOrGlob` `string` — Exact entity Name or id, or a `*` / `?` glob over Names.

```lua
local e = entity.find("enemy_*")
```

## modules/entity/findAll {#modules-entity-findall}

```lua
findAll(nameOrGlob: string?): { entityRef }
```

Enumerate entity proxies. With a `nameOrGlob` argument, returns every
entity whose Name component or id matches (names are not unique): a
plain string matches exactly, while a `*` / `?` glob matches Names. With
no argument, returns every entity in the current snapshot —
`findAll("")` is the exact-match filter for the empty name, which
normally matches nothing. Same-frame pending spawns are included and
same-frame despawns filtered out. Elements are entity proxies, not id
strings — for ids, wrap the result: `entity.ids(entity.findAll(...))`.

**Parameters**

- `nameOrGlob` `string?` _(optional)_ — Exact entity Name or id to filter by, or a `*` / `?` glob
over Names. Omit to enumerate every entity.

```lua
for _, e in entity.findAll("enemy_*") do e:despawn() end
```

## modules/entity/getChildren {#modules-entity-getchildren}

```lua
getChildren(id: string | entityRef): { entityRef }
```

Get an array of the direct children as entity proxies. Each element
carries `.name`, `.id`, `.position`, `.component`, and the rest of the
per-entity surface — the same shape `entity.findAll` returns.

**Parameters**

- `id` `string | entityRef` — Entity id or entity proxy.

```lua
for _, c in entity.getChildren(id) do c.internal = true end
```

## modules/entity/getDescendants {#modules-entity-getdescendants}

```lua
getDescendants(id: string | entityRef): { entityRef }
```

Get every descendant (children, grandchildren, and deeper) of the
given entity as entity proxies in breadth-first order, excluding the
entity itself. Resolves the whole subtree in one linear pass over the
entity set, so a large subtree costs proportionally to the entity count
rather than to the subtree size times the entity count.

**Parameters**

- `id` `string | entityRef` — Entity id or entity proxy.

```lua
local all = entity.getDescendants(id)
```

## modules/entity/getParent {#modules-entity-getparent}

```lua
getParent(id: string | entityRef): entityRef?
```

Get the parent entity proxy, or nil if the entity is a root entity.
The returned proxy carries `.name`, `.id`, `.position`, `.component`,
and the rest of the per-entity surface — the same shape `entity.find`
returns.

**Parameters**

- `id` `string | entityRef` — Entity id or entity proxy.

```lua
local p = entity.getParent(id)
```

## modules/entity/instantiate {#modules-entity-instantiate}

```lua
instantiate(handle: number, count: number, fn: ((number) -> EntityInstantiateOverrides?)?): { string }
```

Spawn `count` instances of a template registered with
`entity.template`. Each instance gets a fresh entity id; the optional
`fn(i)` callback runs per instance (`i` in 1..=count) and may return an
overrides table. Override keys: `name`, `position`, `rotation`, `scale`,
`parent`, `temporary` / `active` / `internal`, `attributes`, `components`
(script components, merged over the template body's data for that type
— a type the template lacks is added fresh), and `ecs` (native
components, merged the same way). Each override supersedes the
template's shared config for that instance. The whole batch crosses in
one call and lands as a single deferred mutation the engine expands
into bulk work — per-instance cost drops from a full round trip to one
callback plus one mutation. Inside `queue()` the batch is deferred onto
the cross-frame ring; outside, it lands in the next frame's drain.
Returns the array of newly-minted entity ids in spawn order.

**Parameters**

- `handle` `number` — Template handle from `entity.template`.
- `count` `number` — Number of instances to spawn (capped at 1,000,000).
- `fn` `((number) -> EntityInstantiateOverrides?)?` _(optional)_ — Per-instance override callback `(i) -> table?`.

```lua
local ids = entity.instantiate(h, 50, function(i) return { position = { i, 0, 0 } } end)
```

## modules/entity/spawn {#modules-entity-spawn}

```lua
spawn(nameOrOpts: (string | SpawnOpts)?, opts: SpawnOpts?): entityRef
```

Spawn a new entity and return its PROXY (the same value entity(id)
yields) — act on it immediately (`entity.spawn(name).component.add(...)`,
`.localPosition = ...`) with no second `entity(id)` round trip. The proxy
still exposes `.id` for the rare site that needs the raw string. An
entity with no components carries only a Transform and is invisible;
pass `components` to give it the components that make it visible in the
same call — `entity.spawn { name = "crate", components = { Model = {
model = "cube" } } }` — or add them afterwards through the returned
proxy. Mirrors `entity.find` / `entity.findAll`, which also return
proxies. The options table can be passed on its own with the name inside
it — `entity.spawn { name = "turret", position = { 1, 2, 3 } }` is the
same call as `entity.spawn("turret", { position = { 1, 2, 3 } })`.

**Parameters**

- `nameOrOpts` `(string | SpawnOpts)?` _(optional)_ — Display name for the entity, or the options table itself.
- `opts` `SpawnOpts?` _(optional)_ — Options: `components` = component types to attach to the new
entity, keyed by type name with each value the component's init table
(attached in sorted type order; a failing add raises), `internal` = take
the entity out of the default entity listings (it still renders —
`entity(id):hide()` stops the draw), `parent` = parent entity id or
proxy, `temporary` = skip this entity (and descendants) from
scene/world saves, `position` / `rotation` / `scale` = place the
entity's Transform at spawn, `id` = restore a previously-assigned entity
id (scene_loader use; leave unset for a normal spawn). An unrecognised
key is rejected loudly.

```lua
local e = entity.spawn("crate", { components = { Model = { model = "cube" } } })
local e = entity.spawn { name = "turret", position = { 1, 2, 3 } }
```

## modules/entity/spawnSynced {#modules-entity-spawnsynced}

```lua
spawnSynced(nameOrOpts: (string | SpawnOpts)?, opts: SpawnOpts?): entityRef
```

Spawn an entity already flagged multiplayer-synced at the root — the
explicit form of `entity.spawn` for SHARED, host-authoritative content.
The entity's existence broadcasts to every peer; joiners receive it from
the relay snapshot instead of spawning their own copy. Use this (NOT
`entity.spawn`) for anything that must be the SAME object on all
clients: enemies, pickups, projectiles, dynamic world props. Call it
ONLY where exactly one client runs the code — a scene's `onHostLoad`
(host-only) phase, or behind `multiplayer.isHost()`. Calling it in
all-client code makes every client spawn+sync its own copy — the
double-spawn "explosion". Equivalent to
`entity.spawn(name, { synced = true })`; identical in every other
respect.

**Parameters**

- `nameOrOpts` `(string | SpawnOpts)?` _(optional)_ — Display name for the entity, or the options table itself.
- `opts` `SpawnOpts?` _(optional)_ — Same options as `entity.spawn` (`synced` is already implied).

```lua
if multiplayer.isHost() then entity.spawnSynced("Goblin") end
```

## modules/entity/template {#modules-entity-template}

```lua
template(def: EntityTemplateDef): number
```

Construct a reusable spawn template. Captures a shared entity config
ONCE and returns a stable handle for `entity.instantiate(handle, count,
fn?)` — one call per batch instead of one per entity. `def` keys:
`components` (script components, `{ [type] = init-data }`), `ecs`
(array of native `ecs.X{...}` components), `temporary` (instances skip
scene/world saves), `active` (spawn state), `internal` (instances are
taken out of the default entity listings; they still render),
`attributes` (`{ key = value }` applied to every instance). Every value
is a shared default; a per-instance `entity.instantiate` override
supersedes it. The template body is captured by value — later edits to
the source table do not affect templates already created.

**Parameters**

- `def` `EntityTemplateDef` — Template definition: `components` / `ecs` / `temporary` /
`active` / `internal` / `attributes`. Per-instance `name` / `position` /
`rotation` / `scale` / `parent` and any override go through the
`instantiate` callback.

```lua
local h = entity.template({ components = { Model = { model = "cube" } } })
```

## modules/entity/tree {#modules-entity-tree}

```lua
tree(opts: { [string]: any }?): { [string]: any }
```

A windowed, lean view over the scene's entity tree, in one crossing.
Rows carry id, name, parentId, depth, childCount, active, sceneLayer and
componentNames — names only, never component values — so the call costs
the rows it answers with rather than the size of the scene. Entities group
under scene layers, per-layer roots and children name-sorted; internal
entities and their subtrees stay out. `expanded` names the ids whose
children unfold, and a collapsed node still reports its `childCount`;
`filter` keeps the rows whose name or id contains the needle plus every
ancestor on a path to one, auto-unfolded, with the actual matches flagged
`matched`. `offset` / `limit` window the flattened rows, `layer` scopes the
window and its `total` to one layer while `layers` still reports every
layer's row count, and `revision` echoes
`getEntitiesRevision("structure")`, which moves only on structural change.

**Parameters**

- `opts` `{ [string]: any }?` _(optional)_ — `{ layer?, expanded?, filter?, offset?, limit? }`.

```lua
local view = entity.tree({ filter = "crate", limit = 50 })
```
