Log inGet started

entity

Updated October 20, 2026

Entity spawn / despawn / query / hierarchy surface. Calling the table itself — entity(idOrProxy) — resolves an id or proxy to its live entityRef proxy, and iterating it (for name, member in entity do) lists its members.

require("@builtin::modules.api.engine.entity") -- entity (also available as global 'entity')

The entity namespace: 142 functions.

entity/proxy/AddDebris

entity(id).AddDebris(self, lifetime: number?) -> ()

Despawn this entity after a lifetime, defaulting to 10 seconds.

Parameters

  • self
  • lifetime number (optional)

Returns ()

entity/proxy/active

entity(id).active -> boolean

Whether this entity is active.

Returns boolean

entity/proxy/activeInHierarchy

entity(id).activeInHierarchy -> boolean

Whether this entity is active via its ancestors.

Returns boolean

entity/proxy/attribute

entity(id).attribute -> entityAttributes

Free-form key/value attributes stored on this entity.

Returns entityAttributes

entity/proxy/attribute/get

entity(id).attribute.get(key: string) -> any?

The value stored under a key, or nil when the entity carries none.

Parameters

  • key string

Returns any?

entity/proxy/attribute/list

entity(id).attribute.list() -> { string }

Every attribute key this entity carries.

Returns { string }

entity/proxy/attribute/remove

entity(id).attribute.remove(key: string) -> ()

Drop the value stored under a key.

Parameters

  • key string

Returns ()

entity/proxy/attribute/set

entity(id).attribute.set(key: string, value: any) -> ()

Store a value under a key on this entity.

Parameters

  • key string
  • value any

Returns ()

entity/proxy/bounds

entity(id).bounds(self) -> { min: any, max: any, center: any, size: any }?

This entity's world-axis bounding box.

Parameters

  • self

Returns { min: any, max: any, center: any, size: any }?

entity(id).bundleLink -> any?

This entity's link back to its bundle.

Returns any?

entity/proxy/bundleProvenance

entity(id).bundleProvenance -> { [string]: any }?

Which bundle produced this entity.

Returns { [string]: any }?

entity/proxy/component

entity(id).component -> entityComponents

Script components attached to this entity.

Returns entityComponents

entity/proxy/component/add

entity(id).component.add(type: AssetRef | string, data: table?) -> table?

Attach a component, answering its live public proxy — nil when the add was deferred or skipped as a duplicate.

Parameters

  • type AssetRef | string
  • data table (optional)

Returns table?

entity/proxy/component/addSynced

entity(id).component.addSynced(type: AssetRef | string, data: table?) -> table?

Attach a component and replicate it to every peer.

Parameters

  • type AssetRef | string
  • data table (optional)

Returns table?

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

entity/proxy/component/clear

entity(id).component.clear() -> ()

Detach every component this entity carries.

Returns ()

entity/proxy/component/create

entity(id).component.create(type: AssetRef | string, data: table?) -> table?

Attach a fresh instance even where one of the type is already present.

Parameters

  • type AssetRef | string
  • data table (optional)

Returns table?

entity/proxy/component/get

entity(id).component.get(type: AssetRef | string, instanceName: string?) -> table?

This entity's live component proxy of the type, or nil when it carries none.

Parameters

  • type AssetRef | string
  • instanceName string (optional)

Returns table?

entity/proxy/component/getAll

entity(id).component.getAll(type: (AssetRef | string)?) -> table

Every component on this entity, or every instance of one type.

Parameters

  • type (AssetRef | string) (optional)

Returns table

entity/proxy/component/getFromChildren

entity(id).component.getFromChildren(type: AssetRef | string) -> table?

The first descendant's component of the type, or nil when no descendant carries one.

Parameters

  • type AssetRef | string

Returns table?

entity/proxy/component/getFromParent

entity(id).component.getFromParent(type: AssetRef | string) -> table?

The nearest ancestor's component of the type, or nil when no ancestor carries one.

Parameters

  • type AssetRef | string

Returns table?

entity/proxy/component/has

entity(id).component.has(type: AssetRef | string) -> boolean

Whether this entity carries a component of the type.

Parameters

  • type AssetRef | string

Returns boolean

entity/proxy/component/list

entity(id).component.list() -> { string }

The identity string of every component type this entity carries, each of which get takes.

Returns { string }

for _, identity in ipairs(entity(id).component.list()) do
    local component = entity(id).component.get(identity)
    component.enabled = false
end

entity/proxy/component/lock

entity(id).component.lock(names: { string } | string, flags: { [string]: any }?) -> ()

Lock named components on this entity against removal or writes.

Parameters

  • names { string } | string
  • flags { [string]: any } (optional)

Returns ()

entity/proxy/component/locks

entity(id).component.locks() -> { [string]: any }

The lock state of this entity's components.

Returns { [string]: any }

entity/proxy/component/pending

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

One record per component whose awake() is held back waiting for an asset field to register, naming the component and the fields it waits on; every such component when no type is given.

Parameters

  • type (AssetRef | string) (optional)

Returns { { type: string, instance: string?, instanceId: string, fields: { { name: string, spec: string, value: string } } } }

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

entity/proxy/component/remove

entity(id).component.remove(type: AssetRef | string, instanceName: string?) -> ()

Detach a component, by type and optionally by instance name.

Parameters

  • type AssetRef | string
  • instanceName string (optional)

Returns ()

entity/proxy/component/setEnabled

entity(id).component.setEnabled(type: AssetRef | string, enabled: boolean) -> ()

Enable or disable a component without detaching it.

Parameters

  • type AssetRef | string
  • enabled boolean

Returns ()

entity/proxy/component/unlock

entity(id).component.unlock(names: ({ string } | string)?, flags: { [string]: any }?) -> ()

Release locks this entity's components hold; every one of them when no name is given.

Parameters

  • names ({ string } | string) (optional)
  • flags { [string]: any } (optional)

Returns ()

entity/proxy/despawn

entity(id).despawn(self) -> ()

Remove this entity from the scene.

Parameters

  • self

Returns ()

entity/proxy/destroy

entity(id).destroy(self) -> ()

Remove this entity from the scene.

Parameters

  • self

Returns ()

entity/proxy/duplicate

entity(id).duplicate(self, name: string?, opts: { [string]: any }?) -> string?

Copy this entity.

Parameters

  • self
  • name string (optional)
  • opts { [string]: any } (optional)

Returns string?

entity/proxy/eulerAngles

entity(id).eulerAngles -> { [string]: any }

World rotation, as euler degrees.

Returns { [string]: any }

entity/proxy/exists

entity(id).exists -> boolean

Whether this entity is still in the world. Never raises — a proxy over an id nothing carries reads false.

Returns boolean

entity/proxy/getChildren

entity(id).getChildren(self) -> { entityRef }

This entity's direct children.

Parameters

  • self

Returns { entityRef }

entity/proxy/getDescendants

entity(id).getDescendants(self) -> { entityRef }

Every entity below this one.

Parameters

  • self

Returns { entityRef }

entity/proxy/getParent

entity(id).getParent(self) -> entityRef?

This entity's parent, or nil.

Parameters

  • self

Returns entityRef?

entity/proxy/hide

entity(id).hide(self) -> ()

Stop drawing this entity.

Parameters

  • self

Returns ()

entity/proxy/hierarchyBounds

entity(id).hierarchyBounds(self) -> { min: any, max: any, center: any, size: any }?

The world-axis box around this entity's subtree.

Parameters

  • self

Returns { min: any, max: any, center: any, size: any }?

entity/proxy/id

entity(id).id -> string

This entity's id string.

Returns string

entity/proxy/internal

entity(id).internal -> boolean

Whether default listings skip this entity.

Returns boolean

entity/proxy/isLocal

entity(id).isLocal(self) -> boolean

Whether this peer owns this entity.

Parameters

  • self

Returns boolean

entity/proxy/localEulerAngles

entity(id).localEulerAngles -> { [string]: any }

Parent-relative euler degrees.

Returns { [string]: any }

entity/proxy/localPosition

entity(id).localPosition -> { [string]: any }

Parent-relative position.

Returns { [string]: any }

entity/proxy/localRotation

entity(id).localRotation -> { [string]: any }

Parent-relative rotation quaternion.

Returns { [string]: any }

entity/proxy/localScale

entity(id).localScale -> { [string]: any }

Parent-relative scale.

Returns { [string]: any }

entity/proxy/lock

entity(id).lock(self) -> ()

Set this entity's destroy lock.

Parameters

  • self

Returns ()

entity/proxy/lock_components

entity(id).lock_components(self, names: { string }, flags: { [string]: any }?) -> ()

Lock named components on this entity.

Parameters

  • self
  • names { string }
  • flags { [string]: any } (optional)

Returns ()

entity/proxy/locked

entity(id).locked -> boolean

Whether this entity carries a destroy lock.

Returns boolean

entity/proxy/locks

entity(id).locks(self) -> { destroy: boolean, components: { [string]: any } }

This entity's current lock state.

Parameters

  • self

Returns { destroy: boolean, components: { [string]: any } }

entity/proxy/lookAt

entity(id).lookAt(self, target: any, up: any?) -> (boolean, string?)

Aim this entity at a world point: writes the world rotation whose forward points at the target. Takes three coordinates, one point table or vec handle, or an entity by id, name or proxy. The optional up decides the roll around the aim.

Parameters

  • self
  • target any
  • up any (optional)

Returns (boolean, string?)

entity/proxy/lossyScale

entity(id).lossyScale -> { [string]: any }

World scale, as the hierarchy leaves it.

Returns { [string]: any }

entity/proxy/name

entity(id).name -> string

This entity's Name component value.

Returns string

entity/proxy/networkScope

entity(id).networkScope -> string

How far this entity replicates.

Returns string

entity/proxy/orientedBounds

entity(id).orientedBounds(self) -> { min: any, max: any, center: any, size: any }?

The subtree's extents in this entity's own frame.

Parameters

  • self

Returns { min: any, max: any, center: any, size: any }?

entity/proxy/origin

entity(id).origin -> string

What produced this entity.

Returns string

entity/proxy/owner

entity(id).owner(self) -> number

The peer owning this entity.

Parameters

  • self

Returns number

entity/proxy/participation

entity(id).participation -> string

This entity's runtime participation.

Returns string

entity/proxy/position

entity(id).position -> { [string]: any }

World position.

Returns { [string]: any }

entity/proxy/rename

entity(id).rename(self, newName: string) -> ()

Change this entity's Name component.

Parameters

  • self
  • newName string

Returns ()

entity/proxy/renderLayer

entity(id).renderLayer -> string

The render layers this entity is on, space-separated.

Returns string

entity/proxy/rotation

entity(id).rotation -> { [string]: any }

World rotation, as a quaternion.

Returns { [string]: any }

entity/proxy/saveAs

entity(id).saveAs(self, name: string) -> any

Save this entity as a content asset.

Parameters

  • self
  • name string

Returns any

entity/proxy/setActive

entity(id).setActive(self, active: boolean) -> ()

Set whether this entity is active.

Parameters

  • self
  • active boolean

Returns ()

entity/proxy/setInternal

entity(id).setInternal(self, internal: boolean) -> ()

Set whether default listings skip this entity.

Parameters

  • self
  • internal boolean

Returns ()

entity/proxy/setNetworkScope

entity(id).setNetworkScope(self, scope: string) -> ()

Set how far this entity replicates.

Parameters

  • self
  • scope string

Returns ()

entity/proxy/setParent

entity(id).setParent(self, parentId: string, opts: { [string]: any }?) -> ()

Reparent this entity.

Parameters

  • self
  • parentId string
  • opts { [string]: any } (optional)

Returns ()

entity/proxy/setParticipation

entity(id).setParticipation(self, mode: string) -> ()

Set this entity's runtime participation.

Parameters

  • self
  • mode string

Returns ()

entity/proxy/setRenderLayer

entity(id).setRenderLayer(self, names: any) -> ()

Set this entity's render layer.

Parameters

  • self
  • names any

Returns ()

entity/proxy/setRenderLayerTree

entity(id).setRenderLayerTree(self, names: any) -> ()

Set render layer for this entity and its descendants.

Parameters

  • self
  • names any

Returns ()

entity/proxy/setRenderLayers

entity(id).setRenderLayers(self, names: any) -> ()

Set this entity's render layers.

Parameters

  • self
  • names any

Returns ()

entity/proxy/setSessionScoped

entity(id).setSessionScoped(self, scoped: boolean) -> ()

Scope this entity to the session.

Parameters

  • self
  • scoped boolean

Returns ()

entity/proxy/setSynced

entity(id).setSynced(self, synced: boolean) -> ()

Set whether this entity replicates.

Parameters

  • self
  • synced boolean

Returns ()

entity/proxy/setSyncedTree

entity(id).setSyncedTree(self, synced: boolean) -> ()

Set replication for this entity and its descendants.

Parameters

  • self
  • synced boolean

Returns ()

entity/proxy/setTemporary

entity(id).setTemporary(self, temporary: boolean) -> ()

Set whether saves skip this entity.

Parameters

  • self
  • temporary boolean

Returns ()

entity/proxy/show

entity(id).show(self) -> ()

Resume drawing this entity.

Parameters

  • self

Returns ()

entity/proxy/synced

entity(id).synced -> boolean

Whether this entity replicates.

Returns boolean

entity/proxy/temporary

entity(id).temporary -> boolean

Whether saves skip this entity.

Returns boolean

entity/proxy/temporaryInHierarchy

entity(id).temporaryInHierarchy -> boolean

Whether saves skip this entity via an ancestor.

Returns boolean

entity/proxy/transform

entity(id).transform -> { [string]: any }

This entity's transform.

Returns { [string]: any }

entity/proxy/unlock

entity(id).unlock(self) -> ()

Clear every lock on this entity.

Parameters

  • self

Returns ()

entity/proxy/unparent

entity(id).unparent(self, opts: { [string]: any }?) -> ()

Detach this entity from its parent.

Parameters

  • self
  • opts { [string]: any } (optional)

Returns ()

entity/proxy/worldMatrix

entity(id).worldMatrix -> { number }

This entity's world matrix.

Returns { number }

globals/entity/__batchReadIds

entity.__batchReadIds()

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

globals/entity/__batchReadToBuffer

entity.__batchReadToBuffer()

Read a component field into a typed substrate buffer.

globals/entity/__batchWriteBound

entity.__batchWriteBound()

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

globals/entity/__batchWriteFromBuffer

entity.__batchWriteFromBuffer()

Write a component field from a typed substrate buffer.

globals/entity/__batchWriteIds

entity.__batchWriteIds()

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

globals/entity/batchAddComponent

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: one table applied identically to every entity — the same shape the second arg to entity(id).component.add(type, data) takes — or an array holding one such table per target, in the targets' order, so each entity is added with its own values.

Returns number — How many entities had the component added.

local n = entity.batchAddComponent(ids, "Debris", { lifetime = 5 })
entity.batchAddComponent({ a, b }, "Debris", { { lifetime = 2 }, { lifetime = 7 } })

globals/entity/batchDespawn

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.

local n = entity.batchDespawn(ids)

globals/entity/batchProxy

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).

local proxies = entity.batchProxy(ids)

globals/entity/batchRead

entity.batchRead(target: { string | entityRef } | binding, component: string?, field: string?, sink: TypedBuffer?) -> { 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. Transform.worldMatrix reads each entity's world matrix — its own transform folded with its ancestors', the one entity(id).worldMatrix() answers — as 16 column-major numbers: into a mat4 buffer, or as an array of 16 per entity. It is read, never written. 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 TypedBuffer (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.

local snapshot = entity.batchRead(ids)
local positions = entity.batchRead(ids, "Transform", "position")
entity.batchRead(ecs.bindEntities(ids), "Transform", "worldMatrix", matrices)

globals/entity/batchReadToBuffer

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, live while its handle is held.
  • 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.

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

globals/entity/batchSpawn

entity.batchSpawn(count: number, name_prefix: string?, opts: { temporary: boolean?, parent: (string | entityRef)? }?) -> { 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".
  • opts { temporary: boolean?, parent: (string | entityRef)? } (optional) — { temporary?, parent? } — a temporary batch is one the scene does not save, the same thing entity.spawn({ temporary = true }) asks for. The component goes on in one crossing, so a batch of a hundred thousand costs what one does. A batch spawned inside an entrypoint that is itself temporary is temporary whatever this says. parent — an id or an entity — makes every entity in the batch its child as it is made, so the batch stands, moves and goes with it.

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

local ids = entity.batchSpawn(100, "grass_")
local particles = entity.batchSpawn(5000, "spark_", { temporary = true })
local parts = entity.batchSpawn(8, "part_", { temporary = true, parent = owner })

globals/entity/batchWrite

entity.batchWrite(target: { string | entityRef } | binding, component: string, field: string, source: { any? } | TypedBuffer) -> 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? } | TypedBuffer — 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.

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

globals/entity/batchWriteBound

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, live while its handle is held.
  • 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.

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

globals/entity/batchWriteFromBuffer

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, live while its handle is held.
  • 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.

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

globals/entity/capture

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

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 }, { any }) — Entity ids minted while the builder ran, in creation order; the error it raised (or the refusals it hit), or nil; the ids a component the builder attached minted in its own lifecycle; and the refusals the scope issued, each with message, at and source.

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

globals/entity/capturing

entity.capturing() -> boolean

Whether an entity.capture builder (a scene build) is running on the calling thread. A component a build adds takes the world's Preset Manager rows in every mode, so a scene built in a play session holds what the same build holds in edit mode.

Returns boolean — true inside a capture scope.

if entity.capturing() then return end

globals/entity/despawn

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.
entity.despawn(id)
entity.despawn("Enemy") -- despawns every entity named "Enemy"

globals/entity/duplicate

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, descendants?: boolean, components?: table, name?: string }: descendants = false copies the source entity alone, leaving its children to it; components lays fields over the copy's script components by type ({ SoftBody = { mass = 2 } }), merged over the state copied from the source; 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.

local copyId = entity.duplicate(id); if copyId then entity(copyId).position = { 1, 0, 0 } end
local copyId = entity.duplicate(id, "Turret", { includeTemporary = true })
local lone = entity.duplicate(id, nil, { descendants = false, components = { SoftBody = { mass = 2 } } })

globals/entity/exists

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.

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

globals/entity/find

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; find is the first entity findAll lists. Inside a build (a scene's content(), a module's builder), a name the build's own entities carry resolves to those first, so entity.spawn("door") followed by entity.find("door") is the door the build just made even when the world around it holds another door.

Parameters

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

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

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

globals/entity/findAll

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(...)). Inside a build (a scene's content(), a module's builder), the entities the build made come first and the world's same-named entities follow.

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.

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

globals/entity/getChildren

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. A string that names a live entity rather than identifying it raises, naming entity.find(name), the lookup that takes a name.

Parameters

  • id string | entityRef — Entity id or entity proxy.

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

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

globals/entity/getDescendants

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. A string that names a live entity rather than identifying it raises, naming entity.find(name), the lookup that takes a name.

Parameters

  • id string | entityRef — Entity id or entity proxy.

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

local all = entity.getDescendants(id)

globals/entity/getParent

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. A string that names a live entity rather than identifying it raises, naming entity.find(name), the lookup that takes a name: answering it as an id would read the same as a root entity.

Parameters

  • id string | entityRef — Entity id or entity proxy.

Returns entityRef? — Parent entity proxy, or nil.

local p = entity.getParent(id)

globals/entity/hierarchy

entity.hierarchy()

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

globals/entity/hierarchy/swap

entity.hierarchy.swap(entityId: string, assetPath: string, opts: { [string]: any }?) -> (string?, string?)

Replace a blockout entity with a generated or imported asset, fitting the asset to the source's bounds. Answers the new entity id, or nil and an error string.

Parameters

  • entityId string
  • assetPath string
  • opts { [string]: any } (optional)

Returns (string?, string?)

globals/entity/ids

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.

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

globals/entity/instantiate

entity.instantiate(handle: EntityTemplate, 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 EntityTemplate — 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.

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

globals/entity/proxies

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

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.

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

globals/entity/sceneWalks

entity.sceneWalks() -> number

How many whole-scene walks entity.findAll has made since the module loaded. A walk is priced by the number of entities the scene holds, so a caller that must not scale with the scene — a per-frame push, a write that reaches its target through a record — is read either side of its work against this count, which a walk raises by one.

Returns number — The count of walks.

local before = entity.sceneWalks(); entity(id):rename("door"); assert(entity.sceneWalks() == before)

globals/entity/spawn

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, attributes = per-entity attribute values keyed by name ({ role = "guard" }, the same values entity(id).attribute.set(key, value) writes and entity.template / entity.instantiate take), synced = flag the entity multiplayer-synced (what entity.spawnSynced implies), active = false spawns it inactive, scene_owner = the scene layer guid the entity is attributed to (inherited from the spawning scene when unset), 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.).

local e = entity.spawn("crate", { components = { Model = { model = "cube" } } })
local e = entity.spawn { name = "turret", position = { 1, 2, 3 } }
local guard = entity.spawn("guard", { attributes = { role = "guard", hp = 3 } })

globals/entity/spawnSynced

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.

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

globals/entity/template

entity.template(def: EntityTemplateDef) -> EntityTemplate

Construct a reusable spawn template. Captures a shared entity config ONCE and returns an EntityTemplate handle for entity.instantiate(handle, count, fn?) — one call per batch instead of one per entity. The template lives as long as a script holds the handle: once nothing reaches it, the collector releases the template, and the entities already instantiated from it stay as they are. Keep the handle (a module-level local, a field) for as long as more instances may follow. 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 EntityTemplate — The template's handle for entity.instantiate.

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

globals/entity/tree

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 }.

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

modules/entity/batchAddComponent

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: one table applied identically to every entity — the same shape the second arg to entity(id).component.add(type, data) takes — or an array holding one such table per target, in the targets' order, so each entity is added with its own values.
local n = entity.batchAddComponent(ids, "Debris", { lifetime = 5 })
entity.batchAddComponent({ a, b }, "Debris", { { lifetime = 2 }, { lifetime = 7 } })

modules/entity/batchDespawn

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).
local n = entity.batchDespawn(ids)

modules/entity/batchProxy

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.
local proxies = entity.batchProxy(ids)

modules/entity/batchRead

batchRead(target: { string | entityRef } | binding, component: string?, field: string?, sink: TypedBuffer?): { 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. Transform.worldMatrix reads each entity's world matrix — its own transform folded with its ancestors', the one entity(id).worldMatrix() answers — as 16 column-major numbers: into a mat4 buffer, or as an array of 16 per entity. It is read, never written. 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 TypedBuffer? (optional) — Typed CPU buffer from substrate.createBuffer({...}) to memcpy field values into. Required when target is a binding.
local snapshot = entity.batchRead(ids)
local positions = entity.batchRead(ids, "Transform", "position")
entity.batchRead(ecs.bindEntities(ids), "Transform", "worldMatrix", matrices)

modules/entity/batchReadToBuffer

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, live while its handle is held.
  • component string — Component type name.
  • field string — Field name to read.
  • buffer number — Destination buffer id (must be the matching type).
entity.batchReadToBuffer(binding.id, "Transform", "position", buf.id)

modules/entity/batchSpawn

batchSpawn(count: number, name_prefix: string?, opts: { temporary: boolean?, parent: (string | entityRef)? }?): { 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".
  • opts { temporary: boolean?, parent: (string | entityRef)? }? (optional) — { temporary?, parent? } — a temporary batch is one the scene does not save, the same thing entity.spawn({ temporary = true }) asks for. The component goes on in one crossing, so a batch of a hundred thousand costs what one does. A batch spawned inside an entrypoint that is itself temporary is temporary whatever this says. parent — an id or an entity — makes every entity in the batch its child as it is made, so the batch stands, moves and goes with it.
local ids = entity.batchSpawn(100, "grass_")
local particles = entity.batchSpawn(5000, "spark_", { temporary = true })
local parts = entity.batchSpawn(8, "part_", { temporary = true, parent = owner })

modules/entity/batchWrite

batchWrite(target: { string | entityRef } | binding, component: string, field: string, source: { any? } | TypedBuffer): 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? } | TypedBuffer — Per-entity values array (nil entries are skipped), or a typed CPU buffer from substrate.createBuffer({...}). A buffer source requires a binding target.
entity.batchWrite(ids, "Transform", "position", positions)

modules/entity/batchWriteBound

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, live while its handle is held.
  • 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.
entity.batchWriteBound(binding.id, "Transform", "position", values)

modules/entity/batchWriteFromBuffer

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, live while its handle is held.
  • component string — Component type name.
  • field string — Field name to write.
  • buffer number — Buffer id from substrate.createBuffer({type="vec3", len=N}).id.
entity.batchWriteFromBuffer(binding.id, "Transform", "position", buf.id)

modules/entity/capture

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

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.
local ids, err, reproduced, refusals = entity.capture(function() entity.spawn("chair") end)

modules/entity/capturing

capturing(): boolean

Whether an entity.capture builder (a scene build) is running on the calling thread. A component a build adds takes the world's Preset Manager rows in every mode, so a scene built in a play session holds what the same build holds in edit mode.

if entity.capturing() then return end

modules/entity/despawn

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.
entity.despawn(id)
entity.despawn("Enemy") -- despawns every entity named "Enemy"

modules/entity/duplicate

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, descendants?: boolean, components?: table, name?: string }: descendants = false copies the source entity alone, leaving its children to it; components lays fields over the copy's script components by type ({ SoftBody = { mass = 2 } }), merged over the state copied from the source; name is the same field the name argument sets, and wins when both are given.
local copyId = entity.duplicate(id); if copyId then entity(copyId).position = { 1, 0, 0 } end
local copyId = entity.duplicate(id, "Turret", { includeTemporary = true })
local lone = entity.duplicate(id, nil, { descendants = false, components = { SoftBody = { mass = 2 } } })

modules/entity/exists

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.
if entity.exists(id) then ... end

modules/entity/find

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; find is the first entity findAll lists. Inside a build (a scene's content(), a module's builder), a name the build's own entities carry resolves to those first, so entity.spawn("door") followed by entity.find("door") is the door the build just made even when the world around it holds another door.

Parameters

  • nameOrGlob string — Exact entity Name or id, or a * / ? glob over Names.
local e = entity.find("enemy_*")

modules/entity/findAll

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(...)). Inside a build (a scene's content(), a module's builder), the entities the build made come first and the world's same-named entities follow.

Parameters

  • nameOrGlob string? (optional) — Exact entity Name or id to filter by, or a * / ? glob over Names. Omit to enumerate every entity.
for _, e in entity.findAll("enemy_*") do e:despawn() end

modules/entity/getChildren

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. A string that names a live entity rather than identifying it raises, naming entity.find(name), the lookup that takes a name.

Parameters

  • id string | entityRef — Entity id or entity proxy.
for _, c in entity.getChildren(id) do c.internal = true end

modules/entity/getDescendants

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. A string that names a live entity rather than identifying it raises, naming entity.find(name), the lookup that takes a name.

Parameters

  • id string | entityRef — Entity id or entity proxy.
local all = entity.getDescendants(id)

modules/entity/getParent

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. A string that names a live entity rather than identifying it raises, naming entity.find(name), the lookup that takes a name: answering it as an id would read the same as a root entity.

Parameters

  • id string | entityRef — Entity id or entity proxy.
local p = entity.getParent(id)

modules/entity/instantiate

instantiate(handle: EntityTemplate, 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 EntityTemplate — 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?.
local ids = entity.instantiate(h, 50, function(i) return { position = { i, 0, 0 } } end)

modules/entity/sceneWalks

sceneWalks(): number

How many whole-scene walks entity.findAll has made since the module loaded. A walk is priced by the number of entities the scene holds, so a caller that must not scale with the scene — a per-frame push, a write that reaches its target through a record — is read either side of its work against this count, which a walk raises by one.

local before = entity.sceneWalks(); entity(id):rename("door"); assert(entity.sceneWalks() == before)

modules/entity/spawn

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, attributes = per-entity attribute values keyed by name ({ role = "guard" }, the same values entity(id).attribute.set(key, value) writes and entity.template / entity.instantiate take), synced = flag the entity multiplayer-synced (what entity.spawnSynced implies), active = false spawns it inactive, scene_owner = the scene layer guid the entity is attributed to (inherited from the spawning scene when unset), id = restore a previously-assigned entity id (scene_loader use; leave unset for a normal spawn). An unrecognised key is rejected loudly.
local e = entity.spawn("crate", { components = { Model = { model = "cube" } } })
local e = entity.spawn { name = "turret", position = { 1, 2, 3 } }
local guard = entity.spawn("guard", { attributes = { role = "guard", hp = 3 } })

modules/entity/spawnSynced

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).
if multiplayer.isHost() then entity.spawnSynced("Goblin") end

modules/entity/template

template(def: EntityTemplateDef): EntityTemplate

Construct a reusable spawn template. Captures a shared entity config ONCE and returns an EntityTemplate handle for entity.instantiate(handle, count, fn?) — one call per batch instead of one per entity. The template lives as long as a script holds the handle: once nothing reaches it, the collector releases the template, and the entities already instantiated from it stay as they are. Keep the handle (a module-level local, a field) for as long as more instances may follow. 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.
local h = entity.template({ components = { Model = { model = "cube" } } })

modules/entity/tree

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? }.
local view = entity.tree({ filter = "crate", limit = 50 })
  • api
  • reference