ReadonlynameHuman-readable identifier used in graph node labels and error messages.
When true, the simulation is frozen: update()/addToGraph still run so
the last frame keeps rendering (camera stays live), but no compute
dispatches or sim bookkeeping happen. Toggle via the field; advance a
single frozen step with requestStep.
Underlying GPU buffer that backs the block_collision heightmap, so an
external GPU compute pass can populate it instead of (or in addition to)
the CPU-side updateHeightmap. Null when the particle graph has
no block_collision modifier.
The patch (grid) field's atomic<i32> storage buffer — layout
[parity][field][z][x], both ping-pong halves. Null when the config has no
patches. Pair with patchUniformBuffer (carries parity, res,
field_count, deposit_scale) to read the current half for a field view.
The PatchUniforms buffer (origin / inv-cell / res / field_count / parity /
wrap / deposit_scale / per-field params). Null when no patch grid.
Patch grid side length in cells (0 when no patch grid).
Link slots per agent (0 when no links).
Scratch f32 slots per agent (0 when no scratch).
The Particle storage buffer (16 floats / 64 bytes per agent: position,
life, velocity, max_life, color, size, rotation, flags, pad). Read-only for
consumers — e.g. drawing link edges between agent positions.
The per-agent link-slot buffer (linksPerAgent i32 per agent, slot = linked
index or < 0). Null when the config has no links.
Particle buffer capacity (agent count for non-reproduction models).
Sets the world-space center + radius of a sphere_collision modifier marked
dynamic, so the collider can move at runtime (e.g. track a physics body).
Radius 0 disables it. Takes effect on the next ParticlePass.update.
No-op for graphs without a dynamic sphere collider (the uniform is ignored).
Conservative CPU-side test for whether any particle could still be alive, so the caller can skip addToGraph (and all its GPU dispatches + draws) for an idle emitter. True while spawning continuously, while a burst is pending, or until the longest possible particle lifetime has elapsed since the last spawn. Never under-reports — a pass it returns false for has definitely emptied. Requires update to have run this frame first.
StaticcreateCreate a ParticlePass for config.
Construction-time options for ParticlePass.create.
OptionalparentSplit?: {Set by the parent pass when this instance is a sub-emitter (split) child. Internal.
OptionalforwardPass?: ForwardPassRequired when the config's renderer.type === 'mesh' and
renderer.renderTarget === 'hdr'. The particle pass borrows this
ForwardPass's lighting+IBL bind group layout for its instanced-PBR
pipeline and its bind group at draw time, so all forward-lit content
shares the same light/IBL state. The host must call
forwardPass.updateLights(...) before ParticlePass.addToGraph.
OptionalheightmapRes?: numberSide length in cells of the block_collision heightmap storage buffer.
Default 128 (matches the historical hard-coded layout). Lower values
trade resolution for cheaper CPU bakes; the shader uses the same uniform
for sample-position math so this is safe to vary per pass. Ignored when
the particle graph has no block_collision modifier.
Seed a patch field from a CPU resolution × resolution grid of values (the
AgentScript importDataSet — load a heightmap / density / cost raster the
agents then sense). data is row-major [z*res + x], in field units (the
deposit_scale fixed-point conversion is applied here). Written to BOTH
ping-pong halves so it's correct from frame 0; for a STATIC field give it
diffuse: 0, evaporate: 1 so it isn't smeared/decayed. No-op without a patch
grid. Call after creation (and after any setConfig rebuild).
Read a patch field back to the CPU as a resolution × resolution grid (the
AgentScript patches.exportDataSet — the measurement side of
setPatchField). Returns row-major [z*res + x] values in field units
(the deposit_scale fixed-point conversion is undone), reading the current
agent-facing parity half. Async — copies the field block into a staging
buffer, submits, and maps it; do NOT call every frame (it stalls on the GPU).
Resolves to an empty array when the config has no patch grid.
Use for plotting/measurement (population density, pheromone totals) or to snapshot a field for export.
Read the agent (particle) records back to the CPU — the turtle-snapshot side
of the measurement layer (AgentScript's turtles accessor / model plotting).
Returns a flat Float32Array of min(count, maxParticles) records, 16 floats
each (the Particle struct): [0..2] position, [3] life (< 0 = dead slot
— skip it when counting live agents), [4..6] velocity, [7] max_life,
[8..11] color (color.a carries species/state in agent models), [12] size,
[13] rotation (heading, or energy in lotka), [14] flags, [15] pad.
Async and synchronizing — for measurement/plotting, not per-frame use. Pass
count to read only the first N slots (e.g. the seeded population in a
reproduction model); defaults to the full capacity.
Optionalcount: numberSet the world transform applied to every rendered particle (default
identity). A rigid + uniform-scale Mat4 mapping the sim's local space into
world space — used to place a local agent/particle sim on the geospatial
globe (the ENU tangent-plane transform at a lat/lon) and follow floating-
origin reanchors without re-simulating. Picked up on the next update().
No effect on the deferred (gbuffer) sprite/mesh paths.
Propagates to split children so an on-death burst (e.g. a rain splash) shares the parent's space — children spawn at the parent's death position in the same sim coordinates, so they must render through the same transform.
Upload the per-agent link topology (the link_springs modifier reads it).
links is linksPerAgent i32 slots per agent (slot = linked particle
index, < 0 = empty), laid out as [agent0 slot0..K-1, agent1 ...]. No-op
when the config has no links. Call after creation and after any
setConfig rebuild (the buffer is fresh each time).
Upload the per-agent scratch array (scratchGet/scratchSet/loop read it).
data is slotsPerAgent f32 per agent, laid out [agent0 slot0..S-1, agent1 ...]. No-op without scratch. Call after creation and after any setConfig
rebuild (the buffer is fresh + zeroed each time).
Read the whole scratch array back to the CPU as maxParticles × slotsPerAgent
f32 (row-major by agent). Async + synchronizing (own encoder + submit) — for
measurement / extracting a result (e.g. the best TSP tour), NOT per frame.
Resolves to an empty array without scratch.
Seed the initial population for a reproduction config: writes count live
agents (each 16 floats / 64 bytes in the Particle layout: position, life,
velocity, max_life, color, size, rotation, flags, trail_meta) into slots
[0, count) and initializes the free-list with the remaining slots
[count, maxParticles). Call once after the pass is created (and the
emitter is typically disabled, spawnRate: 0). No-op without reproduction.
Writes only the heightmap uniform (origin xz / extent / resolution), leaving the data buffer untouched. For callers that bake the heights on GPU and only need to tell the collision shader where the heightmap is positioned in world space.
Updates the SDF's world transform (and uniform scale). The shader pulls
world particle positions back into the SDF's local frame via transform's
inverse before sampling, so this lets the host move/rotate/uniformly-scale
the SDF without re-baking. No-op when the pass has no SDF bound.
transform is a world matrix; the inverse is computed here and uploaded.
scale (default 1) multiplies the sampled distance back into world units —
pass the same uniform scale you used to build transform.
Advance exactly one fixed simulation tick on the next frame even while paused. Lets a host step a frozen sim frame by frame.
Insert the pass into graph for one frame. Implementations call
graph.addPass(name, type, b => { ... }) exactly once and use the
supplied PassBuilder to declare reads, writes, transient
resources, and the execute callback.
Graph being built this frame.
Pass-specific dependency record (handles, scene data, etc.).
Pass-specific output record (typically a set of handles downstream passes will consume).
Release every long-lived GPU resource owned by the pass (pipelines, persistent uniform buffers, samplers, BGLs). Called by the factory or application during teardown. Default implementation is a no-op.
GPU particle system pass (render-graph version).
Compute (spawn -> update -> compact -> indirect-write) followed by an indirect billboard draw, either alpha-blended into the HDR target (forward mode) or additive into the GBuffer (deferred mode), driven by
config.renderer.