A React application exploring agent-based modelling (ABM) and emergent behaviour — using Flocc.js to run two distinct simulations, each demonstrating how complex, unpredictable dynamics arise from agents following simple local rules.
Agent-based modelling is a foundational idea in modern AI: rather than prescribing system-level behaviour, you define individual agents with perception, internal state, and decision rules, then observe what emerges. These simulations are an exploration of that philosophy in a browser-rendered environment.
A spatial ecological simulation modelling population dynamics between a predator and prey species across an 800×600 canvas environment.
What it demonstrates:
- Agents with spatial perception — wolves have a configurable sense radius and scan a bounding box each tick to locate nearby sheep
- Energy-based lifecycles — each agent manages an energy budget; eating replenishes it, moving costs it, and reaching zero triggers death
- Emergent population cycles — neither a boom nor an extinction is hardcoded; Lotka-Volterra oscillations emerge naturally from the interaction rules
- Environmental state — grass is modelled as a pixel-level terrain layer that sheep graze and which regrows over time, adding a third dynamic to the system
Key agent behaviours:
| Agent | Perception | Actions per tick |
|---|---|---|
| Wolf | Scans WOLF_SENSE_RADIUS in all directions for sheep IDs |
Move → Sniff → Chase → Eat → (maybe) Reproduce |
| Sheep | None (random walk) | Move → Graze → Lose energy → (maybe) Reproduce → (maybe) Die |
The wolf's sniff function scans a spatial index (a flat array keyed by x + y * WIDTH) for sheep within its sense area, returning candidate prey IDs. This spatial indexing pattern — maintaining a separate location lookup updated on every agent move — keeps per-tick perception O(radius²) rather than O(n).
// Wolf perception — scans spatial index for nearby sheep
export const sniff = (agent, locations) => {
const { minY, maxY, minX, maxX } = getSenseArea(agent)
const prey = []
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
const preyLocated = findPrey(locations, x, y)
if (preyLocated) prey.push(preyLocated)
}
}
return prey
}Configurable parameters (in constants.js):
STARTING_SHEEP: 50 // initial population
STARTING_WOLVES: 20
SHEEP_REPRODUCE: 0.05 // probability per tick
WOLF_REPRODUCE: 0.3
WOLF_SENSE_RADIUS: 15 // perception range in grid units
SHEEP_GAIN_FROM_FOOD: 10 // energy from grazing
WOLF_GAIN_FROM_FOOD: 20 // energy from eating a sheepAn implementation of Robert Axelrod's "Building New Political Actors" — a model of how state-like actors emerge, accumulate power over others, and eventually dissolve.
"In 20th century history, the Soviet Union provides an example of this lifecycle, emerging with the Russian Revolution of 1917, building its sphere of influence post-WWII, and dissolving in the early 1990s."
What it demonstrates:
- Commitment as state — agents maintain a commitment matrix tracking loyalty strength toward every other agent, updated dynamically by the outcomes of tribute demands and fights
- Alliance formation — the
alliancefunction resolves which agents fight alongside each other by traversing the commitment graph from the challenger outward, stopping when commitment switches allegiance - Wealth redistribution — paying tribute transfers wealth from weak to strong; fighting redistributes it according to the combined strength of each side's alliance
- Emergent hierarchy — dominant actors emerge not by design but through the compounding effect of tribute chains; power is also unstable, dissolving as commitments shift
The turn cycle:
- Three agents are randomly selected as tribute demanders
- Each demander targets another agent based on vulnerability
- The target chooses to pay (increasing commitment, transferring wealth) or fight (decreasing commitment, risking wealth)
- Alliances are resolved — agents committed more strongly to one side join that coalition
- Wealth and commitments update; the chart and table re-render
Alliance resolution (simplified):
// Traverse the commitment graph outward from agent A,
// collecting allies whose commitment to A exceeds their commitment to B
const alliance = (a, b, population) => {
const withA = [a]
// walk left and right along the linear landscape
// stop when you hit an agent more loyal to B than A
...
return withA
}Simulation runs for 1500 turns across a population of 10 agents, with live visualisation via a wealth chart and a commitment table.
| Layer | Technology |
|---|---|
| Simulation engine | Flocc.js v0.5 |
| UI framework | React 18 |
| State | React Redux Toolkit |
| Rendering | Flocc canvas renderer + flocc-ui chart/table components |
| Build | Create React App |
The simulations are mounted via React refs — each scenario component holds useRef handles to the canvas and graph DOM nodes, passed into Flocc's environment at useEffect time. This keeps the simulation lifecycle cleanly decoupled from React's render cycle.
git clone https://github.com/MikeBlakeway/react-flocc-simulations.git
cd react-flocc-simulations
npm install
npm startOpen http://localhost:3000. Both simulations are available from the root route.
The core insight of ABM is that system-level behaviour doesn't have to be programmed — it can emerge from the interaction of agents following local rules. Neither the Lotka-Volterra cycles in Sheep vs Wolves nor the rise-and-fall of political powers in the Tributes Game is explicitly written anywhere in the codebase. They appear because the rules create the right conditions.
This idea is increasingly relevant to how AI systems are designed: rather than scripting every outcome, you define agents with goals, perception, and the ability to act on their environment — and let the system behaviour emerge from their interaction. These simulations are an exploration of that principle in a concrete, observable form.