entity
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
selflifetimenumber(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
keystring
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
keystring
Returns ()
entity/proxy/attribute/set
entity(id).attribute.set(key: string, value: any) -> ()
Store a value under a key on this entity.
Parameters
keystringvalueany
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/proxy/bundleLink
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
typeAssetRef | stringdatatable(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
typeAssetRef | stringdatatable(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
typeAssetRef | stringdatatable(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
typeAssetRef | stringinstanceNamestring(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
typeAssetRef | 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
typeAssetRef | 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
typeAssetRef | 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 } | stringflags{ [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
typeAssetRef | stringinstanceNamestring(optional)
Returns ()
entity/proxy/component/setEnabled
entity(id).component.setEnabled(type: AssetRef | string, enabled: boolean) -> ()
Enable or disable a component without detaching it.
Parameters
typeAssetRef | stringenabledboolean
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
selfnamestring(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
selfnames{ 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
selftargetanyupany(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
selfnewNamestring
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
selfnamestring
Returns any
entity/proxy/setActive
entity(id).setActive(self, active: boolean) -> ()
Set whether this entity is active.
Parameters
selfactiveboolean
Returns ()
entity/proxy/setInternal
entity(id).setInternal(self, internal: boolean) -> ()
Set whether default listings skip this entity.
Parameters
selfinternalboolean
Returns ()
entity/proxy/setNetworkScope
entity(id).setNetworkScope(self, scope: string) -> ()
Set how far this entity replicates.
Parameters
selfscopestring
Returns ()
entity/proxy/setParent
entity(id).setParent(self, parentId: string, opts: { [string]: any }?) -> ()
Reparent this entity.
Parameters
selfparentIdstringopts{ [string]: any }(optional)
Returns ()
entity/proxy/setParticipation
entity(id).setParticipation(self, mode: string) -> ()
Set this entity's runtime participation.
Parameters
selfmodestring
Returns ()
entity/proxy/setRenderLayer
entity(id).setRenderLayer(self, names: any) -> ()
Set this entity's render layer.
Parameters
selfnamesany
Returns ()
entity/proxy/setRenderLayerTree
entity(id).setRenderLayerTree(self, names: any) -> ()
Set render layer for this entity and its descendants.
Parameters
selfnamesany
Returns ()
entity/proxy/setRenderLayers
entity(id).setRenderLayers(self, names: any) -> ()
Set this entity's render layers.
Parameters
selfnamesany
Returns ()
entity/proxy/setSessionScoped
entity(id).setSessionScoped(self, scoped: boolean) -> ()
Scope this entity to the session.
Parameters
selfscopedboolean
Returns ()
entity/proxy/setSynced
entity(id).setSynced(self, synced: boolean) -> ()
Set whether this entity replicates.
Parameters
selfsyncedboolean
Returns ()
entity/proxy/setSyncedTree
entity(id).setSyncedTree(self, synced: boolean) -> ()
Set replication for this entity and its descendants.
Parameters
selfsyncedboolean
Returns ()
entity/proxy/setTemporary
entity(id).setTemporary(self, temporary: boolean) -> ()
Set whether saves skip this entity.
Parameters
selftemporaryboolean
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
selfopts{ [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 ofentity.batchSpawnorentity.findAll).type_namestring— Component type to add to every entity.datatable(optional) — Init data: one table applied identically to every entity — the same shape the second arg toentity(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 ofentity.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.worldMatrixreads each entity's world matrix — its own transform folded with its ancestors', the oneentity(id).worldMatrix()answers — as 16 column-major numbers: into amat4buffer, or as an array of 16 per entity. It is read, never written.targetaccepts an entity-id array or aecs.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 fromecs.bindEntities(ids).componentstring(optional) — Component type name (e.g. "Transform").fieldstring(optional) — Field name (e.g. "position").sinkTypedBuffer(optional) — Typed CPU buffer fromsubstrate.createBuffer({...})to memcpy field values into. Required whentargetis 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
bindingnumber— Binding id fromecs.bindEntities(ids).id, live while its handle is held.componentstring— Component type name.fieldstring— Field name to read.buffernumber— 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
countnumber— How many entities to spawn (capped at 1,000,000).name_prefixstring(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 thingentity.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;valuesis an array the same length asids(nil slots are skipped). Use for one-shot writes.entity.batchWrite(binding, comp, field, values)— binding handle fromecs.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 fromecs.bindEntities(ids).componentstring— Component type name.fieldstring— Field name to write.source{ any? } | TypedBuffer— Per-entity values array (nil entries are skipped), or a typed CPU buffer fromsubstrate.createBuffer({...}). A buffer source requires a bindingtarget.
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
bindingnumber— Binding id fromecs.bindEntities(ids).id, live while its handle is held.componentstring— Component type name.fieldstring— 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
bindingnumber— Binding id fromecs.bindEntities(ids).id, live while its handle is held.componentstring— Component type name.fieldstring— Field name to write.buffernumber— Buffer id fromsubstrate.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
targetstring | 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
sourceIdstring | entityRef— Entity id or entity proxy of the source entity to clone.namestring(optional) — Display name for the copy (defaults to source name + " (copy)").optstable(optional) —{ includeTemporary?: boolean, descendants?: boolean, components?: table, name?: string }:descendants = falsecopies the source entity alone, leaving its children to it;componentslays fields over the copy's script components by type ({ SoftBody = { mass = 2 } }), merged over the state copied from the source;nameis the same field thenameargument 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
idOrProxystring | 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
nameOrGlobstring— 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
nameOrGlobstring(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
idstring | 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
idstring | 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
idstring | 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
entityIdstringassetPathstringopts{ [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
valueany— 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
handleEntityTemplate— Template handle fromentity.template.countnumber— 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
valueany— 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
valueany— 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.optsSpawnOpts(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 valuesentity(id).attribute.set(key, value)writes andentity.template/entity.instantiatetake),synced= flag the entity multiplayer-synced (whatentity.spawnSyncedimplies),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.optsSpawnOpts(optional) — Same options asentity.spawn(syncedis 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
defEntityTemplateDef— Template definition:components/ecs/temporary/active/internal/attributes. Per-instancename/position/rotation/scale/parentand any override go through theinstantiatecallback.
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 ofentity.batchSpawnorentity.findAll).type_namestring— Component type to add to every entity.datatable?(optional) — Init data: one table applied identically to every entity — the same shape the second arg toentity(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 ofentity.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.worldMatrixreads each entity's world matrix — its own transform folded with its ancestors', the oneentity(id).worldMatrix()answers — as 16 column-major numbers: into amat4buffer, or as an array of 16 per entity. It is read, never written.targetaccepts an entity-id array or aecs.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 fromecs.bindEntities(ids).componentstring?(optional) — Component type name (e.g. "Transform").fieldstring?(optional) — Field name (e.g. "position").sinkTypedBuffer?(optional) — Typed CPU buffer fromsubstrate.createBuffer({...})to memcpy field values into. Required whentargetis 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
bindingnumber— Binding id fromecs.bindEntities(ids).id, live while its handle is held.componentstring— Component type name.fieldstring— Field name to read.buffernumber— 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
countnumber— How many entities to spawn (capped at 1,000,000).name_prefixstring?(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 thingentity.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;valuesis an array the same length asids(nil slots are skipped). Use for one-shot writes.entity.batchWrite(binding, comp, field, values)— binding handle fromecs.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 fromecs.bindEntities(ids).componentstring— Component type name.fieldstring— Field name to write.source{ any? } | TypedBuffer— Per-entity values array (nil entries are skipped), or a typed CPU buffer fromsubstrate.createBuffer({...}). A buffer source requires a bindingtarget.
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
bindingnumber— Binding id fromecs.bindEntities(ids).id, live while its handle is held.componentstring— Component type name.fieldstring— 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
bindingnumber— Binding id fromecs.bindEntities(ids).id, live while its handle is held.componentstring— Component type name.fieldstring— Field name to write.buffernumber— Buffer id fromsubstrate.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
targetstring | 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
sourceIdstring | entityRef— Entity id or entity proxy of the source entity to clone.namestring?(optional) — Display name for the copy (defaults to source name + " (copy)").optstable?(optional) —{ includeTemporary?: boolean, descendants?: boolean, components?: table, name?: string }:descendants = falsecopies the source entity alone, leaving its children to it;componentslays fields over the copy's script components by type ({ SoftBody = { mass = 2 } }), merged over the state copied from the source;nameis the same field thenameargument 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
idOrProxystring | 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
nameOrGlobstring— 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
nameOrGlobstring?(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
idstring | 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
idstring | 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
idstring | 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
handleEntityTemplate— Template handle fromentity.template.countnumber— 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.optsSpawnOpts?(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 valuesentity(id).attribute.set(key, value)writes andentity.template/entity.instantiatetake),synced= flag the entity multiplayer-synced (whatentity.spawnSyncedimplies),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.optsSpawnOpts?(optional) — Same options asentity.spawn(syncedis 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
defEntityTemplateDef— Template definition:components/ecs/temporary/active/internal/attributes. Per-instancename/position/rotation/scale/parentand any override go through theinstantiatecallback.
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 })