ReadonlycapacitySlots the device buffers were allocated for. Must equal the emitter's
pool capacity; bindGpuSimulation refuses a mismatch.
ReadonlyisThe brand, a literal true — the isParticleDrawable technique, so
the cross-package check is one property load.
Integrates the first count slots by deltaSeconds under constant
gravity — v += g·dt, then p += v·dt, the emitter's documented
closed form. Never called with count === 0.
Optionalextras: ParticleGpuIntegrateExtrasCopies slot from's position and velocity over slot to — the device
mirror of ParticlePool.kill's swap-remove (from is the last live
slot, to the expired one; from's old contents become dead). Called
in exactly the CPU compaction order, never with from === to.
Writes a freshly spawned particle's position and velocity into slot
index — the CPU-owned spawn state entering device residency.
The GPU integration driver a
simulation: "gpu"emitter steps through (§36 "GPU compute simulation"; §82; gap row R-31).Who implements it, and how the pieces meet
A compute-capable backend mints one —
@fourjs/render-webgpu'sWebgpuRenderer.createParticleSimulation({ systemId, capacity })is the implementor today — and the application binds it withemitter.bindGpuSimulation(...). The interface is structural, like ParticleForceField and@fourjs/render'sParticleDrawable, because the frozen §3.1 matrix gives this packagecore,math,sceneand nothing render-shaped: the two declarations are pinned against each other by tests on both sides, never by the compiler.The division of labour (decision, R-31 wiring)
CPU spawn + GPU integrate. Every §33-bearing decision stays on the CPU — the seeded RNG stream and its pinned four-draws-per-spawn order, burst scheduling, the emission accumulator, capacity accounting, ageing and expiry, and the §36 ramps (functions of age, which never leaves the CPU). The device takes exactly the O(n)-arithmetic half: one semi-implicit Euler step per particle per fixed step, under constant gravity. That split is what keeps a GPU emitter's spawn stream bit-identical to a CPU emitter's with the same seed — pinned by test — while the integrated state lives where the work is.
Determinism and snapshots — the honest posture (§33, §34)
A GPU-simulated system's positions and velocities live on the device. They sit outside every §33 tier this engine claims: the kernel's arithmetic order is fixed, but WebGPU promises no cross-adapter float reproducibility, and device f32 arithmetic differs from the CPU integrator's binary64 intermediates by rounding — so GPU simulation is display-tier motion. No §33 golden may checksum a GPU pool, and none does. §34 follows: there is no particle snapshot surface today, this packet deliberately adds none for GPU emitters — a CPU-side snapshot would capture spawn-stale lanes and restoring it would be a lie — and a readback-based snapshot is its own future packet with its own determinism argument (the GPU-readback RFC the R-1 plan names).
reset()stays valid: an empty pool needs no device state.Call contract (what the emitter guarantees the implementor)
Per
step(dt, time), in order: one ParticleGpuSimulation.integrate over the pre-expiry live count (skipped when that count or the delta is zero — both are identity steps), then zero or more ParticleGpuSimulation.moveSlot calls mirroring the pool's swap-remove compaction exactly (never withfrom === to), then one ParticleGpuSimulation.writeSpawn per successfully spawned particle. All indices are pool slots in[0, capacity).