On this page
Component bundles
A bundle names one component set and compiles its spawn path once:
local playerBundle <const> = world:newBundle(
"Player",
{
required = {tecs.Transform2D, Health},
with = {
[tecs.gfx.Tint] = function()
return tecs.gfx.Tint(1, 1, 1, 1)
end,
[tecs.gfx.Renderable2D] = true,
},
}
)
local player <const> = playerBundle:spawn(
tecs.Transform2D(100, 200), Health(100)
)required sets the positional arguments to spawn. with creates the rest for every entity.
Required components
Declaration order controls spawn order:
local enemyBundle <const> = world:newBundle(
"Enemy",
{
required = {tecs.Transform2D, Health, Damage},
}
)
local enemy <const> = enemyBundle:spawn(
tecs.Transform2D(100, 200), Health(50), Damage(10)
)Every argument must match its declared component. Move any value that varies per spawn into required.
Bundle defaults
Each with value must hold a factory or true. A factory runs once per spawn and returns a fresh instance:
local bulletBundle <const> = world:newBundle(
"Bullet",
{
required = {tecs.Transform2D},
with = {
[Velocity] = function() return Velocity(100, 0) end,
[Damage] = function() return Damage(25) end,
},
}
)true calls the component with no arguments. It suits tags and components whose declared defaults already have the right value:
local propBundle <const> = world:newBundle(
"Prop",
{
required = {tecs.Transform2D},
with = {
[tecs.gfx.Renderable2D] = true,
[Static] = true,
},
}
)A spawn cannot override a component from with. Put that component in required when callers need to supply it.
The definition may name each component once across required and with. Registration rejects duplicates, invalid with values, and duplicate bundle names.
Spawning and deferred scopes
The bundle object and the world registry call the same compiled spawn path:
local first <const> = playerBundle:spawn(
tecs.Transform2D(0, 0), Health(100)
)
local second <const> = world:spawnBundle(
"Player", tecs.Transform2D(20, 0), Health(100)
)Bundle spawns follow world:spawn timing. Outside a deferred scope, the entity occupies its archetype before the call returns. Inside query iteration, callbacks, a batch callback, or an explicit deferred scope, the world stages the spawn until commit. The returned ID works immediately for later staged operations:
for _archetype, _length in query:iter() do
local id <const> = playerBundle:spawn(
tecs.Transform2D(0, 0), Health(100)
)
world:set(id, Selected)
endRegistry lookup
world:getBundle(name) returns one bundle or nil. world:getBundles() returns a fresh name-to-bundle map, so changing the map does not change the registry.