๐ Go Swarm Simulation
๐ด Red Virus vs ๐ต Blue Flock โฆConvert or Be Converted ๐ฆ ๐
๐ Overview
Go Swarm Simulation is a "Game of Life on steroids" that demonstrates the power of the Actor Model for building concurrent, decentralized systems.
A graphical experiment in decentralized decision-making using the Actor Model (GoAkt) and Ebitengine.
Instead of a central controller managing the state of every entity, each individual dot in this world is an autonomous Actor running in its own goroutine. They possess their own state, personality, and decision-making logic.
The simulation visualizes two distinct behaviors interacting in a 2D world.
(live demo โ 25 red vs 250 blue boids tiny spaceships fighting for ideological supremacy)
A real-time, visually polished swarm simulation in Go where Red aggressive hunters try to infect Blue flocking prey.
One side uses raw pursuit and conversion, the other relies on classic Boids rules + safety-in-numbers.
Watch emergent strategies appear: defensive circles, sacrifice plays, collapse waves, and total extinction events.
Pure Go โข GoAkt actors โข Ebitengine โข Zero shared mutable state โข Live-tunable parameters
๐ Features
- 100% Actor Model Architecture: Built on GoAkt (no central "God object", no locks)
- Thread-Safe Config: Each actor owns its config values โ no shared mutable state, updates via message passing
- ProtoBuf Messages: Utilizing Protocol Buffers for high-performance, type-safe message passing
- Spatial Hashing: Optimized neighbor lookups using a spatial grid, allowing for efficient O(1) interaction checks even with large populations
- Proto State Caching: Pre-computed protobuf states per tick reduce allocations by ~90%
- Dynamic Behavior Switching: True hot behavior swapping via
ctx.Become() โ actors literally change personality when converted
- Flocking Behaviors: Implementation of Reynolds' Boids algorithm for realistic group movement
- Real-Time Visualization: Renders thousands of concurrent updates smoothly using Ebitengine
- Full Live UI: Collapsible control panel with 20+ sliders & checkboxes, color-coded by actor type
- External Config:
config.json + JSON Schema validation with field names prefixed by actor color (e.g., redDetectionRange, blueCohesion)
- Pre-rendered Sprites: ASCII-art spaceships with glow trails
- Hot Restart: Change any parameter and click Restart โ no recompile needed
- Profiling Support: CPU and memory profiling via command-line flags
- Race Condition Fixes: Defensive validation handles async message ordering edge cases
๐ ๏ธ Architecture
The project follows a clean separation of concerns:
- The World (Brain): The
WorldActor manages the authoritative state and the Spatial Grid. It handles collision detection and broadcasts updates to all Individual actors.
- The Individuals (Actors): Each entity is an actor that owns its config values and decides how to move based on its current behavior (Red or Blue).
- The Protocol (Protobuf): All messages (
Tick, GetState, ActorState, UpdateConfig) are strictly defined in proto files for type safety.
- The View (Ebiten): The main game loop simply drains the update channel and renders the latest known state.
Data Flow Diagram
graph TD
subgraph "Game Loop (Main Thread)"
Update[Ebiten Update] -->|1. Tick| World[World Actor]
Draw[Ebiten Draw]
end
subgraph "Actor System (Goroutines)"
World -->|2. Rebuild Grid| Grid[Spatial Grid]
World -->|3. Cache Proto States| Cache[Proto Cache]
World -->|4. Forward Tick + Perception| Ind[Individual Actors]
Ind -->|5. Apply Boids/Chase Logic| Ind
Ind -->|6. Push State| Channel[Buffered Channel]
end
subgraph "Config Updates"
UI[UI Sliders] -->|UpdateConfig| World
World -->|Broadcast UpdateConfig| Ind
end
Channel -->|7. Consume State| Draw
๐ฆ Prerequisites
- Go: Version 1.22 or higher
- Protoc Compiler: (Optional) Only needed if you modify the
.proto definitions
๐ Getting Started
1. Clone the Repository
git clone https://github.com/lao-tseu-is-alive/go-swarm-simulation.git
cd go-swarm-simulation
2. Install Dependencies
go mod tidy
3. Run the Simulation
go run ./cmd/simulation
4. Run with Profiling (Optional)
# CPU and memory profiling
go run ./cmd/simulation -cpuprofile cpu.pprof -memprofile mem.pprof
# Analyze with pprof
go tool pprof cpu.pprof
๐ Project Structure
.
โโโ cmd/
โ โโโ simulation/ # Main entry point (Ebiten Game Loop)
โโโ pkg/
โ โโโ simulation/ # Core Actor Logic (World, Individual, Config, Entity, Boids)
โ โโโ ui/ # UI widgets for Ebiten (buttons, sliders, checkboxes)
โ โโโ geometry/ # 2D Vector math library
โ โโโ version/ # Build version info
โโโ pb/ # Protobuf definitions and generated code
โโโ config.json # Runtime configuration (validated against schema)
โโโ config_schema.json # JSON Schema for config validation
โโโ go.mod
๐ฎ Controls
| Action |
Effect |
Click < Settings |
Show/hide the control panel |
| Adjust sliders |
Change simulation parameters in real-time |
Click Restart Simulation |
Apply population changes and reset the world |
Configuration Parameters
| Section |
Parameters |
| ๐ด Red Hunter |
Detection Range, Attack Range, Aggression |
| ๐ต Blue Flock |
Flock Vision, Personal Space, Defense Range |
| ๐ต Flocking Tuning |
Cohesion, Separation, Alignment, Edge Avoidance |
| Physics (Both) |
Max Speed, Min Speed |
| Population |
Red Actors, Blue Actors (requires restart) |
| Visualization |
Show Detection Circle, Show Defense Circle |
๐ง How It Works
Behavior Switching
One of the most powerful features of the Actor Model is Behavior Switching. When a Red actor is "converted" to Blue, it hot-swaps its entire message processing function:
// pkg/simulation/individual.go
func (i *Individual) handleConversion(ctx *actor.ReceiveContext, msg *pb.Convert) {
oldColor := i.State.Color
i.State.Color = msg.TargetColor
// Hot-swap behavior function
if i.State.Color == pb.TeamColor_TEAM_RED {
ctx.Become(i.RedBehavior)
} else {
ctx.Become(i.BlueBehavior)
}
// Visual feedback: "Explosion" bounce effect
i.State.Vel = i.State.Vel.Mul(-1.5)
}
Thread-Safe Config
Each Individual actor owns its config values (no shared *Config pointer). Updates flow via messages:
// World broadcasts config changes to all actors
case *pb.UpdateConfig:
w.cfg.RedAggression = msg.GetAggression()
// ... update world's copy
// Broadcast to all individuals
for _, pid := range w.pids {
ctx.Tell(pid, msg)
}
// Each Individual applies updates to its local copy
func (i *Individual) applyConfigUpdate(msg *pb.UpdateConfig) {
i.redAggression = msg.GetAggression()
i.blueCohesion = msg.GetCenteringFactor()
// ...
}
| Optimization |
Description |
| Spatial Hashing |
O(1) neighbor lookups instead of O(nยฒ) |
| Pre-squared Distances |
Avoid sqrt() in hot loops |
| Proto State Caching |
Each entity's protobuf computed once per tick, reused for all perception queries |
| Zero-allocation Filtering |
Slice re-slicing ([:0]) for race condition fixes |
| Batched Sprite Rendering |
Pre-rendered spaceship images |
๐บ๏ธ Roadmap / Dreams
- GoAkt remoting โ 50k+ actors across multiple machines
- Headless replay server + GIF export
- WASM build (yes, it runs in the browser)
- Genetic evolution of parameters (watch new strategies evolve)
- Obstacles, resources, multiple factions
๐ Credits & Thanks
๐ค Contributing
PRs or any contributions are welcome!
ยท May your flock hold the line (or may it dramatically fail โ both are fun).
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature)
- Commit your Changes (
git commit -m 'Add some AmazingFeature')
- Push to the Branch (
git push origin feature/AmazingFeature)
- Open a Pull Request
๐ License
Distributed under the MIT License. See LICENSE for more information.
Built with โค๏ธ by Lao-Tseu-is-Alive in 2025 using GoAkt and Ebitengine