Log inGet started

procNode: one reusable step of procedural generation

Updated October 13, 2026
MyNode.procNode/
  init.luau     -- the node
  README.md     -- what it produces, its inputs, its outputs
  .metadata     -- description and tags

What a node states

  • g:describe(text): what the node produces and when to use it. It is the text search, the node catalogue and the Inspector show.
  • g:input(name, default, opts): one input of the node. The default types it (Float, String, Bool, Vec3, Color, List<T>, Record); opts adds type (for a default that does not state one, or none), description, options, min, max and optional. A call names inputs in a table, or passes them in declaration order.
  • Outputs: the returned table names them (return { geometry = geometry }), and g:output(name, value) declares one as the node builds. A node with one output answers that value to its caller; a node with several answers a record by output name; a node with none answers nothing.
  • Written as literals. What a node takes and gives is read from its source without running it, the way search and description read it here and the way ZeroMind reads it, so each declaration states its values as written: g:describe and each g:input once, at the top level of the function, with a literal name, default and options (a file constant holding a literal, or asset.resolve("<identity>", "<type>"), reads as one); outputs named by the table the function returns, or by g:output, the same names on every branch of a condition and never inside a loop. asset.interface(node) answers what the source declares, with each part it cannot read as a problem, and a push refuses a node with such a problem (interface.unreadable).
  • The body is the node's work: calls to other nodes, arithmetic over sockets, for, folds, steps and state, as the builder README (proc.module/builder.module/README.md) describes. A node that uses another node is recorded in the graph that calls it, so a node can be built from nodes.

How a graph calls a node

A node inside a <family>.nodebox is called by its box and folder name: points.nodebox/disc.procNode is g.points.disc({ ... }), and nothing inside the node states that name. A node is also reached by:

  • its path (g["/zero/source/fx/ring.procNode"]),
  • ~, the nearest enclosing package, effect or world root (g["~/ring"]),
  • its guid,
  • a short name only one node answers to (g.disc).

A name that matches two nodes and a name that matches none are errors that name the candidates. Each call is recorded in the calling graph by the node's guid, so the node travels with every graph that uses it, and a rename or a move keeps the reference. A .procGraph is called the same way and answers what it returns.

Nodeboxes

A nodebox is the family a node lives in: a namespace folder grouping related nodes (points, mesh, field, terrain). nodebox.assetType describes it. A node placed in a box takes the box's name as its prefix; a node outside any box is reached by path, ~ or guid. asset.create("nodebox", family) makes a box and asset.create("procNode", name, { into = box }) makes a node in it.

Finding nodes

asset.list("procNode") lists every node. The procgen toolbox's searchNodes finds them by keyword and by meaning and lists them grouped by family, and its describeNode answers one node's inputs, their types and defaults, and its outputs. Both read what each node's source declares and run no node. The type's indexing recipe embeds a node's README and its source, so a search by meaning finds it by its description and by its inputs.

Registration

Every node the asset index lists is known to the procgen registry by what its source declares, read when it is asked for and read again when the source changes; nothing runs a node to list it. Its code loads the first time a build runs it. After that, the lifecycle hooks keep it current: onChange loads it again and hot-reloads on an edit, onDelete removes it. Editing init.luau cooks again every graph that reaches the node, and only the nodes downstream of the change do work. init.luau is compiled from its current source on each load, so a module it pulls in uses an absolute require (require("@builtin::systems.procgen.proc")). A graph that references a node which is not registered errors at load, naming the missing guid. A graph that reaches itself, directly or through other nodes, errors at cook.

Content requirements

README.md carries real text: what the node produces, its inputs and its outputs. .metadata carries a description (what the node computes and the value type it answers) and tags (the family, so search finds it).

In the Inspector

Operation: the node's id and what it does, its cost and caching when it declares them, and its inputs and outputs, from the procgen registry's schema for it. A node that did not register says so.

  • asset-type
  • reference