GPU populations — engine v21 Use these reusable data tools for emitters, selection, tracking, local neighborhoods and gameplay events. All work on ordinary createGPUProgram buffers. No new per-particle GameObjects, no replacement for CS rigidbodies. Check getGPUCapabilities().gpuPopulations; refresh Game if false. Population operations require the WebGPU player and a paused program with no pending work. Await each operation before its consumer. Keep related data together positions: f32 ×3; velocities: f32 ×3; ids, active, selection, previousSelection, previousIds: u32 ×1, equal capacity. IDs identify particles; indices are temporary slots. Zero ID is hidden by the point renderer. Use fresh nonzero IDs when recycling. Reordering must include EVERY correlated buffer, including event history; otherwise you corrupt identity. GLSL/HLSL is not accepted as custom program source: existing compileGPUProgram uses WGSL fn update(i:u32). Assign identity and display GPU data await assignGPUIds('cloud', 'ids', 1); await showGPUPopulation('cloud', 'positions', 'ids', {active:'active', selection:'selected', radius:0.07, color:{x:0.1,y:0.6,z:1}, highlight:{x:1,y:0.55,z:0.08}}); Select a region without reading every position await selectGPUPopulation('cloud', 'positions', 'selected', {mode:'sphere', center:{x:0,y:0,z:0}, radius:2, active:'active'}); var selected = await collectGPUPopulation('cloud', 'selected', 'ids', 64); selected.records contains index and stable id; total counts all matches, dropped counts those beyond the cap. Order is unspecified. Choose threshold mode for scalar ranges, box mode for bounds. Remove inactive particles and leave reusable zeroed slots var result = await compactGPUPopulation('cloud', 'active', ['positions','velocities','ids','active','selected','previousSelection','previousIds']); result.total is the occupied prefix length. Capacity does not change. Your WGSL spawn pass initializes free slots and assigns fresh IDs; this toolkit does not invent an emitter policy. Sort with sortGPUPopulation(name, keys, channels) for stable ascending u32 keys; equal keys retain original order. Optional count processes only that prefix and leaves the rest untouched. scanGPUBuffer(name, source, destination) is exclusive u32 prefix sum with total and overflow; arithmetic wraps modulo2^32. Find local neighbors await buildGPUNeighborGrid('cloud','positions','ids',{active:'active',lower:{x:-5,y:-5,z:-5},upper:{x:5,y:5,z:5},cellSize:1}); var near = await queryGPUNeighbors('cloud',{x:0,y:0,z:0},2,64); Records include distance. This is a snapshot: rebuild after program stepping, writes, compaction, sorting or other population mutations. Stale queries fail. Grid upper bound is exclusive. No truncation inside a cell: linked lists include every valid item. Query returns at most256 records while total counts all matches. Not nearest-first; do not choose the first result as the nearest. Radius at most4cellSize; choose cell size accordingly. This is a bounded interactive query, not a new all-pairs fluid or gravity solver. React to entering/leaving a selection var changes = await getGPUPopulationEvents('cloud','selected','ids','previousSelection','previousIds',64); for (var event of changes.records) { if (event.kind === 'enter') indicator.vel.y = 2; } indicator is a normally loaded CS object. Never use a GPU slot number as a CS object name. These are sampled region transitions, not collision impulses; a particle crossing entirely between samples may be missed. First call reports already selected particles. Recycled IDs produce an old-ID exit plus a new-ID enter. Overflow advances history; dropped events are reported, not replayed. Poll after a completed simulation step, not on an overlapping interval. Reorder history channels with all other channels. Reset history on intentional restart. Limits and persistence Population count1..262144; global typed-buffer budget128MiB includes temporary work and grid; grid up to1048576 cells. Stable bitonic sort costs more than scan; use on demand, not unconditionally every frame. The8192 physics-contact cap is unchanged; these ordinary typed-buffer tools do not expand it. GPU instances/galaxy rendering have their own limits. Renderer is lightweight scientific point glyphs, not shaded water or PBR objects. The existing checkpoint system saves typed channels including IDs/masks/history; rebuild the derived neighbor grid and reattach visuals after restoring. All demo code reconstructs its buffers when Run is pressed. No scene-specific engine commands. Five complete student-readable recipes: catalog/examples/gpu-populations-lifecycle.js, selection.js, identity.js, neighbors.js, events.js. All experts can use these; Jeff for scientific data, Rowan for CS physics integration, Vikram for identity/bounds/overflow checks. Native and browser evidence: roadmap-batch10/RESULTS.txt. No RunPod required to play demos.