Eusocial: a biology-grounded colony sim
A Godot and C# colony sim where a superorganism emerges from fixed-threshold workers reading an eight-channel pheromone field, with the simulation kept engine-free and tested.
Overview
Eusocial is a colony management game modelled on eusocial animals: ants, bees, termites and naked mole rats. The player manages a superorganism rather than individual units, through three tiers: a Queen who lays at whatever rate the colony can pay for, a Princess per caste who weights which signals her caste responds to, and fungible Daughters, the workers, who run simple stimulus-response rules. Each Daughter’s thresholds are fixed at birth, and the colony’s behaviour emerges from them reading a shared pheromone field.
Status: base-layer MVP. A colony founds itself, raises brood, forages along a real carrying chain, recognises the chambers it digs, fills its court and fights off a raid, all watchable through a debug view with pheromone overlays. There is no player interface yet, and one known regression: left alone, a colony marks ground for a new chamber but nobody digs it. That is measured, understood and the next stage on the roadmap.
Engineering specs
| Area | Implementation | Purpose |
|---|---|---|
| Engine boundary | /Simulation never references Godot; tests compile it without GodotSharp |
A colony can be run and measured without starting the engine |
| Daughters | Struct-of-arrays state, one flat array per attribute | Cache-friendly ticks for a colony of ~150 workers |
| Pheromones | Eight channels, flat per-channel arrays: deposit, decay, flood | Every behaviour is a response to a number on a grid |
| Rendering | MultiMeshInstance2D driven by the flat arrays |
No scene-tree node per worker |
| Tests | xUnit, around 48 test files, with slow load cases filterable | Mechanics validated by measured runs, not assumption |
Technical deep dive
The project is C# on Godot 4.4 and .NET 8, with about 18,500 lines of simulation and rendering
code and 11,000 lines of tests. The engine boundary is enforced by the build, not by convention:
the test project compiles the /Simulation sources into an assembly with no Godot reference, so
the moment simulation code reaches for a Node, the tests stop compiling.
Per-tick work is organised as loops: foraging, tending, digging, the nest loop, the caste-flip
check and the raid. Each loop acts only on the bodies whose intent names it, never re-decides an
intent another loop made, and never moves a body another loop owns. An arbiter hands each Daughter
to one loop, and the order they run in is an explicit list in ColonyRun.Step.
The design docs (about 1,900 lines) are the source of truth, and many decisions in them are written up as measured findings. Foragers find their way home by path integration first, with error that grows with distance, and only then by reading a trail downhill; plain trail reversal was rejected because the first forager has no trail to follow, so the foraging economy would never start. Alarm is anchored to the raider rather than the witness, because marking the witness’s own cell made Soldiers converge on each other. Detection is a caste skill expressed as range, not volume, since a volume-based design meant a colony with no Soldiers could never raise the alarm that creates Soldiers.
Skipping pheromone channels that hold nothing, plus a cell mask, took a 144-worker colony from about 426 to 1,300 ticks per second, and a later stage optimising the relaxation it kept took the tick rate from 430 to 3,930 ticks per second.
Key challenge: making a spreading pheromone field that a threshold check can read and a gradient-following worker can climb. The per-hop relaxation that spreads signals was meant to be temporary, replaced by letting an over-capacity cell spill its excess into open neighbours. Tried properly, the spill model produced a standing checkerboard at every ceiling, high or low: cells alternating near the cap and near zero. Clamping a cell and splitting its excess off a snapshot is an explicit diffusion step run past its stability limit, so no tuning could fix it. The result was recorded as a negative finding: relaxation became permanent, and the effort went into making it fast instead.