Falling-sand simulations: materials, reactions, and efficient plain-JS implementation

Scope: how falling-sand cellular automata (Noita, The Powder Toy, Sandspiel, Sandboxels, the classic Falling Sand Game and Powder Game) model many materials, and how to build one in plain JavaScript at about 160x120 to 320x180 on phones, for Tycho (which can push a pixel buffer each frame through drawPixels).

How the sources were read:

1. Materials and their properties across the major games

Takeaway

Every game sorts materials into a few movement classes: empty, static solid, powder, liquid, gas, plus special “living” or “energy” cells such as fire. A small set of numbers then sets each material apart:

Reactions are either data tables of (A touches B, then A becomes X and B becomes Y, with chance p) as in Sandboxels, or hand-written per-element update functions as in Sandspiel and The Powder Toy. Noita adds two things the toys lack: free-flying particles off the grid, and destructible rigid bodies.

Cited Findings

Sandspiel (Max Bittker), the minimal model

The Powder Toy (TPT), a numeric property schema per element

Sandboxels (R74n), data-driven elements

Noita (Nolla Games, the “Falling Everything” engine)

Genre history (classic Falling Sand Game, Powder Game)

Inferences

Gaps

2. Reaction tables (with probabilities)

Takeaway

The sources agree on a core set of reactions, and give real per-tick probabilities:

Typical probabilities: 1/50 to 1/2000 per tick for slow dissolving or growth, 5–10% for acid and burning, and 100% for instant ones such as lava plus water.

Cited Findings

Water and lava make stone and steam

Sand and heat make glass

Acid dissolves (almost) everything except glass

Salt, water and salt water

Oil floats on water and burns

Fire, burning, ash and smoke

Plants grow into water

Ice melts near heat; water freezes

Steam condenses

Other good reactions

Demo material table (26 materials)

This table is a synthesis for a Tycho demo, not a copy of any one game.

# Material Class Weight Liquid dispersion / viscosity Ignite chance; burn time Melts or boils (°C) → Freezes or condenses (°C) → Key reactions (chance per tick) Source basis
1 Empty / air gas (void) 0 – – – – holds ambient heat all
2 Wall / bedrock static ∞ – none none – immune to acid Sandspiel Wall
3 Stone static (or powder when knocked loose) 90 – none 950 → lava – flowing water erodes to sand (1/1000); acid eats (1/1000) TPT STNE, Sandboxels rock
4 Sand powder 90 – none 1700 → molten glass – water → wet sand (100%); acid (1/1000) Sandboxels sand, TPT SAND
5 Wet sand powder (no diagonal slide, sturdy) ~95 – none 100 → sand + steam – dries in heat Sandboxels wet_sand
6 Dirt powder ~85 – none – – water → mud (100%) Sandboxels dirt
7 Mud sturdy powder ~100 – none 100 → dirt or mudstone + steam – – Sandboxels mud
8 Water liquid 30 dispersion 4–5, visc 0 extinguishes fire 100 → steam 0 → ice salt → salt water (1/50); lava → stone + steam; fire → steam (1/30) TPT WATR, Sandboxels water
9 Salt water liquid 35 dispersion 4 extinguishes 102 → steam + salt −2 (Sandboxels) / −21 (TPT) → ice slowly salts water (1/2000) TPT SLTW, Sandboxels
10 Salt powder 75 – none 801 → molten salt – water → salt water (1/50, then 1/3 for salt); ice → salt water (10%) TPT SALT, Sandboxels
11 Ice static 100 – none 5 → water – freezes adjacent water (7%); fire or lava → water Sandspiel Ice, Sandboxels
12 Snow powder ~40 – none 18 → water – salt → salt water (25%) Sandboxels snow
13 Steam gas (rises, diffuses) 1 – none – 95 → water two steam near ceiling → cloud (5%) TPT WTRV, Sandboxels
14 Oil liquid (floats on water) 20 dispersion 2, visc 250 (≈25% sideways) 2%; burns about 50–300 ticks 500 → fire (TPT: 60 → gas) – burning oil + water → flare; floats TPT OIL, Sandboxels, Sandspiel
15 Lava liquid (thick, hot) 45 dispersion 1, visc 10000 (10% sideways) ignites flammables – below 800 → stone or basalt water → stone + steam (100%); melts sand to glass; 2.5% spawns fire above Sandboxels magma, TPT LAVA
16 Molten glass liquid (thick) ~50 visc high ignites – below 1500 → glass – Sandboxels, TPT lava(glass)
17 Glass static 100 – none 1500 → molten glass – acid-proof Sandboxels glass, TPT GLAS (Hardness 0)
18 Acid liquid 10 dispersion 3 4% (TPT Flammable 40) – – dissolves non-glass, non-wall neighbors (TPT: Hardness/1000; Sandboxels: 5–10%); about 25 dissolves then dies; makes heat; acid + gunpowder → fire TPT ACID, Sandboxels acid
19 Wood static 100 – 2% (TPT) / 5% (Sandboxels); burn 90 (Sandspiel) to 300 (Sandboxels) ticks 400 → fire – burns into ember, charcoal or ash, giving smoke all three
20 Plant static (grows) 100 – 2% / 15%; burn 20–60 ticks 100 → dead plant (Sandboxels); 300 → fire (TPT) −2 → frozen plant grows into adjacent water (1/50 TPT; about 19% Sandspiel); salt kills (0.1%) TPT PLNT, Sandspiel, Sandboxels
21 Seed powder → grows ~80 – burns – – lands on sand or dirt: germinates, stem grows up (24%), then flower Sandspiel Seed
22 Fungus / moss static (spreads) 100 – burns 10 ticks – – creeps over any solid surface; eats wood Sandspiel Fungus
23 Fire gas-like (rises, short life) 2 – – – life ends → smoke ignites neighbors by flammability; killed by water TPT FIRE (life 180–259), Sandspiel (ra decays 2/tick), Sandboxels (burnTime 25 → smoke)
24 Smoke gas (dissipates) 1 – none 1000 → fire – deletes itself 5% per tick Sandboxels DGAS, TPT SMKE
25 Ash / ember powder (ember: brief glowing gas) ~70 – ember is burning – ember below 0 → ash water → dirty water Sandboxels ash, ember
26 Gunpowder powder 85 – 60% (TPT 600/1000) / 100% (Sandboxels); burn 1 tick 600 → explosion – ignites to explosion with radius (Sandboxels EX:10); acid → fire TPT GUNP, Sandboxels
27 Methane / swamp gas gas 1 – 60–85%; burn 5 ticks 537 → fire – burns with blue-cyan fire, a flash fire TPT GAS (Flammable 600), Sandboxels methane
28 Metal static (conducts) 100 – none 1000 (TPT METL 1273 K) → molten metal – carries electricity (spark life 4); HeatConduct 251 TPT METL

Inferences

Gaps

3. Implementation techniques

Takeaway

The standard CPU design is a single in-place grid:

The alternatives:

Cited Findings

Update order, bias and clock bits

Margolus / block cellular automata

Double buffering versus in-place

Noita: chunks, dirty rectangles, lock-free threading

Sandspiel: WASM cells plus a GPU wind field

Per-cell color variation stored at spawn

Fire and light rendering

Velocity and momentum for particles

Temperature fields

Inferences

Gaps

4. Performance in plain JS on phones

Takeaway

No source gives a measured “cells per frame in plain JS on a phone” figure. The best anchors:

At 160x120 (19,200 cells) to 320x180 (57,600 cells), a typed-array JS sim with sleeping chunks is plausible on phones (an inference, not a measurement). Bit-packed cells, no per-cell objects, and no per-frame string or color parsing are the essentials.

Cited Findings

Inferences

Gaps

5. Game design: what makes the toy fun, and how games use it

Takeaway

The toy is fun because of emergent chains between simple, legible rules: fire spreads to oil, which boils water to steam, which condenses to rain, which grows plants that then burn. One-tap brushes, an immediate audience, and low-fidelity art that invites imagination help too. Games built on it (Noita, Sandustry) succeed by making the simulation the main verb, then constraining it:

Cited Findings

Inferences

Gaps