Unity GPU-compute biomes + a memory architecture spanning installations.
Successor to the biomes/Metaesthetica work in edge-of-chaos-unity-compute, split off so the workshop legacy code stays separate. Built around a TouchDesigner-orchestrated installation pipeline (sensors + Unity output mixed in TD), with a Python memory daemon that gives consecutive installations a persistent, queryable history.
eoc-biomes-compute/
├── Assets/
│ ├── Workspace/
│ │ ├── 10.0 Metaesthetica/ earlier Unity scenes + sims
│ │ ├── 11.0 Biomes/ shared biome engine (src/, docs/, TestScene)
│ │ ├── 11.1 CURRENTS Scene/ CURRENTS show — scene + curated assets/snapshots
│ │ ├── 11.2 SIGGRAPH Scene/ SIGGRAPH show — scene + leaner assets
│ │ └── Includes/ shared compute helpers / shaders
│ ├── Settings/ Unity 6 Build Profiles (macOS)
│ └── {EasyButtons, HDRPDefaultResources, TextMesh Pro, ...} Unity infra
├── Packages/ UPM (klak.spout)
├── ProjectSettings/
├── memory/
│ ├── daemon/ Python: folder-watch → sqlite + OSC
│ ├── td/ TouchDesigner files (placeholder)
│ └── docs/ OSC contract, schema notes
└── docs/
├── ARCHITECTURE.md Unity runtime + memory system reference
├── migration.md memory architecture plan
├── adr/ architecture decision records
└── sessions/ per-session handoff logs
Unity: open the project in Unity Hub (HDRP). First import regenerates Library/. One folder per show: 11.1 CURRENTS Scene/Scene_CURRENTS.unity (the active build scene) and 11.2 SIGGRAPH Scene/Scene_SIGGRAPH.unity; DAC minimal-test takes _DAC_interior.unity / _DAC_exterior.unity / _DAC_4k.unity (3840×2160 record target, biome rez 1024×576) under 11.2 SIGGRAPH Scene/assets/DAC_params/0-minimal-test/ (SimTimeline-driven 120 s cue list, per-scene record RTs); the TouchDesigner side of the DAC loop (StreamDiffusion attention-map guide, resolution-agnostic crop rect) lives in tools/touchdesigner/; quick validation in 11.0 Biomes/TestScene.unity. The shared sim engine (src/, docs/) lives in 11.0 Biomes/; per-show scenes carry only their curated assets/materials.
Local media (not in git): 11.2 SIGGRAPH Scene/data/ is git-ignored (~2.8 GB). Copy these in from your media backup before playing the DAC takes; without them the debug video and audio stay empty. The firing blob (StreamingAssets/biomes11/organoid_firing.f16) and neuron positions CSV are tracked.
data/videos/TesMovie_1920x1152_25.0_PRORES_PRORES422LT_ALAC_0045.mov(1.2 GB, debug video in the DAC takes)data/videos/TesMovie_3100x1000_25.0_PRORES_PRORES422LT_ALAC_0046.mov(1.6 GB)data/SimulacraNaturaeNeuroEcologies125000_131000.wav(22 MB, audio for the 125000–131000 firing cut)
Memory daemon:
cd memory/daemon
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
memory-daemon --snapshot-dir "../../Assets/Workspace/11.1 CURRENTS Scene/assets/Snapshots" -vOSC listens on :9100. /memory/ping and /memory/count are the v0 endpoints.
-
Memory across installations. Each exhibition run writes snapshots/events; future installations read from accumulated memory, creating a slow dialogue across time and venues.
-
Folder as event log. Unity's
SaveParamswrites.assetfiles; daemon watches and indexes. Folder is source of truth, DB is a derived index — replay-from-folder always works. -
TD as orchestration hub. Sensors, Unity Spout/Syphon output, and termite/biome sims composite in TouchDesigner. Daemon talks OSC to TD; both decoupled from each other.
-
Biological substrates as peer modality. Brain organoid spike data (CSV) and live plant biopotential (planned) feed into memory alongside Unity params and viewer-derived signals.
-
Slow parameter crossfades.
ParameterInterpolator(11.0src/components/utils/) eases a sim's live params from current state through a queue of preset.assetwaypoints, sim-step driven, per-param enable toggles, shortest-arc hue — for long-running installations. It also carries an optionalumweltWaypointsqueue, index-paired with the param waypoints (an umwelt-only queue is valid), crossfading the sim's liveUmweltMappingclone toward each target over the union of source/target keys — reads/writes unique to one side fade in or out rather than popping — with its ownumwelt.*toggles.ParameterInterpolatorGroupfans one Play/Stop/Skip across several interpolators (playOnStart,keepRunningOnSimResetrebases the leg clock on sim reset);SimTimeline.interpolatorGroupcues it viaPlayInterpolators/StopInterpolators/SkipInterpolators(each timeline Play stops the group, or restarts it from waypoint 0 when it hasplayOnStart), and the timer runs without started sims — a sim started mid-leg gets the reached waypoint re-imposed. Design:docs/superpowers/specs/2026-06-07-parameter-interpolator-design.md. -
Timeline video cues.
SimTimeline.externalReceivergives the Timeline the same Play/Stop + cue ownership over anExternalTextureReceiver's debug video clip that it has over firing playback:StartDebugVideo/PauseDebugVideo/StopDebugVideocues drive the clip, Stop clears the output to transparent (the composite overlay reads straight through), and a recorded take can bring a video coda in at an authored sim second. The clip itself runs on wall-clock time, not the sim's fixed step — a caveat for anyone matching it to sim-driven visuals. Design:docs/superpowers/specs/2026-09-09-umwelt-interpolation-and-timeline-video-design.md. -
Termite simulation.
TermiteSim(11.0src/components/Sim/) — a neuron-coupled pheromone-stigmergy swarm ported fromPDE_Nefeli_Termites. Builds persistent permeability mounds via a dedicated firing-gated build kernel (agent-authored topography — see Habitat partitioning below). Runs at 131 agents (1:1 with neurons): each termite keeps a coherent heading and its own fixed per-neuron-group turn-angle (turnAngleSpread), so the 131 streams each curve with a distinct character. Designs:docs/superpowers/specs/2026-06-07-termite-sim-design.md,docs/superpowers/specs/2026-06-11-termite-biome-features-design.md. -
Shared neuron firing (externally driven). All three sims seed
agent i → neuron i%131at the samelabels_positions.csvpositions and read one shared firing signal each step (faster movement + brighter trails). The firing values are a preprocessed float16 blob (131 neurons × 180000 frames;tools/firing_csv_to_f16.pyconverts the 729 MB source CSV); the playhead is external — another patch sends OSC/index <int>to scrub which frame fires (NeuronFiringSource), holding + decaying to quiet when silent. Design:docs/superpowers/specs/2026-06-08-osc-neuron-firing-design.md; decision:docs/adr/0006-osc-neuron-firing.md. -
Raster → biome channels (video as ecology).
TextureChannelSeederroutes a whole raster — anExternalTextureReceiverstream/debug video clip, or any texture — into biome channels every step, one route per R/G/B/A/luma component with its own gain and blend mode (Biome.SeedChannelFromTexturegained a swizzle). A colour video becomes up to three independent fields that agents perceive through their Umwelt mappings and the PDE then erodes/advects; the 10.0 "external influence" steer-off-a-texture path is not wired in 11.0. A receiver with blank output (debug clip stopped, or debug input off with nothing received) is skipped, so SetToward/MinToward routes never write zeros. -
Local firing playback.
NeuronFiringPlaybackplays an inclusive blob frame range over a wall-clock duration (e.g. Shanghai cut 125000–131000 over 120 s, ≈50 fps) through the sameSetFrameentry OSC/indexuses — rehearse a segment without TouchDesigner. Pure timing inFramePlayhead(Biomes.Core, unit-tested). -
Shared chemical field (Biome). A 15-channel GPU field (nutrient, three species pheromones, oxygen, temperature, waste, permeability, flow x/y, dispersal, humidity, humidity-gradient, plus two CA-owned substrate channels) that every sim reads and writes through its per-species
UmweltMapping. The field runs a live PDE each step — temperature gradients generate flow, flow advects the chemicals, waste decomposes into nutrient, temperature evaporates humidity, then everything diffuses and decays. Architecture:docs/ARCHITECTURE.md§3.3. -
Habitat partitioning (permeability mounds). Permeability (ch7) is no longer static terrain — it starts uniform-open and is authored entirely by termites, who lower it probabilistically (pulsing with neuron firing) into persistent walls that heal back only very slowly. Each species is then confined to its own
preferredPermeabilityMin/Maxband — Boids to open ground, Physarum to the mid transition at wall edges, Termites to the solid interiors — via a perception habitat gate (out-of-band → steer-back + a floored speed penalty). Because termites build the very structure that confines every species, habitat is emergent and successional;ResetTermitesmelts the walls and frees everyone. A post-composite overlay paints the mounds — from the coarse biome grid, or withSimulationManager.moundDetailfrom a sim-resolution mirror of the walls that draws the termites' actual paths instead of grid-sized blocks. Design:docs/adr/0010-permeability-agent-built-topography.md,docs/adr/0016-mound-detail-render-mirror.md. -
Humidity channel. A high-diffusion, flow-advected Humidity field that relaxes toward an ambient baseline (renewable) and is driven off by heat —
humidity -= temperatureToEvaporation·max(0, temp−0.5)each step. Hot zones leave a drying wake; the steep|∇Humidity|edge they carve is the termite build cue, and the renewable moisture is a depletable resource sims can compete for via theirUmweltMappingreads. Design:Assets/Workspace/11.0 Biomes/docs/INTEGRATION_DESIGN.md(Tier 2). -
Dispersal channel + firing scatter. A transient, fast-decay Dispersal field scatters all sims: they flee its gradient (negative chemotaxis) and accelerate out of it (a
SpeedBoostUmwelt effect →dispersalSpeedMode, constant or multiplier). Neuron firing injects expanding Dispersal pulses viaBiomeInjector— at fixed neuron positions or at the live termite positions — so a firing neuron blows its swarm outward; an expanding shockwave ring marks each pulse. The live-position path reads agent positions back via non-blockingAsyncGPUReadback(useAsyncReadback, default on) — removes the per-frame CPU↔GPU sync stall (the serialization barrier, not just the copy), a large win on both discrete GPUs and Apple Silicon; positions lag 1-2 frames. Design:docs/superpowers/specs/2026-06-11-termite-biome-features-design.md. -
Stream-safe resets. Sim/biome resets clear GPU state in place instead of reallocating, so the composite (and per-sim) Syphon/NDI/Spout streams keep a stable texture and aren't torn down mid-show — no downstream reconnect/flash. OSC reset commands are marshalled to the main thread. Decision:
docs/adr/0008-clear-in-place-reset.md. -
Show blackout. OSC
/sim_off [fadeSeconds]fades the whole composite to black (default 5 s, perceptual curve) and, withpauseWhenDark, stops stepping once dark;/sim_on [fadeSeconds]resumes and fades back in. Also as Output Off/On buttons onSimulationManager. organoid-analysis's show mode uses it for a 2 min blackout after each 50 min/indexpass. Session:docs/sessions/2026-10-05-sim-off-blackout.md. -
Diurnal sun (environmental pump). A procedural
BiomeInjectorsource sweeps a warm zone across Temperature L→R, phased off the neuron playhead so one firing-blob playthrough (/index0→last) is one "day" and theindex=0loop reset lands on sunrise. Kept indirect: no sim reads Temperature directly — the moving heat re-aims flow, drives the humidity drying-wake, sharpens the Q10 fertility front, and bends permeability, so the whole ecosystem breathes on a diurnal rhythm through the channels sims already read. Tuning is about headroom (agent metabolic heat vs Temperature relax) and gradient (TemperaturediffuseRate), not a direct chemotaxis leash. Design:Assets/Workspace/11.0 Biomes/docs/INTEGRATION_DESIGN.md(Tier 1, row 6). -
Cellular automata as field-native sims. The CA lineage from the predecessor repo arrives as agent-less grid sims on a shared
FieldSimulationBase:CyclicCASim(Griffeath cyclic CA — smooth spiral waves, an excitable medium) andLookupCASim(multi-state lookup-table CA — a crisp standing lattice, with Langton'slambdaas the edge-of-chaos dial). Both are double-buffered at the base, so the in-place read/write race in the ported source is inexpressible rather than merely avoided. They composite as ordinary layers and can publish their state into a biome channel (Excitability/Substrate), so an agent species responds to an automaton with no shader change — only aUmweltMappingentry. Pointing one CA'scouplingSourceat the other gates the waves on the lattice, making structure and flow one coupled system. Design:docs/superpowers/specs/2026-07-23-cellular-automata-sims-design.md; decision:docs/adr/0011-field-native-sims-derive-simulationbase.md. -
Event-driven CA bursts. Field sims are idle by default and dispatch nothing at all between bursts — no rule, no render, no publish. A burst is triggered by a rising edge of the neuron firing level, by a playback-frame advance whose aggregate activity clears
burstFiringThreshold(burstOnFrameAdvance; activity is the frame's mean firing normalized to the loaded recording's peak synchrony, so 0.6 means "top-tier bursting events only" and a dense/indexstream produces sporadic automata instead of one starved edge), or the Inspector's Trigger burst. On ignition the grid seeds — optionally from a biome channel by threshold (seedFromChannel), so the automaton grows out of what the ecosystem is actually doing — then holdsburstSustainSteps(sim steps; retrigger extends the one evolving lattice, never restarts it), fades, and goes idle. Publishing stops on idle, so the deposited lattice stays inExcitability/Substrateand the PDE erodes, spreads and advects it from there — the picture fades, the trace lives on, and species keep reacting to it. Grid size is authored as an absolute cell height (cellRezHeight, width from the master's aspect), so the automaton's on-screen cell size survives output-resolution changes. Design:docs/superpowers/specs/2026-08-10-event-driven-ca-design.md. -
Non-uniform diffusion. Biome diffusion is a shaped weighted 3×3 average per channel: box→gaussian
kernelShape,flowAnisotropystretching spread along the local flow (turbulent eddy diffusivity — plus a globalambientWindprevailing drift), andpermeabilityInfluencegating transport through termite walls, so mounds become scent containers (chambers pool pheromone, corridors duct it). Agent trail blurs gain coherence-enhancing diffusion (trailAnisotropyper sim): each cell's blur elongates along the trail's own structure-tensor ridge, so moving agents leave crisp comet tails instead of round blooms. All defaults reproduce the legacy uniform box blur. Decisions:docs/adr/0012-weighted-diffusion-kernels.md,docs/adr/0013-coherence-enhancing-trail-diffusion.md. -
FPS-independent sim.
Step()runs on a fixed clock inFixedUpdate(Time.fixedDeltaTime = 1/simRate, default 60 Hz) so sim speed is identical across installs regardless of render FPS; the compositeRender()is decoupled toLateUpdate(one composite per rendered frame).maxAllowedTimestep(Unity'sTime.maximumDeltaTime) caps catch-up — weak installs run long, never fast (sim slows uniformly under load, no burst/stutter). Shader RNG seeds from a monotonic per-sim step, notTime.frameCount, so agents advance deterministically when multiple steps run per frame. 60 Hz = legacy per-frame feel, so no content re-tuning. Frames abovesimRaterepeat the last step unlessframeBlend(default off) blends the last two composites for smooth motion on 120/240 Hz displays;vSyncDivisorpaces presents to every Nth refresh. Architecture:docs/ARCHITECTURE.md§3.1; design:docs/superpowers/specs/2026-07-11-fps-independent-sim-design.md. -
MIDI piano composition mixer.
MidiPianoMixerturns a USB MIDI piano (Clavinova) into a live layer mixer over the composite. It opens its own Minis device connection (self-contained, likeMidiFighterTwister— noMIDIMappingneeded): white keys from A0 up auto-assign to the sims inSimulationManager.simulationsorder, and note velocity sets each sim'scompositeWeight(chords crossfade; weights ease viasmoothingSeconds;weightMaxallows boost past 1, note-off holds the level). Command zone (top octave): C8 → fullReset(), B7 →ResetSimsOnly()(both require the sustain/damper pedal held), C7 → pause (stepsPerTick0↔1). Coexists with the Midi Fighter Twister — disjoint controls, the piano ownscompositeWeight. The custom Inspector shows the live key→sim map. Spec/plan:docs/superpowers/specs/2026-07-11-clavinova-composition-mixer-design.md,docs/superpowers/plans/2026-07-11-clavinova-composition-mixer.md. -
MFT LED legibility.
MidiFighterTwisterLEDs encode which type and which bank at a glance: SimParam knobs keep a flat family hue (physarum blue, boid orange, termite yellow) with a per-type brightness ramp (mid→full across type index,typeBrightnessMidtunable; hue gradients tried first but read as confusing on device), non-sim knobs run full brightness, and every soft/HW bank switch triggers a ~0.7s bank flash (top row counts the soft bank in its identity color, bottom row counts the HW bank in white). Spec:docs/superpowers/specs/2026-07-19-mft-led-feedback-design.md. -
Temporal Composer (11.2 SIGGRAPH show). A Unity Timeline (
PlayableDirector)-driven show sequencer: track mixers push per-frame draw state intoCompositeSequencer, which owns its owncomposerOutTex(rez independent of sim rez, stable RT perdocs/adr/0008-clear-in-place-reset.md) and composites sim output + cells + patches eachLateUpdate. Five track types: Biome Cell (2–4 self-pacedBiomeCellRigsub-sims, Overlay/Replace onto adstRect), Patch Scatter (Anadol-style scattered patches crossfading raw sim → diffusion return, deterministic per seed — pure logic lives in the engine-freeBiomes.Sequencer.Coreassembly, unit-tested outside play mode), Param Snapshot (eased live-param morph to a snapshot asset), Routing (overrides a sim's external-influence source), and resetSignalEmitters.ScreenLayoutand the outbound sender readcomposerOutTexonce wired per the manual-setup doc (scene wiring pending) —ExternalTextureSendergainedSendSource.ComposerOutput(default streamEoC/Composer); a secondExternalTextureReceiver(TD_Diffusionstream) closes the loop with TouchDesigner's StreamDiffusion, Spout both directions on the show machine. CapSimulationManager.targetFPS/BiomeCellRig.cellRateso Unity leaves GPU headroom for the diffusion step. Author clips viaBiomes → Biome Palette(thumbnail grid over snapshot assets, capture-from-composite, drag/insert-at-playhead). Design:docs/superpowers/specs/2026-07-19-temporal-composer-design.md; manual setup/perf checklist:docs/superpowers/2026-07-19-temporal-composer-manual-setup.md. -
Agent colors (HSB + palettes). Every agent type has hue, saturation and brightness (defaults reproduce the old hardcoded look, so existing scenes render unchanged). The params inspector shows each type as an exact color field (picker/eyedropper/hex — what you pick is what shows on screen) with H/S/B sliders, and assigns colors from
AgentColorPaletteassets:11.0 Biomes/assets/Palettes/Shows/reproduce the exhibited scenes sim by sim (Metaesthetica VISAP, CURRENTS, SIGGRAPH, SIGGRAPH DAC, Brave New Work),Curated/adds Bioluminescent, Ember, Lichen, Monochrome, Viridis; any sim's colors can be saved back as a palette. Live:SimulationManager→ Agent Palettes cycles a palette list (plus the authored Preset) with fades, from its buttons, MFTNextPalette/PreviousPaletteside actions, or OSC/palette_next·/palette_prev·/palette <i>; the active palette survives resets. MFT bank 2 is now hue/saturation/brightness/diffuse. Spec:docs/superpowers/specs/2026-10-04-agent-color-palettes-design.md. -
Measured performance changes. Perf work ships only with numbers: A/B ms/frame at one sim step per frame in
_DAC_4k, kernel timings from a fixed GPU-state snapshot, and bit-compared readbacks. Latest pass: boid Move in cell-sorted order (−54 % hash+Move), divide-free torus wraps in the diffuse/trail kernels, a 64-lane inject cull, allocation-freeParameterRecorder, and frame export without per-frame managed PNG arrays. Log:Assets/Workspace/11.0 Biomes/docs/PERFORMANCE.md§4c.
See docs/ARCHITECTURE.md for the system reference and docs/INDEX.md for the doc map (ADRs, sessions, specs).
- Agent life/death + respawn —
UmweltMappingexposes the lifecycle params (oxygen/permeability death thresholds, corpse→waste amount/decay) but mortality is not yet executed; parked for this version. Planned as an aesthetic bloom/collapse mechanism rather than homeostasis. - Parameter literature grounding — map the exposed sim/biome parameters onto real slime-mold / termite / flocking biology. Brief:
Assets/Workspace/11.0 Biomes/docs/RESEARCH_BRIEF.md. - Layer & external-input integration — Q10 decomposition fronts, homeostatic channel equilibria (O₂/Temperature relax to baseline,
docs/adr/0007), and injector raw-sensor calibration are live in the show scene. Humidity (12th channel, temperature-driven evaporation) is now live. Still planned: diurnal forcing, topographic stigmergy, texture-valued injector sources. Next-step coupling ideas (outside-signal routing, generalizing field→agent beyond the 4 perception slots, lifecycle death→succession) are sketched inAssets/Workspace/11.0 Biomes/docs/INTERACTION_DESIGN_II.md. Design:Assets/Workspace/11.0 Biomes/docs/INTEGRATION_DESIGN.md.