Skip to main content

Module cellular

Module cellular 

Source
Expand description

The cellular system: a discrete cellular automaton stepped on a ping-pong grid (ADR-0012), with every rule space it runs as a named family selected by [cellular] family (ADR-0180 rule 1).

life_like is the birth/survival family: a dead cell with k live neighbours is born when bit k of birth is set, and a live one survives when bit k of survive is. Conway’s Life is birth = 8, survive = 12.

larger_than_life widens the neighbourhood to a box of side 2 * radius + 1 and replaces the masks with two intervals over the box’s filled fraction: a dead cell is born inside birth_lo..=birth_hi, a live one survives inside survive_lo..=survive_hi. The fractions are turned into whole counts on the CPU (interval_counts), so the shader compares integers. The box is summed separated — a row pass, then a column sum in the step pass — at 2 * (2r + 1) reads a cell rather than (2r + 1)^2, and radius is capped by the tier on top of that (TierConfig::cellular_radius).

cyclic is the cyclic automaton: a cell holds one of states colours and advances to the next round the cycle when at least threshold of its eight neighbours already hold it. There is no dead state, so every cell is lit and its palette coordinate is its state index over states, with no remap.

§The grid is content, not a resolution

[cellular] grid is how many cells a side holds, and a pattern is a fixed number of cells — a glider is five — so the grid decides how large every pattern looks. It is declared by the preset and never follows the window. The tier caps it (TierConfig::cellular_grid), and because the cap changes content rather than density, a grid clamped to it is announced — returned from configure as a CapOverflow — never silently reduced. A bound radius past the tier’s cap is announced the same way, per frame, through Scene::mirror_overflow. The present is a plain normalized stretch of the grid over the target and computes no screen-destined geometry, so there is no aspect for it to take from the wrong place (ADR-0037).

§Generations, not frames

step_rate is in generations per second. GenerationClock integrates it over the injected dt and hands render a whole number of generations to run, so the automaton advances by the same count in the same wall time at any refresh rate. Because an automaton is path-dependent, two runs at different rates diverge for good — which is the meaning of the parameter, not a defect of it.

§Every cell is drawn from the seed

The field is seeded, and every reseed refills a disc of it, from an integer hash of the cell’s coordinates and a seed derived from the preset’s pinned salt (ADR-0051) — never from a clock. The hash is u32 arithmetic, exact on every adapter, and each rule step is integer counting over exact texel values, so a field is a pure function of its config and the sequence of bound values and dts it was driven with.

§The age channel

A binary field reads as noise; what reads as structure is its history. So the state texture’s second channel counts the generations since each cell last changed state, and the present paints a dead cell by it: it fades out over trail generations while sliding age_tint of the way along the palette. trail = 0 is the binary field exactly.

GPU resources are built lazily, on first render, as the reaction-diffusion scene’s are and for its reason: a capture that never activates this scene never builds them, so the WARP software adapter the golden suite captures on never holds them beside another scene’s.

Structs§

CellularConfig
[cellular] — the structural configuration, fixed while the preset is loaded and delivered through Scene::configure.
CellularScene
A discrete cellular automaton on a ping-pong grid, driven by named preset parameters and one [cellular] table.

Enums§

CellularFamily
Which rule space the automaton runs (ADR-0180 rule 1).

Constants§

DEFAULT_GRID
The grid an absent [cellular] grid means: reaction_diffusion’s own 256, for the same reason that scene pins it — pattern scale is content.
FAMILY_PARAMS
Every parameter only some families read (ADR-0180 rule 4), with the range that reads there — so the generated reference prints birth as life_like’s and inert elsewhere. A parameter missing from here reads the same on every family.
MAX_GENERATIONS_PER_FRAME
The most generations one frame encodes. A stall would otherwise queue unbounded passes (the accumulator spiral ADR-0012 names); past this the backlog is dropped and the automaton briefly slows rather than racing. At 30 fps it still carries 240 generations a second.
MAX_GRID
The largest grid the loader accepts: a million cells, two 8-byte texels each across the ping-pong pair, 16 MB.
MAX_RADIUS
The largest radius any tier lets a larger_than_life preset ask for — the top of its declared range, and the constant the row and column loops run to.
MAX_RULE
The largest rule bitmask: one bit for each neighbour count 0..=8.
MAX_STATES
The most colours a cyclic cycle may hold. A half float holds every state index exactly far past this; the bound is where a cycle stops reading as a cycle across a 256-texel palette.
MAX_STEP_RATE
The fastest step_rate the clock integrates, in generations per second. Well past the declared range’s top, so it bounds a runaway binding rather than an author.
MAX_THRESHOLD
The most neighbours a cyclic threshold can ask for: all eight.
MAX_TRAIL
The longest trail the present reads, in generations: the age channel’s own ceiling, since a wake cannot outlast the count it is read from.
MIN_GRID
The smallest grid the loader accepts. Below it a glider’s wake meets its own head on a torus before it has read as motion.
PARAMS
The parameter names this scene consumes — the vocabulary a preset binding is checked against at load (ADR-0020). Keep in sync with set_param below; declared_params_match_set_param in core/tests/suite/preset.rs fails if the two drift.