# World A world owns the complete ECS runtime: entities, archetypes, queries, systems, resources, bundles, event observers, snapshot handlers, and state. ```teal local world = tecs.ecs.newWorld({ timestep = 1 / 60, }) ``` The default world supports about one million concurrent entity slots. A configuration may raise `maxEntities` to the packed-ID limit of `2^22 - 1`. Tests and specialized hosts may also supply a pipeline factory. ## Lifecycle `Application` creates and drives its world. After the entry plugin finishes, it calls `startup()` once, `update(dt)` every host iteration, and `shutdown()` at teardown. Tests, tools, and benchmarks may drive those calls directly: ```teal world:startup() world:update(1 / 60) world:shutdown() ``` Before phase dispatch, `update` calls `unwind` to close any scope an earlier error left open. After the pipeline returns, it clears component dirty bits. Render extraction runs inside that pipeline and consumes the bits first. `getFixedTiming()` returns the timestep, residual accumulator, and clamped interpolation alpha without allocating. `fixedStepCount()` returns the number of fixed steps since world construction. The scheduler advances those values even when callers disable fixed phases. ## Entity IDs Entity IDs pack a slot and generation into an opaque number: ```teal local old = world:spawn() world:despawn(old) local current = world:spawn() assert(not world:isAlive(old)) assert(world:isAlive(current)) ``` Slot reuse changes the generation, so stale handles fail lookups. Do not inspect IDs with LuaJIT bit operations; packed values may exceed 32 bits. Use `EntityKey` for the few authored entities that runtime code must rediscover: ```teal world:spawn(tecs.ecs.EntityKey("player"), tecs.ecs.Name("Player ship")) local player = world:requireKey("player") ``` Callers choose keys. Tecs owns the unique index, releases entries on removal or despawn, and rebuilds it after snapshot load. ## Spawning entities `spawn` accepts initial components and returns an ID immediately: ```teal local player = world:spawn( tecs.Transform2D(100, 100), tecs.gfx.Tint(1, 1, 1, 1), tecs.gfx.Renderable2D, tecs.ecs.Name("Player") ) ``` Outside a scope, the entity occupies its archetype before `spawn` returns. Inside a scope, the world reserves the ID and stages placement. Later staged `set`, `remove`, or `despawn` calls may use that ID. `spawnAt` and `batchSpawnAt` place caller-chosen packed IDs. Snapshot loading uses them to preserve relationship targets and generations. The caller must ensure that each chosen slot has no live entity. ## Batch mutation `batchSpawn` resolves one component signature and opens one fill callback: ```teal local signature = { tecs.Transform2D, tecs.gfx.Tint, tecs.gfx.Renderable2D, } local firstId, ids = world:batchSpawn( 1000, signature, function(archetype, firstRow, lastRow) local transforms = archetype:getMut(tecs.Transform2D) local tints = archetype:getMut(tecs.gfx.Tint) for row = firstRow, lastRow do local transform = transforms[row] transform.x = row - firstRow transform.y = 0 transform.z = 0 transform.layer = 1 transform.rotation = 0 transform.scaleX = 1 transform.scaleY = 1 local tint = tints[row] tint.r = 1 tint.g = 1 tint.b = 1 tint.a = 1 end end ) ``` The contiguous allocator returns `firstId`; the fallback returns an explicit `ids` list. Both paths reserve IDs before placement. Batch placement follows these rules: - Component constructors do not run. Tecs writes only `requires` defaults before the callback, so the callback must initialize every field it uses. - `EntityKey` cannot participate because each row needs a distinct index claim. - Batch spawn emits no `OnSpawn`; use the fill callback or `onEntitiesAdded`. - Sparse relationship proxies reject writes. Attach each target through `world:set(reservedId, Relationship(target))`. - The callback runs during drain, either inside the call at depth zero or when the outer scope closes. `batchSet` adds or replaces one component across a query. Its constant form accepts an instance. Its callback form accepts a bulk-safe component type and opens the destination column for caller writes. Relationships use the constant form; sparse relationships route through their world stores. `batchRemove` removes one component across matching archetypes. `batchDespawn` removes every matched entity. Relationship cleanup, cascade delete, entity observers, and component cleanup force per-entity work where needed; otherwise the batch path clears whole ranges. Every despawn still emits `OnDespawn`. ## Entity clearing and storage maintenance `clearEntities()` removes entity data, pending transactions, sparse stores, keys, queued events, and entity-address observers. It preserves systems, queries, global observers, bundles, component registrations, archetypes, and column capacity. Use a new world when systems and queries must also disappear. `compact()` prunes unreachable empty relationship archetypes and shrinks excess column capacity. Call it only at a quiet boundary after `commit`; level transitions suit it better than frame loops. `forEachArchetype` and `dirtyArchetypes` expose engine-owned archetype handles for read-only inspection. Do not mutate the world during those iterations. Dirty-archetype iteration resets after each update. `getStats(fill?)` writes current counts into a caller-owned table. Callers may reuse and read that table; Tecs writes its fields on each call. ## Deferred operations {#deferred-operations} Scope depth chooses immediate or staged structural mutation. Query iteration, query callbacks, and batch calls open scopes automatically. Callers may open one explicitly: ```teal local function extinguish(world: tecs.World, entity: integer) world:defer() world:set(entity, tecs.gfx.Tint(0.2, 0.2, 0.2, 1)) world:remove(entity, tecs.gfx.PointLight2D) world:remove(entity, tecs.gfx.Renderable2D) world:commit() end ``` `defer` increments depth. `commit` decrements it and drains when the outermost scope closes. Calls may nest. A `commit` at depth zero remains harmless and never discards work. Inside a scope, spawns, despawns, component additions, and component removals remain invisible until drain. A value update to an existing component may write through immediately while that entity has no staged structural change. The [mutation model](/modules/ecs/mutation-model) defines the complete contract. An archetype query loop must run to exhaustion. A loop that may stop early uses `query:newCursor()` and calls `cursor:close()`, or it leaves the world deferred. `unwind()` closes every scope and drains pending work. It supplies the recovery path after a system throws inside iteration. ## Plugins and resources A plugin configures one world. Games, engine features, and reusable mechanics all use the same function shape: ```teal local RATE : tecs.data.Key = tecs.data.Store.newKey( "game.spinRate" ) local function spinPlugin(world: tecs.World) world.resources[RATE] = 1.5 -- Build queries and register systems here. end world:addPlugin(spinPlugin) ``` Callers own resource values and may replace them. Tecs owns resource-key identity. Always name keys so hot reload, tooling, `Store.findKey`, and `Store.listKeys` on `tecs.data` can find the same key. Snapshots omit `world.resources`; register a [snapshot handler](/modules/ecs/save-games#snapshot-handlers) for durable resource state. ## World subsystems The world exposes the shared entry points for: - [Components](/modules/ecs/components/) and [relationships](/modules/ecs/relationships/). - [Bundles](/modules/ecs/components/bundles). - [Queries](/modules/ecs/queries/) and hierarchy traversal. - [Systems](/modules/ecs/systems), [phases](/modules/ecs/phases), and [plugins](/modules/ecs/plugins). - [States](/modules/ecs/states). - [Events](/modules/ecs/events). - [Snapshots](/modules/ecs/save-games). Those pages own their interaction rules; generated Teal reference owns individual method signatures and records.