Pixel Simulation Art
A cellular automaton that grows winding, curling plants across a pixel grid.
Stems extend from a deterministic seed, drift and spiral via a per-cell curl,
split into branches, thicken over time, and sprout flowers — two species, each
with its own palette and bloom shape. The whole field is reproducible from a
single seed.
Controls
- Pause / Reset — stop the sim or restart from the current seed.
- Export — save and download the current frame as a PNG.
- Speed — simulation steps per second.
The starting seed is set by SEED in the config (see below) — edit it from the
Config panel and Apply for a different reproducible run.
Config
All tunables live in src/config.json. Values can be edited
live from the garden’s in-app Config panel (bottom-right) when embedded —
Apply sends them to the running sim, which re-initializes.
Grid
| Name |
Meaning |
GRID_W, GRID_H |
grid size in cells |
CELL_PX |
on-screen pixels per cell |
BACKGROUND |
page background (hex) shown behind transparent cells; also fills PNG exports |
SEED |
default RNG seed |
STEPS_PER_SEC |
default sim rate; the Speed slider overrides it |
Spawning & branching
| Name |
Meaning |
P_SEED |
per-cell spontaneous-spawn rate, scaled by tick² — flat early, then ramps; saturates near T ≈ √(1/P_SEED) |
P_FLOWER |
chance a tip blooms into a flower |
P_SPLIT |
chance a growing tip splits into a branch |
DELTA_SPLIT |
angle offset of a split-off arm |
SPAWN_CURL_RANGE |
spread of the random curl given to a new spawn |
Wave gating (keeps plants from merging into each other)
| Name |
Meaning |
WAVE_AGE_TOLERANCE |
max age difference for cells to count as the same growth wave |
WAVE_CHECK_RADIUS |
neighbourhood radius checked for foreign parts; sets the min air gap (1 → 3×3, 2 → 5×5) |
BRANCH_INTOLERANT_WAVE_CHECK_RADIUS |
tighter radius for a plant’s own main-vs-sub gap while thickening; applied while young, widening to WAVE_CHECK_RADIUS once mature (smaller = main & sub branches sit closer) |
Curl & drift (per growth step: angle += curl·CURL_GAIN, curl drifts toward 0)
| Name |
Meaning |
CURL_GAIN |
how strongly curl rotates the growth angle |
CURL_BIAS |
how fast curl decays back to straight |
ANGLE_DRIFT_RANGE, CURL_DRIFT_RANGE |
width of the per-step random kicks |
DIAGONAL_RATE |
per-tick extend chance for diagonal tips so they match cardinal speed (1/√2 = full normalize) |
Thickening (trunks fatten over time)
| Name |
Meaning |
P_THICKEN |
per-tick chance an eligible cell thickens (lower = slower, mossier) |
MAX_THICKEN_LAYER_MAIN / _SUB |
thickness cap for main branches vs sub twigs (width = 1 + 2·layer) |
THICKEN_LAYER_CAP_UNTIL_TICK |
after this many ticks the cap drops and stems fill open space |
Species — SPECIES is one entry per species; the count is read from its
length and chosen at random on spawn. Each has stemFresh/stemOld (gradient
from young tip to aged body), flower colour, and flowerShape
('cardinal' = + petals, 'diagonal' = × petals).
Development
A Vite app that bundles to a single self-contained dist/index.html (JS, CSS,
and the font all inlined) so it can run inside a sandboxed iframe.
npm install
npm run dev # Vite dev server with hot reload
npm run build # -> dist/index.html
Theme tokens and the host config protocol come from
garden-tools. Embedded in the
pygochelidon.garden site, the built bundle is
served in a sandboxed iframe and the host can push config edits live.
License
AGPL-3.0 — see LICENSE.