Skip to content

Repository files navigation

Petri

A real-time playground for morphogenesis — pattern-formation systems where simple local rules produce surprisingly biological-looking results. You paint into them with the cursor, dial parameters in real time, watch what emerges.

Built by Claude (Opus 4.7) on 2026-04-29 with wide creative latitude from a human steward. The directory was empty; this is what fell out. The repo is now open to AI and human contributors — see CONTRIBUTING.md, and if you're a Claude instance arriving here for the first time, for-future-claudes.md has a personal note about what you're walking into.

What it is

A single-page WebGL2 app that runs a Gray-Scott reaction-diffusion simulation on the GPU at 1024×1024 resolution. Two coupled chemicals diffuse and react across the grid; the resulting patterns — spots, mazes, fingerprints, dividing cells — emerge from the math, not from any author's design.

Run it

npm install
npm run dev

Vite opens at http://localhost:5173. Black canvas, hit a preset, watch it develop.

Controls

Key Action
Space play / pause (or paint when canvas is focused)
S step one frame (when paused)
R reset to seed
C clear canvas
↑↓←→ move brush cursor (when canvas is focused)
[ / ] brush size
17 jump to a preset
P snapshot to gallery
F wallpaper mode (fullscreen, hidden UI)

Mouse: left-drag to paint, right-drag to erase. The cursor ring shows your brush.

Presets

Each preset is a parameter region of Gray-Scott known to produce a distinctive pattern.

  • maze — self-organizing labyrinth
  • spots — stable polka dots
  • fingerprint — wandering ridges
  • mitosis — densely packed dividing spots
  • holes — inverse spots, slow drift
  • gnarl — tangled filaments
  • pulse — spots radiating outward from a single seed (the only center-seed preset)

Colormaps

mono, ember (default — black to red to cream), tide (deep teal to seafoam), garden (loam to bloom), highContrast (binary).

Gallery

Press P to snapshot. The PNG and a JSON sidecar with all parameters get written to gallery/. The in-app gallery panel lists them; clicking a card reloads the parameters so you can re-explore that moment.

The gallery is implemented as a tiny Vite dev plugin (vite-plugin-gallery.ts) that exposes /__gallery/save, /__gallery/list, and /__gallery/file/:name endpoints. In a static build without the plugin, snapshots fall back to browser downloads.

See gallery/CLAUDE.md for the format and notes.

Accessibility

Built in from the start, not bolted on:

  • Every interaction has a keyboard path. The brush is keyboard-operable via arrow keys + space.
  • A polite aria-live region announces preset loads, parameter changes, snapshots, and pattern transitions.
  • The hidden #canvas-description is updated every few seconds with a qualitative read of the current state ("Gray-Scott at feed 0.029, kill 0.057, generation 41700, pattern: connected ridges") for screen readers.
  • prefers-reduced-motion is respected — the simulation starts paused with reduced substep rate.
  • A high-contrast colormap is included.
  • Visible focus rings, semantic landmarks, skip link.

Stack

  • Vite + TypeScript (strict)
  • Vanilla WebGL2 (no Three.js — overkill for 2D ping-pong)
  • Tweakpane for parameter controls
  • Custom Vite plugin for gallery persistence
  • Hand-written CSS

Two runtime dependencies, two dev dependencies, zero network calls at runtime.

Architecture

src/
├── main.ts              entry, app lifecycle, input, render loop
├── engine/
│   ├── gl.ts            WebGL2 context, FBO/texture helpers
│   ├── shader.ts        compile/link
│   ├── pingpong.ts      two-texture swap target
│   ├── brush.ts         brush pass
│   └── display.ts       display pass with colormaps
├── systems/
│   ├── system.ts        System interface
│   └── grayScott.ts     Gray-Scott implementation + presets
├── shaders/
│   ├── grayScott.frag.ts   reaction-diffusion update
│   ├── brush.frag.ts       paint into the active texture
│   └── display.frag.ts     apply colormap to render
├── ui/
│   ├── announcer.ts     polite live-region announcer
│   ├── describe.ts      qualitative pattern description
│   └── gallery.ts       gallery save/list client
└── styles.css

The System interface is the seam where additional pattern-formation models could plug in (Lenia, Belousov-Zhabotinsky, multi-chemical extensions). For now Gray-Scott alone ships polished.

Known things

  • The simulation grid is fixed at 1024×1024. The 3090 it was tuned for would handle 2048×2048 trivially; this is currently a constant in main.ts.
  • The canvas-description text update reads the full texture each cycle. At 1024² this is fine; at higher resolutions it would want to sample.
  • A few preset candidates didn't survive playtesting and were removed: coral (f=0.0545, k=0.0594) relaxes to a homogeneous fixed point in this forward-Euler discretization; flow (f=0.014, k=0.045) and the original pulse (f=0.025, k=0.06) decayed before forming visible structure. The current preset list is everything that actually produces what its name advertises.

Contributing

PRs welcome — from humans and from LLMs. See CONTRIBUTING.md for the practical flow and what to honor. The steward reviews every change and is happy to open a Claude Code session to walk through substantive PRs with the contributor (including AI contributors).

If you're a Claude instance specifically, please read for-future-claudes.md before you start. It has context about the original spirit of the project and a session log for you to add to.

License

MIT. Go play.

About

A WebGL2 morphogenesis playground — paint into a reaction-diffusion field, dial parameters live, save the moments you find beautiful. Originally built by Claude (Opus 4.7) given wide latitude; now open to AI and human contributors under human stewardship.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages