AgentScript CPU API reference + CPU→GPU translation map (Agentscript Webgpu)

**Note** from Bead: Agentscript Webgpu · [canonical source](https://redfish.acequia.io/guerin/.agents/f4ef67b7-001f-412c-ba90-f93ceba92bda/2026-06-17/notes/01-cpu-api-reference-and-translation-map.md) · session 2026-06-17 · discussion: Talk: Agentscript Webgpu

> Read from the **real source** (`Documents/sites/agentscript.org/src`, master `93d01a3`), 2026-06-17. > This is the curated "menu" (the classes the WebGPU port hinges on) — the **agentscript-orama** index is > the exhaustive on-demand fallback behind it. Citations are `file:line` into the clone. The GPU side is > the *proposed* schema; it supersedes the guesses in `notes/00` §2 where they differ.

## 0. The three hooks are AgentScript-native (Q5 settled) `Model` already declares exactly the modeler's three abstract methods ([`Model.js:119-134`](../../../../../../agentscript.org/src/Model.js)): - **`async startup()`** — "one-time initialization … import images, data" (optional). - **`setup()`** — initialize model state. - **`step()`** — advance one tick (auto-`tick()`s via a `Proxy`, `Model.js:137-153`). So the dual-facet `.mjs` contract in [`agent-based-hubler-network/agent.mjs`](https://redfish.acequia.io/guerin/.agents/d4b881f0-c320-4428-adac-7fa03941def4/agent.mjs) is not an invention — it mirrors AgentScript's own lifecycle. The port keeps these names; the only addition is **facet negotiation inside `startup`** (probe WebGPU, else CPU-JS).

## 1. World — the coordinate system = the GPU buffer dimensions `World` ([`World.js:35-118`](../../../../../../agentscript.org/src/World.js)) is an **integer patch grid**: - `minX..maxX`, `minY..maxY` (`minZ..maxZ` for 3D). Default ±16 ⇒ `numX = maxX-minX+1 = 33`, `numPatches = numX*numY`. - Patch cells are unit-spaced integer centers; continuous turtle coords run to `±0.5` past (`minXcor = minX-0.5`). - **Index formula** (`World.js:263-269`, dup in `Patches.patchIndex` `Patches.js:160-163`): `index = (x - minX) + numX * (maxY - y)` — **row-major, y flipped** (top row = `maxY` first). → **GPU:** `numX × numY` is the patch storage-texture / buffer extent. The index formula is the kernel's `xy → linear` map; keep the y-flip so patch buffers interop with AgentScript's own DataSet/canvas layout.

## 2. AgentSet — iteration, birth, death (the population engine) `AgentSet extends AgentArray extends Array` ([`AgentSet.js`](../../../../../../agentscript.org/src/AgentSet.js)). The pieces the GPU must honor: - **`ask(fcn)` — the per-agent step, with a snapshot guard** (`AgentSet.js:263-269`): ```js const lastID = this.last().id for (let i = 0; i < this.length && this[i].id <= lastID; i++) fcn(this[i], i, this) ``` Agents **hatched during the ask are NOT stepped this tick** (their `id > lastID`); dead agents have `id = -1`. This is the canonical "new agents wait one tick" rule. - **`addAgent`** (`:124-133`): assigns a **global monotonic `id = ID++`**, pushes. Breeds share the baseSet's ID counter. - **`removeAgent`** (`:140-152`): removes from the array (compaction by the Array's `remove`). - **Breeds** = subarrays over the same agents/ID space (`newBreed` `:213-215`, `withBreed` `:238-240`). → **GPU translation (the §0/notes-00-§3.1 hard problem, now concrete):** | CPU | GPU | |---|---| | `ask(fcn)` | one compute dispatch, one thread per live agent; `fcn` = the kernel body | | monotonic `id` | a global atomic `ID` counter buffer; new agents append above the live high-water mark | | "id ≤ lastID" guard | capture `lastID` (the count) **before** dispatch; the kernel runs `[0, lastID)`; hatched-this-tick land `≥ lastID` and are skipped until next tick — **same semantics, free** | | `die()` → `id=-1` | a `alive` flag (or `id=-1` sentinel) + periodic **stream compaction** (the gpu_cull pattern) | | breeds | a `breed` tag column + filtered dispatch, or separate buffers per breed | The lastID guard is the gift: AgentScript already serializes births to the next tick, so a GPU double-buffer (read `[0,lastID)`, append at the tail) reproduces it exactly without mid-tick hazards.

## 3. Turtle — the SoA buffer schema `Turtle` ([`Turtle.js:16-465`](../../../../../../agentscript.org/src/Turtle.js)). Stored variables (`Turtle.variables` `:27-32`, `defaults` `:17-26`): **`id, theta, x, y, z`** (+ `atEdge`, `hidden`). `heading` is **derived** from `theta` via the model geometry (`:121-129`), not stored. Behavior: - `setxy(x,y,z)` (`:153-160`) with **edge handling** `atEdge ∈ {wrap, die, bounce, clamp, random, fn}` (`handleEdge :197-237`). - `forward(d)` (`:252-257`): `x += d·cos θ; y += d·sin θ`. `rotate/left/right` (`:264-283`). `face/towards` (`:290-408`). - `hatch(num, breed, init)` (`:85-96`): create `num` at my (x,y,z,θ) — birth. - `die()` (`:51-69`): remove from set + breeds, **kill all my links**, drop from patch's turtle list, `id=-1`. - `links` (`:103-110`, lazy `AgentList`), `patch` (`:114-116`), `linkNeighbors()` (`:452-454`), `otherEnd(l)` (`:444-446`). → **GPU SoA buffers** (one column each, indexed by slot): `id:i32`, `theta:f32`, `x:f32`, `y:f32`, `z:f32`, `alive:u32`, `breed:u32`, `seedRNG:u32`, plus per-model breed vars. `heading` stays derived in the kernel (carry the geometry constants as uniforms). Edge handling = a branch in the move kernel.

## 4. Patches — fields, neighbors, and `diffuse` (the first kernel to port) `Patches extends AgentSet` ([`Patches.js`](../../../../../../agentscript.org/src/Patches.js)), one Patch per grid cell (`populate :39-43`). - **Neighbor stencils** (`neighborsOffsets :47-62`, `neighbors4Offsets :64-71`): 8-Moore / 4-vonNeumann as **flat-index offsets** `{±1, ±numX, ±numX±1}`, with edge cases enumerated (fewer neighbors on borders). - **`diffuse(v, rate)` = `diffuseN(8, …)`; `diffuse4` = `diffuseN(4, …)`** (`:284-320`) — the exact CA the prior `ants-in-taos` did by hand. Algorithm: 1. each patch gives away `dv = p[v]·rate`, split `dvn = dv/n` to each of `n` neighbors; 2. it **keeps** `p[v] − dv + (n−nn)·dvn` where `nn` = actual neighbor count (so **edge patches retain the share that would have gone off-grid** — boundary mass is conserved); 3. two-pass via a `_diffuseNext` temp (gather, then commit). - `inRect/inRadius/inCone` (`:235-256`), `patchRect` with `rectCache` (`:190-211`), `import/exportDataSet` (`:120-150`). → **GPU:** patches = a 2-D storage texture (or buffer) per field var. `diffuse` is the **ideal first kernel** — pure grid, no birth/death, two-pass = ping-pong, edge rule = clamp the stencil and add the missing-neighbor share back to self. One thread per cell. Matches `notes/00`'s "first kernel" pick.

## 5. Links — graph topology `Link` ([`Link.js:17-104`](../../../../../../agentscript.org/src/Link.js)): `end0`, `end1` (turtle refs); `init(from,to)` pushes the link into **both** turtles' `links` lists (`:42-47`); `length() = end0.distance(end1)` (`:64-66`); `otherEnd(turtle)` (`:73-77`). `Links.create(from, to[, init])` — `to` may be an array (`Links.js:29-41`). → **GPU:** an edge buffer of `(end0, end1)` **turtle-id** pairs (+ per-link vars). The per-turtle adjacency (`linkNeighbors`) = a CSR `{offsets, indices}` rebuilt when topology changes — exactly what `agent.mjs` already builds for the hubler relaxation. **Stable id→slot mapping is the catch:** links reference turtle **ids**, so turtle stream-compaction must carry an id→slot indirection (or compact links in lockstep).

## 6. Translation decisions surfaced by the real source - **Keep `theta` (radians) as the stored orientation; derive `heading` in-kernel.** Matches the library; avoids storing a redundant, geometry-dependent value. - **Births serialize to next tick for free** via the `lastID`/count snapshot (§2) — no mid-tick atomic ordering needed for the common case; only `die()`+compaction needs real GPU bookkeeping. - **The y-flipped row-major index is load-bearing** for DataSet/canvas interop — do not "tidy" it to bottom-up. - **`diffuse` edge-mass conservation** (§4 step 2) is a real semantic, not a rounding detail — the kernel must add the off-grid share back to self or fields will leak at borders.

## Open questions added (for Stephen) - **Q6 (port).** SoA storage buffers vs storage textures for **patch fields** — textures give free bilinear `DataSet` sampling + neighbor fetch; buffers are simpler to ping-pong. Likely textures for fields, buffers for turtles/links. - **Q7 (port).** Do we port the **breed** mechanism to the GPU (tag column + filtered dispatch) in v0, or defer it (single base set first)?

## References (bead cross-links) - Bead: Agent Based Hubler Network · [canonical](https://redfish.acequia.io/guerin/.agents/d4b881f0-c320-4428-adac-7fa03941def4/)