o
odinpkg.dev
packages / library / EvoSim

EvoSim

8baf8f2library

An evolution simulation built with Odin

No license · updated 2 months ago

EvoSimOdin

EvoSimOdin is a 2D evolution simulator written in Odin and rendered with raylib. Each organism moves around a bounded world, senses food, spends energy, attempts to mate, and carries a mutable genome that defines its neural controller. Over time, the simulation selects organisms indirectly through a configurable mating rule and uses their genomes to seed the next generation.

Purpose

The project is a compact evolutionary playground for experimenting with:

  • agent-based simulation
  • genome mutation and crossover
  • simple neural control
  • selection pressure based on environment or behavior

It is not a full NEAT implementation, but it borrows NEAT-style ideas such as innovation tracking, structural mutation, and genome crossover.

High-Level Flow

When the application starts, it opens a menu where you configure:

  • mating rule
  • population size
  • food cap
  • generation duration

Once a run starts:

  1. The world is reset and generation 0 is seeded.
  2. Each organism receives a genome and a runtime brain compiled from that genome.
  3. Every frame, the world updates organisms, food, and mating opportunities.
  4. Organisms lose energy over time, gain energy by eating, and can create offspring if they are mature, ready, close enough to a partner, and satisfy the active selection rule.
  5. Offspring genomes are queued into a next-generation pool.
  6. When the population dies out or the generation timer expires, the current generation ends.
  7. The next generation is seeded by cloning and mutating genomes from the queued offspring pool. If no pool exists yet, fresh minimal genomes are created instead.

Project Structure

.
|-- build.sh
|-- src/
|   `-- main.odin
|-- lib/
|   |-- lib.main.odin
|   |-- food/
|   |   `-- food.odin
|   |-- genome/
|   |   `-- genome.odin
|   |-- organism/
|   |   `-- organism.odin
|   `-- world/
|       `-- world.odin
`-- artefacts/

Module Guide

src/main.odin

Owns the application loop and UI flow.

  • creates the window
  • initializes and destroys the global simulation context
  • handles menu navigation and runtime hotkeys
  • replenishes food
  • advances the world each frame
  • triggers generation rollover
  • draws the menu and runtime dashboard

lib/lib.main.odin

Defines shared simulation-level configuration and state.

  • screen constants
  • default run settings
  • SelectionCriterion for choosing the active mating rule
  • RunConfig for user-selected settings
  • SimulationContext for innovation tracking, generation bookkeeping, and the next-generation genome pool

lib/world/world.odin

Owns the live simulation state.

  • active organisms
  • active food
  • generated wall rectangles
  • generation seeding and reset logic
  • per-frame world update
  • mating and eating coordination

This is the module that orchestrates most of the simulation lifecycle once a run is active.

lib/organism/organism.odin

Defines organisms and their behavior.

  • organism state such as energy, age, generation, fitness, and reproduction state
  • a compiled runtime Brain
  • sensing functions for walls, food, and nearby mates
  • movement and energy drain
  • readiness checks for mating
  • fitness calculation
  • drawing and cleanup

lib/genome/genome.odin

Defines the inheritable representation of an organism brain.

  • node genes
  • connection genes
  • genome cloning and destruction
  • global innovation bookkeeping through the shared context
  • sparse initial genome creation
  • weight and bias mutation
  • structural mutation by adding connections or hidden nodes
  • crossover between two parent genomes

This module is the core of the evolutionary part of the project.

lib/food/food.odin

Defines food entities that organisms consume to restore energy.

How the Simulation Works

1. World Initialization

main creates a SimulationContext, initializes a World, and generates walls with world.make_walls.

2. Organism Creation

world.seed_generation creates organisms in one of two ways:

  • from fresh minimal genomes when no offspring pool exists
  • from cloned and mutated genomes taken from ctx.next_generation_pool

Each organism is created by organism.make_organism, which assigns spawn position, starting energy, lifespan, and a compiled brain.

3. Sensing

Every frame, organism.update_organism builds a 10-value input vector:

  • 3 wall ray sensors
  • 3 food arc sensors
  • 3 mate arc sensors
  • 1 normalized energy value

These inputs are fed into the organism brain.

4. Thinking and Motion

The brain produces 4 outputs that drive:

  • forward/backward movement
  • steering
  • eating intent
  • mating intent

Movement drains energy. Turning also has a cost. Organisms die when energy reaches zero or they outlive their lifespan.

5. Eating

If an organism comes within EAT_RADIUS of a food item, it consumes the food, gains energy, and increments its food_eaten counter.

6. Mating and Selection

Mating is not purely random. An organism must:

  • be old enough
  • have enough energy
  • signal readiness
  • not be on cooldown
  • satisfy the active selection criterion
  • be physically close to a compatible partner

The current selectable criteria are:

  • RIGHT_SIDE
  • LEFT_SIDE
  • TOP_SIDE
  • BOTTOM_SIDE
  • HIGH_ENERGY
  • FOOD_FORAGERS

If mating succeeds:

  • both parents lose energy
  • both enter cooldown
  • a child genome is produced by crossover and mutation
  • that genome is queued for future generation seeding
  • a live child organism is also spawned immediately into the current world

7. Fitness

Fitness is recalculated from:

  • survival time
  • amount of food eaten
  • offspring produced
  • distance travelled
  • remaining energy

8. Generation Rollover

A generation ends when either:

  • all organisms die
  • generation time reaches the configured limit

At rollover:

  • living organisms are destroyed
  • best fitness is preserved in the context
  • food is cleared
  • the next generation is seeded from the queued offspring genome pool

Controls

Menu

  • Up / Down: select setting
  • Left / Right: change value
  • Enter: start run

During Simulation

  • P: pause/resume
  • R: restart run
  • M: return to menu

Build

The repository includes a small build script:

./build.sh

This builds the executable to:

artefacts/evosim

Equivalent direct build command:

odin build src -out:artefacts/evosim

Notes and Observations

  • The project uses raylib for rendering and input.
  • The SimulationContext owns the innovation map used during structural mutations.
  • The next generation is driven by genomes stored in next_generation_pool, not by copying full organisms.

Suggested Reading Order

If you want to understand the project quickly, read the files in this order:

  1. src/main.odin
  2. lib/lib.main.odin
  3. lib/world/world.odin
  4. lib/organism/organism.odin
  5. lib/genome/genome.odin
  6. lib/food/food.odin

That order follows the same path as the runtime: app loop -> global config -> world update -> agent behavior -> evolution internals.