home/ modules/ physics-agents-and-emergence

Physics, autonomous agents, and emergence

  • learner can build object-oriented agents with velocity/acceleration physics and collision detection
  • learner can define local rules (boids, Braitenberg, springs, repulsion) that produce emergent global behaviour
  • learner can reason about how initial conditions and noise shape the emergent form of a rule-based system

Implement a live agent-based ecosystem — flocking boids or a spring-mass/repulsion network — where OOP agents with physics and collisions self-organise, and demonstrate how changing initial conditions and noise reshapes the emergent form.

This module is where your visuals stop being drawings and start being populations. In a live set, an agent ecosystem is the visual layer that stays alive between your edits: a murmuration drifting over a techno drop, a spring network twitching in time with a bassline. The audience sees an organism, not a loop — and the whole task here is to build one on a Processing/p5 canvas that you can perturb live without it collapsing into either uniformity or chaos.

The arc starts scaffolded: define one agent class (“a class is a template; an instance is one concrete object”), give it the velocity-plus-acceleration update, and get a single body bouncing. Then multiply it into an array and add circle-circle collision — these two frame-loop mechanics are the part-task drills, because during a performance you will retype them from muscle memory. With mechanics in hand, the module shifts to behaviour: Braitenberg’s sensor-to-motor wiring is the minimal template for “agents that seem to want things,” and Reynolds’ three boids rules or the spring/repulsion force pair show how purely local rules self-organise into flocks and readable layouts. The final, unsupported stretch is compositional control: reseeding initial positions and injecting bounded jitter, so you can steer the macro form without touching the rules — exactly what the capstone demands you demonstrate live.

Required atoms are the load-bearing set: OOP structure, physics integration, collision, the emergence concept, each named local-rule family, and the two perturbation principles — the capstone fails without any one of them. Supporting atoms widen the palette: inheritance for specialised agent variants, the multiply-by-100 instinct, and cousins like circle packing, DLA growth, and human-driven agents that suggest where this ecosystem thinking goes next.

Walkthrough

You’ll build a population that looks alive — a flock that self-organises from local rules — starting from one bouncing dot. Paste each sketch into the p5 web editor and press Run (▶). Each step is a complete, standalone sketch. Vectors (createVector, p5.Vector) do the physics.

1 — one agent, one class. A class is a template; an instance is one concrete agent with its own state. Give it a position and velocity and a show(), and bounce it off the walls — the object owns its own behaviour ([[oop-classes-instances]], [[processing-oop-class]]).

class Mover {
  constructor(x, y) { this.pos = createVector(x, y); this.vel = createVector(2.4, 1.6); }
  update() {
    this.pos.add(this.vel);
    if (this.pos.x < 0 || this.pos.x > width) this.vel.x *= -1;
    if (this.pos.y < 0 || this.pos.y > height) this.vel.y *= -1;
  }
  show() { noStroke(); fill(255, 180, 0); circle(this.pos.x, this.pos.y, 18); }
}
let m;
function setup() { createCanvas(400, 400); m = new Mover(80, 120); }
function draw() { background(20); m.update(); m.show(); }

2 — physics, and a population. Real motion integrates acceleration into velocity into position. Add a downward acc (gravity), a floor bounce that loses energy, and push 30 movers into an array — the “multiply one into many” instinct ([[processing-velocity-acceleration]]).

class Mover {
  constructor(x, y) {
    this.pos = createVector(x, y);
    this.vel = createVector(random(-2, 2), 0);
    this.acc = createVector(0, 0.06);            // gravity
  }
  update() {
    this.vel.add(this.acc);
    this.pos.add(this.vel);
    if (this.pos.y > height) { this.pos.y = height; this.vel.y *= -0.8; }   // lossy floor
    if (this.pos.x < 0 || this.pos.x > width) this.vel.x *= -1;
  }
  show() { noStroke(); fill(120, 200, 255); circle(this.pos.x, this.pos.y, 14); }
}
let movers = [];
function setup() {
  createCanvas(400, 400);
  for (let i = 0; i < 30; i++) movers.push(new Mover(random(width), random(height / 2)));
}
function draw() { background(20); for (let m of movers) { m.update(); m.show(); } }

3 — collisions couple them. Agents that ignore each other aren’t an ecosystem. Test every pair with a circle–circle distance check and reverse both on contact — the crudest collision, but enough to make the population interact ([[collision-detection-circles]]).

class Ball {
  constructor(x, y) { this.pos = createVector(x, y); this.vel = createVector(random(-1, 1), random(-1, 1)).setMag(2.2); this.r = 16; }
  update() {
    this.pos.add(this.vel);
    if (this.pos.x < this.r || this.pos.x > width - this.r) this.vel.x *= -1;
    if (this.pos.y < this.r || this.pos.y > height - this.r) this.vel.y *= -1;
  }
  show() { noStroke(); fill(255, 120, 150); circle(this.pos.x, this.pos.y, this.r * 2); }
}
let balls = [];
function setup() { createCanvas(400, 400); for (let i = 0; i < 8; i++) balls.push(new Ball(random(60, 340), random(60, 340))); }
function draw() {
  background(20);
  for (let i = 0; i < balls.length; i++) {
    for (let j = i + 1; j < balls.length; j++) {
      let a = balls[i], b = balls[j];
      if (dist(a.pos.x, a.pos.y, b.pos.x, b.pos.y) < a.r + b.r) { a.vel.mult(-1); b.vel.mult(-1); }
    }
    balls[i].update(); balls[i].show();
  }
}

4 — emergence from local rules. Emergence is global order that no single agent contains — it arises from everyone following the same local rule. Two simple forces prove it: a gentle pull toward centre, and repulsion from close neighbours. The population settles into a ring nobody designed — the Braitenberg lesson that “wanting” is just wiring ([[emergence-definition]], [[agent-repulsion-layout]], [[braitenberg-vehicles-emergent-behaviour]]).

class Agent {
  constructor() { this.pos = createVector(random(width), random(height)); this.vel = createVector(); }
  step(all) {
    let f = createVector(200, 200).sub(this.pos).setMag(0.05);   // rule 1: pull to centre
    for (let o of all) {                                          // rule 2: repel neighbours
      let d = this.pos.copy().sub(o.pos), m = d.mag();
      if (m > 0 && m < 40) f.add(d.setMag(0.6 / m));
    }
    this.vel.add(f).limit(2.2); this.pos.add(this.vel);
  }
  show() { noStroke(); fill(150, 255, 200); circle(this.pos.x, this.pos.y, 10); }
}
let agents = [];
function setup() { createCanvas(400, 400); for (let i = 0; i < 60; i++) agents.push(new Agent()); }
function draw() { background(20); for (let a of agents) a.step(agents); for (let a of agents) a.show(); }

5 — boids: the three flocking rules. Reynolds’ classic. Each boid looks only at neighbours within a radius and blends three steers — separation (avoid crowding), alignment (match their heading), cohesion (move toward their centre). Local-only, yet the whole flock murmurates ([[boids-flocking-algorithm]], [[rule-based-generative-system-design]]).

class Boid {
  constructor() { this.pos = createVector(random(width), random(height)); this.vel = createVector(random(-1, 1), random(-1, 1)).setMag(2); this.acc = createVector(); }
  flock(boids) {
    let sep = createVector(), ali = createVector(), coh = createVector(), n = 0;
    for (let o of boids) {
      let d = dist(this.pos.x, this.pos.y, o.pos.x, o.pos.y);
      if (o !== this && d < 50) {
        sep.add(this.pos.copy().sub(o.pos).div(max(d, 0.1)));   // separation
        ali.add(o.vel);                                             // alignment
        coh.add(o.pos);                                             // cohesion
        n++;
      }
    }
    if (n > 0) {
      ali.div(n); coh.div(n).sub(this.pos);
      this.acc.add(sep.setMag(0.06)).add(ali.setMag(0.04)).add(coh.setMag(0.03));
    }
  }
  update() {
    this.vel.add(this.acc).limit(3); this.pos.add(this.vel); this.acc.mult(0);
    this.pos.x = (this.pos.x + width) % width; this.pos.y = (this.pos.y + height) % height;  // wrap
  }
  show() { noStroke(); fill(180, 220, 255); circle(this.pos.x, this.pos.y, 7); }
}
let flock = [];
function setup() { createCanvas(400, 400); for (let i = 0; i < 80; i++) flock.push(new Boid()); }
function draw() { background(15); for (let b of flock) b.flock(flock); for (let b of flock) { b.update(); b.show(); } }

6 — a live ecosystem (the capstone). Two moves turn the flock into something you can perform: a per-boid Perlin-noise wander so it never freezes into one dead clump, and initial conditions — here all boids start clustered at centre — that reshape the whole emergent form. Change either and the macro shape changes without touching the rules. Trails tie the motion together ([[noise-prevents-system-homogenization]], [[initial-conditions-shape-emergent-form]]):

class Boid {
  constructor(x, y) { this.pos = createVector(x, y); this.vel = createVector(random(-1, 1), random(-1, 1)).setMag(2); this.acc = createVector(); this.seed = random(1000); }
  flock(boids) {
    let sep = createVector(), ali = createVector(), coh = createVector(), n = 0;
    for (let o of boids) {
      let d = dist(this.pos.x, this.pos.y, o.pos.x, o.pos.y);
      if (o !== this && d < 55) { sep.add(this.pos.copy().sub(o.pos).div(max(d, 0.1))); ali.add(o.vel); coh.add(o.pos); n++; }
    }
    if (n > 0) { ali.div(n); coh.div(n).sub(this.pos);
      this.acc.add(sep.setMag(0.07)).add(ali.setMag(0.045)).add(coh.setMag(0.03)); }
    let ang = noise(this.seed, frameCount * 0.005) * TWO_PI * 2;   // noise wander keeps it alive
    this.acc.add(createVector(cos(ang), sin(ang)).mult(0.05));
  }
  update() {
    this.vel.add(this.acc).limit(3); this.pos.add(this.vel); this.acc.mult(0);
    this.pos.x = (this.pos.x + width) % width; this.pos.y = (this.pos.y + height) % height;
  }
  show() { noStroke(); fill(180, 230, 255, 200); circle(this.pos.x, this.pos.y, 6); }
}
let flock = [];
function setup() {
  createCanvas(400, 400);
  // INITIAL CONDITIONS: a tight central cluster blooms outward; spread them instead and it never coalesces
  for (let i = 0; i < 90; i++) flock.push(new Boid(200 + random(-60, 60), 200 + random(-60, 60)));
}
function draw() {
  noStroke(); fill(12, 40); rect(0, 0, width, height);   // trails
  for (let b of flock) b.flock(flock);
  for (let b of flock) { b.update(); b.show(); }
}

What good looks like. A living population reads as one organism with internal structure — you should see coherent motion (aligned streams, a settling ring) that is never fully uniform and never pure noise. The two failure poles are named in the atoms: too much cohesion and it collapses to a dead clump (add separation or noise); too little and it’s TV static (raise alignment/cohesion). The tell of a good flock is that you can point at emergent features — lanes, swirls, splits — that you never coded. (Skill map: live-visualist Domain B3 — motion with life and rhythm; and A4 — the generative-systems paradigm.)

Now make it yours. Reseed the flock spread across the whole canvas and watch the form change. Tune the three boid weights live — a fader per rule. Swap flocking for a spring-mass network: connect neighbours with spring forces (F = -k·x) for a twitching lattice ([[spring-mass-node-network]]). Colour each boid by its speed. Add a predator boid the others flee.

Runnable examples

Generated from the context/ instrument corpus by concept (redistributable idioms only). Do not edit — regenerate with gen-module-examples.mjs.

particle-system

let pts = font.textToPoints('P5', 0, 200, 200, {sampleFactor: 0.2})

p5live-0029 · CC0-1.0

physics-sim

let dir = p5.Vector.random2D().mult(random(2, 5))

p5live-0013 · CC0-1.0

Atoms in this module

Required — these gate the capstone

A class is a template; an instance is one concrete object created from that template
Concept L2 First instrument H
Processing classes bundle related fields and methods into reusable, instantiable objects
Concept L2 First instrument H
Physics simulation models position with velocity plus acceleration, where each value updates the next
Concept L2 First instrument H
Circle-circle collision is detected by comparing center distance to sum of radii
Procedure L3 Craft H
Emergence is complex organized behavior arising from many simple local interactions
Concept L3 Craft H
Realistic flocking simulation requires only three local rules: separation, alignment, cohesion
Principle L3 Craft H
Wiring a sensor to an actuator with a straight or crossed connection produces approach or avoidance behavior
Concept L2 First instrument HL
Braitenberg Vehicles show lifelike behaviour emerging from direct sensor-to-motor coupling
Concept L3 Craft H
Nodes that mutually repel each other self-organise into an even spatial distribution without explicit placement logic
Concept L3 Craft H
Connecting nodes with springs that pull toward a rest length produces force-directed layouts and elastic motion
Concept L3 Craft H
Varying the initial spatial distribution of agents changes the global form that emerges from identical local rules
Principle L3 Craft HL
A small random deviation prevents emergent systems from collapsing into a uniform state
Principle L3 Craft HL
Defining a small set of behavioral rules for elements produces emergent visual complexity through their interactions
Procedure L3 Craft HL

Supporting — enrichment, not gating

Processing class inheritance lets subclasses extend and override their parent's behaviour
Concept L3 Craft H
Multiplying a single generative element by many instances reveals emergent system behavior
Principle L2 First instrument H
Humans acting unconsciously in a system can serve as autonomous generative agents
Concept L4 Performance H
Circle packing fills a region by growing circles until they touch, then freezing each on collision
Concept L3 Craft H
Diffusion-limited aggregation grows organic dendritic clusters by snapping each new particle onto the nearest existing one
Concept L3 Craft H
P5LIVE realizes particle systems with real per-element p5.Vector state, making the concept portable from GLSL-fake to genuinely simulated
Concept L2 First instrument LH