An evolving ASCII aquarium in your terminal.
No predefined species. No artificial food drops. Every creature emerges from a
continuous genome through mutation, crossover, and natural selection — rendered
as procedural ASCII art — sustained entirely by evolving genome-driven producer colonies.
- Bottom-up emergent evolution — no templates or predefined species; all creatures and producer colonies emerge from continuous genomes through mutation and selection
- Creature genome — 35+ float genes control body plan, appendages, eyes, coloring, behavior, and brain topology
- Producer genome — 20 float genes control colony geometry, light capture, nutrient affinity, palatability, reserve allocation, and dispersal strategy; producer strategies emerge continuously rather than from hardcoded species types
- Complexity as a master gate — a single 0.0–1.0 gene controls which morphological features are expressed in both creatures and producers; always mutates on reproduction (±0.15 drift) ensuring continuous exploration
- Bioenergetic creature reproduction — consumers mature gradually, accumulate reproductive buffer from sustained surplus, and only then reproduce asexually or sexually
- Producer reproduction — producer colonies allocate reserve surplus into broadcast propagules and local fragments; parent reserve is debited, establishment depends on light and crowding, and dispersal-vs-local-spread trade-offs are genome-controlled
- Smooth complexity transitions — gradual shift from asexual to sexual reproduction prevents evolutionary traps
- Complexity rewards — higher complexity grants +50% sensory range and ~25% metabolism efficiency for creatures; +15% photosynthesis efficiency for producers
- Evolvable mutation rate —
mutation_rate_factorgene (0.5–2.0) scales the base mutation rate in both creatures and producers; meta-evolution tunes evolvability itself - Sexual selection —
mate_preference_huegene drives Fisherian runaway selection by preferring mates whose color matches the preference - Fitness sharing — NEAT-style speciation protection; larger species have proportionally less reproduction chance, protecting novel innovations
- Runtime diversity coefficient — arrow keys adjust a 0.25–2.5 slider that scales mutation rates and fitness-sharing strength in real time, letting you dial evolutionary pressure up or down without restarting
- Trait-based producer trade-offs — dispersal, nutrient affinity, light capture, reserve allocation, and grazing value are all continuous genes so producer strategies emerge from trait combinations rather than from species templates
- NEAT-style evolving topology — networks start minimal (16 inputs → 7 outputs, 112 connections) and grow via structural mutations that add hidden nodes (8%), connections (12%), and recurrent self-loops (6%)
- Evolvable per-node activation functions — each hidden/output node evolves its own activation (Tanh, ReLU, Sigmoid, Abs, Step, Identity) via swap mutations (2%), enabling diverse nonlinear representations
- Per-node bias — each node has an evolvable bias term that shifts the activation threshold; biases mutate alongside weights and are crossed over during reproduction
- Innovation numbers — historical gene markings enable meaningful crossover between creatures with different network topologies
- Hebbian lifetime learning (Oja's rule) — evolvable
learning_rategene (0.0–0.1) allows weight plasticity during a creature's life via self-normalizing Oja's rule; 10x faster than typical alife learning rates; learned changes are NOT inherited (Baldwin Effect) - Neuromodulation — hidden nodes can evolve into Modulator role (1% rate), whose sigmoid-gated output scales the Oja learning rate for all connections, enabling selective plasticity
- Attention mechanism — hidden nodes can evolve into Attention role (0.5% rate), computing softmax-weighted blends of their inputs instead of simple weighted sums
- Module duplication — a rare mutation (0.5% rate) copies a connected subgraph of hidden nodes with new innovation numbers, enabling functional modularity
- Recurrent self-connections — hidden and output nodes can evolve self-loops that feed trace memory back as input, giving creatures temporal memory without breaking feedforward topology
- Exponential trace memory — each node has an evolvable trace decay rate (0.0–0.99) modeling synaptic Ca²+ dynamics; enables habituation, sensitization, and chemotaxis-style temporal comparison
- Sensory inputs — energy, hunger, nearby food/predators/allies, walls, light, speed, pheromone concentration & gradient
- Behavioral outputs — steering, speed, foraging, fleeing, schooling, pheromone emission
- Evolved, not designed — both weights and topology are part of the genome and evolve through crossover and mutation
- Chemical communication — creatures deposit pheromones at their position; evolvable
pheromone_sensitivitygene controls response strength - Grid-based diffusion — pheromone concentrations spread to neighboring cells (5% per tick) and decay (×0.95 per tick, ~3.5s half-life)
- Gradient sensing — neural network receives local concentration and directional gradient as inputs, enabling trail following and collective behavior
- Evolved, not designed — pheromone emission is a neural network output; whether and how creatures use chemical signals emerges through evolution
- Genome-driven producer colonies — producers evolve via a 20-float
ProducerGenome; colony geometry, physiology, nutrient strategy, and dispersal mode all emerge from the genome - LAI-based photosynthesis with nutrient co-limitation — canopy capture follows Beer–Lambert-style light interception, then realized growth is limited by light, dissolved nitrogen, and dissolved phosphorus
- Allometric producer metabolism — maintenance and recovery scale with biomass rather than with fixed stage tables, following metabolic-scaling ideas
- Producer storage and regeneration — dormant biomass and regenerative banks buffer dark periods, post-grazing recovery, and local fragmentation
- Rasterized canopy shading — a tank-wide
LightFieldapplies continuous depth attenuation plus canopy, phytoplankton, and epiphyte shading - Demand-limited grazing — grazers remove active producer biomass first, can scrub fouling load, and intake is limited by consumer demand rather than by a fixed prey damage fraction
- Producer lifecycle stages — colonies progress through Cell → Patch → Mature → Broadcasting → Collapsing from reserve status, biomass fill, stress load, and age
- Procedural producer ASCII art — 4 complexity tiers (speck, tuft, mat, plume) selected by raw genome complexity; producer size within each tier scales with lifecycle stage
- Reserve-cost producer reproduction — mature producer colonies invest reserve surplus into broadcast propagules and fragments; establishment depends on local light, depth, and crowding
- No artificial food rain — the ecosystem sustains itself entirely through producer photosynthesis; manual food drops still available via
fkey - Nutrient cycling — dead creatures become detritus entities, producer turnover returns N and P to dissolved/sediment pools, and nutrient load can drive phytoplankton shading; nitrogen fixation prevents irreversible N-depletion and a nutrient floor (5% of initial) ensures the ecosystem can always recover
- Substrate zones — procedurally generated Sandy/Rocky/Planted substrate affects producer establishment; rocky zones favor high-hardiness producers, planted zones boost clonal spread
- Continuous depth/light attenuation — underwater light declines smoothly with depth and water clarity rather than via three hard depth bands
- Night metabolism stress — creatures burn 30% more energy at night, favoring complex creatures with metabolism efficiency bonuses
- Temperature effects — Q10-based metabolic scaling affects both creatures and producers; cold snaps (−10°C) create strong selection pressure
- Morphology-driven feeding —
FeedingCapabilityderived from mouth size, aggression, and hunting instinct; full spectrum from grazers to apex predators emerges naturally - Predator-prey dynamics — body-size ratio hunting, speed advantage checks, energy transfer on kill
- Procedural ASCII art — 4 complexity tiers for both creatures (cells, simple, medium, complex) and producers (speck, tuft, mat, plume) generated from their respective genomes; no hardcoded art
- Boids flocking — separation, alignment, cohesion with size-aware spacing and wall avoidance
- Day/night cycle — sine-based lighting, palette shifts from bright day through dusk to dark night
- Random events — algae blooms, feeding frenzies, cold snaps (−10°C), earthquakes (every ~60s)
- Multi-threaded — brain, boids, and hunting systems parallelized with rayon
- Soft population cap — reproduction suppressed above 600 creatures to maintain responsiveness
- HUD overlay — population, generation, complexity, species count, diversity coefficient, split creature/producer birth-death counters, day, time, temperature, light, speed, plus a toggleable ecology diagnostics panel and a help popup (
?) explaining all abbreviations - PNG screenshots & GIF recording —
psaves a full-resolution PNG;gtoggles streaming GIF recording at configurable resolution and frame rate (seeconstants.rs), ideal for creating evolution time-lapses - Single founder-web startup — the visible run starts from low-biomass producer colonies and simple consumer founders, with no hidden warmup
- Rust 1.70 or later
- A terminal with color support (most modern terminals work)
git clone https://github.com/srad/tuiquarium.git
cd tuiquarium
cargo run --releaseFor a fixed-size cross-platform window frontend backed by ratatui-wgpu:
cargo run --release -- --frontend gpuThe GPU frontend uses the bundled JetBrains Mono font and keeps the aquarium at a
fixed 136x44 simulation size instead of stretching the tank to the host terminal
dimensions. Resizing the window changes the visual font scale and viewport so the
same simulation fills the window like the terminal layout, with black background
gutter only for leftover pixels that do not fit a full text cell.
The default visible run starts directly from a simple aquatic founder web:
- low-biomass producer colonies
- simple motile consumer founders
- no hidden warmup
cargo test --workspace # 258 passing tests (235 core + 21 render + 2 app)| Key | Action |
|---|---|
q / Esc |
Quit |
Space |
Pause / Resume |
→ |
Speed up (0.5x increments, max 100x) |
← |
Slow down (0.5x decrements, min 0.5x) |
↑ |
Increase diversity coefficient (+0.1, max 2.5) |
↓ |
Decrease diversity coefficient (−0.1, min 0.25) |
r |
Reset speed and diversity to defaults (1.0) |
f |
Drop food pellet (manual only; no auto-spawning) |
d |
Toggle ecology diagnostics overlay |
t |
Cycle rendering theme (Ocean, Deep Sea, Coral Reef, Brackish, Retro CRT, Blueprint, Frozen, Classic) |
? / h |
Toggle help popup (explains all HUD abbreviations) |
p |
Save PNG screenshot to ~/.tuiquarium/screenshots/ |
g |
Toggle GIF recording (output to ~/.tuiquarium/recordings/) |
tuiquarium strictly separates simulation from rendering through traits and dependency injection:
tuiquarium/
├── src/main.rs # Entry point, TUI event loop
├── crates/
│ ├── tuiq-core/ # Pure simulation logic (zero rendering deps)
│ │ ├── animation.rs # Frame sequencing & timing
│ │ ├── behavior.rs # Behavioral action types & speed multipliers
│ │ ├── boids.rs # Boids flocking (rayon-parallelized)
│ │ ├── bootstrap.rs # Founder genomes & ecosystem initialization
│ │ ├── brain.rs # NEAT neural network brains (rayon-parallelized)
│ │ ├── calibration.rs # Runtime calibration parameters
│ │ ├── components.rs # ECS components (Position, Velocity, Appearance, ...)
│ │ ├── ecosystem.rs # Energy, metabolism, grazing/hunting, death
│ │ ├── environment.rs # Day/night cycle, temperature, currents, events, substrate zones
│ │ ├── genetics.rs # Crossover, mutation, genomic distance
│ │ ├── genome.rs # CreatureGenome + ProducerGenome
│ │ ├── lib.rs # AquariumSim orchestrator, Simulation trait, tick()
│ │ ├── needs.rs # Hunger/safety/social drift
│ │ ├── phenotype.rs # Genome → physical stats (creatures + producers)
│ │ ├── pheromone.rs # Chemical signaling grid (deposit, diffusion, decay)
│ │ ├── physics.rs # Position integration, boundary handling
│ │ ├── producer_lifecycle.rs # Producer genome → procedural ASCII art, lifecycle stages
│ │ ├── producer_reproduction.rs # Producer broadcast & clonal propagation
│ │ ├── spatial.rs # Spatial hash grid with distance-filtered queries
│ │ ├── spawner.rs # Asexual/sexual reproduction system
│ │ ├── stats.rs # SimStats, EcologyInstant, EcologyDiagnostics
│ │ └── systems.rs # Trait abstractions: BrainSystem, EcosystemSystem, HuntingSystem, ReproductionSystem, ProducerLifecycleSystem
│ │
│ └── tuiq-render/ # Ratatui rendering (depends on tuiq-core)
│ ├── ascii.rs # Procedural ASCII art generation from genome
│ ├── constants.rs # Tunable rendering/recording constants (font sizes, fps)
│ ├── effects.rs # Bubble particle system
│ ├── gif_recorder.rs # Streaming GIF recorder (terminal buffer → animated GIF)
│ ├── hud.rs # Stats overlay (pop, gen, complexity, species, diversity, ...)
│ ├── palette.rs # Day/night color palette shifts
│ ├── screenshot.rs # Buffer → PNG screenshot rendering
│ └── tank.rs # Tank border, water, substrate, creature rendering
- Simulation knows nothing about rendering. The
Simulationtrait exposes read-only access to the ECS world. Rendering code never mutates simulation state. - Trait-based system architecture. Each major ECS system (brain, ecosystem, hunting, reproduction, producer lifecycle) is defined as a trait with a concrete zero-cost implementation. The
tick()orchestrator delegates through trait methods, enabling testability and future extensibility. - ECS architecture with hecs — lightweight, no global state, manual system orchestration.
- Fixed timestep game loop — 50ms ticks (20 ticks/sec simulation), ~60fps rendering, accumulator pattern.
- Spatial hash grid reduces neighbor queries from O(n²) to ~O(n).
- Shared entity info map built once per tick, passed to brain/boids/hunting to eliminate redundant world queries and HashMap constructions.
- Runtime diversity coefficient — a single
[0.25, 2.5]slider scales mutation rates and fitness-sharing strength, letting the user tune evolutionary pressure interactively without restarting.
Every creature has an evolving neural network (NEAT-style) evaluated each tick. Networks start minimal and grow via structural mutations. Each node has its own activation function, bias, and optional specialized role.
graph TB
subgraph Inputs["🔵 Sensory Inputs · 16 nodes"]
direction LR
I_body["Energy · Hunger\nSafety · Reproduction"]
I_env["Light · Speed\nWall X · Wall Y"]
I_target["Food ↕↔\nPredator ↕↔\nAlly ↕↔"]
I_chem["Pheromone\nconc · gradient"]
end
subgraph Hidden["🟣 Evolving Hidden Layer · up to 60 nodes"]
direction LR
S["Standard Node\nTanh · ReLU · Sigmoid\nAbs · Step · Identity"]
M["Modulator Node\nσ-gated Oja\nlearning rate"]
A["Attention Node\nsoftmax-weighted\ninput blending"]
T(["Trace Memory\ndecay 0.0–0.99\nself-loop per node"])
S -.->|"self-loop"| T
M -.->|"gates learning"| S
end
subgraph Outputs["🟢 Behavioral Outputs · 7 nodes"]
direction LR
O_move["Steer X · Steer Y\nSpeed"]
O_behav["Forage · Flee\nSocial · Pheromone"]
end
Inputs ==> Hidden ==> Outputs
style Inputs fill:#fff,stroke:#4a9eff,color:#333
style Hidden fill:#fff,stroke:#b08aff,color:#333
style Outputs fill:#fff,stroke:#4aff6a,color:#333
Networks begin as direct input→output connections (112 weights) and grow via structural mutations:
- Add node (8% per generation): splits an existing connection, inserting a hidden neuron with a random activation (Tanh/ReLU/Sigmoid)
- Add connection (12% per generation): adds a new feedforward connection between non-connected nodes
- Add self-connection (6% per generation): adds a recurrent self-loop on a hidden or output node, enabling short-term memory
- Activation swap (2% per generation): changes a random non-input node's activation function to a randomly chosen one from {Tanh, ReLU, Sigmoid, Abs, Step, Identity}
- Modulator flip (1% per generation): converts a hidden node to Modulator role (forced Sigmoid activation), whose output gates the Oja learning rate globally
- Attention flip (0.5% per generation): converts a hidden node to Attention role (forced Identity activation), computing softmax-weighted input blends
- Module duplication (0.5% per generation): copies a 1-hop subgraph around a random hidden node, creating duplicate modules with new node IDs and innovation numbers
Maximum topology: 60 nodes total, 300 connections. Innovation numbers track each structural mutation's history, enabling meaningful crossover between creatures with different topologies.
Per-node genes: Each node has an ActivationFn (6 variants), a bias (−2.0 to +2.0), and a NodeRole (Standard/Modulator/Attention). These are stored in a NodeGene struct per node and evolve through mutation and crossover.
Output scaling: all outputs are scaled by complexity.max(0.3), ensuring even simple creatures can take meaningful actions while complex creatures have full control authority.
Hebbian learning (Oja's rule): an evolvable learning_rate gene (0.0–0.1) controls lifetime weight plasticity. Weights update via Oja's rule: Δw = η · post · (pre − post · w), which is self-normalizing and prevents weight saturation. The learning rate is scaled by 0.01 (10x faster than typical alife rates) so creatures can adapt within their lifespan. A small weight decay (0.02% per tick) prevents drift. Biases also update via an analogous Oja-like rule. If Modulator nodes are present, their sigmoid-squashed output gates the learning rate for all connections — modulators near 0.0 suppress learning, near 1.0 allow it. Learned weights are NOT inherited — only innate genome weights evolve (Baldwin Effect).
Attention nodes: Instead of computing a weighted sum, Attention nodes compute a softmax over connection weights to produce attention scores, then blend input activations proportionally. This enables selective focus on the most relevant inputs.
Module duplication: When triggered, this mutation selects a random hidden node, finds all nodes within 1 connection hop, duplicates the subgraph with fresh node IDs and innovation numbers, and wires the copy's external connections analogously to the original. This enables functional modularity — successful subnetworks can be reused and diverge independently.
Recurrent self-connections with trace memory: hidden and output nodes can evolve self-loop connections where the node's trace feeds back as additional input. Unlike raw previous-tick activations, each node maintains an exponential moving average: trace = decay * trace + (1-decay) * activation. The evolvable trace_decay parameter (0.0–0.99) controls memory depth — at 0.0 the node has no memory (current tick only), at 0.9 the half-life is ~10 ticks. Default trace decay is seeded from the node's activation function: fast neurons (ReLU, Abs: 0.05), standard neurons (Tanh: 0.15), gating neurons (Sigmoid: 0.3), and modulators get an additional +0.2 bonus for slow gating. This models biological Ca²+ dynamics and enables three key behaviors: slow integration (accumulating evidence over time), habituation (trace saturates under repeated stimulus), and temporal comparison (current - trace acts as a derivative for chemotaxis-style gradient following). Self-loops are separated from the feedforward topological sort so they don't create cycles in the forward pass.
| # | Input | Description |
|---|---|---|
| 1 | Energy fraction | How full the creature's energy bar is (0–1) |
| 2 | Hunger | Current hunger need level (0–1) |
| 3 | Safety | Current safety/threat level (0–1) |
| 4 | Reproduction need | Urge to reproduce (0–1) |
| 5 | Nearest food distance | Proximity to closest edible target (0=far, 1=close) |
| 6 | Nearest food angle | Direction to food (−1 to +1, atan2/π) |
| 7 | Nearest predator distance | Proximity to closest threat |
| 8 | Nearest predator angle | Direction of threat |
| 9 | Nearest ally distance | Proximity to similar-sized neighbor |
| 10 | Nearest ally angle | Direction to ally |
| 11 | Wall proximity X | Distance to left/right walls (−1 to +1) |
| 12 | Wall proximity Y | Distance to top/bottom walls |
| 13 | Light level | Current ambient light (day/night cycle, 0–1) |
| 14 | Own speed | Current speed as fraction of max (0–1) |
| 15 | Pheromone concentration | Local pheromone level at creature's position (0–1) |
| 16 | Pheromone gradient | Directional pheromone gradient for trail following (−1 to +1) |
| Output | Effect |
|---|---|
| Steer X, Y | Steering force direction |
| Speed multiplier | How fast to move (0.1x–1.5x) |
| Forage tendency | Drives food-seeking behavior |
| Flee tendency | Drives predator avoidance |
| Social tendency | Drives schooling/flocking behavior |
| Pheromone emission | Deposits chemical signal at creature's position |
Decision logic: flee > 0.3 with predator nearby → Flee; forage > 0.3 with food nearby → Forage; social > 0.3 → School; speed < 0.3 → Rest; else → Explore.
All genes are continuous floats. There are no discrete categories or predefined species:
| Gene Group | Genes | Range |
|---|---|---|
| Art | body elongation, height ratio, size, tail fork/length, top/side appendages, pattern density, eye size, primary/secondary hue, brightness | 0–2 (varies) |
| Animation | swim speed, tail amplitude, idle sway, undulation | 0–2 |
| Behavior | schooling, aggression, timidity, speed factor, metabolism factor, lifespan factor, reproduction rate, mouth size, hunting instinct, mutation rate factor, mate preference hue, learning rate, pheromone sensitivity | 0–2 (varies) |
| Brain | NEAT genome: variable-length connection genes with innovation numbers, per-node genes (activation function, bias, role), evolving topology | weights −3 to +3, bias −2 to +2 |
| Complexity | master gate controlling feature expression | 0.0–1.0 |
| Generation | inherited from parents + 1 | 0–∞ |
Producer colonies have their own 20-float genome plus generation tracking. It drives colony geometry, physiology, stress tolerance, nutrient use, herbivory resistance, and dispersal strategy. Producer genomes evolve through mutation during reproduction (no crossover — producer colonies stay asexual in this model).
| Gene Group | Genes | Range | Ecological Basis |
|---|---|---|---|
| Morphology | stem_thickness, height_factor, leaf_area (capture-area proxy), branching, curvature, primary_hue | 0–1 | aquatic producer geometry; light access and colony-shape trade-offs |
| Physiology | photosynthesis_rate, max_energy_factor, hardiness, nutritional_value, nutrient_affinity, epiphyte_resistance, reserve_allocation | 0–1.5 (varies) | metabolic scaling, nutrient co-limitation, herbivory and attached-growth trade-offs |
| Propagation | seed_range, seed_count, seed_size, lifespan_factor, clonal_spread | 0–2 (varies) | broadcast-vs-local spread trade-offs, propagule-pressure ecology |
| Evolution | complexity, mutation_rate_factor | 0–2 | Meta-evolution |
| Lineage | generation | integer | Heritable lineage tracking |
Key models:
- Beer–Lambert canopy capture — effective LAI = leaf_area × (1 + branching×0.5) × (0.5 + height×0.5); light capture has diminishing returns with canopy density
- Monod-style resource limitation — realized growth is limited by saturating responses to light, dissolved nitrogen, and dissolved phosphorus rather than by hard thresholds
- Allometric maintenance — biomass maintenance scales sublinearly with size, while tissue turnover and senescence increase under chronic stress
- Reserve-cost propagation — reserve surplus is partitioned into broadcast propagules and local fragments;
seed_count,seed_size, andclonal_spreaddefine strategy trade-offs - Visual complexity tiers — producer appearance is complexity-gated by raw genome complexity: <0.12 speck, 0.12–0.35 tuft, 0.35–0.65 mat, ≥0.65 plume; within each tier, size scales with lifecycle stage
Initial producers are spawned with minimal_producer():
- Complexity: 0.05–0.25 (starts as speck or tuft tier)
- Low-profile geometry: all morphology genes randomized to colony-like low-to-moderate values
- Moderate physiology: photosynthesis_rate ~1.0, balanced broadcast vs fragmentation strategy
- Staggered start: initial energy randomized 50–100%, initial age randomized 0–20s to desynchronize lifecycle stages
- Generation 0: first generation, no parent lineage
The simulation begins with minimal_cell() organisms:
- Complexity: 0.0–0.1 (simplest possible)
- Small body: 0.3–0.5 size, no appendages or tail
- Timid grazers: low aggression (0–0.2), small mouth (0–0.2), no hunting instinct
- Random brain: with biased forage neuron for innate food-seeking
| Complexity | Mode | Details |
|---|---|---|
| < 0.32 | Asexual only | Clone + mutate (25% mutation rate) |
| 0.32 – 0.62 | Sexual preferred, asexual fallback | Tries to find compatible mate first |
| 0.62 – 0.82 | Sexual preferred, rare asexual (45%) | Smooth transition zone |
| ≥ 0.82 | Sexual only | Must find a compatible mate |
Readiness: consumers must be mature, hold reproductive buffer above an offspring threshold, clear a brood cooldown, and maintain strong body condition. Needs.reproduction is now a derived behavioral signal, not a timer.
Sexual crossover: each gene randomly selected from one parent (uniform). Brain weights are a per-weight coin flip between parents. Offspring mutation rate: 15%.
Asexual division: clone parent genome + mutate at 25% rate. Higher drift enables faster exploration of gene space.
Mate compatibility: genomic distance must be < 8.0. Distance calculated as sum of absolute gene differences with brain distance weighted at 0.5× to prevent instant speciation from brain divergence alone.
Reproduction cost: explicit parental energy plus reproductive-buffer investment. Offspring start energy is reduced, and parents enter a cooldown instead of immediately breeding again.
Instead of fixed trophic roles, each creature derives a FeedingCapability from its genome:
- max_prey_mass — body_mass × mouth_size × 2.0
- hunt_skill — aggression × mouth_size × speed × hunting_instinct
- graze_skill — (1 − aggression) × (1 − mouth_size×0.5) × 0.5 + 0.5
This allows the full spectrum from grazers to apex predators to emerge naturally.
graph TD
Sun["☀ Sunlight"] --> Photo["Producer\nPhotosynthesis"]
Photo --> Biomass["Producer Biomass"]
Biomass --> Graze["Grazing by\nCreatures"]
Graze --> Energy["Creature Energy"]
Energy --> Repro["Creature\nReproduction"]
Energy --> Death["Death"]
Death --> Det["Detritus"]
Det --> Decomp["Decomposition"]
Decomp --> Nutrients["Dissolved N & P"]
Nutrients --> Photo
Biomass --> PTurnover["Producer Turnover"]
PTurnover --> Nutrients
Biomass --> PRepro["Producer\nReproduction"]
PRepro --> Biomass
Energy -->|"Predation"| Energy
style Sun fill:#f9a825,stroke:#f57f17,color:#000
style Nutrients fill:#1565c0,stroke:#0d47a1,color:#fff
style Det fill:#6d4c41,stroke:#4e342e,color:#fff
No artificial food is injected into the ecosystem. Energy enters through producer photosynthesis, but realized producer growth is filtered by canopy shading, water-column attenuation, dissolved nutrients, fouling load, and herbivory.
| Parameter | Model | Effect |
|---|---|---|
| Producer max energy | 15 × max_energy_factor × (1 + mass) | Genome-controlled reserve storage |
| Producer photosynthesis | Beer–Lambert capture × Monod light/N/P limitation × canopy/fouling shading | Carbon gain limited by the scarcest resource |
| Producer maintenance | Allometric biomass maintenance + tissue turnover + senescence turnover | Older or chronically stressed producers lose reserve and tissue gradually |
| Producer reserve buffering | Dormant biomass + regenerative bank translocation | Attached producer stands can survive dark/stress periods and regrow after defoliation |
| Producer reproduction | Reserve surplus × reserve_allocation × maturity | Parent reserve is debited; broadcast and fragment propagules differ in cost and dispersal |
| Producer establishment | Local light × crowding filter × propagule type | Fragments establish reliably nearby; broadcast propagules disperse farther but fail more often |
| Producer mortality | Reserve exhaustion or severe tissue loss | No deterministic age kill-switch for healthy producer colonies |
| Nutrient pool | Dissolved/sediment N and P + phytoplankton load | Couples producer growth, detritus recycling, and turbidity |
| Grazer intake | Consumer energy deficit, body mass, graze skill, and handling-limited intake over time | Satiated grazers do less damage; hungry grazers strip leaf tissue first |
| Detritus recycling | Dead creatures and plant turnover feed dissolved/sediment nutrients | Closes the nutrient loop |
| Creature max energy | 100 × body_mass | Larger creatures store more |
| Creature metabolism | mass^0.75 with complexity discount | Allometric cost for mobile consumers |
| Predation transfer | Fraction of prey reserve on successful capture | Hunting remains high-reward relative to grazing |
| Reproduction cost | Fraction of parental reserve | Investment in offspring remains explicit |
| Prey Type | Requirements |
|---|---|
| Plants | graze_skill > 0.2, non-trivial hunger/energy deficit, within 4 cells |
| Mobile prey (pursuit) | hunt_skill > 0.3, predator speed ≥ 0.6× prey speed, prey mass < max_prey_mass, within 2.5 + mass^0.33 cells |
| Mobile prey (ambush) | ambush factor (low speed ratio × camouflage) > 0.35, within 2.0 + mass^0.33 × 2.0 cells; enables slow, camouflaged predators |
| Trait | Benefit | Cost |
|---|---|---|
| Large body | More energy storage, can hunt larger prey, larger brain capacity, longer lifespan | Higher metabolism |
| Streamlined (elongated) | Faster, lower drag coefficient | Less energy storage |
| Bright colors | Higher visibility to allies | Attracts predators |
| Large eyes | Better sensory range (scales with body size) | Slight metabolism cost |
| High complexity | +50% sensory range, ~20% metabolism efficiency, richer morphology | Must find compatible mates, brain maintenance cost |
| High aggression | Better hunt_skill, can target mobile prey | Lower graze_skill |
| High timidity | Better flee response, avoids predators | Less time foraging |
Creatures have an internal needs system that drives behavior through the neural network:
| Need | Rate | Effect |
|---|---|---|
| Hunger | Rises at 0.02/s | Fed by eating, drives foraging |
| Safety | Decays 50%/s when safe | Spikes near predators, drives fleeing |
| Reproduction | Derived from maturity + reproductive buffer | Triggers mate-seeking only when life-history gates are satisfied |
| Rest | Rises at 0.01/s | Drives resting behavior |
| Social | Drifts to baseline | Drives schooling |
| Curiosity | Rises at 0.01/s | Drives exploration |
NeedWeights are derived from each creature's genome, making baseline hunger/rest/social drift evolvable. Reproductive timing is handled separately by ConsumerState.
| # | System | Description |
|---|---|---|
| 1 | Environment | Advance time, light, temperature, currents, random events |
| 2 | Spatial grid | Rebuild neighbor lookup structure |
| 3 | Entity info map | Build shared HashMap for all systems |
| 4 | Needs | Hunger rises, safety decays, and behavioral needs drift |
| 5 | Brain + Pheromone deposit | Neural network sensory processing, decisions, chemical signaling (parallel) |
| 6 | Pheromone grid | Decay and diffuse pheromone concentrations |
| 7 | Boids | Separation, alignment, cohesion steering forces (parallel) |
| 8 | Physics | Integrate velocities, enforce boundaries, update facing |
| 9 | Nutrients + LightField | Update dissolved/sediment nutrients, phytoplankton load, and rasterized canopy shading |
| 10 | Metabolism + Producer ecology | Drain creature energy, recycle detritus, run producer photosynthesis/maintenance/turnover, and buffer regenerative reserves |
| 10b | Consumer life history | Update maturation, reproductive buffer, and brood cooldown from energetic state |
| 11 | Hunting | Predator-prey interactions, demand-limited grazing, energy transfer (parallel) |
| 12 | Reproduction | Mate pairing or asexual division for creatures, then reserve-cost broadcast/fragment propagation for producers |
| 13 | Death | Remove starved entities, recycle producer biomass, spawn detritus for nutrient cycling |
| 14 | Animation | Advance frame timers |
Optimized for 600+ creatures at 20 ticks/sec:
| Optimization | Effect |
|---|---|
| NEAT brain with topological sort | Efficient variable-topology forward pass |
| Rayon parallelism | Brain, boids, hunting run on multiple cores |
Shared EntityInfoMap |
One world query + one HashMap per tick (not three) |
| Spatial hash grid (cell_size=6) | Distance-filtered neighbor queries in ~O(1) |
| Sensory range cap (scales with body size) | Prevents tank-wide scans that defeat spatial partitioning |
| Stats caching | Species/complexity recomputed every 20 ticks, not every frame |
| Zero-alloc rendering | Left-facing flip by index reversal, no string cloning |
| Soft population cap (600) | Reproduction suppressed to maintain frame rate |
| Crate | Version | Purpose |
|---|---|---|
| hecs | 0.11 | Lightweight ECS (entity component system) |
| ratatui | 0.29 | TUI rendering with double-buffered diffing |
| crossterm | 0.28 | Cross-platform terminal I/O |
| rand | 0.10 | RNG for procedural generation + simulation |
| rayon | 1.10 | Data-parallel computation for brain/boids/hunting |
| fontdue | 0.9 | Font rasterization for screenshots and GIF recording |
| image | 0.25 | PNG screenshot encoding |
| gif | 0.13 | Streaming animated GIF encoding |
| Algorithm | Module | Description |
|---|---|---|
| NEAT neuroevolution | brain.rs |
Evolving topology networks with innovation numbers, per-node activation functions (6 variants), bias, structural/role mutations, topological sort forward pass |
| Oja's rule | brain.rs |
Self-normalizing lifetime weight plasticity: Δw = η·post·(pre − post·w) + weight decay, with neuromodulation gating (Baldwin Effect) |
| Boids flocking | boids.rs |
Craig Reynolds (1986) — separation, alignment, cohesion + wall avoidance |
| Genetic evolution | genetics.rs |
Uniform crossover, Gaussian mutation, NEAT-aligned brain crossover, evolvable mutation rate; producer mutation with independent perturbation per gene |
| Fitness sharing | spawner.rs |
NEAT-style speciation: greedy clustering by genomic distance, proportional reproduction penalty |
| Allometric scaling | phenotype.rs, ecosystem.rs |
Metabolism ~ mass^0.75 for creatures and producers (Kleiber, 1947) |
| Beer–Lambert canopy capture | phenotype.rs, ecosystem.rs |
LAI-based light interception with diminishing returns |
| Monod resource limitation | ecosystem.rs |
Saturating light, nitrogen, and phosphorus limitation for producer growth |
| Procedural producer art | producer_lifecycle.rs |
Genome-driven colony ASCII art with stage-scaled speck/tuft/mat/plume forms |
| Dispersal trade-offs | genome.rs, lib.rs |
Broadcast propagule count vs size and local fragmentation trade-offs |
| Producer lifecycle | producer_lifecycle.rs |
Biomass/reserve/stress-based stage transitions with cell/patch/mature/broadcasting/collapsing states |
| Demand-limited grazing | ecosystem.rs |
Grazers remove active producer biomass and reserve according to consumer demand rather than fixed prey damage |
| Nutrient cycling | ecosystem.rs |
Dead creatures → detritus entities → grazable decomposition |
| Rasterized light field | ecosystem.rs |
Continuous depth attenuation plus canopy, phytoplankton, and epiphyte shading |
| Emergent predation | ecosystem.rs |
Morphology-derived feeding capability, body size ratio + speed checks |
| Pheromone signaling | pheromone.rs |
Grid-based chemical communication with decay, diffusion, gradient sensing |
| Spatial hashing | spatial.rs |
Grid-based bucketing with distance-filtered queries |
- Save/load simulation state (serde serialization)
- Terminal resize handling
- Configuration file for simulation parameters
Contributions are welcome! Feel free to:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please make sure cargo test --workspace passes before submitting.
- Kenneth O. Stanley & Risto Miikkulainen, Evolving Neural Networks Through Augmenting Topologies (Evolutionary Computation, 2002) — NEAT neuroevolution
- Christoph Adami, Charles Ofria, Travis C. Collier, Evolution of Biological Complexity (PNAS, 2000) — complexity emergence via natural selection
- Larry Yaeger, Computational Genetics, Physiology, Metabolism, Neural Systems, Learning, Vision, and Behavior (ALife III, 1993) — Polyworld ecosystem simulation
- Geoffrey Hinton & Steven Nowlan, How Learning Can Guide Evolution (Complex Systems, 1987) — Baldwin Effect
- Erkki Oja, Simplified Neuron Model as a Principal Component Analyzer (Journal of Mathematical Biology, 1982) — Oja's rule: self-normalizing Hebbian learning rule used for lifetime weight plasticity in creature brains
- Craig Reynolds, Flocks, Herds, and Schools: A Distributed Behavioral Model (SIGGRAPH 1987)
- Steering Behaviors for Autonomous Characters
- Litchman & Klausmeier, Trait-Based Community Ecology of Phytoplankton (Annual Review of Ecology, Evolution, and Systematics, 2008) — supports modeling aquatic producers as continuous trait combinations along light capture, nutrient use, and dispersal trade-offs
- Azam et al., The Ecological Role of Water-Column Microbes in the Sea (Marine Ecology Progress Series, 1983) — supports the founder-web framing of phototrophic producers, heterotrophs, detritus, and recycling in a microbial aquatic ecosystem
- Liu et al., Coupling Between Carbon and Nitrogen Metabolic Processes Mediated by Coastal Microbes in Synechococcus-Derived Organic Matter Addition Incubations (Frontiers in Microbiology, 2020) — supports routing part of active producer carbon through a labile microbial-loop pathway instead of forcing all heterotroph intake through direct tissue grazing
- Weedall & Hall, Sexual reproduction and genetic exchange in parasitic protists (Parasitology, 2015) — supports allowing genetic exchange in morphologically simple aquatic consumers instead of restricting crossover to animal-like complexity
- Robert H. MacArthur & Edward O. Wilson, The Theory of Island Biogeography (Princeton University Press, 1967) — r/K selection theory: trade-off between many cheap offspring (r) and few costly offspring (K); maps to propagule count vs propagule size
- He et al., Trait-mediated light and depth responses in submerged macrophytes (2019) — supports morphology-driven biomass targets and canopy access to brighter water
- Yu et al., Nitrogen enrichment and indirect shading effects on submerged plants (2018) — supports phytoplankton/periphyton shading and canopy-form vs low-form trade-offs
- Mebane et al., Nutrient co-limitation in aquatic primary producers (2021) — supports explicit dissolved N and P limitation instead of single-resource producer growth
- Li et al., Vegetative Propagule Pressure and Water Depth Affect Biomass and Evenness of Submerged Macrophyte Communities (2015) — supports local establishment limits and patch-occupancy effects on propagules
- Thompson & Eckert, Trade-offs between sexual and clonal reproduction in aquatic plants (2004) — supports reserve-cost dispersal vs local spread allocation
- Ren et al., Water depth affects submersed macrophyte more than herbivorous snail in mesotrophic lakes (2024) — supports grazing that can reduce attached growth while depth/light remains the dominant producer driver
- J. Philip Grime, Evidence for the Existence of Three Primary Strategies in Plants and Its Relevance to Ecological and Evolutionary Theory (The American Naturalist, 1977) — C-S-R triangle: Competitor/Stress-tolerator/Ruderal strategies; maps to producer genome trade-offs between photosynthesis rate, hardiness, and seed count
- Aristid Lindenmayer, Mathematical Models for Cellular Interaction in Development (Journal of Theoretical Biology, 1968) — L-systems: formal grammar for modeling branching plant morphology; informs producer colony geometry from branching and curvature genes
- Max Kleiber, Body Size and Metabolic Rate (Physiological Reviews, 1947) — Kleiber's law: metabolic rate scales with mass^0.75; used for both creature and producer maintenance costs
- Gillooly et al., Effects of Size and Temperature on Developmental Time (Nature, 2002) — supports scaling consumer maturation with body size on a weaker timescale than total lifespan in the aquatic founder web
- Brown et al., Toward a Metabolic Theory of Ecology (Ecology, 2004) — supports allometric maintenance and reserve-allocation framing across organisms
- Bradford A. Calder III, Size, Function, and Life History (Harvard University Press, 1984) — allometric lifespan scaling: lifespan ∝ mass^0.25 across taxa; used for body-mass-dependent consumer longevity
- Robert Henry Peters, The Ecological Implications of Body Size (Cambridge University Press, 1983) — broad allometric scaling survey; supports mass-dependent lifespan, metabolic rate, and home-range scaling
- Richard Bainbridge, The Speed of Swimming of Fish as Related to Size and to the Frequency and Amplitude of the Tail Beat (Journal of Experimental Biology, 1958) — burst swimming speed scales positively with body length in fish; informs the speed ∝ body_size^0.35 allometry
- Daniel M. Ware, Bioenergetics of Pelagic Fish: Theoretical Change in Swimming Speed and Ration with Body Size (Journal of the Fisheries Research Board of Canada, 1978) — sustained swimming speed scales with body length; supports positive speed-size exponent for aquatic consumers
- Eric R. Pianka, Convexity, Desert Lizards, and Spatial Heterogeneity (Ecology, 1966) — sit-and-wait vs active-foraging continuum; informs the dual pursuit/ambush predation model
- Raymond B. Huey & Eric R. Pianka, Ecological Consequences of Foraging Mode (Ecology, 1981) — trade-offs between ambush and pursuit predation strategies; supports camouflage-gated ambush hunting
- Paul W. Webb, Body and Fin Form and Strike Tactics of Four Teleost Predators Attacking Fathead Minnow Prey (Canadian Journal of Fisheries and Aquatic Sciences, 1984) — strike distance and kinematics scale with predator body size; informs body-mass-dependent strike range
- Eleanor M. Caves, Nicholas C. Brandley & Sönke Johnsen, Visual Acuity and the Evolution of Signals (Trends in Ecology & Evolution, 2018) — visual acuity scales with eye diameter and body size; informs size-dependent sensory range cap
- Harry J. Jerison, Evolution of the Brain and Intelligence (Academic Press, 1973) — encephalization quotient: brain volume scales as body_mass^0.67 across vertebrates; used for body-size-dependent brain capacity limits
- Leslie C. Aiello & Peter Wheeler, The Expensive-Tissue Hypothesis (Current Anthropology, 1995) — brain tissue is metabolically expensive (~22× skeletal muscle per gram); used for brain maintenance cost that scales with hidden neuron count
- Suzana Herculano-Houzel, The Remarkable, yet Not Extraordinary, Human Brain as a Scaled-Up Primate Brain and Its Associated Cost (PNAS, 2012) — neuron count, not brain mass, best predicts cognitive ability; supports scaling brain capacity (node count) rather than a single intelligence parameter
- Alexander Kotrschal, Bjørn Rogell, Andreas Bundsen et al., Artificial Selection on Relative Brain Size in the Guppy Reveals Costs and Benefits of Evolving a Larger Brain (Current Biology, 2013) — larger-brained guppies had better cognition but reduced gut size; supports the metabolic trade-off between brain capacity and body maintenance
- Beer–Lambert law — exponential light attenuation through a medium; used for LAI-based canopy capture and water-column attenuation
- Jacques Monod, The Growth of Bacterial Cultures (Annual Review of Microbiology, 1949) — provides the saturating half-saturation model used for light/N/P limitation
- C. S. Holling, The Components of Predation as Revealed by a Study of Small-Mammal Predation of the European Pine Sawfly (The Canadian Entomologist, 1959) — informs the demand-limited, saturating consumer-resource framing used for grazing
- Lotka–Volterra equations — predator-prey dynamics
- Wa-Tor simulation — population dynamics inspiration
- Fisherian runaway selection — sexual selection via mate preference feedback loops
This project is licensed under the Business Source License 1.1 (BUSL-1.1). Non-commercial use is permitted. On 2031-03-31 the code converts to the Apache License 2.0. See the LICENSE file for details.
Built with Rust, ratatui, and a love for emergent life.
