Log inGet started

procPack — a family of procedural ops as content

A .procPack is a FAMILY of procedural operations authored as content: a folder whose init.luau returns a pack table. When the asset registers, every def in its ops map joins the proc op registry…

This is the one path an op family reaches the registry through. Procgen's own families are packs — procPacks/ in this package holds one per family, and terrain's terrain.procPack sits in its own package the same way — so the packs shipped here are worked examples of exactly what you are writing, not a separate mechanism the built-ins are exempt from.

MyPack.procPack/
  init.luau     -- returns the pack table
  README.md
  .metadata

The pack table

return {
    name = "myPack",                        -- the asset's own name when omitted
    description = "What this family is for.",
    ops = {
        ["myPack.twist"] = {
            description = "Twist a Geometry about an axis.",
            inputs = { geometry = { type = "Geometry" } },
            params = { turns = { type = "Float", default = 1.0 } },
            outputs = { geometry = { type = "Geometry", default = true } },
            eval = function(ctx, inputs, params) … end,
        },
        ["myPack.bend"] = { … },
    },
}

Each def is the same table every op in the registry is described by — typed input sockets, params, typed output sockets, and an eval(ctx, inputs, params) returning the outputs. ctx carries seed / nodeId / compositePath / hash / checkpoint. Validation is the same for every pack, so a malformed socket or an undeclared param type is refused here exactly as it is for the ones shipped in this package.

procPack or procNode

Both contribute ops that a graph evaluates, and both take the same def table. They differ in how an op is addressed:

.procNode.procPack
contributesone opa family
addressed bythe asset's guid, resolved through asset.resolve(id, "procNode")the string id the pack names it with
a graph writesg:node("n", ref)g:node("n", "myPack.twist")

Reach for a .procPack when the ops belong together and you want to read the family's name at every call site; reach for a .procNode when one operation stands on its own.

Registration lifecycle

init.luau is compiled from its own source on every registration rather than required, so an edit lands immediately and anything it pulls in uses an absolute require (@builtin::modules.proc). Every op's version is derived from the pack's source, so editing the file re-keys the node caches of every graph that reaches any op in the family — those graphs recook.

Editing the pack to REMOVE an op unregisters it. A graph still naming it errors loudly at eval rather than cooking against a definition that no longer exists, and deleting the .procPack folder unregisters the whole family the same way.

  • asset-type
  • reference