Artificial life simulator. Agents start with random neural networks and figure out how to survive through natural selection.
I wanted to see if complex behavior could emerge from pure evolutionary pressure, so I built this with Claude. Rust handles the simulation at 60 ticks/sec, Python runs NEAT for the neural evolution, Godot visualizes everything over WebSocket.
Rust Simulation (60 ticks/sec)
|
+-- Vision rays --> Python NEAT brain --> movement decisions
|
+-- Energy, food, reproduction mechanics
|
+-- WebSocket --> Godot client (visualization)
Simulation Engine (Rust): Handles physics, energy systems, life cycles, and reproduction. Runs the main loop at 60 ticks/second, managing hundreds of agents efficiently.
AI Engine (Python): Implements NEAT (NeuroEvolution of Augmenting Topologies) from scratch. Genomes evolve through mutation and crossover. Networks are evaluated via topological sort.
Visualization (Godot 4.5): Connects via WebSocket to render agents, food, predators, and statistics in real-time. Supports pause, speed control, and agent inspection.
- Rust (stable, 2021 edition)
- Python 3.10+
- Godot 4.5+ (optional, for visualization)
# Clone the repository
git clone https://github.com/yourusername/eon.git
cd eon
# Install Python dependencies
cd ai-engine
pip install -r requirements.txt
cd ..
# Build and run the simulation
cd simulation-engine
cargo run --release# Default: run with visualization (connect Godot client)
cargo run --release
# Headless training (faster, no rendering)
cargo run --release -- --train --ticks 1000000
# Load previously evolved brains
cargo run --release -- --load evolved_brains.json
# Evaluation mode (frozen, no reproduction)
cargo run --release -- --load brains.json --eval- Open
godot-client/project.godotin Godot 4.5+ - Run the project
- It will connect to the simulation via WebSocket on
localhost:9001
Each agent has a NEAT neural network:
- 19 inputs: 8 vision rays (food + danger channels), energy level, velocity
- 2 outputs: movement direction (x, y)
- Activation: tanh (bounded outputs)
Vision rays cast outward in 8 directions, detecting food, predators, obstacles, and walls. The network processes these inputs and outputs movement decisions each tick.
| Stage | Age (ticks) | Notes |
|---|---|---|
| Youth | 0-90 | Cannot reproduce |
| Prime | 90-600 | Full food absorption |
| Mature | 600-2700 | Absorption decays to 70% |
| Elder | 2700-3600 | Cannot reproduce |
| Senescent | 3600+ | Accelerated energy drain |
Sexual reproduction between nearby agents:
- Both parents must be in fertile age range (90-2700 ticks)
- Both must have >60% energy
- Offspring inherits crossover of parent genomes with mutation
- Cost: 20 energy per parent
Four predators roam the world:
- Detection radius: 250 units
- Speed: 40 (slower than agent max of 50)
- On contact: 50 energy damage (survivable if healthy)
- Behavior: chase nearest agent, wander when none in range
There is no fitness function. Survival is the fitness. Agents that find food live longer. Agents that avoid predators keep their energy. Agents that reproduce pass their genes. Bad genes die out. Good genes spread.
Mutations at birth:
- 80% chance: weight perturbation
- 5% chance: add new connection
- 3% chance: add new node
Key constants in the codebase:
| Parameter | Value | Location |
|---|---|---|
| World size | 1500x1500 | main.rs |
| Initial agents | 80 | main.rs |
| Max population | 200 | simulation/mod.rs |
| Max food | 450 | simulation/mod.rs |
| Predator count | 4 | simulation/mod.rs |
| Base energy drain | 0.04/tick | simulation/mod.rs |
| Food energy | 40-60 | simulation/mod.rs |
| Key | Action |
|---|---|
| Space | Pause/Resume |
| +/- | Adjust speed (0.25x - 20x) |
| WASD/Arrows | Pan camera |
| Mouse wheel | Zoom |
| Ctrl+S | Save simulation |
| Click agent | Inspect details |
eon/
+-- simulation-engine/ # Rust simulation core
| +-- src/
| +-- main.rs # Entry point, CLI
| +-- simulation/ # Main loop, life cycle
| +-- agent/ # Agent struct, sensors
| +-- ai/ # Python bridge (PyO3)
| +-- network/ # WebSocket server
| +-- world/ # World, obstacles
+-- ai-engine/ # Python NEAT implementation
| +-- src/
| +-- neat/ # Genome, network, population
| +-- brain/ # BrainManager interface
+-- godot-client/ # Visualization client
| +-- scripts/ # GDScript files
| +-- scenes/ # Scene definitions
+-- saves/ # Checkpoints and evolved brains
Why boom-bust cycles? Population peaks around 150, then declines as agents age past fertility. When population drops below 20, the system respawns from survivors plus fresh mutations. This prevents stagnation and maintains evolutionary pressure.
Why predators? Static obstacles do not create enough pressure. Moving threats force agents to develop spatial awareness and evasive behavior. Agents that cannot learn to avoid predators die quickly.
Why age-based absorption decay? Prevents immortal optimal agents from dominating forever. Forces gene propagation while young. Creates realistic life cycle pressure.
