Guide7 min read5 sections

Typed surface graphs

docs/MATERIAL_GRAPHS.mdSource updated 2026-10-25
Markdown
On this page 5 sections

The surface graph compiler is an engine foundation for the material/effect tools in milestone 12. It accepts a versioned literal DAG, validates typed connections and budgets, and emits controlled WGSL. The native Observatory study compiles those functions into the owned forward renderer, including its textured, normal-mapped, masked and skinned pipeline variants.

Saved objects now reference a place-owned recipe catalog. The material editor provides three starting recipes, editable constants and a visual connection workshop. Preview, saved undo, shared revision publication, place residency and graphics reconstruction use the same source. The full material/effect graph requirement remains open: this is a bounded surface-output language, not a general effect system.

#Source contract

A graph has exactly format: 1, nodes, and outputs. Outputs name a color (vec3), roughness (float), metalness (float) and emission (vec3) node. Nodes have stable ASCII identifiers used for connections and diagnostics. Creator identifiers never become WGSL identifiers; the compiler assigns numeric local names and keeps a separate source map.

Supported nodes:

Operation Inputs Result
constant finite number or triple, each within ±64 float or vec3
input local/world position, normal, UV, time, incoming color/roughness/metalness/emission declared surface type
add, multiply two matching types same type
scale vec3, float factor vec3
dot two vec3 values float
mix two matching values, float factor clamped to 0–1 same type
sin, fract, saturate float or vec3 same type
noise vec3 domain float
channel vec3, x/y/z float
combine three floats vec3

Every node must contribute to an output. Validation rejects unknown fields/operations/inputs, duplicate identifiers, missing references, cycles, mistyped connections and unused nodes. Errors include the source path, such as nodes.grain or outputs.color. Source declaration order does not change the canonical topological order or generated shader.

The graph supports 32 nodes, 16 dependency levels, 16 KiB canonical source, and 96 weighted operation units. Noise costs 24 units, sin 4, dot/mix 3, other arithmetic 1, and constants/connections 0. These are static admission weights, not measured GPU timings or a physical-device performance guarantee. GPU code has no creator loops, storage writes, texture allocations, network access, bindings or arbitrary WGSL fragments.

Intermediates clamp to ±10¹² after every node. The bound keeps products/dot products finite in float32 without clipping ordinary world coordinates or multi-hour presentation time. Mix factors and saturate clamp to 0–1. Surface outputs clamp color to 0–4, roughness to .06–1, metalness to 0–1, and emission to 0–20. UV is exposed as (u,v,0). Time is presentation time in seconds; it does not create simulation state. The study's incoming surface values already include the regular material tint and imported texture maps. Output emission is added in linear HDR before tone mapping and does not illuminate neighboring geometry.

#Saved surfaces and authoring

World format 16 (surface-world.16) adds Place.surfaceGraphs and Material.graph. Catalogs contain at most 8 recipes / 128 total nodes / 384 total weighted operations. IDs are bounded to 48 ASCII identifier characters. Every base and story-variant material reference must resolve in its place. Presentation envelope 10 carries the catalog once in the environment; draw records carry only their recipe ID, including imported-model parts. Prior world and presentation versions keep their existing identities.

Select an object in the atelier, choose Material…, then Procedural surface. Start with Moss & limestone, Gilded porcelain or Breathing lantern; adjust the recipe's constants and preview. Open visual workshop exposes connected node cards and a value inspector. Start from a recipe or the original surface, add any supported operation, and connect output/input ports or use typed connection menus. Rename, remove, refine, undo and redo within the workshop; Use recipe returns the validated source to the material dialog. Advanced recipe source retains the literal JSON editor. Preview is temporary until Save. Reopening the dialog retains unsaved recipe values. Original surface and the existing finish buttons remove the object's graph reference. Unassigned recipes remain available for reuse. Editing a shared recipe updates every reference in the place; the dialog identifies other affected objects.

The workshop owns an isolated draft with up to 60 local undo steps. Closing it or pressing Escape discards that draft. Removing a node clears its connections explicitly; it never silently deletes other nodes. Typed menus reject mismatched values and cycles; changing a value type can leave a visible error that must be repaired. Canonical schema admission still checks every node, output and budget before acceptance. Node placement and zoom are view state, never saved world data. The workshop does not compile per keystroke: use the existing material preview to see the actual rendered surface.

set-surface-graphs and the material assignment commit in one transaction. Deleting a still-referenced recipe fails. Durable undo restores both source and assignment, including the prior world format, without undoing unrelated atmosphere edits. Changing only recipe internals leaves canonical simulation entities and player state intact.

Portable arrangement source 3 (prefab-world.3) includes the referenced recipe catalog and a format-16 world. Capture includes only used recipes. Import remaps differing recipe-name collisions and member references; placement copies recipes into the destination catalog under its aggregate limits. Source 1/2 retain their existing compiler identities. The isolated arrangement and template previews install graph pipelines before accepting their scene. A placed recipe remains editable as part of the destination place; its saved arrangement source is a reusable initial definition.

#Atomic GPU activation

The renderer compiles one catalog bundle with numeric recipe slots; slot zero retains regular PBR. It preserves imported maps, alpha tests, skinning and shadow variants. A reserved portion of the existing material instance value carries the recipe slot without adding buffers or textures. Creator names appear only in source maps, never WGSL identifiers.

Graph compilation has one native build and one latest pending request, with a 15-second deadline. The enclosing graphics revision has its existing 20-second preparation deadline. Models, lettering, graph pipelines and a validated presentation baseline stage together; commit switches their ownership together. A rejected, discarded or cancelled candidate cannot replace the accepted scene. Cancelled native work retains the compile slot until it settles, preventing repeated cancellation from creating unbounded builds. The active bundle is separate from the last compiled candidate, so preparing a recipe never activates it early.

Startup and graphics recovery compile from the retained place source before scene delivery begins. Gateway travel uses the staged revision path. Cosmetic graph time shares presentation-effect time; editor reduced-motion mode holds it fixed. Pipeline references are released when superseded or disposed, but WebGPU has no explicit pipeline destroy API. Buffer/texture counts do not measure the driver's pipeline cache.

#Compiler and renderer study

packages/schema/material-graph.ts owns strict parsing, type inference, cycle/depth checks and canonical source. packages/render/material-graph.ts uses compiler identity surface-graph-wgsl.1 and generates engine-owned expressions and node source locations without importing DOM/GPU APIs. The shader adapter in packages/render-webgpu/material-graph.ts inserts the generated function into explicit forward-renderer template points and maps native shader line diagnostics back to graph nodes.

The original language study applies one graph to its whole scene; a separate catalog study exercises assigned imported-model parts among ordinary surfaces. Moss & limestone, Gilded porcelain and Breathing lantern exercise spatial noise, bands/metallic finish and cosmetic time. They are sample graph data, not special shader branches. The existing bounded shader replacement controller compiles all forward variants before switching; failed compilation retains the last-good pipelines. It admits one native build and one latest pending request, with the existing fifteen-second deadline. GPU pipelines have no explicit destroy API; retired JS references are released by that controller. The probe reports buffer/texture ownership, not physical pipeline-cache memory.

#Verification

Run npm run check, npm test, and the native browser probe:

Terminal
npx playwright test tests/browser/probe.spec.ts --grep 'owned renderer works'
DW_PLAYTEST=garden-ferry VITE_DW_PLAYTEST=garden-ferry DW_EDITOR_TOKEN=editor-browser-test-only-key-123456 npx playwright test tests/browser/editor-surfaces.spec.ts

The native study checks graph arithmetic by dispatching generated WGSL and reading the resulting surface values from a GPU buffer. It also checks distinct rendered recipes, time animation, maximum connected node count and operation cost, source-mapped shader rejection, retained images during compilation, repeated replacement, actual device destruction and image reconstruction. The catalog study additionally verifies slot isolation, staged discard, injected native compilation failure, held native cancellation, later successful activation, and reconstruction after device destruction. Headless checks cover persisted source, simulation checkpoints, undo after restart, portable arrangements and build-worker identities. Browser journeys cover visual graph construction, isolated cancellation, type repair, local and saved undo, editor refinement, invalid source, narrow layout, two travelers using worker/main graphics, device recovery and arrangement placement.

Language behavior follows the WGSL specification. Cross-vendor noise/transcendental bit identity, hardware performance, general texture nodes, custom normals, displacement, transparency and particle/effect graph outputs remain outside this foundation's acceptance.

Run npm run test:browser:surfaces for the combined workshop, shared-player recovery and arrangement journeys. Use the supported Node 22 runtime; on hosts with a full /tmp quota, set TMPDIR to an existing project-local temporary directory. The test server remains isolated from the interactive editor on port 5181.

End of Material graphs