Skip to content
Русский

The [voxel] table

A voxel preset runs a cellular automaton in three dimensions: a cube of cells, each generation every cell deciding its next state from how many of its neighbours are live. The cube is drawn as a glowing volume: a ray from the camera gathers the light of every cell it crosses, and each cell also absorbs some of the light from behind it. [voxel] picks the rules, the cube and how it starts:

system = "voxel"
[voxel]
grid = 64 # cells per side, 16..=128; the default is 64
rules = ["clouds", "coral"] # 1..=8 rules, picked by the `rule` param; default ["clouds"]
seed_radius = 1.0 # the seeded ball's radius, in half-sides, 0..=2
seed_fill = 0.6 # the chance a cell in that ball starts live, 0..=1
wrap = false # false (the default): every cell past a face is dead
shells = 8 # radial shells the spectrum lights, 0..=16; default 0
present = "glow" # "glow" (the default) or "solid"
KeyValuesNotes
gridinteger 16..=128Cells per side. Like [cellular] grid it is content, not resolution: doubling it halves how large every structure looks. The tier caps it, and the app says so.
rulesa list of 1 to 8 entriesEach entry is a roster name (below) or an inline rule. The rule parameter picks one by its place in the list, counting from 0.
seed_radius0..=2The radius of the ball of cells the cube starts with, as a fraction of its half-side; 2 covers the whole cube. Absent, the first rule’s own.
seed_fill0..=1The chance each cell inside that ball starts live. Absent, the first rule’s own.
wraptrue / falsetrue makes the cube a torus, so a structure crossing a face comes back on the opposite one. Default false.
shellsinteger 0..=16How many radial shells the spectrum lights, bass at the centre and treble at the faces. 0 lights none, and shell_gain is then inert.
present"glow" or "solid"How a live cell is drawn: as glowing light (the default), or as a lit solid cube (below).

Every key is optional and so is the table: system = "voxel" alone runs clouds in a 64-cell cube. Every key is read once, at load, and none is bindable. A grid, a seed or a shell count out of range, an empty or over-long list, an unknown roster name, and an inline rule out of range are load errors, and a rule’s error names its entry.

Every cell is drawn from the seed. The starting ball, and every ball a reseed refills, is hashed from the preset’s [generator] seed, never from a clock, so the same preset driven by the same music runs the same history.

A rule is two lists of counts

A cell is dead, live, or decaying. A dead cell with a count of live neighbours in birth is born; a live cell with a count in survive stays live; a live cell that does not survive starts to decay, steps one stage further each generation, and is dead again after its last stage. Only a live cell counts as a neighbour. An inline rule writes this out:

[voxel]
rules = [
"clouds",
{ birth = [4], survive = [4], states = 5, neighbourhood = "moore" },
]
KeyValuesNotes
birthintegersRequired. The live-neighbour counts at which a dead cell is born.
surviveintegersRequired. The counts at which a live cell stays live.
statesinteger 2..=255Dead, live and the decay stages. 2, the default, has no decay stages.
neighbourhood"moore" or "von_neumann"moore (the default) counts the 26 cells sharing a face, an edge or a corner; a count runs 0..=26. von_neumann counts the 6 sharing a face; a count runs 0..=6.

The 3D catalogues write a rule as survive/birth/states/neighbourhood, 4/4/5/M, which is the reverse of the B…/S… order 2D Life uses. The roster below gives both lists by name, so nothing needs translating.

The roster

Every roster rule is held by the test suite to a live fraction strictly between 0 and 0.9 of a 32-cell cube at generations 100 and 400, grown from its own seed. A list that opens with a roster rule takes that rule’s seed when seed_radius and seed_fill are absent.

NamebirthsurvivestatesNeighboursSeed (radius, fill)What it does
445445Moore0.35, 0.2A sparse ball that burns out to a few still cells.
amoeba5-7, 12-13, 159-265Moore2.0, 0.5A sparse field that swells into a mass filling most of the cube.
builder4, 6, 8-92, 6, 910Moore0.5, 0.5A burst of long-decaying growth that leaves a still scatter of cells behind.
clouds13-14, 17-1913-262Moore1.0, 0.6A dense field that settles into rounded clouds. The default.
coral6-7, 9, 125-84Moore0.5, 0.5A slowly churning reef with a skin of decaying cells.
crystal1, 30-62von Neumann0.2, 0.3A small seed growing into a lattice that fills most of the cube.
pyroclastic6-84-710Moore0.5, 0.3A restless, never-settling foam of cells and long decay.
slow_decay13-261, 4, 8, 11, 13-265Moore1.0, 0.5A dense field that freezes almost at once.

Most 3D rules settle. 445 and builder burn out to a still residue of a few cells, and clouds, amoeba, crystal and slow_decay reach a shape and hold it. Only pyroclastic and coral keep moving. reseed is how a cube stays alive, as on cellular: bind it to an onset or a beat and every ball of fresh cells it drops grows again.

Switching rules on the bar

rule is a structural parameter: it is rounded, it picks an entry of rules by its place, and past the end of the list it runs the last. A new rule takes effect at the next generation. Bound to audio it would re-pick every frame, so hold it on a musical edge:

[voxel]
rules = ["coral", "pyroclastic", "clouds"]
[params]
rule = "mod(bar_index, 3)"
[hold]
rule = "bar"

A cell in a decay stage past the new rule’s states falls dead on the switch, so changing from a rule with long decay to one with less can drop the volume’s haze at the bar line.

The music and the light

  • step_rate is generations per second, not per frame, so the automaton runs at the same speed at 30 fps and at 144.
  • reseed fills one ball of reseed_radius with fresh cells, once per rise past 0.5, at a centre drawn from the seed and the count of rises so far.
  • shell_gain lights the cube by the spectrum: with shells set, the band array is folded into that many concentric shells, bass at the centre, and each cell’s light is multiplied by 1 + shell_gain × level of its shell.
  • density is how strongly the volume absorbs the light behind it. At 0 it is pure additive glow, with no front or back: the far side shows through the near one. Higher gives the volume a surface and hides its inside.
  • trail and age_tint paint history. A decaying cell keeps trail of its light at each stage, so trail is inert on a rule with states = 2. age_tint slides a live cell along the palette as it stays live, and puts a decaying one at the far end.

Solid cells

present = "solid" draws every live cell as a cube with a lit surface instead of as light. A ray from the camera stops at the first live cell it meets and takes the colour of the face it entered through. The decay stages in front of that cell still glow and absorb as they do under glow, so a solid structure can sit inside a haze of its own debris; trail and density set how much of it there is.

[voxel]
present = "solid"
[params]
key = "0.4 + 1.6 * bass" # the key light's strength
ambient = "0.3" # the light every face gets
ao = "1" # how far the creases darken
light_yaw = "-0.6" # the light stands to the viewer's left...
light_pitch = "0.6" # ...and above

A face is shaded as its palette colour, times brightness, its shell’s light and the camera’s fog, times ambient × occlusion + key × (how squarely the face turns toward the light).

  • key is one directional light. A face turned straight at it takes all of key, one edge-on or turned away takes none.
  • ambient is the light every face gets whichever way it turns. With key = 0 and ambient = 1 every face reads its own palette colour.
  • ao is the ambient occlusion: a face darkens toward the creases and inner corners where it meets other cells, which is what makes a cluster of cubes read as a structure. It darkens the ambient term only. 0 is none.
  • light_yaw and light_pitch place the light relative to the camera, in radians: (0, 0) shines from the eye, a positive light_yaw moves it to the viewer’s right and a positive light_pitch above. The light turns with an orbit, so the lit side stays lit. It is not fixed on the world.

These five are inert under glow, and the reference marks them so.

A lit face blooms like any other light. Nothing marks it as reflected light, so a face brighter than the bloom threshold blooms, and a bright enough solid preset can glow until it reads emissive again. Whether the faces bloom is a choice made through brightness and key: keep them under the threshold for crisp cubes.

Trails smear shading as if it were light. The trails stage reads a dark face as dim light, not as a surface, so on a turning camera it drags the shaded sides behind the lit ones. Leave trails off a solid preset unless that smear is the look.

Cells that light each other

bounce lets a solid face take light from the live cells in front of it, so a structure glows onto itself rather than only on screen. 0 is off and is the default, and the declared range runs to 2.

[params]
bounce = "1" # faces take their neighbours' light
bloom_amount = "0.3" # so the bloom can come down

What it reads: the live cells, gathered into a coarse field of blocks four cells a side, each holding how much of the block is live and how old its live cells are. The field is softened once, and each lit face samples it half a cell in front of itself. A face adds its palette colour at the neighbours’ mean age, times bounce, times how much light reached it, times brightness, its shell’s light and the camera’s fog. Decaying cells give no light to it. The colour is looked up when the face is drawn, so a bound hue or palette_mix moves the bounced light every frame. Only how much light there is, and its age, wait for the next generation.

  • It is solid-only. Under glow every cell already emits volumetrically, and bounce is inert there; the reference marks it so, like the five light parameters above.
  • It leaks through walls. The field has no notion of what blocks light, so a face on the far side of a thin slab is lit as readily as an open one. The light reaches about six cells. On thin structures this reads as translucency.
  • It is soft by construction. One block of the field spans four cells, so the light cannot pick out a single neighbour. A crisp one-cell glow is out of its reach.
  • The shell is the receiver’s. With shell_gain on, the bounced light a face gets follows that face’s own band, not the bands of the cells that lit it.
  • It composes with bloom. Bounced light is light like any other: a face it lifts past the bloom threshold blooms. With bounce doing the work inside the structure, a preset can lower its bloom_amount and keep the glow off the background.
  • It ignores the section plane. The field is built from every live cell, cut or not, so with cut bound a section face takes the light of the hidden cells behind it and glows. Following the plane would refill the field every frame a swept cut moves.

The field costs nothing until a frame binds bounce above 0. It is then built once and refilled each time the automaton steps or a reseed lands, so its cost follows step_rate rather than the frame rate.

Cutting the cube open

A dense rule drawn solid shows only its skin: the ray stops at the first live cell, so whatever the structure holds inside is hidden. cut hides every cell beyond one flat plane, so the inside shows as a section:

[params]
cut = "0.5 + 0.12 * sin(time * 0.4)" # sweep the plane through the middle
cut_yaw = "0.8" # which way the hidden side faces...
cut_pitch = "0.4" # ...and how far it tilts up

A dense lattice of small amber cubes on a dark ground, cut flat across by a tilted plane: the cut
face is a fine staircase of lit cube tops and sides, with the lattice's gaps showing as dark
pits

present = "solid", rules = ["crystal"], cut sweeping about 0.5 — the teaching preset docs/examples/voxel/section.toml

  • cut is how much of the cube the plane hides, measured along its facing: 0 hides nothing, 0.5 cuts through the centre, 1 hides everything. It is a parameter like any other, so it can sweep on time or open on the bass.
  • cut_yaw and cut_pitch turn the plane, in radians. At (0, 0) the hidden side is the cube’s +z half. cut_yaw turns it around the cube’s vertical axis, cut_pitch tilts it toward the top (negative toward the bottom).

The plane turns with the structure, not with the camera. It is held in the cube’s own frame, so an orbiting camera sees the same section from every side, and the cut side swings past the viewer once a turn. A sweep bound to time and an orbit bound to time can line up into a section that seems to stand still; give them different rates.

The section is stepped, at the size of a cell. A cell is cut whole or not at all, and a face behind the plane is lit as every face is, so a tilted section reads as a staircase of cubes, fine at grid = 64 and coarse at 16. Nothing caps the cut: a hollow structure shows its hollows. The occlusion treats a cut cell as empty, so a section face is not darkened by cells the viewer cannot see.

Only the view is cut. The automaton keeps running the hidden cells, and they still seed their neighbours, so a structure keeps growing across the plane as if nothing were there. cut works under glow too, where it opens a volume whose absorption hides its core.

The camera is the shared one: yaw, pitch, distance, fov and fog behave as on plexus. focus and aperture are inert, because the volume has no drawn primitive to blur, and the line systems’ solid parameter is not read here: a voxel is drawn solid by present = "solid" above.

grid is capped by the quality tier: the Floor tier allows 64, the Rich tier 128. A preset asking within 64 runs identically on both; one asking for more runs at the cap and says so. Author against 64. The volume is also drawn at a resolution the tier caps, then stretched to the window, which changes only how fine the picture is, never its shape.

The range that reads for each parameter is printed in the voxel table of Parameter roster.

Built from f4f807a at version 0.178.0. This site tracks main and is not versioned per release.