The bitmap to step.
Optional tick counter that controls L/R alternation.
Pass an incrementing integer (e.g. frame counter) so
the bias flips each call. Default 0 — fine for
one-shot tests but produces a slight bias if called
repeatedly without changing.
One-tick cellular-automaton step over the supplied bitmap.
Iterates the bitmap's sparse ChunkedBitmap.activeCells set — only cells that might have changed (or are known to have ongoing state like a fire timer or sand rest counter) are processed. The set is maintained automatically: every ChunkedBitmap.setPixel call adds the changed cell and its 8 neighbors so external carve / deposit / paint ops AND the sim's own swap-mutations keep activation propagating organically. Cells that didn't move and have no ongoing state drop out of the set and don't return until a neighbor's mutation re-activates them — once a world reaches steady state,
stepbecomes a no-op.On the very first call the bitmap is scanned once (ChunkedBitmap.enableActiveCellTracking) to seed the set with cells placed before tracking was enabled.
Within each tick the snapshot is processed bottom-up (
y = H-1 → 0) so material falling from rowycan't be re-processed in rowy+1. Side preference is per-cell (goRight === (x is even)), so contiguous fluid blocks spread symmetrically instead of shifting en masse. Thetickparameter flips a global L/R bias each call so a body of fluid alternates its preferred side, killing residual asymmetries.Five mobile fluid kinds are implemented; their behavior is parameterised over a single generic
stepFluidhelper.'sand'— falls straight down (density swap with any lower-rank fluid below), slides diagonally into pure air. No horizontal flow. OptionallysettlesToa static variant aftersettleAfterTicksstationary ticks (v2.2 bridge).'water'— falls straight down (density swap), diagonal into air, multi-cell horizontal flow into air. Spread per tick isMaterial.flowDistance(default4); pools level off over ~width / flowDistanceticks.'oil'— like water but rank 3 (< water rank 4): can't displace water on a fall, so oil floats on water.'gas'— rises straight up (density swap), diagonal-up into air, horizontal spread into air. Bubbles up through liquids since gas rank 0 < liquid ranks.'fire'— stationary. Each tick: ignites the first adjacentflammableneighbor it finds (top, sides, bottom); the new fire cell starts at timer 0. Increments its own timer and dies (→ air) at theburnDurationthreshold. Stays in the active set until it dies regardless of whether anything flammable is nearby.Density ranks for vertical swaps:
Static materials never swap (regardless of rank). Vertical swaps only happen between different ranks, with the heavier ending up deeper — for downward motion that's
srcRank > targetRank, for upward motionsrcRank < targetRank. Diagonal slides and horizontal flow are air-only — the swap bookkeeping stays single-cell (no mid-flight three-cell shuffle).Cost: O(N log N) per tick where N is the number of currently- active cells (the log factor is the snapshot sort that orders rows bottom-up). For a mostly-settled world the active set drops to zero and
stepreturns immediately. For a continuous pour it scales with the moving cells, not the world dimensions.The bitmap is mutated in place. Affected chunks are dirtied via the bitmap's regular
setPixelpath, so a subsequentTerrainRenderer.repaintDirty()picks up the changes. Chunk- collider rebuilds are NOT triggered for fluid-only mutations because the rebuild path filters to static materials only — seechunkToContours/componentToContours.