01 · Problem
What is it for?
Particle updates need deterministic boundaries and collision rules before rendering performance matters.
02 · Mental model
How should you think about it?
A grid step reads one generation and writes a new generation so each particle moves at most once.
Runs a deterministic CPU sand reference and bounded neighbor queries. The verified core conserves particles and uses snapshot updates. Full WebGL simulation, water and chunk synchronization remain future integrations.
03 · Control flow
What happens in which order?
- Read a snapshot of the grid.
- Visit cells in a documented order.
- Choose permitted destination cells.
- Resolve conflicts deterministically.
- Publish the next grid and render it separately.
04 · Building blocks
The important contracts
SandSimulation.step / snapshotAdvance one deterministic CPU step and read a copy.NeighborProvider.getNeighborsFilter positioned items within a bounded radius.05 · Build it yourself
Implement a small core
Start with this small independent implementation, then use the steps below to make its contract explicit.
- Read a snapshot of the grid.
- Visit cells in a documented order.
- Choose permitted destination cells.
- Resolve conflicts deterministically.
- Publish the next grid and render it separately.
const EMPTY = 0, SAND = 1;
function stepSand(grid: Uint8Array, width: number, height: number) {
const next = grid.slice();
for (let y = height - 2; y >= 0; y--) for (let x = 0; x < width; x++) {
const from = y * width + x, below = from + width;
if (grid[from] === SAND && grid[below] === EMPTY) {
next[from] = EMPTY; next[below] = SAND;
}
}
return next;
}06 · Verify
What should you test?
- Conserve particle count during a step.
- Keep cells within bounds.
- Verify deterministic conflict resolution and independent snapshots.
Try it yourself
Turn the first verification claim into a failing test, implement the smallest fix, then add a boundary case.
Conserve particle count during a step.
Compare your approach
Your check should fail when the stated invariant is deliberately broken, and pass again after the fix. Keep setup deterministic and assert an observable result.
07 · Limits
Trade-offs and failure modes
- CPU correctness does not imply GPU correctness.
- Water behavior and chunk boundaries need separate rules.
- GPU resources belong to an explicit lifecycle.
The verified core conserves particles and uses snapshot updates. Full WebGL simulation, water and chunk synchronization remain future integrations.
08 · Keep exploring
Source code and related ideas
yarn tsx packages/webgl-sand-engine/examples/basic.ts