Preview environment - you also see content that isn't published yet.

The world

Stage 0 - the brain-agnostic core: state, perception as bit-packing, reflexes, the loop and the first aha moment - stigmergy.

Before a brain exists, we build the world. It is brain-agnostic: it never knows who decides.

State

An ant is just position, heading and load. Plus some bookkeeping that only later brains need (Q-learning history, LLM thought):

{ id, x, y, heading, load,                   // core
  prevS, prevA, prevMarkA, rAccum,           // for Q-learning
  llmAction, llmMark, llmThought, thinking } // for the LLM brain

The world itself: a nest in the centre, a few sugar piles, and a list of markers (markers) - scent trails an ant leaves behind and others can smell. A marker has a position and a strength that fades over time. What else lives inside a marker - a reach and a small message - we build out in the next chapter.

Perception: 4 booleans → 16 states

This is the cleverest part. Perception is bucketed so it serves both (a) as a finite state for Q-learning and (b) as short text for an LLM:

function perceive(a, world, cfg) {
  const traegt      = a.load > 0;
  const sichtZucker = nearestSugarInSight(a, world, cfg.vision);
  const spur        = nearestMarkerInRange(a, world) !== null;
  const amBau       = dist(a, world.nest) < NEST_R + 18;
  // Bit-packing: 4 booleans → one number 0..15
  const s = (traegt?8:0) + (sichtZucker?4:0) + (spur?2:0) + (amBau?1:0);
  return { traegt, sichtZucker, spur, amBau, s };
}

Agent lesson: Perception is always a reduction of the world. The ant doesn't see “everything”, it sees four bits. Every agent - including an LLM with tool outputs - works on such a reduced, partial view. How you design that reduction decides what the agent can even learn.

Executing an action: decision + reflexes

The world executes what the brain decides. A decision has two parts: the navigation and - optionally - a marker to lay. Pick-up and drop-off, by contrast, are reflexes: they happen automatically on contact, nobody decides them.

function applyAction(a, decision, world, p) {
  steer(a, decision.nav, world, p);                       // set heading
  moveAnt(a, world);                                      // take one step
  if (decision.mark) addMarker(a, world, decision.mark);  // decided, not a reflex
  const picked    = reflexPickup(a, world);               // reflex on contact
  const delivered = reflexDrop(a, world);                 // reflex at the nest
  return { picked, delivered };
}

Agent lesson: The split decision vs. reflex is the single most important design choice in the whole world. The smaller the decided part, the easier a brain masters it - fixed rule, learning net or LLM alike. That the marker is decided rather than dropped automatically is exactly what makes it so interesting in the next chapter.

How it really looks (TypeScript)

The sketches above show the idea. Here is how the code really sits in the lab - in TypeScript, exactly as it runs there. Perception carries, beyond the four bits, a few fields about the marker it senses; what those mean is the topic of the next chapter.

// engine/world.ts (trimmed)
export const ACTIONS = ["ERKUNDE", "ZUM_ZUCKER", "FOLGE_SPUR", "ZUM_BAU"] as const;
export type ActionIndex = 0 | 1 | 2 | 3;

// A decision is navigation + an optional marker (details: next chapter).
export type BrainDecision = { nav: ActionIndex; mark: MarkChoice | null };

export type Percept = {
  traegt: boolean; sichtZucker: boolean; spur: boolean; amBau: boolean;
  s: number;          // 0..15 - bit-packing for Q-learning
  markType: 0 | 1 | 2 | 3; markPayload: number; markDir: number; // → chapter 3
};

export const NEST_R = 22;
const VISION = 60;

// Perception is always a REDUCTION of the world: four bits, packed into 0..15.
export function perceive(a: Ant, world: World): Percept {
  const traegt = a.load > 0;
  const sichtZucker = nearestSugarInSight(a, world) !== null;
  const mark = nearestRelevantMarker(a, world);
  const spur = mark !== null;
  const amBau = dist(a, world.nest) < NEST_R + 18;
  const s = (traegt ? 8 : 0) + (sichtZucker ? 4 : 0) + (spur ? 2 : 0) + (amBau ? 1 : 0);
  return { traegt, sichtZucker, spur, amBau, s, /* mark fields … */ } as Percept;
}

// Pick-up and drop-off are REFLEXES (they happen on contact). The marker, by
// contrast, is DECIDED - the world lays it only when the decision carries one.
export function applyAction(a: Ant, d: BrainDecision, world: World, p: Percept): StepEvent {
  steer(a, d.nav, world, p);
  moveAnt(a, world);
  if (d.mark) addMarker(a, world, d.mark);
  const picked = reflexPickup(a, world);
  const delivered = reflexDrop(a, world);
  return { picked, delivered };
}

The loop

A requestAnimationFrame loop. Several simulation steps per frame (speed), then the markers fade (evaporation) and rendering:

for (let step = 0; step < tempo; step++) {
  for (const a of world.ants) {
    const p        = perceive(a, world);
    const decision = brain.decide(p);     // ← the swappable box
    applyAction(a, decision, world, p);
  }
  evaporateMarkers(world); // markers weaken and disappear - forgetting
}

Let it run and out of random milling ant trails emerge - purely through markers, with no central control. That is stigmergy, and it is the first “aha” moment: coordination needs no omniscience, only local communication.

→ Try it in the lab - toggle markers on/off