How it is built, and how a world gets made
The module layers and the streaming cook path, in two figures. Everything here is the same library the landing page introduces; this is the level below it.
Architecture
Layered core, optional adapters
The core has zero dependencies and never imports three.js or WebGPU — guard tests enforce that only the adapters may. Each layer depends only on the ones before it.
%%{init: {"theme": "neutral", "flowchart": {"htmlLabels": false, "curve": "basis", "subGraphTitleMargin": {"top": 12, "bottom": 6}}}}%%
flowchart TB
subgraph L1["foundation"]
direction LR
RANDOM["src/random — PCG32 · hashing"]
DATA["src/data — SoA attributes · domains"]
end
subgraph L2["values"]
direction LR
FIELDS["src/fields — deferred values · combinators"]
NOISE["src/noise — 5 noise types as fields"]
end
subgraph L3["execution"]
direction LR
GRAPH["src/graph — executor · memo cache · subgraphs"]
NODES["src/nodes — 61 types · registry · JSON"]
SPATIAL["src/spatial — uniform grid · adjacency CSR"]
end
subgraph L4["orchestration"]
direction LR
RUNTIME["src/runtime — World · streaming"]
SPAWN["src/spawn — InstanceBatch protocol"]
end
subgraph L5["authoring"]
direction LR
PRIMS["src/primitives — 37 named recipes"]
CLI["src/cli — pcg: validate · cook · inspect · render"]
end
THREE["src/three — optional adapter, the only module importing three"]
GPU["src/gpu — optional WGSL compiler + WebGPU device runtime"]
L1 --> L2 --> L3 --> L4 --> L5
L4 -. render-agnostic boundary .-> THREE
L2 -. structural resolver boundary .-> GPU
Fig. 1 — module layers; arrows are the only allowed dependency direction.
src/datatyped-array attribute storage, geometry, promote/transfersrc/randomPCG32 RNG and seed hashing — no Math.random anywheresrc/fieldsdeferred values, evaluation contexts, memoizationsrc/noisefive noise fields with published raw ranges + normalized modesrc/graphnodes, pins, scheduler, caching, subgraph compositionsrc/nodesstandard library, self-describing registry, JSON round-tripssrc/spatialuniform grid and adjacency CSR the spatial nodes sharesrc/runtimehierarchical World, partitioned cooking, invalidationsrc/spawnrender-agnostic instancing terminalsrc/primitivesnamed subgraph recipes referenced from JSON by namesrc/clithepcgbinary — validate, cook, inspect, rendersrc/gpufield-expression → WGSL compiler, WebGPU evaluator, device-resident runs, parity-budgetedsrc/threeBufferGeometry/Curve converters, InstancedMesh, scene binding
How a world gets made
From a seed to instances on screen
%%{init: {"theme": "neutral", "flowchart": {"htmlLabels": false, "curve": "basis"}}}%%
flowchart TB
SEED(["seed + viewpoint"]) --> WORLD["World: coarse to fine levels"]
WORLD --> CELLS["wanted cells: hysteresis · LRU"]
CELLS --> COOK["cook per cell, seeded by coords"]
COOK <--> CACHE[("memo cache")]
COOK --> ITEMS["DataItems: geometry · instances"]
ITEMS --> BATCH["InstanceBatch: assetId · transforms"]
BATCH --> ADAPTER["three adapter"]
ADAPTER --> MESH(["InstancedMesh in the scene"])
Fig. 2 — the streaming cook path. Budgeted, cancellable, resumable; identical results in any cook order.
// Scatter points in a box, displace them with a noise field. import { Graph, cook, firstGeometry, pointScatterInBounds, jitterPoints, fbm, perlinNoise, remap } from "pcg-ts"; const graph = new Graph(42); // every node seed derives from this const scatter = graph.add(pointScatterInBounds, { count: 500, boundsMin: [0, 0, 0], boundsMax: [50, 0, 50], }); // `amount` is field-capable: scalar noise, evaluated per point. const jitter = graph.add(jitterPoints, { amount: remap(fbm(perlinNoise, { seed: 7, frequency: 0.05 }), -1, 1, 0, 1), }); graph.connect(scatter, "out", jitter, "in"); graph.output(jitter, "out", "points"); const result = await cook(graph); const geo = firstGeometry(result.outputs.points); console.log(result.stats); // { cooked: 2, cached: 0, elapsedMs: … }
Cook again: both nodes are cache hits. Change one param: only its dependents recook.
Determinism
What byte-identical means, and where it stops
Every random decision flows from a seed through one hash chain, so the same graph and seed produce byte-identical output across runs, platforms, cook orders and streaming paths. That is a promise about one path re-run — same seed, any cook order, any budget, any machine.
Cooking on a GPU device is the one documented exception, and it
is an exception with a published size rather than a disclaimer: elementwise
arithmetic is bit-exact, and the noise interiors round in f32 within the per-family
tolerances the parity table in docs/authoring.md states. The CPU is the
reference and the device is a documented approximation of it; whatever cannot run
on the device falls back to the CPU with a machine-readable reason rather than
silently doing something else.
Next
Where to go from here
The user manual is the long form of all of this, chapter by chapter. The corpus gallery is every graph in the repository, cooked and shot. The editor opens any of them and lets you change it.