Skip to content
v0.17.0 npm

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/transfer
  • src/randomPCG32 RNG and seed hashing — no Math.random anywhere
  • src/fieldsdeferred values, evaluation contexts, memoization
  • src/noisefive noise fields with published raw ranges + normalized mode
  • src/graphnodes, pins, scheduler, caching, subgraph composition
  • src/nodesstandard library, self-describing registry, JSON round-trips
  • src/spatialuniform grid and adjacency CSR the spatial nodes share
  • src/runtimehierarchical World, partitioned cooking, invalidation
  • src/spawnrender-agnostic instancing terminal
  • src/primitivesnamed subgraph recipes referenced from JSON by name
  • src/clithe pcg binary — validate, cook, inspect, render
  • src/gpufield-expression → WGSL compiler, WebGPU evaluator, device-resident runs, parity-budgeted
  • src/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.

← back to the landing page