Rodney Brooks' subsumption architecture (1986) replaced the classical sense→model→plan→act pipeline with a stack of independent, concurrently-running reactive behavior modules, each wired directly from sensors to motors. No layer holds a world model; each only sees what it needs.
Higher layers subsume — inhibit and override the motor output of — lower layers when their own trigger condition is active. This robot runs three layers, evaluated top-down every frame:
for each frame:
if minObstacleEdgeDist < avoidRange: # Layer 1 — highest priority
heading → away from obstacles (inverse-square repulsion)
elif seekEnabled: # Layer 2 — subsumed by 1
heading → toward light target
else: # Layer 3 — default fallback
heading → random walk
position += speed * dir(heading) * dt
The steering vectors are simple potential fields: Avoid sums a repulsion contribution (robotPos − obstaclePos) / edgeDist² over every obstacle whose surface lies inside sensor range, so closer obstacles push harder. Seek points straight at the glowing light target. Wander adds scaled random jitter to the current heading. No layer ever "asks permission" from another — arbitration is a fixed priority switch, which is why the panel above lights up exactly one active layer at a time.
- Avoid ON/OFF — disable Layer 1 to watch the robot drive straight through obstacles.
- Seek ON/OFF — disable Layer 2 to see pure wander-and-avoid with no goal.
- Obstacle count / arena size / sensor range — denser arenas, a bigger arena, or a shorter sensor range change how much of the run Avoid dominates; the strip chart below the arena shows this directly over time.
- Max turn rate / wander jitter — a low turn rate makes the robot overshoot around obstacles; low jitter makes Wander look almost like a straight line, high jitter makes it look like Brownian motion.
- Drag to pan, scroll to zoom the arena view; Reset View recenters and re-fits it.
This is the same layered-priority idea used in real behavior-based robots — from Brooks' original six-legged walker Genghis to modern vacuum robots that flee a cliff sensor before anything else, however "smart" their higher-level path planning is.
Note on this build: the wall-bounce reflection in the original 3D build had its two axis branches swapped (hitting the x-wall reflected the z-heading component and vice-versa), which let the robot get pinned sliding along a boundary indefinitely. This 2D build reflects the correct velocity component per wall — see the bounceOffWalls() comment in the source.