population
A hardware-instanced population at rest: one mesh drawn at every transform of a baked placement, as ONE draw call per variant at any instance count.
The asset is the recipe — which .mesh and material each variant draws with,
plus the instance matrices as bytes. Its instantiate method is what turns
that recipe back into a live draw, so a scene records ONE entity for a
population of a million instances instead of a million entities.
Layout
<name>.population/
population.json { version, total, variants: [ { mesh, material | materialKey, count, offset } ] }
transforms.bin total × 64 B — little-endian f32, column-major mat4 per instance
offset counts INSTANCES, not bytes: variant k's matrices start at
offset × 64 bytes of transforms.bin and run for count × 64. Each matrix
is column-major, which puts its translation at floats 13/14/15 of the block —
the layout the engine reads a transform buffer as array<mat4x4<f32>>. The
matrices are world-space: a population draws where the recipe placed it.
mesh and material are asset references ({ "__ref": "<guid>" }), so the
geometry and material a population draws travel with it in the dependency
graph. A variant drawing with a material the renderer's registry holds but no
asset backs names it in materialKey instead.
Putting one in a scene
local pop = asset.resolve("forest", "population")
pop:instantiate(nil, { position = { 0, 0, 0 } }) -- one entity, one draw call
That spawns an entity carrying the Population component, which owns the live
GPU buffers and draw registrations for as long as the entity lives — a reload
replaces them rather than stacking a second set. The same contract is what
Asset { source = <population> } drives, which is how a baked procgen bundle
carries a population: a root record plus one child holding the reference.
Building one
proc's population.write sink persists a Population value:
local pop = proc.registry.get("instances.draw").eval({}, { instances = iset }, {}).population
proc.registry.get("population.write").eval({}, { population = pop }, { name = "forest" })
Or through asset.create directly, with each variant's matrices as a flat
count × 16 column-major float array:
asset.create("population", "forest", {
variants = { { mesh = meshGuid, material = materialGuid, transforms = flat } },
})
Reading and drawing one by hand
local recipe = pop:spec() -- { version, total, variants }
local live = pop:draw() -- allocate buffers + register draws
print(live:count(), live:drawCalls())
live:destroy() -- drop the registrations, free the buffers
The value :draw() returns OWNS those resources. Hold it and release it —
a registration nobody holds can be neither enumerated nor dropped afterwards.