# Showcase: Physarum network

A *Physarum* (slime-mold) transport network: tens of thousands of agents deposit a pheromone
trail, sense their own trail ahead, and steer toward it. Positive feedback condenses diffuse
pheromone into sharp branching veins; decay prunes the unused ones; the network continuously
rewires itself. It is among the gentlest of the showcases in terms of engine footprint, so it's
the best place to learn the shared pattern.

<Aside type="note" title="Read Foundations first">
This guide assumes the [Foundations](/guides/foundations) page — the animation contract, the
post-FX surface and its `fadeTrail → draw → composite` frame, and the pure-module discipline. Here
we cover only what's unique to Physarum.
</Aside>

## What the engine provides here

Narrowly: `GameLoop` + `CoroutineManager` (`@flare-engine/core`), `SeededRNG` + `clamp`/`lerp`
(`@flare-engine/math`), an optional spark `ParticlePool` (`@flare-engine/particles`), and the
bloom/aberration finish (`@flare-engine/postfx`). **The entire simulation — the agents and the
pheromone field — is your own framework-free code.** No ECS, no physics package. That's the point:
the engine makes a heavy custom sim run deterministically and glow; it doesn't run it.

<Aside type="note" title="Now shippable as a package">
Since this guide was written, the stigmergy primitive it builds by hand has shipped in
**`@flare-engine/simulation`** (0.2.0): `createScalarField` for the pheromone field and
`createStigmergy` / `senseAt` / `decideTurn` for the sense-rotate-move agents. The walkthrough below
still builds it from scratch — that's the pedagogy — but you can now reach for the package instead.
</Aside>

## Data-oriented agents

With tens of thousands of agents, per-entity objects would thrash the garbage collector. Store
them as a **structure of arrays** — parallel `Float32Array`s — instead:

```ts
const px = new Float32Array(count);    // x position
const py = new Float32Array(count);    // y position
const heading = new Float32Array(count); // direction, radians
```

Scatter them reproducibly with the engine's `SeededRNG` so a given seed always yields the same
network — invaluable when you're tuning:

```ts

const rng = new SeededRNG(seed);
for (let i = 0; i < count; i++) {
  px[i] = rng.float(0, width);
  py[i] = rng.float(0, height);
  heading[i] = rng.float(0, Math.PI * 2);
}
```

## The pheromone field as a pure module

`physarum/field.ts` is a `TrailField` — a coarse scalar grid with no engine or CanvasKit imports,
unit-tested with `bun test`. It exposes a handful of allocation-free operations:

- `senseAt(x, y)` — bilinear sample of the field (what an agent "smells").
- `deposit(x, y, amount)` — additive drop where an agent stands.
- `step()` — one diffuse + decay tick (below).
- `toRGBA(...)` — pack the whole grid into an RGBA byte buffer for the renderer.

## The Jones rule

Each fixed step, every agent reads three sensors — front, front-left, front-right — turns toward
the brightest, adds a little chaos, then steps forward (with toroidal wrap) and deposits:

```ts
function stepAgents(field: TrailField, dt: number): void {
  for (let i = 0; i < count; i++) {
    const a = heading[i];
    const f  = field.senseAt(px[i] + Math.cos(a) * SENSE, py[i] + Math.sin(a) * SENSE);
    const l  = field.senseAt(px[i] + Math.cos(a - SENSE_ANGLE) * SENSE, /* ... */);
    const r  = field.senseAt(px[i] + Math.cos(a + SENSE_ANGLE) * SENSE, /* ... */);
    heading[i] = decideTurn(a, f, l, r) + (rng.float(-1, 1) * chaos);
    px[i] = wrap(px[i] + Math.cos(heading[i]) * SPEED, width);
    py[i] = wrap(py[i] + Math.sin(heading[i]) * SPEED, height);
    field.deposit(px[i], py[i], DEPOSIT);
  }
}
```

`decideTurn` and `senseAt` are pure functions with their own tests — the rule's correctness is
verified without ever touching a canvas.

## Allocation-free diffuse + decay

The network's life is in the field's `step()`: a **separable 3×3 box blur** (a horizontal pass
then a vertical pass into a ping-pong buffer — far cheaper than a 2D kernel) followed by a
multiplicative decay and a clamp. Reusing two preallocated buffers means **zero allocation per
frame**:

```ts
step(): void {
  boxBlurH(this.buf, this.tmp, this.w, this.h);   // horizontal pass → tmp
  boxBlurV(this.tmp, this.buf, this.w, this.h);   // vertical pass → buf
  for (let i = 0; i < this.buf.length; i++) {
    this.buf[i] = clamp(this.buf[i] * this.decay, 0, this.max); // prune + stabilize
  }
}
```

Decay is what makes it loop forever with no reset: unused veins fade, active ones are continuously
re-fed, so the network is always rewiring.

## Driving it on the loop

Run the agents and the field on the **fixed** step so behavior is frame-rate-independent; let the
`CoroutineManager` choreography ride the **variable** step:

```ts
loop.onFixedUpdate.add((dt) => { stepAgents(field, dt); field.step(); });
loop.onUpdate.add((dt) => coro.update(dt));
```

## Living choreography

A few nutrient sources walk a `seed → branch → migrate → reknit` cycle and **emit into the
field**, so the web grows veins toward food (the famous Tokyo-rail result). Each wait is gated on
the network's live density, never a clock:

```ts
function* nutrients() {
  placeSeed();
  yield* waitUntil(() => density() > seedThreshold);   // wait for a core to form
  branch();
  yield* waitUntil(() => density() > branchThreshold);
  migrate();                                             // move the food; veins chase it
  yield* waitUntil(() => reknit());
}
```

## Rendering: the field *is* the image

The key rendering move: don't draw cells. Pack the entire field into one RGBA buffer with a
palette-binned ramp, build one GPU texture with `kit.MakeImage`, and blit it upscaled with a
single `drawImageRectOptions` call. Nutrient nodes, a sparse agent shimmer, and ignition sparks
ride on top; then the shared surface composites bloom + aberration:

```ts
draw(sim: PhysarumSim): void {
  const sc = this.surface.sceneCanvas();
  this.surface.fadeTrail(BG, 0.2);
  const bytes = sim.field.toRGBA(this.scratch);            // grid → RGBA, palette ramp
  const img = this.kit.MakeImage(this.imageInfo, bytes, this.rowBytes);
  sc.drawImageRectOptions(img, srcRect, dstRectUpscaled, /* nearest */, paintPlus);
  img.delete();
  drawNutrients(sc); drawAgentShimmer(sc);
  this.surface.composite(sim.glow());                      // engine bloom + aberration → present
}
```

<Aside type="caution" title="One image, not many draws">
`MakeImage` allocates a GPU texture each frame. That's fine for one full-field blit; it would be
ruinous per cell. The single-image pattern is what keeps tens of thousands of agents cheap to draw — one full-field blit instead of tens of thousands of per-cell draws.
</Aside>

## Recap

<Steps>

1. Agents as parallel `Float32Array`s, scattered with `SeededRNG`.
2. A pure, bun-tested `TrailField`: sense, deposit, separable blur + decay, `toRGBA`.
3. The Jones sense-rotate-move rule on `loop.onFixedUpdate`.
4. Density-gated nutrient choreography on a `CoroutineManager`.
5. Render the field as **one** `MakeImage` blit; finish with the engine's post-FX surface.

</Steps>

The engine carried the loop, the RNG, an optional spark pool, and the glow. Everything that makes
it *Physarum* is the pure field + agent code — yours to keep and to unit-test.

<Aside type="note">
See it live in the [hero](/) (pick "Physarum"). Next:
[Nuclear reactor](/guides/reactions) adds the ECS and tweens.
</Aside>
