▶ Live demo: run this tutorial — built from src/08_measurement.ts.
A model isn't much use if you can't measure it. AgentScript plots populations and exports datasets; Taos Engine does the same by reading GPU state back to the CPU. Here we run the Lotka-Volterra ecosystem from tutorial 6 and draw a live predator/prey population plot from the readback.
The two readback calls#
On the ParticleFeature:
readAgents(count?)→Promise<Float32Array>— snapshots the agent records, 16 floats each (theParticlestride). The AgentScriptturtlessnapshot.readPatchField(field)→Promise<Float32Array>— a patch grid as a row-majorres²array (deposit-scale undone). The AgentScriptexportDataSet.
Both are async and synchronizing (they copy to a staging buffer and map it), so call them a few times a second — never every frame.
Counting a population#
The 16-float record: [0..2] position, [3] life (< 0 = dead slot — skip it),
[4..6] velocity, [7] max_life, [8..11] color (color.a = species/state),
[12] size, [13] rotation, [14] flags, [15] pad. Counting live prey vs.
predators:
const a = await particles.readAgents(); // full capacity
let prey = 0, pred = 0;
for (let i = 0; i < a.length; i += 16) {
if (a[i + 3] >= 0 && a[i + 12] > 0) { // alive & active
if (a[i + 11] > 0.7) { pred++; } else { prey++; } // color.a = species
}
}Driving it from the frame loop#
Throttle the readback with a delta-time accumulator and push the counts into a scrolling 2D-canvas plot:
let measureAccum = 0;
engine.beforeFrame((frame) => {
measureAccum += frame.dt;
if (measureAccum >= 0.4) { measureAccum = 0; void measure(); } // ~2.5×/s
});measure reads the
agents, counts species, and strokes two lines on a <canvas> overlay — the classic
out-of-phase predator/prey oscillation, measured live.
Don't poll the buffer every frame. Each readback stalls until the GPU catches up. A few times a second is plenty for a plot and costs nothing visible.
Next#
- Tutorial 9 — Defining Your Own Model: the full
AgentModelConfig, building a NetLogo classic from scratch.