Skip to content

srad/TuiQuarium

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tuiquarium

An evolving ASCII aquarium in your terminal.

Rust License: BUSL-1.1 Tests Platform Status

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.

Simulation video

tuiquarium_1774917579


Features

Evolution & Genetics

  • 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 ratemutation_rate_factor gene (0.5–2.0) scales the base mutation rate in both creatures and producers; meta-evolution tunes evolvability itself
  • Sexual selectionmate_preference_hue gene 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

Neural Network Brains

  • 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_rate gene (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

Pheromone Signaling

  • Chemical communication — creatures deposit pheromones at their position; evolvable pheromone_sensitivity gene 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

Self-Sustaining Ecosystem

  • 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 LightField applies 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 f key
  • 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 feedingFeedingCapability derived 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

Simulation

  • 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 recordingp saves a full-resolution PNG; g toggles streaming GIF recording at configurable resolution and frame rate (see constants.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

Quick Start

Prerequisites

  • Rust 1.70 or later
  • A terminal with color support (most modern terminals work)

Build & Run

git clone https://github.com/srad/tuiquarium.git
cd tuiquarium
cargo run --release

For a fixed-size cross-platform window frontend backed by ratatui-wgpu:

cargo run --release -- --frontend gpu

The 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

Run Tests

cargo test --workspace    # 258 passing tests (235 core + 21 render + 2 app)

Controls

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/)

Architecture

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

Design Principles

  • Simulation knows nothing about rendering. The Simulation trait 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.

Neural Network Brains

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.

Network Architecture

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
Loading

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.

Sensory Inputs

# 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)

Behavioral Outputs

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.

Evolution

Genome Structure

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 Genome

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, and clonal_spread define 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

Primordial Producer Colonies

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

Primordial Cells

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

Reproduction

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.

Feeding Capability

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.

Energy Economy

Self-Sustaining Food Web

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
Loading

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.

Energy Parameters

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

Hunting Rules

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

Evolutionary Tradeoffs

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

Needs System

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.

Simulation Systems (per tick)

# 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

Performance

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 Dependencies

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

Algorithms

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

Roadmap

  • Save/load simulation state (serde serialization)
  • Terminal resize handling
  • Configuration file for simulation parameters

Contributing

Contributions are welcome! Feel free to:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please make sure cargo test --workspace passes before submitting.

References

Neuroevolution & Artificial Life

Flocking & Behavioral Models

Aquatic Producer Ecology

Allometric Scaling & Metabolism

  • 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

Swimming Speed & Locomotion

Predation Strategies

Brain & Intelligence

Photosynthesis & Light Capture

Population Dynamics

License

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.

About

An evolving ASCII aquarium in your terminal

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Contributors

Languages