pcg-ts

Real-time procedural content generation for TypeScript — deterministic by construction, WebGPU accelerated, in the browser and Node, with optional three.js interop.

npm install pcg-ts Read the manual → ← this header is cooked from it. Same seed, same field — always.

What it is

A node-graph PCG library, deterministic by construction

pcg-ts generates content — scattered and filtered points, sampled surfaces and splines, resampled paths, networks over shared points, instanced geometry, streamed worlds around a moving camera — from seeded node graphs you build in code, in JSON, in the visual editor, or from the pcg command line.

Any parameter can vary across space instead of being a constant: a value can be a function of where it lands, resolved per point.

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.

The heavy work moves to the GPU: field expressions compile to WGSL, and instance matrices are composed there and handed straight to three.js — without ever touching the CPU.

It is built to be driven by AI agents as much as by humans: nodes carry machine-readable schemas, graphs serialize to stable JSON, and every error names the node, pin, or param at fault and states the fix.

2,841tests, all green
38registered node types
34named primitives
42field-grammar functions
0required dependencies
v0.15MIT · ESM · strict TS

Foundations

Four concepts the rest is built on

Attribute data model

Attributes live on four domains — point, vertex, primitive, detail — as SoA typed-array columns, with promote and transfer between them. The standard point carries transform, density, bounds, color, and its own seed. A 2-vertex polyline over shared points is an edge, so a network needs no fifth domain.

Deferred fields — Field<T>

A value can be a function of evaluation context, resolved only when it lands on a domain. Node params accept T | Field<T>; noise, trig, and combinators compose into expression trees that also serialize to JSON.

Graph runtime

A pull-based executor with revision-keyed memoization, time-budgeted and cancellable cooking, per-output partial cooks, and serializable subgraphs. Recook an unchanged graph and every node is a cache hit.

Streaming world

Hierarchical grid levels — 2D or 3D cells, plus an unbounded level — cook cells around a viewpoint with hysteresis and LRU eviction. Cell content is provably independent of cook order, path, and evictions.

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 — 38 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 — 34 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.

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.

Built for agents

Runtime introspection and stable serialization

Examples

Nine demos, live in your browser

Each one is the real library cooking real graphs — click to run. Also browsable from the demo index.

Behind them sits a corpus of 37 single-concept graphs that are data rather than pages: pcg cook examples/graphs/<name>.json. None of them uses dataInput, because its items do not survive serialization and an example an agent cannot run teaches nothing. Eight of them are one settlement pipeline — ground and wall, district centres, lots, buildings, roads, plus three edit variants — where each stage is the previous file plus nodes and nothing removed, and the earlier stages cook bit-identically inside the later ones.

Roadmap

What shipped in each release