Phlux is a tiny rule language for cellular automata. A rule answers exactly one question — given this cell and its neighbors right now, what should this cell become next step? Write that as an assignment to a color channel, and the grid does the rest.
R = {AVG, (-1:1,-1:1).R} // red spreads to its neighbors. that's a rule.
Not a general-purpose language. Phlux does one thing: it describes how a grid of cells changes, one step at a time. That narrowness is the point — the useful rules are one to five lines long, and you can read someone else's rule in about as long as it takes to read a sentence.
There is no separate simulation buffer hiding behind the canvas. Each cell's
R G B
A channels are its state, in the 0–255 range. Paint a
pixel and you have edited the simulation.
Read yourself with R, a neighbor with
N.R, a whole region with
{AVG, MOORE.R}. Everything complex on this page comes out of
local reads — no cell has a view of the whole grid.
Rules that only use channels, maths and area reductions compile to a shader automatically. Reach for randomness, functions or scatter and it falls back to the CPU — same result, just slower on big grids.
Every animation below is being computed in your browser right now from the rule printed underneath it — these are not recorded GIFs. Each is the complete rule, not an excerpt.
The classic, in two lines. MOORE is the 8 surrounding cells, so
COUNTNZ is literally "how many neighbors are alive".
LET _n = {COUNTNZ, MOORE.R}
_n == 3 | (_n == 2 & R > 0)
? [255,255,255,255] : [255,0,0,0]
Spawn drops along the top edge, move them down, and let BASE —
the original untouched pixel — restore the scene behind them.
PARAM _rate = 6
Y == 0 ? (RN < _rate ? [255,90,150,255] : [BASE]) :
(N.B > 230 & N.R < 120 ? [255,90,150,255] :
(B > 230 & R < 120 ? [BASE]))
Gray–Scott, with the two chemicals living in R and
G. The bracketed term is the discrete Laplacian.
LET _f = (255 - R) * G * G / 65025
R = R + .2 * (N.R+S.R+E.R+W.R - R*4) + _f - .035*R
G = G + .1 * (N.G+S.G+E.G+W.G - G*4) + _f - .095*G
B = {SAT, 70 - R - G}
A wave needs to know where it was, so G is used as a
time-delay buffer: it holds R from one step ago.
LET _r = R - 128
LET _p = G - 128
LET _l = N.R+S.R+E.R+W.R - 4*R
LET _n = (2*_r - _p + .4*_l) * .9985
G = R
R = {SAT, _n + 128}
B = {SAT, 128 + (_r - _p) * 3.5}
Most rules only write to themselves. N.R += writes into a
neighbor — so a toppling pile pushes grains outward.
LET _t = {FLOOR, R / 256}
_t > 0 ? N.R += _t * 64
_t > 0 ? S.R += _t * 64
_t > 0 ? E.R += _t * 64
_t > 0 ? W.R += _t * 64
R = R - _t * 256
An empty cell takes the mean color of its living neighbors plus a small mutation. Color becomes a heritable trait, and lineages drift apart.
LET _v = {COUNTNZ, MOORE.R}
LET _r = {SUM, MOORE.R} / _v
LET _g = {SUM, MOORE.G} / _v
LET _b = {SUM, MOORE.B} / _v
R+G+B < 40 & _v > 0 & RN < 24 ?
[255, _r + RN*.22 - 28,
_g + RN*.22 - 28,
_b + RN*.22 - 28]
Why the browser versions look slightly different. These canvases are a small JavaScript re-implementation written for this page. The real engine runs at 16-bit precision per channel and on much larger grids, so a rule that decays slowly will hold detail there that gets crushed here. The browser demo runs the actual engine.
Phlux started as a way to animate pixel art without drawing frames, and grew into a general cellular-automata language. Both uses are first class.
Want to see finished pieces rather than rules? Browse 62 animated examples →
This is all of it. Phlux has no imports, no classes, no loops and no I/O — the grid loop is the program, and your rule is its body.
Every cell holds four values in the 0–255 range. Reading gives the current value; assigning sets what the cell becomes next step.
| Token | Meaning |
|---|---|
R G B A | Red, green, blue and alpha of this cell. |
R = G // copy green into red
A = 255 // force fully opaque
Channels are just numbers. Nothing forces them to mean color. Use
G as a velocity, an age counter, a chemical concentration or a
previous frame — several examples on this page do exactly that.
Prefix a channel with a direction to read the cell next door.
| Token | Offset | Token | Offset |
|---|---|---|---|
N | (0, −1) | NE | (+1, −1) |
S | (0, +1) | NW | (−1, −1) |
E | (+1, 0) | SE | (+1, +1) |
W | (−1, 0) | SW | (−1, +1) |
| Form | Meaning |
|---|---|
(dx, dy).R | Relative offset — (2,-1).R is two right, one up. |
[x, y].R | Absolute coordinate. |
BASE.R | This cell's original value — what Reset restores. |
TAG | The cell's painted tag, 0–15. A label you can branch on. |
Out-of-bounds neighbors read as the edge, unless wrap (toroidal) is on — then they come from the opposite side.
| Token | Meaning |
|---|---|
X Y | This cell's coordinate. R = X is a gradient. |
GW GH | Grid width and height, for normalized coordinates. |
TICK | The step counter, 0, 1, 2 … Reset returns it to 0. Drives time-based animation. |
RN | A fresh random 0–255, per cell, per step. Seedable for reproducibility. |
BASE | The original design state of this cell. |
R = {WAVE255, TICK * .2} // pulse without drawing frames
RN < 3 ? G = 255 // ~1% of cells light up each step
arithmetic + - * / %(mod) ^(power) $(root) unary minus: -R
compare == != < > <= >=
logical & (and) | (or) ! (not)
group ( … )
Comparisons return 1 or 0, so they work directly in arithmetic —
R = R * (G > 128) zeroes red unless green is bright.
Two things that bite people. Unary minus binds tighter than
^, so -2 ^ 2 is 4, not −4. And division
or mod by zero yields 0, not infinity or NaN — deliberately, so a rule can't
poison a grid with NaN.
| Form | Meaning |
|---|---|
R = expr | Set the channel. |
R += / -= / *= | Compound assignment. R *= .95 is a fade. |
[A, R, G, B] | Color literal — assign the whole cell at once. An empty slot keeps the current value. |
[BASE] | Restore the entire original cell. |
[{SAMPLEC, dx, dy}] | Read the whole current pixel at an offset — this is what makes feedback trails possible. |
a ; b | Several statements. A newline separates them too, so ; is optional at line ends. |
// comment | To end of line. |
The ternary ? : works two ways — as a value inside an
expression, and as a statement whose branches do something.
R = R > 128 ? 255 : 0 // value form
R > 128 ? [255,255,255,255] : [255,0,0,0] // statement form
RN < 3 ? G = 255 // else is optional
Omit the : and cells that fail the test are simply left
unchanged. Nest by parenthesising a branch:
a ? x : (b ? y : z).
An area selector scans a rectangle of neighbors.
| Selector | Meaning |
|---|---|
(-1:1, -1:1) | A 3×3 block centered on this cell. |
(-2:2, 0:0) | A horizontal line of 5 cells. |
MOORE | The 8 surrounding cells — the 3×3 block without this one. |
That last one is what life-like automata mean by "my neighbors", so the classic live count is simply:
LET _n = {COUNTNZ, MOORE.R}
The terse prefixes +area.R (sum),
#area.R (count) and -area.R (range)
exist as shorthand, but the named functions below read better and compile to the GPU just
the same.
Call as {NAME, arg, …}.
Over an area
| Function | Meaning |
|---|---|
{AVG, a.ch} | Average — blur, smoothing. |
{SUM, a.ch} {COUNT, a} | Sum, and number of cells. |
{COUNTNZ, a.ch} | Count of non-zero cells — alive neighbors. |
{MIN, a.ch} {MAX, a.ch} | Extremes. Morphology: erosion and dilation. |
{RANGE, a.ch} | max − min — edge strength. |
{MEDIAN, a.ch} | Median — removes salt-and-pepper noise. |
{STDDEV, a.ch} {VAR, a.ch} | Spread — texture and edge detection. |
{LAPLACE, a.ch} | Discrete Laplacian — diffusion. |
Maths & helpers
| Group | Functions |
|---|---|
| Range | CLAMP SAT SAT01 LERP MIX REMAP REMAP01 |
| Numeric | MIN MAX ABS SIGN FLOOR CEIL ROUND FRAC |
| Power | SQRT SQ POW EXP LOG |
| Trig | SIN COS TAN ATAN2 RAD DEG |
| Shaping | STEP SMOOTH SIGMOID GAIN GAMMA THRESHOLD PULSE |
| Color | LUMA INVERT BRIGHTEN OVER SCREEN MULT ADDSAT |
| Space | DIST LEN MANHATTAN NOISE NOISE255 |
| Timing | SAW PINGPONG PROG EASEIN EASEOUT EASEINOUT IMPULSE SWING |
| Sprite | SAMPLE SAMPLEC ROTDX ROTDY SCALEDX SCALEDY |
You can define your own with the same {NAME, …} syntax, either
globally or per project.
Normally a rule writes only to itself. Scatter lets a cell deposit into its neighbors.
Only += and -= are allowed: many
cells may target the same place, so deposits accumulate, which keeps the
result order-independent and deterministic.
N.R += R * .5 // push half my red upward
(1,1).G += 10 // into the cell down-right
N.G += 60 WHERE R < 25 // only if the RECEIVING cell is dark
This is the one construct that inverts the usual direction of a cellular automaton, and it is what makes falling sand, particle deposition and flow accumulation expressible.
LET and PARAMLET freezes a value for the rest of this cell's evaluation —
essential when you use RN twice and need the same number both
times, or when several channels must read the same snapshot.
LET _n = {COUNTNZ, MOORE.R} // compute once, use many times
_v = 12 // LET is optional for _names
PARAM _rate = 5 // a live knob with a default
PARAM is the same thing with a dial attached: it appears as a
slider while the simulation runs, and the timeline can keyframe and interpolate it — so
rain can get heavier over a loop without touching the rule.
You never choose. A rule compiles to a shader when it can, and silently falls back otherwise — the result is the same either way.
| Runs on GPU | Falls back to CPU |
|---|---|
| Channels, neighbors, maths | RN randomness |
| Conditions and color literals | Scatter |
Area reductions — AVG SUM COUNT COUNTNZ MIN MAX RANGE | BASE, LET, PARAM, TAG |
| Folds and wrap | MEDIAN, STDDEV, LAPLACE, user functions |
The precision difference is real. The GPU path is 8 bits per
channel per step, so R = R * 0.98 reaches zero much faster there
than on the 16-bit CPU path. For slow decays, that difference is visible.
A rule is either global — it runs on every cell in the grid — or it is painted onto specific cells with the rule brush and runs only there. Sweeping effects like weather are global; a rule that makes one torch flicker is not.
A rule can also carry a separate condition that gates it entirely, which is often cheaper and clearer than wrapping the whole body in a ternary.
Short rules that do a surprising amount. Copy any of them straight into a rule editor.
[{AVG, (-1:1,-1:1)}]
Average the whole 3×3 cell and assign it back. Runs on the GPU at any grid size.
R = {RANGE, (-1:1,-1:1).R}
G = R
B = R
Max minus min over the neighborhood is high exactly where the image changes.
[{SAMPLEC, -1, 0} * .92]
Read the pixel to the left of the current picture and dim it. Always multiply by a decay — feedback accumulates.
LET _n = {COUNTNZ, MOORE.R}
_n == 3 | (_n == 2 & R > 0)
? [255,255,255,255]
: [255,0,0,0]
Change the numbers and you have HighLife, Seeds, Day & Night — any B/S notation.
[{MIX, BASE, .06}]
Every effect that damages a scene wants this: the artwork slowly heals back to what you painted.
RN < 2 ? [255,255,255,255]
R *= .88
G *= .88
B *= .88
Rare bright pixels plus a global decay is the whole of glitter, embers and starfields.
Phlux runs inside Phluxel, a cellular automata and pixel animation editor for Windows. The browser demo needs no install and no account — paint a few pixels, type one line, press play.
Phlux is a small domain-specific language for describing cellular automata. A Phlux rule takes one cell and its neighborhood and produces that cell's next state, written as assignments to its color channels. It is the rule language of Phluxel, an editor for cellular automata and pixel art animation.
No, and deliberately so. There are no loops, functions with side effects, imports, file access or network calls. The only control flow is a conditional. The grid iteration is the loop, and your rule is its body — which is why useful rules stay one to five lines long.
No. The most common first rule is one line —
R = {AVG, (-1:1,-1:1).R} — and the editor ships with a library of
ready-made rules you can apply with one click and then edit. If you can read a spreadsheet
formula you can read Phlux.
Phlux is the language; Phluxel is the application you write it in. Phluxel provides the canvas, the drawing tools, the simulation engine, the timeline and the GIF, MP4, APNG and sprite sheet exporters. Phlux is the part you type.
Mostly in how much is already decided for you. There is no grid allocation, no double-buffering, no neighbor indexing, no boundary handling and no render loop — all of that is the runtime's job. You write only the part that differs between one automaton and another.
The other difference is that the canvas is directly editable. You can pause a simulation, paint into it with a brush, and resume — which is awkward when your state lives in a numpy array or a texture you have to round-trip.
When it can. Rules using only channels, neighbors, arithmetic, conditions
and area reductions are compiled to a fragment shader automatically. Randomness, scatter,
BASE, LET,
PARAM and user-defined functions run on the CPU instead. You do
not choose or annotate anything; the editor shows you which path a rule took.
For anything that fits a 2D grid with four channels per cell, yes — life-like rules, cyclic automata, excitable media, reaction–diffusion, sandpiles and totalistic rules are all straightforward. It is not built for 1D elementary automata, hexagonal or non-rectangular lattices, or continuous-space models.
The browser demo runs the real engine in WebAssembly. The free desktop edition for Windows has every feature, with a watermark on exports.