population
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 taken in the space of the entity drawing the population, so it
draws where the recipe placed it relative to that entity and moves with 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.
In the Inspector
Selecting a population opens its Population section: how many instances it places, across how many variants, and a table of each variant's mesh, material, instance count and offset, with a link to each variant's mesh.