▶ Live demo: run this tutorial — built from src/09_custom_models.ts. Distilled from samples/agent_termites.ts.
A model in Taos Engine is just a function that returns an
AgentModelConfig— there's no class to extend, no plugin, no registration step (unlike AgentScript'sclass extends Model). To show the whole surface, we build NetLogo "Termites" from scratch: termites carry wood chips, drop them onto existing piles (or rarely seed a new one), and pick up loose chips — and the chips self-organize into a few big piles.
The whole spec#
interface AgentModelConfig {
world: { min: [number, number]; size: [number, number];
topology?: 'torus' | 'bounded' | 'bounce' | 'die' };
population: number; // sizes the GPU buffer
movement?: 'position' | 'velocity'; // default 'position'
spawn?: AgentSpawn; // region, color, size, on-spawn actions
patches?: PatchConfig; // a diffusing grid (tutorial 3)
neighbors?: SpatialHashConfig; // enables neighbor queries (tutorial 4)
links?: LinksConfig; // link slots (tutorial 7)
reproduction?: boolean; // GPU births/deaths (tutorial 6)
step?: Action[]; // the per-agent program
behaviors?: ModifierNode[]; // built-in neighbor/physics behaviors
render?: { shape?, blendMode?, billboard?, sizeMul?, emit?, tiltToSurface? };
integrateVelocity?: boolean; // false → free `velocity` as a render channel
}You compose the pieces from earlier tutorials. Termites need: a static patch
field (chips), state in color.a (carrying vs. searching), and branching.
The termites model#
State machine in color.a (> 0.7 = carrying), reading the chip density here:
const carrying = expr.gt(expr.attribute('alpha'), 0.7);
const here = agent.sense(0, 'bilinear');
step: [
action.if_(carrying,
[ // carrying: wander; drop onto a pile (or rarely seed one)
agent.turn(agent.randomTurn(0.5)),
action.if_(expr.or(expr.gt(here, 1.5), expr.lt(expr.random({mode:'perFrame',seed:11}), 0.01)),
[agent.deposit(0, 1), action.set('alpha', 0.4), reorient(21)]),
],
[ // searching: wander; pick up a chip you're standing on
agent.turn(agent.randomTurn(0.5)),
action.if_(expr.gt(here, 0.5), [agent.deposit(0, -1), action.set('alpha', 1.0), reorient(31)]),
],
),
agent.moveForward(9),
action.set('rgb', expr.select(carrying, expr.constVec3([1.4,0.8,0.25]), expr.constVec3([0.25,0.35,0.5]))),
],Everyone starts carrying (spawn.color alpha 1.0); the chip field starts empty and
fills via the rare seed drops, then piles snowball because carriers drop onto
existing density. Give it ~30 s and a handful of big amber piles emerge — emergent
sorting from a three-line rule, the NetLogo classic, at 30k agents.
Where the boundaries are#
The step expresses arbitrary per-agent math, branching, sensing, depositing, and
the neighbor/distance verbs. What it can't do (by design, for the fixed-width GPU
model) is spawn agents directly (use reproduction + a behavior) or iterate a
variable-length link list. New neighbor/physics behaviors are added as
ModifierNode variants in the engine — see the module guide.
Next#
- Tutorial 10 — Agents on the Globe: the geo capstone.