Log inGet started
·
nodebox · drop-in viewer
asset⌬ nodeboxnodebox·part ofpackage procgen.package·originates fromworld 07158574-5…

geometry

Nodes that move, join, cut, round, thicken and sample meshes. A graph transforms a geometry (`geometry.transform`, `geometry.set_position`, `geometry.merge`), welds and shades it (`geometry.weld`, `geometry.smooth_normals`), models it (`geometry.extrude`, `geometry.solidify`, `ge…

by◐lumi·posted 4d ago
What it does

geometry

Nodes that move, join, cut, round, thicken and sample meshes. A graph transforms a geometry (geometry.transform, geometry.set_position, geometry.merge), welds and shades it (geometry.weld, geometry.smooth_normals), models it (geometry.extrude, geometry.solidify, geometry.bevel, geometry.subdivide, geometry.decimate), builds surfaces from outlines (geometry.sweep, geometry.revolve, geometry.loft, geometry.ribbon) and reads it into volumes (geometry.voxelize, geometry.to_sdf). Each is built from the core nodes and the kernels of the library.

Nodes

  • geometry.transform, geometry.set_position, geometry.set_material, geometry.merge, geometry.triangulate, geometry.measure: the basic edits and the measurement of a geometry.
  • geometry.weld (with match = "position+normal" it keeps hard edges), geometry.smooth_normals: merging points and shading normals.
  • geometry.extrude, geometry.solidify, geometry.bevel, geometry.subdivide, geometry.decimate: modelling.
  • geometry.sweep, geometry.revolve, geometry.loft, geometry.ribbon: surfaces from outlines and paths.
  • geometry.voxelize, geometry.to_sdf: a mesh as cells and as a distance field.
  • geometry.boolean: union, subtract or intersect of two closed meshes, over the intersect kernels.
  • geometry.selected, geometry.flip, geometry.profile, geometry.tangents, geometry.sides, geometry.shared, geometry.bvh, geometry.rings, geometry.first: the parts the nodes above are built from, each usable on its own.
  • geometry.unit, geometry.perpendicular, geometry.rotate, geometry.transport: vector arithmetic over sockets that the nodes which orient points and normals share.

Conventions

  • Each node's folder name is its call name inside the box: geometry.<folder>.
  • Inputs and outputs carry the names of the op each node replaces.
  • A node that answers a Data table from numbers read back off the device holds the Luau for it in a readings.module inside its own folder.
  • A node with one output answers that output's value; a node with several answers a record by output name.
  • A fold's index runs from 1: the first pass is 1, so a pass that works on level, chunk or step n reads index - 1 when it counts from 0 (geometry.bvh, geometry.to_sdf, geometry.sweep, geometry.loft).
  • A field bound to a kernel takes the length of that dispatch, so a node writes a value out as a column (a gather with an explicit count) before it binds one whose length differs from the dispatch's.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

This asset doesn't declare a schema or structured payload.
⌬ Metadata
descriptionGeometry nodes: transform, weld, shade, extrude, solidify, bevel, subdivide, decimate, sweep, revolve, loft, ribbon, voxelize and to_sdf.

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

Showing the 199 items nearest this part. It holds more than this page lists.

199+items
·
procnode · born here
❒asset
# geometry.bevel Round the sharp edges of a mesh. Every selected edge is replaced by a strip of `segments` quads that follows a circular arc tangent to both faces; the faces are trimmed back from the edge to make room, and what is left open where several bevelled edges meet is closed with a patch. The mesh is triangulated and welded (`core.weld`, a merged point standing where its lowest-numbered point stood) at a millionth of the length of its longest side. Then: 1. An edge is selected when exactly two faces meet along it and they turn by more than `angle` (the least that counts is a ten-thousandth of a radian), narrowed to the edges whose two ends are in `group`. Selecting no edge is refused, naming the sharpest turn the mesh has. 2. One width serves the whole mesh: a face trimmed back by `w` from each of its bevelled sides keeps an area while the sum of `w` over the altitudes to those sides stays under 1, so the narrowest face sets the limit. `maxWidth` is 0.999 of it; a `width` past it is clamped to it, or refused when `overflow` is "error". 3. Every corner of every triangle is pulled in to where the offset lines of its bevelled sides meet: along the side before it by the offset of the side after it, and along the side after it by the offset of the side before it, each over the sine of the corner's angle. 4. Where a face's corner stopped short of its neighbour's along an unbevelled edge, the neighbour's side takes the farther corner as a point, and a face that took a point is a fan of triangles around the middle of its ring of points, so the two faces meet along the whole edge. 5. At each end of a bevelled edge the strip runs along a circle of radius `width` times the cotangent of half the turn between the faces, tangent to both, from the first face's corner to the second's (`segments - 1` points between them), and is cut into triangles wound the way the two faces are. 6. The openings left where strips meet are found as the loops of open edges of the welded faces and strips (`core.boundary`); a loop of three edges closes with the one triangle its points make and a longer one with a triangle on each of its edges to a point at the middle of the loop. The trimmed faces keep three points of their own each, on the normal of the face they came from. The strips and patches share the points they meet at. `shading` "flat" gives every bevel triangle points of its own on its face's normal; "smooth" shades the bevel as one surface; "auto" is flat at one segment and smooth above. ## Inputs - `geometry` (Surface). The surface whose edges are rounded. - `width` (Number, default 0.05). How far the bevel reaches onto each face. Above 0. - `segments` (Number, default 1). Quads across a bevelled edge; 1 is a flat chamfer. - `angle` (Number, default 30). The turn, in degrees, past which an edge is bevelled. - `group`, `class`, `promote`, `threshold`. The part of the surface whose edges are bevelled: an attribute on `class` ("edge", "point", "primitive" or "vertex") past `threshold`, taken to the points of an edge by `promote` ("all" or "any"). - `overflow` ("clamp" or "error", default "clamp"). What a `width` past the widest the mesh admits does. - `shading` ("auto", "flat" or "smooth", default "auto"). ## Outputs - `geometry` (Geometry). The surface with its edges bevelled. - `info` (Data). `bevelledEdges`, `pairedEdges` (the edges with two faces), `cornerPatches`, `width` (used), `requestedWidth`, `maxWidth`, `clamped`, `segments`, `vertexCount` and `triangleCount`.
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.blank A column of one zero word, for a binding a stage of a kernel declares and does not read. A dispatch binds each buffer at one slot only, so a binding with nothing to read takes a column of its own, and every call makes one. ## Inputs None. ## Outputs - `words` (Column<Int>). One element, 0.
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.boolean Combine two closed meshes into one closed surface: "union" keeps everything either encloses, "intersect" only what both enclose and "subtract" what `a` encloses and `b` does not. Each input is triangulated and welded (`core.weld`, each merged point standing where its lowest-numbered point stood), then held to what a solid is, and refused by name when it is not: an input with fewer than four triangles, one carrying degenerate triangles (collapsed by the weld, or enclosing less area than `weld` squared), one that is not closed (an edge used by one triangle), not manifold (an edge shared by more than two), wound inconsistently (two triangles run an edge the same way), enclosing no volume, or wound inside-out (negative signed volume). The two are then cut against each other with the `intersect` kernels, which cut the curve where the surfaces cross and the regions where they coincide: 1. The bounding volume hierarchy of `b` (`geometry.bvh`), then `intersect_pairs` (a count stage, a prefix sum, a write stage) lists the entries of the cut, three words each (`a`, `b`, part), ordered by the triangle of `a`. 2. `intersect_cuts` writes the segment each entry is cut along; `intersect_unify` gives every endpoint that names the same crossing one identity, so the pieces of `a` and of `b` share their cut points. A cut point's position comes from the record that names it: the plane crossing of an edge and a triangle, the crossing of two coplanar edges, or a mesh point. 3. `intersect_retri` retriangulates every triangle an entry reaches (`side` 0 for `a`, with the entries as they stand; `side` 1 for `b`, with the entries sorted by their triangle of `b`). 4. Each piece is classified by the winding number of the other mesh just outside and just inside its centre (`ray`, winding); the kernels' README holds the table of what each operation keeps, which also settles the faces the two meshes share. 5. The kept pieces are joined and welded. A triangle the weld leaves with two corners at one point encloses nothing and is dropped, and so are two triangles that stand on the same three points: where the cut runs straight across a corner within the weld distance of the line between its neighbours, each mesh keeps the sliver beside that line, and the two slivers face opposite ways over one another. The triangles are put in the order of their sorted corners (`sort` three times, by the highest, the middle and the lowest corner) so that triangles on the same points stand next to each other. 6. The surface is refused when it has no triangle (the result is empty) or when it does not close (an edge used by one triangle, by more than two, or run the same way by both), and otherwise every face is given three points of its own and its own normal, so a shaded read of the result agrees with its winding. Two meshes that never cross skip the cutting and are classified whole. ## Inputs - `a` (Surface). The first closed mesh. - `b` (Surface). The second closed mesh. - `op` ("union", "subtract" or "intersect"). Which part of each cut surface survives; swapping `a` and `b` changes the result of a subtract. There is no default: a missing `op` is refused. - `weld` (Number, default 0). The distance below which two positions are one point while the inputs are cleaned and the pieces reassembled. 0 derives it as a hundred-thousandth of the diagonal of the smaller of the two inputs, since a mesh's coordinate precision scales with its size. May not exceed `epsilon`. - `epsilon` (Number, default 0). The thickness of a plane: how far either side of a piece the other solid is probed is at least this. 0 derives it as the weld distance. At least `weld`. ## Outputs - `geometry` (Geometry). The combined surface: triangles of three points each, with a flat normal on every face. - `report` (Data). The `operation`, the `weld` and `epsilon` the run used, the result's `pointCount` and `triangleCount`, and the volumes enclosed by the two inputs (`volumeA`, `volumeB`) and by the result (`volume`), summed on the device in single precision. It is measured only when asked for.
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.first Read the first element of a column back from the device as a Number, for a count the rest of the graph sizes its work by: the total of a prefix sum, or the index a reduction picked. The cook waits for the device to answer. ## Inputs - `value` (Column). The column read. Only its first element is answered. ## Outputs - `value` (Number). The first element.
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.following Read a column of a closed run one place on: element i of the answer is element i + 1 of the column, and the last element reads the first. The next point round a ring, for each point of it. ## Inputs - `values` (Column or Field). The values read, the elements of one closed run in order. - `count` (Number). How many elements the run holds. ## Outputs - `values` (Column). The column shifted back one place, wrapping at the end.
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.kept The places of a run that a mask keeps, in order, the number of them as a Number, and for every place how many kept places stand before it. Built from `core.points` and `core.attribute.write` (the mask as a column of 0 and 1), `geometry.offsets` (the prefix sum of those flags), `geometry.first` (the total read back as a Number) and `core.scatter` (each kept place sends its own number to the slot its rank names). ## Inputs - `mask` (Column or Field). A place is kept where the value is not 0. - `count` (Number). How many places the run holds. ## Outputs - `indices` (Column<Int>). The kept places in order, `total` of them. - `total` (Number). How many places are kept. - `before` (Column<Int>). For each place, how many kept places stand before it: a kept place's rank among the kept places.
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.offsets Turn a count per element into the place each element's run starts: the exclusive prefix sum of the counts, and the sum of them all. ## Inputs - `counts` (Column<Int>). How many slots each element takes. ## Outputs - `offsets` (Column<Int>). For each element, the sum of the counts before it. - `total` (Column<Int>). A one-element column holding the sum of every count.
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.perpendicular A unit vector perpendicular to a unit vector, per element, found against the world axis the vector leans on least so the result stays well conditioned. ## Inputs - `vector` (Vec3). The unit vector the answer is square to: one value, or one per element of a column. ## Outputs - `perpendicular` (Vec3). A unit vector with a zero dot product against `vector`.
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.rings Make the points a surface built ring by ring stands on: `rings` rings of `size` points each, ring after ring. Answers the points, the ring each point belongs to and its place along that ring, so a node writes each point's position as a formula over the two numbers. ## Inputs - `rings` (Number, at least 1). How many rings there are: a Number, or a live count. - `size` (Number, at least 1). How many points each ring holds. ## Outputs - `surface` (Geometry). `rings * size` points and no primitives. - `ring` (Column<Int>). The ring each point belongs to, from 0. - `along` (Column<Int>). The place of each point along its ring, from 0.
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.rotate Turn a Vec3 by Euler angles in degrees, applied about X, then Y, then Z (R = Rz * Ry * Rx), per element. ## Inputs - `vector` (Vec3). The vector turned: one value, or one per element of a column. - `rotate` (Vec3, default zero). Degrees about X, Y and Z. ## Outputs - `vector` (Vec3). The turned vector.
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.split The x, y and z components of a Vec3, per element: one value, or one per element of a column. ## Inputs - `vector` (Vec3). The vector split: one value, or one per element of a column. ## Outputs - `x`, `y`, `z` (Float). The components, each the dot product of the vector with its axis.
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.transport Carry a vector along as the unit direction `from` turns onto the unit direction `onto`: rotated about their common perpendicular by the angle between them, and made square to `onto` again. Frames carried this way along a curve stay free of twist. ## Inputs - `vector` (Vec3). The Vec3 carried. - `from` (Vec3). The unit direction before the turn. - `onto` (Vec3). The unit direction after the turn. ## Outputs - `vector` (Vec3). The carried unit vector, square to `onto`.
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.trim Drop every primitive that has no area: the triangles a pole or an apex leaves, whose corners stand at one place or on one line. What remains is a surface whose every side is shared by the faces that are really there. ## Inputs - `geometry` (Geometry). The geometry trimmed. - `least` (Number, default 1e-9). The area a primitive has to exceed to stay. ## Outputs - `geometry` (Geometry). The geometry without the primitives of no area.
▲ 0↑ born
·
procnode · born here
❒asset
# geometry.unit A Vec3 scaled to unit length, per element. A vector with no length answers `fallback`. ## Inputs - `vector` (Vec3). The vector scaled: one value, or one per element of a column. - `fallback` (Vec3, default (0, 1, 0)). The unit vector a zero-length `vector` becomes. ## Outputs - `unit` (Vec3). The unit vector.
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
procnode · born here
❒asset
▲ 0↑ born
·
assettype · born here
❒asset
# Nodebox (asset type) A nodebox is a namespace folder that groups related `.procNode/` children, the way a toolbox groups `.tool/` children. A node's name is its folder name inside its box: `points.nodebox/disc.procNode` is called `points.disc`, and nothing inside the node states that name. Moving or renaming a node is a VFS move that keeps its guid. ## Where it lives - Source: `<anywhere>/<family>.nodebox/`, usually inside a `.package`, an effect or a world's content. - Identity: `<family>` (the `.nodebox` suffix strips). - Folder shape (open child set): - `README.md`: what the family is for. Required. - `.metadata`: description and tags. Required. - `<node>.procNode/`: any number of nodes. ## How a graph reaches a node `g.<family>.<node>(args)` resolves the node by its asset identity: the box and the folder name together (`g.points.disc`), a path (`g["/zero/source/fx/ring.procNode"]`), `~` (the nearest enclosing package, effect or world root: `g["~/ring"]`) or a short name the asset system resolves (`g.disc`). A name that matches two nodes is an error naming both; a name that matches none is an error naming the closest nodes. Each call is recorded in the graph's reference table by the node's guid. ## How to create one ```luau asset.create("nodebox", "points", { folder = "fx" }) local box = asset.resolve("points.nodebox") asset.create("procNode", "disc", { into = box }) ``` ## Discovery - `asset.list("nodebox")`: every box. - `asset.inspect("points")`: the nodes in it and what each does. - `asset.list("procNode")`: every node, in or out of a box. ## In the Inspector Nodes: how many nodes the box holds, each node's full name and what it does, and a link to each.
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
·
assettype · born here
❒asset
# procNode: one reusable step of procedural generation A `.procNode` is a node authored as content: a folder whose `init.luau` is the node. It returns `proc.define(function(g) ... end)`, the same function-of-`g` form a `.procGraph` is written in, so a node is a small graph that other graphs call. Its `g:describe` text says what it makes and when to use it, its `g:input` declarations are what a call passes, and what it returns (or declares with `g:output`) is what the call gives back. ``` 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.
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
·
assettype · born here
❒asset
# assetType (asset type) The `assetType` is the **type of types** — the meta-type that every `<typename>.assetType/` folder is an instance of, including itself. A `<typename>.assetType/` folder declares a new asset type called `<typename>`: its on-disk shape, its identity rule, and the structural contract instances of the type must satisfy. This type is self-hosting. `assetType.assetType/` conforms to its own schema — a `type.yaml` plus a `README.md` — so the type system is defined in terms of itself rather than a hidden engine special-case (the same way a C compiler is written in C). Every asset in the engine, including each `.assetType` definition, now resolves to a registered assetType: a `.material` resolves to `material.assetType`, and `material.assetType` resolves to `assetType.assetType`, which resolves to itself. ## Why it exists Assets used to be mapped to their type *implicitly*, by matching the folder suffix against a name-keyed registry. That match carried no stored link: nothing on a `Foo.material` recorded *which* `material.assetType` it was written against, so two worlds shipping a same-named type could silently disagree. Making `assetType` a real, resolvable asset closes that gap — the link from an asset to its type is now an explicit reference pinned in the asset's `.refs` sidecar (`via: "asset_type"`), exactly like every other dependency. ## Where it lives - Source: `/zero/source/.../<typename>.assetType/` - Identity: `<typename>` (the `.assetType` suffix strips from the identity; the folder retains the suffix on disk). - Folder shape: - `type.yaml` — the structural spec the validator reads. **Required.** - `README.md` — type-level documentation. **Required.** - `about.yaml` — three short plain-language texts saying what the type is for (see "Say what your type is for" below). Every built-in type ships one. - `behavior.luau` — optional behavior/scaffolding module. - `template/` — optional canonical placeholder body the templater copies into a new asset of the type. - `<name>.module/` — optional shared code the type ships to **every instance** of it, conventionally `shared.module/` (see below). ## Shared code its instances reach (`asset.containing` + `.modules`) A type ships code every instance of it uses by declaring the modules in its own `behavior.luau`, as a map of tracked `require`s: ```luau -- inside <typename>.assetType/behavior.luau M.modules = { shared = require(".shared") } -- the sibling shared.module/ ``` An instance reaches it with one location-independent line: ```luau -- inside any foo.<typename>/init.luau local api = asset.containing(__FILE__).modules.shared ``` `__FILE__` is the chunk's own VFS path; `asset.containing` resolves the calling asset, and `.modules.<name>` follows the instance's pinned `typeRef` (the type's **guid**, recorded in the instance's `.refs` as `via = "asset_type"`) to the module its type declares. Because resolution follows that link rather than a category name, the same line in an instance of a different type resolves to that type's module, and identically-named modules in two types never collide. `asset.typeRef(target)` reads that guid for any target; `asset.resolve(asset.typeRef(target))` gives the full `AssetRef<assetType>`. See `assetTypes/README.md` § "Shared code: type-owned modules" for the full mechanism. ## How to create one Authoring a new asset type is just creating a `<typename>.assetType/` folder under `/zero/source/`. The engine registers `<typename>` as a category **the moment the folder is written** — no engine restart, no Rust change, and it works for built-in (`@builtin/assetTypes/`) and user-defined types identically. Once `<typename>` is registered, `<name>.<typename>/` folders are recognised as assets of that type and resolve their `asset_type` link back to this definition. Creating a `<name>.<typename>/` folder *before* its `<typename>.assetType/` exists fails fast: the suffix has no registered type, so the folder can't be a valid asset. Author the type first. ## Say what your type is for (`about.yaml`) Every type carries a small `about.yaml` beside its `type.yaml`, and the README you write for the type documents how to author an asset of it. Both are required, and both are meant to be written as you make the type, while you know what it is for. `about.yaml` answers three questions in plain words, one key each: ```yaml intent: "Sets the typeface of the text in your game: UI, labels, signs and 3D lettering." function: "A font, from a TTF, OTF or WOFF file, made available to all of Zero's text, including 3D text." usage: "You choose it by name for your UI or your text, and the text appears in that typeface." ``` - **`intent`** is what the type is for in someone's game. Name the whole range of things it covers, in the words a game maker would use for them. - **`function`** is how it works today: what an asset of the type is made of and what the engine does with it. - **`usage`** is what a person or an agent does with one: how they pick it, place it, call it, or hand it to something else. A few habits keep it useful. Write about the type as a whole, so the text holds for every asset of it, and leave out any one asset's name. Say what the type does right now. Stay with words a game maker would say, and mention a file name or a call only where a game maker would know it. The limits are in bytes: `intent` 800, `function` 1500, `usage` 1200. Each is generous, and a few sentences usually say it. Why bother: agents working in a world read these three texts to learn what your type is for, so they reach for it when it fits and use it the way you meant. People searching find the type by what they want to make, and the three texts let them tell at a glance whether it is the one they need. `asset.create("assetType", name)` scaffolds the file with all three keys, and a README whose first paragraph is a `[[describe]]` note. Each value starts with `[[describe]]` followed by a note on what to write; replace the whole value with your own words. A value or a README line that still starts with `[[describe]]` counts as not written yet, which the push's check of each asset it carries against its type reports, naming the file. A type you leave as it was is not asked, so older types stand. Many types at once, or one you would rather not write by hand: `services describe` lists the types that lack these texts and what filling them costs, and `{ mode = "all" }` writes them into the same files, where you edit them like anything else you wrote. Where the texts show up: `asset.inspect("<typename>", "assetType")` returns them as `detail.about`, `assetTypeRef:getAbout()` returns them as a table with the keys `intent`, `function` and `usage`, the type's `types/<typename>` guide page ends with an "About this type" section holding them, and the Inspector's Type section lists them at the top. The guide page is this README plus that section, so a README that explains the type well and an `about.yaml` that says what it is for read well together. ## Ask your assets to say what they are A type can ask each of its assets to carry a description: a file, written by the author or generated, that says what that asset is and what it is for. The people and agents who find an asset read it to decide whether it fits, and the author knows what it is for at the moment of making it. **1. Declare the file with a `described` check.** This is the whole requirement: ```yaml files: required: - path: "README.md" # any file name works kind: "file" content: - { check: "described", message: "Say what this widget is for and where it goes." } ``` With that in place: - An asset whose file is missing, empty, or still has a line starting with `[[describe]]` does not meet the type's requirements. `asset.validate` names it, `zm status` and `world.publishBlockers()` list it, and a push that carries it is refused with `message`. - `services describe` finds it (`mode = "find"` lists it and the price) and can write the description for it (`mode = "all"`). It writes only this file. For a YAML file that holds several texts, name the keys and their limits in bytes, the way this type does for `about.yaml`: ```yaml content: - check: "described" keys: - { key: "intent", max_bytes: 800 } - { key: "usage", max_bytes: 1200 } message: "Write intent and usage." ``` `headings` names sections a Markdown file must have. `assetTypes/README.md` has the full check. **2. Optionally, steer what gets generated with a `describe:` block:** ```yaml describe: guidance: "Say what the widget shows, what input it reacts to, and where it fits in a UI." examples: - "<identity of an asset of this type whose description reads well>" ``` `guidance` says what a description of your type covers, and `examples` names assets whose descriptions show the shape you want. Without the block, generation still works from the general describing guide and the check's `message`. An example is an asset of your type in the library whose description is a true, written one, never a scaffold's placeholder, and never the asset being described. When the library has none, leave `examples` empty rather than name one that is not. A type whose assets the model cannot perceive (a sound it cannot hear, a motion it cannot see) says so in `guidance`: the description comes only from what the asset's own files state, such as its name and the `.metadata` where a generation service records the prompt that made it, and when they state nothing the model answers `cannot` with that reason. `services describe` then fails that one asset with the reason and leaves its file as it was. **3. Start the file unwritten in your template.** Put a heading and one line that starts with `[[describe]]` and a short note on what to write, in `template/<file>`, so a new asset starts unwritten rather than with text that only looks written. Do not put example content in it: every new asset carries the template verbatim, and the same text on many assets makes them impossible to tell apart in search. Types whose assets say what they are through their own content (a mesh, a texture, a sound) leave this out: declare nothing and nothing is asked. ## The `behavior.luau` contract `behavior.luau` is the type's code. It returns ONE table, and the keys below are the ones the framework reads off it for EVERY type — the surface you get by declaring them. Anything else on the table is your own: reachable by name through `require("@builtin::assetTypes.assetType.shared.ref").loadTypeModule("<typename>")` for a system that knows your type (`computeShader` publishes `dispatchKey` that way), and never called by the framework. ```luau local M = {} M.ref = { ... } -- methods on every AssetRef of this type M.modules = { ... } -- shared code the instances reach M.global = {} -- reserved key; ship the empty table function M.onCreate(name, opts) ... end return M ``` ### The tables | Key | Shape | What the engine does with it | |-----|-------|------------------------------| | `M.ref` | `{ [name] = function(self, ...) }` | Every function becomes a method on every `AssetRef` of this type: `ref:name(...)`, with `self` the ref. This is how a type gives its instances an API. Two names in here are contracted — see below. | | `M.modules` | `{ [name] = require(".sibling") }` | Shared code the type's instances reach through `asset.containing(__FILE__).modules.<name>`, resolved by the instance's pinned `typeRef` guid. See the section above. | | `M.refShapes` | `{ [method] = "TypeName" }` | Declares the result type of a `M.ref` method so the LSP can check what a call site does with it. See the `M.refShapes` section below. | | `M.events` | an events schema | The events an ASSET of this type fires, declared the way a component declares its own. Readers subscribe through `ref.events.<name>:connect(...)`; firing authority stays with the type. Keyed by the asset's guid, so every resolver of the same asset shares the signals and a re-resolved ref re-attaches to subscriptions already there. | | `M.namePattern` | a Lua pattern string | The name shape `asset.create` enforces for instances of this type, replacing the default `^[A-Za-z][A-Za-z0-9_]*$`. | | `M.global` | `{}` | Reserved. Ship the empty table. | ### The lifecycle hooks Each is optional; a type that omits one costs nothing. The engine calls them: | Hook | Signature | When it fires | |------|-----------|---------------| | `M.onCreate` | `(name, opts) -> { [filename] = contents }` | On `asset.create("<type>", name, opts)`. Returns the files that scaffold the new instance, as a map of relative path to contents; the framework writes them. The `opts` type annotation is the schema `asset.create` validates the caller's arguments against, so annotate it. | | `M.onReplace` | `(dest, files) -> { [filename] = contents }` | On an `asset.create` that writes over an asset already at `dest`, after `onCreate`. Receives the files `onCreate` produced and returns the files to write, so a type that re-derives its content (a rig re-imported from its source file) can carry what an author added to the asset it replaces. Optional. | | `M.onRegister` | `(self)` | Once per instance, the first time it registers: on `engine.onWorldLoaded` for instances already in the world, immediately on a live `asset.create`, and when an instance arrives after that sweep (installed, pulled, or synced from a peer). Guarded by the instance's path, so a double trigger never double-registers. An instance that leaves the runtime releases the guard, so one arriving at the same path again registers. This is what makes "write an asset into the world and it takes effect live" work for content that registers into a runtime registry. The `@builtin` library is excluded from the sweep. | | `M.onChange` | `(ref, change)` | On every VFS write to a file INSIDE one of this type's instances. `change` is `{ path, asset, type, kind, origin }`: `kind` is `"edited"` or `"seeded"`, `origin` is `"local"` or `"remote"` (an importer runs on the originator only). **Filter on `change.path`**: the hook fires for any file under the folder, so act on the one you care about. It runs as its own task and may wait on what it reads: a `vfs.read` of bytes only the blob store holds (a binary file on the web build) fetches them. The writes to one asset reach the hook in the order they were made, each once the hook before it has finished. A re-entrancy guard suppresses writes back into the same asset routed inline from the hook, and a write the hook makes is routed on a later frame like any other, so the hook must be convergent on its own: diff the meaningful state and short-circuit while your own regeneration is in flight. | | `M.onDelete` | `(ref)` | When an instance of this type leaves the runtime: it is deleted, or the engine swaps away from the world binding that held it. Its files are gone from `/zero/source` by then. A removal routed while the instance's `onChange` is waiting reaches `onDelete` once that hook, and every write queued behind it, has finished. On a swap the instance still exists in the world it belongs to, and a VFS write the hook makes reaches the world now bound. | | `M.validate` | `(assetRef) -> { { code, message, severity? } }` | On `asset.validate`, after the structural `type.yaml` check, for the type's own SEMANTIC validation. `severity` defaults to `"error"`; error-severity problems flip `ok` to false, warnings do not. A hook that raises or returns a non-table is itself reported as a `validate.hook_failed` error. `world.push` runs this per user asset, so declaring it enforces your type's rules at publish time with no further wiring. | ### The two contracted `M.ref` names Most `M.ref` methods are your type's own surface: name them what you like, return what you like. Two are read by the engine and mean the same thing for every type, so their shape is fixed: - **`instantiate(self, target?, opts?) -> (root, idMap)`** — makes an instance part of the scene. Defining it is the whole opt-in: `ref:canInstantiate()` is true exactly when it exists. Its **return is a contract** — see the next section. - **`inspect(self) -> detail`** — the type-specific half of `asset.inspect`. See its section below. ### Names a type cannot shadow Some keys resolve on every `AssetRef` before per-type dispatch, so an `M.ref` entry of the same name is never reached: `canInstantiate`, `getSource` / `getBytes` / `getText`, `exists`, `deps`, `meta`, `runtime`, `events`, `modules`, `typeRef`, and the residency flags `has_backing_asset`, `has_runtime_changes`, `cpu_resident`, `gpu_resident`. These are the behaviours every asset must expose identically, which is why they win. ### Reloading A `behavior.luau` edit ripples to existing refs with no restart: dispatch reads `mod.ref` fresh each time and `require`'s cache is re-run in place. A type whose folder appears at runtime is picked up on the next dispatch — negative results are not cached either. ## The names your type's assets hold (`references:`) An asset that names another asset by a string depends on it: the named asset travels with it into a pack or a pull, and a name nothing answers to is an unresolved dependency `worldValidation` reports and the push gate refuses. The engine reads `require(...)` and the asset lookups on its own; a name your type's format or API carries anywhere else is one you declare, in `type.yaml`: ```yaml references: # A top-level key of this type's own files that holds a name (or a table of them). keys: - { key: "shader", type: "shader" } # A call's argument, counting from 1, wherever the call is written. args: - { call: "g:node", position: 2, type: "procNode" } # A field of the options table a call takes. fields: - { call: "fx.beam", field: "material", type: "material" } ``` `type` is the category the name resolves in, one of `asset.categories()`. An argument declared once per category, at the same `call` and `position`, takes the name from whichever category answers. A key or a field holds one category: declared again with another `type`, it takes the later one. The block is declared as the type registers, when the library or the world holding it is laid into the VFS, so every reference table reads it whatever script has or has not run. `asset.declareReferenceKey`, `asset.declareReferenceArg` and `asset.declareReferenceField` declare the same things from a script, for a surface that is no type's. ## What search does with your type (`indexing:`) A type declares what search embeds for its instances. **This is not optional and there is no useful default**: ZeroMind runs your declaration and adds nothing of its own, so anything you do not point at is absent from the index — silently, and for every instance of the type forever. Twenty types once shipped with no `indexing:` block and every instance of them was unfindable. A type whose instances are never searchable (a run record, a log) says so with `indexing: none`; every other type declares a recipe. A type without an `about.yaml` beside its `type.yaml` refuses every push that carries it, whether or not the push edited the type. Texts in it that are not written yet follow the rule every `described` file follows: they refuse the push that edits, stages or commits the type. Two words carry the whole thing, and they are the same two the search tool exposes: - **`identity`** — what an instance IS. For anything you can look at, that is the picture (`content:` + `modality: image`, matched directly by a text query). For everything else it is the authored text that says what it is. - **`capability`** — what it DOES and how it is made. Code, settings, the model-written summary of them. A type with no behaviour has no `capability` entry. A type with nothing to look at has no image. Leaving a slot empty is a statement; filling it with whatever happens to be lying around is not. The one you have to decide when authoring a type is: *for an instance of this, what is the thing a person would recognise it by, and what is the thing it does?* A texture answers "the image" and "its compression settings". A module answers "its README" and "its code". Write those two answers into `indexing:` and the rest follows. Three specifics worth knowing before you write one: - **`derive` is instructed by this type's own `README.md`.** A model reads an instance's source and writes a description; what it embeds is the description, not the source. It follows your README to know what it is looking at, so a type whose README says what its instances are gets good derivations for free. There is no prompt to name. - **`derive` loses detail.** It keeps the main technique and drops secondary ones. If the source is code you want searchable by its own vocabulary, declare the same files a second time as `capability` with `extractor: verbatim`. Two entries, same role, different failure modes. - **Never `source: { field: name }`.** A filename is one or two words with no usable embedding, and a corpus indexed that way answers "language runtime" with `say_runtime_2.soundClip`. `facets:` is the second stage: a `file:` embedded whole (`.metadata`, so keys nobody declared still get indexed) plus computed keys that state a value for every instance including `false` — the raw file can imply "not rigged" and can never say it. The full schema, every extractor and the facet sources are in [`assetTypes/README.md`](../README.md); the scaffolded block with inline guidance is in [`template/type.yaml`](template/type.yaml). ## A container whose contents work together (`system: true`) A type that is a container can say its contents are one working whole by declaring `system: true` at the top level of its `type.yaml`. `package` declares it: the components, modules, scenes and tools a package ships are parts of one system. The declaration is plain data, read from the decoded `type.yaml` (`require("@builtin::assetTypes.assetType.shared.declaration").of("package").system`), and a type that leaves it out is an ordinary type. ## What your type looks like (`preview:`) A type declares how its instances are pictured, and the engine makes the picture when something needs it: staging an instance whose `preview.png` is missing or pictures content it no longer holds queues a render, a view that shows the instance asks for one, and `ref:preview()` renders one on demand. An edit renders nothing on its own. Each `preview.png` records the content it pictured, which is how a stale one is told from a current one. The preview studio owns the light, the backdrop, the framing and the camera, and nothing in the world reaches it, so an instance pictures the same in every world. Declare one of four recipes: | `kind` | The picture is | Your type provides | |---|---|---| | `instantiate` | the asset stood in the studio through its own `instantiate` | `ref.instantiate` | | `image` | a file the instance holds, fitted to the frame, nothing rendered | `source.files`, the candidate files in order | | `surface` | the asset painted on a builtin mesh | `mesh`, and `ref.previewMaterial(self) -> { material }` | | `custom` | whatever `ref.preview(self, opts)` returns | `ref.preview` | ```yaml preview: kind: surface mesh: sphere ``` A scene-instantiable type that declares nothing still answers `ref:preview()` through its `instantiate`, but persists no picture: the declaration is what makes the engine keep one. List `preview.png` among the type's files so an instance may hold it. The picture is findable only when `indexing.content` names it. The two are separate statements, and neither is derived from the other: ```yaml indexing: content: - role: identity modality: image source: { file: "preview.png" } ``` `@builtin::assetTypes.assetType.shared.previewRecipe` reads the declaration; the scaffolded block with inline guidance is in [`template/type.yaml`](template/type.yaml). ## Typing a result from the instance (`M.refShapes`) Every method on `M.ref` is checked against the type its `--!return` names, and every asset of the type gets the same one. That is right for most methods and wrong for the ones whose result is shaped by the ASSET: a method answering one entry per child folder, per row of a config, per binding an input map declares. The widest true annotation for those is `{ [string]: Thing }`, and an indexer accepts every key — so a caller's typo reads as valid and nothing reports. `M.refShapes` lets the type state the result per instance. Each entry is `function(self) -> (typeExpression, source?)`: a Luau type expression for THIS asset, and the module whose type vocabulary the expression uses. Read the asset; never run it — stating a type must not take effect. ```lua -- inputMap.assetType/behavior.luau — the shipped example. -- `activate()` hands back one handle per control the map declares. M.refShapes = { activate = function(self): (string, string) local names = {} for _, control in ipairs(M.ref.controls(self)) do table.insert(names, control.name .. ": Handle") end if #names == 0 then return "", "" end return "{ " .. table.concat(names, ", ") .. " }", "@builtin::modules.zinput.scheme" end, } ``` With it, `map.jump:onPressed(fn)` resolves and `map.noexisting` is reported with the map's real controls. Without it, both pass silently. ### What the call site has to say A per-instance type is matched by the asset's IDENTITY, so it applies only where the reference the method is called on says WHICH asset. Two spellings do: ```lua -- a component field, whose declared default names the asset public = { map = Field.assetRef("inputMap", "@builtin::inputMaps.default", Sync) } local controls = public.map:activate() -- typed for THAT map -- an annotation, naming category and identity local m: AssetRef<"inputMap", "@builtin::inputMaps.default"> ``` `asset.resolve("<identity>", "<category>")` carries the CATEGORY, so the methods the category defines are checked on the result — but not which asset it found, so a per-instance result keeps the category's declared return. A reference that names no asset (`asset.resolve(someVariable)`, a value passed in as a parameter) carries neither, and calls on it are unchecked. That is the same rule everywhere: the checker states what the code states, and a name computed at runtime states nothing. ### The result is a value, not a binding The type belongs to the expression, so it travels the way any inferred type travels. Indexing the call's result is checked; stashing it in a module local and indexing it from another function is not, because the local's declared type is what carries across — and `any` (or no annotation) carries nothing: ```lua -- checked: `d.greting` is reported function awake() local d = public.dialogue:lines() d.greeting:say() end -- NOT checked: `lines` is a module local typed `any` local lines: any = nil function awake() lines = public.dialogue:lines() end function start() lines.greting:say() end ``` Bind what you need at the call site and the names stay checked in both places — the asset-derived type at the boundary, ordinary scoping after it: ```lua local lineGreeting = nil function awake() local d = public.dialogue:lines() lineGreeting = d.greeting -- `d.greting` is reported here end function start() lineGreeting:say() end ``` ### When nothing is reported and you expected something From a call site, a type that is correct and a type that was never published look the same: no diagnostic either way. `require("@builtin::assetTypes.assetType.shared.refShapes").published()` answers which it is. It returns two maps — the category surfaces, and the per-asset results keyed by identity and then by method: ```lua local shapes = require("@builtin::assetTypes.assetType.shared.refShapes").published() shapes.categories.dialogue --> "{ lines: () -> any, lineNames: () -> { string } }" shapes.returns.Shopkeeper.lines --> "{ browsing: Line, farewell: Line, greeting: Line, wares: Line }" ``` An identity absent from `returns` was never published for, which is a different thing from published-and-correct — and the reason to look here rather than at the call site. The answers are recomputed when an instance is written, so a control added to a map reaches the checker with no restart. Only types that declare `refShapes` are read per instance, and at most 64 assets of one type are — past that the type keeps its declared return and the engine log names what was dropped. ## The instantiate hook (`M.ref.instantiate`) — a contracted return A type opts into becoming part of the scene by defining `instantiate` on its `ref` table. That is the whole opt-in: `ref:canInstantiate()` is true exactly when the hook exists, so consumers offer a scene path — an `Asset.source` field, a viewport drop, a tool argument — by CAPABILITY rather than by a list of type names. Unlike `inspect`, this hook's **return is a contract**. A caller writes one piece of code against every instantiable type, so what comes back cannot vary by type: ```lua -- behavior.luau local Instantiable = require("@builtin::assetTypes.assetType.shared.instantiable") local M = {} M.ref = { instantiate = function(self, target, opts) local root = Instantiable.root(self, target, opts) -- IN: the base opts -- ... compose whatever this type is, under `root`, NOW ... return Instantiable.result(self, root, idMap) -- OUT: the contract end, } return M ``` **IN.** `target` is an owning entity ref: the instance lands under (or, for a hierarchy type, onto) that owner. With no target the type spawns a fresh root. `position`, `rotation`, `scale`, `name` and `temporary` mean the same for every type — `Instantiable.root` applies them to a root you mint, `Instantiable.place` to one you adopt, so you never re-read the spec. Honour more opts of your own if your type needs them, and document them in the hook's `--!arg` docs. **OUT.** Return `(root, idMap)` through `Instantiable.result`: - **`root`** — the composed root, as an `EntityRef`. Composition is **synchronous**: everything your type builds is live when you return, so the caller can parent to it and read its components in the same statement. Do not defer the work to a component's `awake` and return an empty shell — a root that is not live is refused. - **`idMap`** — the `originalId -> runtimeId` map naming what you spawned, or nil for a type with no addressable children (`result` normalises it to `{}`). **A caller never receives nil.** A component that re-composes your asset on every load keeps this map and passes it back in, which is how a cross-entity reference into the composition survives a reload. `AssetRef` runs every `instantiate` through `result` on the way out whether or not your type called it, so returning something else fails at your own call rather than handing a caller a nil root. Calling it yourself is still how you say what you return, and running it twice changes nothing. `@builtin::assetTypes.assetType.shared.instantiable`'s README is the full reference for both halves. ## The inspect hook (`M.ref.inspect`) `asset.inspect(ref)` resolves `ref`, builds a common envelope shared by every asset (`identity`, `name`, `guid`, `source`, `typeName`, `typeDefinitionPath`, `scope`, `origin`, `description`, `tags`), then calls the resolved type's own inspect hook — `M.ref.inspect(self)` on `behavior.luau` — for the type-specific `detail`. The hook returns *only* `detail`; the framework fills in everything else: ```lua -- behavior.luau local M = {} M.ref = { inspect = function(self) -- self.path is the asset's own folder. Parse the asset's OWN -- files — source text, a native payload header, its `.metadata` -- — never runtime state (a live component instance, a GPU -- upload, a compiled schema): inspect must work on an asset that -- hasn't been compiled, uploaded, or registered yet. return { -- type-specific fields, whatever this type wants to surface } end, } return M ``` A type that omits `M.ref.inspect` yields the generic record: the envelope alone, `detail = nil`. That's a valid, working state — not an error — but it means agents calling `asset.inspect` / `tools.use("assets","describe", ...)` on an instance of the type see only identity and scope, none of what makes an instance of it meaningful. Declaring the hook is what makes a type's instances inspectable for what they actually are. A hook that raises leaves `detail = nil` and sets `record.warning` to the error text — `asset.inspect` itself never raises for a resolved ref. ## Discovery - `asset.list("assetType")` — every registered asset type. - `asset.inspect("<typename>")` — the type's identity, source path, and this README, plus its `about.yaml` texts as `detail.about`. - `asset.resolve("<typename>", "assetType")` — the `<typename>.assetType` asset itself (used by the validator to find the type's `type.yaml`). ## Related - `assetTypes/README.md` — the full `type.yaml` schema and validator rules. - `@builtin::assetTypes.assetType.shared.instantiable` — the instantiation contract's shared implementation, both halves. - Every other `*.assetType/` here — the concrete types built on this meta-type. ## The Inspector a type ships Every type names the file its Inspector sections come from with `inspector: { type: "inspector.luau" }` in its type.yaml. `asset.create("assetType", name)` scaffolds the block and an `inspector.luau` whose Contents section lists an asset's files, built from `ctx.parts`. `asset.validate` on a type that names none reports `assetType.inspector_required` (an error for a `@builtin` type the engine's library authors; a warning for a world's own, and for one the library carries by reference, which the world that published it answers for), and one whose file does not load `assetType.inspector_invalid`. ## In the Inspector Type: what the type.yaml declares (suffix, container, primary files, the settings and inspector blocks), whether the type ships a behavior, how many instances it has, and **Open** for its files. Schema: the settings every instance carries, as read-only typed controls at their defaults (a type whose schema is per instance says where it is declared). Instances: a link to each of the first 20 assets of the type, and how many there are when there are more.
▲ 0↑ born
❒
package · born here
❒asset
▲ 0↑ born
·
computeshader · born here
❒asset
# gather Builds a column from another column and a run of indices: element `i` of the result is element `index[i]` of `src`. An index below 0 or past the end of `src` gives zeros. The element type is the `T` specialization (`f32`, `i32`, and two to four lanes of either); a three-lane column is read and written tightly packed, twelve bytes an element. Dispatch over `n`, the number of indices. Exact: each result element has one writer. Over a live count (`COUNT=device`, see the abi README) `n` is the capacity and the first `domain_count` slots are live: a slot at or past it holds zeros. It binds 4 storage buffers (3 in the plain variant), and `dst` holds `n` elements.
▲ 0↑ born
·
procnode · born here
❒asset
# math.dot (procNode) The dot product of two vectors: a number per element. The `dot` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# field.index The element number, counted from 0. Unwired it is an Int field over whatever reads it (`fieldc.leaves.elementId`: the number of the element being written). With a `domain` it is an Int column of 0 to n - 1, one per element of the domain.
▲ 0↑ born
·
procnode · born here
❒asset
# math.cos (procNode) The cosine of an angle in radians, element by element. The `cos` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# core.select (procNode) Keep the elements of a geometry a mask picks and drop the rest, renumbering the links between points, vertices and primitives. Built from `math.ne` over the mask, `compact` (the kept elements and the inverse map), `core.owner`, `gather`, a segmented `reduce` (`segments = primitive.count`), `scatter`, `attribute.carry` for every class and `scan` for the new run starts. Keeping points drops every primitive that loses a vertex; keeping primitives keeps their vertices and, by default, the points those vertices read; `points` set to "all" keeps every point. ## Inputs - `geometry` (Geometry). The geometry to filter. - `class` (one of point, primitive). Default `"point"`. Which elements the mask is over. - `points` (one of used, all). Default `"used"`. For class "primitive": which points stay, the points the kept primitives use or every point. - `mask` (Value). Default `1`. A column or field over the class's elements: an element is kept where the value is not 0. ## Outputs - `geometry`: the kept elements with the links renumbered.
▲ 0↑ born
·
procnode · born here
❒asset
# math.where (procNode) Choose between two values by a condition: `a` where `cond` is not 0, `b` where it is. The `where` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# core.weld (procNode) Merge the points of a geometry that stand within a distance of each other, joining the primitives that used them. Built from `core.pairs` (the points within the distance), `jump` in label mode folded until no label changes (every point takes the smallest point number of the points joined to it), `compact`, `scatter` with a mean fold for the merged positions (with `place` "first" the carried position stands), `attribute.carry` for every other attribute (a merged point takes the values of the lowest-numbered point of its group) and `gather` to renumber `vertex.pt`. With `match` set to \"position+normal\" two points merge only when their `N` normals differ by no more than `angle` degrees. Every pair within the distance ends in one group, so a chain of points each within the distance of the next merges whole. ## Inputs - `geometry` (Geometry). The geometry welded. - `distance` (Number). Default `0.0001`. Points closer than this merge. - `match` (one of position, position+normal). Default `"position"`. What has to agree: position alone, or position and the `N` point normal. - `angle` (Number). Default `1.0`. For "position+normal": how far apart two normals may be and still merge. - `place` (one of mean, first). Default `"mean"`. Where a merged point stands: at the mean of its group, or at the position of the lowest-numbered point of the group (the point `attribute.carry` takes the other attributes from). ## Outputs - `geometry`: the welded geometry.
▲ 0↑ born
·
procnode · born here
❒asset
# attribute.transfer Transfer a point attribute, or every point attribute, from a source geometry to a target by nearest point, on the device. The sources are laid on a spatial grid, and each target reads the cells around it. Of two sources equally near, the one earlier in the source's order gives the value. ## Inputs - `geometry`: What receives the column. A copy carrying the transferred column comes out, with one value per one of its points. - `source`: What the column is read from. Each target point takes the value of whichever source point is nearest to it in space; the two need not match in count, spacing or topology. - `name`: Which point column is carried across, read on the source and written on the receiving geometry. Left empty, every point column the source holds is carried under its own name, its position `P` excepted: the receiving points keep where they stand. - `as`: The name the transferred column is written under. Left empty it is `name`.
▲ 0↑ born
·
computeshader · born here
❒asset
# expand Turns a count per element into runs. Element `j` takes `counts[j]` slots, in element order; slot `s` of the result holds `owner[s]`, the element it belongs to, and `local_index[s]`, its place within that element's run. `offsets` is the exclusive prefix sum of `counts` (what the `scan` kernel makes of it), so every slot finds its element by a binary search and has one writer. `owner` is an appended output: its live count is the sum of the counts, up to the capacity the dispatch was given. Slots at and past the live count hold -1. An element with a count of zero has no slot; a column of all zeros gives a live count of zero. Dispatch over `n`, the capacity of the result. Exact. Over a live count (`COUNT=device`, see the abi README) the elements are the first `domain_count` of `counts` and `offsets`, so they can be held in runs larger than their live count, and the counter of `owner` states the sum of their counts as it is, above the capacity too: the first `n` slots are written and the sum minus `n` is how many did not fit. A node that states `n` as a bound checks the count against it. The plain variant clamps the count to `n`. It binds 6 storage buffers (5 in the plain variant).
▲ 0↑ born
·
procnode · born here
❒asset
# core.normals.point (procNode) The normal of the surface at every point: the normals of the primitives that use the point, summed with each weighted by its area, by the angle it spans at the point, or equally, and normalized, written as `N` on the point class. Built from `core.normals.face`, `attribute.promote` (primitive to point, sum) for "area" and "uniform"; for "angle" the corner angle `math.acos` of the dot product of the two unit edges at each vertex, the face normal gathered to the vertex by its owner, scaled by that angle and scattered to the point. ## Inputs - `geometry` (Geometry). The geometry whose points get normals. - `weighting` (one of area, angle, uniform). Default `"area"`. How the primitives around a point count: "area" weights each by its size, "angle" by the angle it spans at the point, "uniform" counts each equally. - `name` (String). Default `"N"`. The attribute the normals are written under on the point class. ## Outputs - `geometry`: the geometry with a normal on every point.
▲ 0↑ born
·
procnode · born here
❒asset
# core.points (procNode) Points made from columns: a count of points, each attribute given as a column, a field or a constant. Built from the `make` kernel (`SHAPE = "points"`) for the elements and a point-class write for every entry of `columns`. The call is `g.core.points({ columns = { P = positions, mass = 1.0 }, count = 64 })`; `count` is how many points there are. ## Inputs - `columns` (Record, optional). The attributes, by name: each a column of the point count, a field evaluated over the points, or a constant. `P` places the points; the other names are carried as point attributes. - `count` (Number). Default `0`. How many points. ## Outputs - `points`: the points.
▲ 0↑ born
·
procnode · born here
❒asset
# math.length (procNode) The length of a vector: a number per element, from the vector's components. The `length` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# core.reduce (procNode) Fold a column to one value, or to one value per segment: sum, mean, least, greatest, count or the place of the least or greatest. One `reduce` kernel run. `fold` names the fold. `starts` holds where each segment starts; the kernel then gets `segments = starts.count`, and without `starts` it folds the whole column with `segments = 0`. ## Inputs - `values` (Column). The column folded. - `fold` (one of add, mean, min, max, count, argmin, argmax). Default `"add"`. How the elements fold: added up, averaged, the least, the greatest, how many, or the index of the least or the greatest. - `starts` (Column<Int>, optional). Where each segment starts in the column; the fold is then one value per segment. ## Outputs - `value`: the first folded element. - `values`: one element per segment (a single element without `starts`). - `picked`: for argmin and argmax, the chosen element's index.
▲ 0↑ born
·
procnode · born here
❒asset
# math.max (procNode) The greater of two values, element by element. The `max` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# attribute.carry (procNode) Move every column of one class through an index map: element i of the result takes element `index[i]` of the source; an index naming no element reads zeros. Built from the `gather` kernel, run once for each column the class holds (`core.names` lists them, the structure columns and stored numbers such as `id`, `N` and `uv` included), then `geometry[class].count = length`. `size` states the result length when `index` is a field; -1 takes the length of `index`. ## Inputs - `geometry` (Geometry). The geometry whose columns move. - `class` (one of point, vertex, primitive). Default `"point"`. The class whose columns are moved. - `index` (Column<Int>). For each element of the result, the source element it takes. - `size` (Number). Default `-1`. How many elements the result holds; -1 takes the length of `index`. An `index` that is a field over the result's elements needs it stated. ## Outputs - `geometry`: the geometry whose class holds every column moved through `index`, counted by the result length.
▲ 0↑ born
·
procnode · born here
❒asset
# core.normals.face (procNode) Give every primitive its normal: the direction its vertices wind around, written as `N` on the primitive class. `scaled` is the same direction scaled by twice the primitive's area. Built from `core.corners`, `attribute.promote` (point to vertex), `gather` of the next corner, `math.cross` and a segmented `reduce` over each primitive's run (`segments = primitive.count`). ## Inputs - `geometry` (Geometry). The geometry whose primitives get normals. - `name` (String). Default `"N"`. The attribute the normals are written under on the primitive class. ## Outputs - `geometry`: the geometry with a normal on every primitive. - `scaled`: the same direction scaled by twice the primitive's area, a Vec3 column per primitive.
▲ 0↑ born
·
procnode · born here
❒asset
# core.scatter (procNode) Fold a column into the places an index names: every element sends its value to the element its index names, and each receiver folds what reached it. Three `scatter` kernel runs (`init`, `fold`, `finish`) over columns of the result's type. The same answer every run for the folds that do not depend on arrival order. ## Inputs - `values` (Column). The column sent. - `index` (Column<Int>). For each element, the place it is sent to; an index outside the result sends nowhere. - `size` (Number). Default `0`. How many places there are. - `fold` (one of add, mean, min, max, set). Default `"add"`. How values that meet fold: added up, averaged, the least, the greatest, or the one from the greatest source index. - `fill` (Value). What a place nothing reached holds; zero when left out. With a `fill` the answer is a Field over the places. ## Outputs - `value`: the folded column, `size` elements of the type of `values`.
▲ 0↑ born
·
procnode · born here
❒asset
# math.sin (procNode) The sine of an angle in radians, element by element. The `sin` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# attribute.set Set a named attribute on a class of a geometry (point, vertex, edge, primitive, detail). The value is a field or column with one entry per element, or one number, vector or record every element takes. A copy carrying the column comes out. ## Inputs - `geometry`: What the column is written on. A copy carrying it comes out. - `class` (one of: detail, edge, point, primitive, vertex): Which rows the column has one value for: point, vertex (one per triangle corner), edge, primitive (one per triangle) or detail (one value for the whole geometry). - `name`: What the column is called. Writing a name the class already carries replaces that column. - `value`: The column's values: a field or column evaluated per element, or one value every element takes. A record is stored as it stands, which is how an element carries a component.
▲ 0↑ born
·
procnode · born here
❒asset
# math.mod (procNode) The remainder of `a` divided by `b` that keeps the sign of `b`, so a repeating pattern wraps the same way on both sides of zero. The `mod` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# core.boundary (procNode) The open edges of a geometry and the rings (or chains) they form, each ordered along its walk. Built from `core.edges`, `compact` of the open edges, the `loops` kernel (the edge that follows each one), `jump` rounds (the smallest edge in reach, then the distance to the end of each list; the rounds are `ceil(log2(edges))`), `loops_cut` and `loops_place`, and `scan` of the ring lengths (`segments = 0`). ## Inputs - `geometry` (Geometry). The geometry whose boundary is walked. ## Outputs - `edges`: Record `a`, `b`, `from`, `to`, `source`, `count` of the open edges. - `rings`: Record `ring`, `position`, `order`, `starts`, `count` of the rings.
▲ 0↑ born
▣
module · born here
❒asset
# proc Code-authored, graph-backed procedural generation for the Zero engine. A graph is one function of `g`: `proc.define(function(g) ... end)`. Nodes are called by family and name (`g.<family>.<name>({ ... })`), inputs are declared with `g:input(name, default, opts)`, the returned table is the graph's outputs, and `io.output` is how the graph stands in the world. `builder.module/README.md` holds every rule of that form. This module owns the whole procedural model: the graph builder + data model, the operation registry, the type system, the lazy cached evaluator, deterministic scoped hashing, and the node-family op implementations. Authored `.procGraph` graphs are persisted via `procGraph.assetType`; the `procgen.preview` / `procgen.spawn` operations live in `procgen.toolbox` and are invoked as tools: `use_tool` from an agent, or `tools.use("procgen", "preview", args)` from a script. Generated meshes/materials/textures/bundles are persisted through their own `.assetType`. An op family can also come from outside this package: a `.procPack` asset registers its ops in the same registry, called as `g.op.<id>(...)` exactly like the ones shipped here (`proc.registerPack(name, ops)` is what it registers through, and what a test or a tool calls to hold a family for the life of one VM). `terrain.*` is one such pack, shipped in `terrainSystem.package/procPacks/` and covering a whole landscape; `guides { path = "systems/terrainSystem/documentation/terrain" }` is its guide. To find out what the registry holds, use the `procgen` toolbox: `searchNodes` searches it and `describeNode` describes one node or op. `proc.search_ops(query)` and `proc.schema(id)` are the same answers in-process, for a graph builder looking something up while it runs.
▲ 0↑ born
·
procnode · born here
❒asset
# math.floor (procNode) The greatest whole number not above the value, element by element. The `floor` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# math.cross (procNode) The cross product of two Vec3 values: the vector perpendicular to both, per element. The `cross` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# math.abs (procNode) The magnitude of a value, element by element. The `abs` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# math.acos (procNode) The angle whose cosine is the value, in radians, from 0 to pi, element by element. A value past -1 or 1 reads as the nearest of the two. The `acos` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# core.concat (procNode) Join several geometries into one: points, vertices and primitives end to end, links renumbered, every column any geometry holds. Built from the `concat` kernel, one run per column any geometry holds in each class (the `concat` module lists them, the structure columns and stored numbers such as `id`, `N` and `uv` included). `core.join` answers where each part starts. The `concat` module in this node's folder decides every column of a class in one call: the type it takes (the type the first part with elements holds it as; a part that holds it at another width is refused by name, one that holds it as another type of the same width is cast by value), the part that decides it, its description words, and whether the parts bind as they stand. A column every part holds as that type, with elements, in a run of its own, costs two dispatches whatever the number of parts: the typed destination and the join. Any other column first casts each part by value to the joined type, and the elements of a part that holds no such column stand at zero for it. The link columns shift by what comes before: `vertex.pt` by the points of earlier parts, `primitive.vstart` by their vertices. A column of a type the device cannot hold (text, a record) is laid end to end by the `concat` module in this node's folder, as a column of values; a part that holds no such column leaves its elements without a value. ## Ids The point `id` of each part moves past the ids of the parts before it, so every id of the result is unique. A part spans the larger of its next id and its point count. A part without an `id` column is numbered in order after them. A column carries no statement of what its values mean, so the join keeps a list of the point columns that hold the `id` of another point, in the `concat` module of this node: `parent`. Where some part holds ids, each part's `parent` values move by the amount its ids move, a negative value (no parent) stays, and a part without a `parent` column parents nothing (-1). Where no part holds ids, `parent` values refer to nothing the join renumbers and join as they are. A hierarchy built in two parts, each numbered from 0, keeps its parenting through the join. ## Inputs - `geometries` (List<Geometry>). The geometries joined, in order. ## Outputs - `geometry`: the joined geometry.
▲ 0↑ born
·
procnode · born here
❒asset
# math.clamp (procNode) The value held between a lower and an upper bound, element by element. The `clamp` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# attribute.promote Move a named attribute from one class of elements to another: point, vertex, primitive, detail or edge, folding the values that meet with sum, mean, min or max. Built from the `gather`, `scatter` and `reduce` kernels and `core.owner`; only the route `from` to `to` names cooks: - point to vertex: gather at `vertex.pt`. - vertex to point: scatter fold at `vertex.pt`. - vertex to primitive: reduce over each primitive's run, `segments = primitive.count`. - primitive to vertex: gather at the vertex's owner. - point to primitive and primitive to point: through the vertices. - detail to a class: gather of the one value; a class to detail: reduce of the whole column (`segments = 0`). - point to edge: the fold of the two end points over `core.edges`. - edge to point: the edge class's column scattered to both end points of every edge and folded there; a point no edge reaches holds 0. The same class is the attribute unchanged. A route that does not exist refuses the build by name. ## Inputs - `geometry` (Geometry). The geometry holding the attribute. - `name` (String). Default `""`. The attribute to promote. - `from` (one of point, vertex, primitive, detail, edge). Default `"point"`. The class the attribute lives on. - `to` (one of point, vertex, primitive, detail, edge). The class the attribute is moved to; every caller states it. - `op` (one of sum, mean, min, max). Default `"mean"`. How values that meet on one element combine: added up, averaged, the least or the greatest. - `as` (String). Default `""`. The name written on the target class; empty keeps `name`. ## Outputs - `geometry`: the geometry with the promoted column written on the target class under `as` (or `name`).
▲ 0↑ born
·
procnode · born here
❒asset
# io.luau (procNode) Run a function of a Luau module on the CPU and answer what it returns. The module is required, the function read by name, and the function called with the arguments; the result is cached by the module's checksum, the function name and the arguments, so a module edit re-cooks it. Built from `op.cpu.call` of `require` on the module, a member read of the function by name, and `op.cpu.call` of that function. Both calls run on the CPU, so a graph holding this node shows a CPU call wherever it sits. ```luau local ids = g.io.luau({ module = "@builtin::systems.procgen.nodes.scene.reads", fn = "idsOf", args = { "label", refs }, returns = { "List<String>" } }) ``` Outputs: `value` is the first returned value and `values` is the list of every returned value. ## Inputs - `module` (AssetRef<module>). Default `""`. The module that holds the function: its identity, path, AssetRef or guid. It is recorded as a dependency of the graph. - `fn` (String). Default `""`. The name of the module's function to call. - `args` (List<Value>, optional). The values the function is called with, in order. A device column is passed as a reference to its buffer. - `returns` (List<TypeName>, optional). The procgen types of the values the function returns, in order. - `ceilingMs` (Number). Default `0`. The time the function may take, in milliseconds; a call past it is refused. 0 leaves the runtime's default ceiling. ## Outputs - `value`: the first returned value. - `values`: every returned value, in order.
▲ 0↑ born
·
procnode · born here
❒asset
# math.to_int (procNode) A value truncated to a whole number held as an integer. The `to_int` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
assettype · born here
❒asset
# Module (asset type) A module is a reusable, stateless (or globally-stateful) Luau library that other code pulls in with `require()`. Modules are the unit of factored-out logic in a world: helpers, math libraries, format parsers, registries, shared algorithms. ## When to use one - You have logic you want to share across multiple components, services, scenes, or tools. - You have a registry, lookup table, or shared cache that callers all read from / write to. - You want a stable API surface (`M.fn`) other code can depend on. If the logic is bound to one entity, use a `.component`. If you want a named UI of fields rather than functions, use a `.preset`. ## Where it lives - Source: `/zero/source/.../<name>.module/` - Identity: the source path with `/` written as `.` and each segment's type suffix dropped — `/zero/source/demo/beats.module` is `demo.beats`, and `/zero/source/kit.package/beats.module` is `kit.beats`, the container's name kept and its `.package` suffix gone. `asset.create` returns this string, `asset.resolve(...).identity` reads it back, and the `require` forms below write it as `<identity>`. - Folder shape: - `init.luau` (or `init.lua`) — module body. **Required.** Returns a table (conventionally named `M`). - `README.md` — instance-level documentation. **Required.** - `.metadata` — agent-editable tags + free-form fields. **Required.** ## How to create one ```luau asset.create("module", "<name>") -- Creates: /zero/source/<name>.module/ -- init.luau (canonical `local M = {} … return M`) -- README.md (instance README template) -- `folder` places it in a subfolder of /source, and that subfolder -- leads the identity: `{ folder = "lib" }` produces `lib.<name>`. -- `into` authors it inside a resolved container (a package, a toolbox), -- whose own identity leads the same way: asset.create("module", "<name>", { folder = "lib" }) asset.create("module", "<name>", { into = asset.resolve("myPkg.package") }) ``` ## How it operates 1. **Registration.** Writing `init.luau` into a `.module/` folder indexes the asset and registers the module path with the resolver. 2. **Loading.** The first `require("<identity>")` runs the file's body and caches the returned table. Subsequent requires from anywhere return the **same table** — this is the shared-state lever for "module-local" registries. 3. **Hot reload.** Editing the file re-runs the module body and folds what it returns into the table already loaded, so that table keeps its identity: a caller that captured it with `require` reads the new source's functions and values through the reference it already holds, and a later `require` returns that same table. A key the new source drops goes away; a key something set at runtime stays. The body's own `local`s start again from their initial values, so state that must outlive an edit lives in `modules.state()` (`guides { path: "core/scripting-and-tasks" }`). 4. **Identity resolution.** A module is reached by its `<identity>`, and where the caller lives decides which spelling of it resolves. For a module in a **world**, from a caller in that same world — another module, a component, a scene entrypoint, or an `execute` chunk: - `require("<identity>")` — the identity on its own, resolved against the caller's own root. It carries every folder segment, so a module created with `{ folder = "demo" }` is `require("demo.beats")`. - `require("@root::<identity>")` — the same root-relative resolution, written out. - `require(".sibling")` — a module in the same folder, by its bare name; `..name` steps up one folder, `...name` two. Logs, stack traces and hot-reload notices name that same module `@local.source.<identity>` — its key under the world source root. The `<identity>` tail of that key is the form above, so a trace reading `@local.source.demo.beats` is `require("demo.beats")`. For a module in a **library** — content under `/zero/source/libs/@<lib>/` — `@<lib>::` addresses that library's root from anywhere: `require("@builtin::modules.transform")`. Mounting a world as a library moves its files under such a root and flips the caller root to `@<lib>::`, which is what keeps the root-relative forms above resolving across the move. ## Discovery - `asset.list("module")` — every registered module. - `asset.inspect("<name>")` — public functions, source path, this type README. - `ls /zero/source/<name>.module` lists the files the folder holds. ## Authoring conventions - Return a single `M` table from `init.luau`. Top-level statements with side effects run on first require — useful for one-time initialization, dangerous if they touch the engine before it's ready. - Write exported functions as `typed function`, not `function`: the argument types are enforced at the call and the checker knows the signature, so a caller's mistake is reported where they made it. - Annotate exported functions with `--!desc` / `--!arg` / `--!return` / `--!example` so the LSP, `tools.list`, and `asset.inspect` surface them. A function with no `--!desc` is reachable only by someone already reading this file. - Declare a VALUE with `Field.<kind>(default, mode, description)`, and a table whose members are reached through a metatable with `--!members <TypeName>` — the latter gives the surface its accepted member set, its type, and its documentation from one table. See `guides { path: "core/authoring" }` for all four rules and `modules/api/engine/entity.module/members.module` for a worked member table. - Keep module state in module-local upvalues or in `modules.state()`. Globals leak across reloads. Module-locals start again from their initial values with each hot reload; the table `modules.state()` returns is the same table on both sides of every reload. - Prefer focused modules over kitchen-sink modules. If two halves of a module have no shared state, split them. ## Common pitfalls - **Cyclic requires.** A `require` chain that loops will return the partial table (the half built before the cycle was detected). Design for one-way dependencies. - **Engine-time side effects.** Don't `entity.spawn` at module top-level — the engine may not be ready. Expose an `M.init` and call it from a scene entrypoint. - **Hot-reload + module-local cache.** A module that caches expensive computation in upvalues loses that cache on reload — fine for dev, but be aware in performance work. - **`init.lua` vs `init.luau`.** Either works, `.luau` is canonical. - **Writing one while play runs.** A `/source` write made while play is running lands on the play shadow, and a guarded play-exit discards it unless it is kept. One `.module` write shadows `init.luau`, the `README.md` and the `.metadata` together. `vfs.write(path, bytes, { durable = true })` skips the shadow, and `vfs.promotePlayShadow` keeps what is already on it — the `core/vfs` guide has the model and what each route costs. ## Related types - `.component` — for entity-bound state + lifecycle hooks. - `.service` — generates content you don't have yet (a mesh, sound, texture, …) via a metered provider. - `.tool` — for a single agent-callable function with a YAML schema. - `.package` — to group several modules + components + scenes into one shippable folder. ## In the Inspector Exports: what the module exports (name, kind, signature), parsed from its source without running it, with **Check** (the LSP's diagnostics for its entry file) and **Open**.
▲ 0↑ born
·
procnode · born here
❒asset
# math.min (procNode) The lesser of two values, element by element. The `min` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
computeshader · born here
❒asset
# sort A stable radix sort of a column's keys into a permutation: place `s` of `perm` holds the index of the element that sorts `s`-th, so a gather by `perm` puts any column in key order. | Specialize | Options | Meaning | |------------|---------|---------| | `KEY` | `f32`, `i32` | the scalar a key holds | Elements with equal keys keep their run order, in both directions (`params.descending` 1 sorts from the greatest key down), and `-0` and `+0` are one key. The sort works 4 bits of the key at a time from the least significant up, and a pass whose digit is the same in every key is skipped, so a run of keys inside a narrow range takes fewer passes. `work` is the sort's second permutation; its contents after a dispatch are not an answer. The dispatch size `n` is the number of keys. One workgroup of 128 works the whole run; the other workgroups of a larger dispatch return. Exact: the sort is a function of the keys and their order, so the same keys give the same permutation on every dispatch. Over a live count (`COUNT=device`, see the abi README) `n` is the capacity and the first `domain_count` keys are live: they are sorted, and a slot at or past the live count holds its own index in `perm`. A live count of 0 sorts nothing and `perm` is the identity. It binds 4 storage buffers (3 in the plain variant).
▲ 0↑ born
·
computeshader · born here
❒asset
# intersect_pairs The entries of two meshes that cut each other. One thread per triangle of mesh A walks the bounding volume hierarchy of mesh B (the `bvh` family builds it, one primitive a triangle) and, for every triangle of B whose bounds overlap, lists each part of the cut of the two triangles that `intersect_core` states (the ordinary cut, the coplanar clips, the points on shared edges). An entry is three words: `a`, `b` and the part. Two stages with an exclusive prefix sum (the `scan` kernel) between them: `STAGE count` writes how many entries each triangle of A has, `STAGE write` writes the entries at the offsets the sum gives, ordered by `a` and, within a triangle of A, in the order the tree is walked and the parts of a pair in increasing order. Exact: the entries and their order are the same on every run. Bind `apts`, `atri`, `bpts`, `btri`, `nodes` and `start` (any buffer in the count stage); params `nb` (triangles of B) and `total` (the sum of the counts, which sizes `pairs`, three words an entry). ## The intersect family `intersect` is a kernel family: `intersect_pairs`, `intersect_cuts`, `intersect_unify` and `intersect_retri` (shared by `intersect_core`). Two triangulated meshes A and B go in; the pieces of both, cut along their common curve and along the regions they share, come out. A node composes the family with `bvh`, `scan`, `sort`, `compact` and `gather`: 1. Build the hierarchy of B (`bvh_codes`, `sort`, `bvh`, `bvh_pyramid` per level, `bvh_internal`), one primitive a triangle (`vstart` 3 t, `vcount` 3). 2. `intersect_pairs` `STAGE count`, an exclusive `scan` of the counts, `STAGE write`: `pairs` (a, b, part), ordered by a. `total` is the scan's total, the number of entries. 3. `intersect_cuts` over `total` entries: the endpoint records, slot `2 * entry + e`. Word 0 to 2 of a record are the position as f32 bits. 4. `intersect_unify` (three stages) over `2 * total` slots: `rep`. `compact` of the slots with `rep[s] == s` numbers the cut points; a cut endpoint's point is the number of `rep[s]`, and its position is the record's position. 5. `intersect_retri` for A with `runs` from the counts and `start` and `order` the entry indices, `side` 0, then for B with the entry indices stably sorted by b (`sort` on the b words) as `order`, `runs` the first and count of each b and `side` 1. `STAGE count`, `scan`, `STAGE write`. Vertex references at or above `npts` are cut endpoint slots, mapped through `rep`. The pieces of each mesh share the cut points, so the two results agree along the curve and along the boundary of every shared region. Each piece lies on one side of the other mesh, or on its surface. ## What a boolean reads A boolean keeps pieces by what lies just outside and just inside each piece's centre, probed along the piece's normal against the other mesh (`ray` winding at both probe points). Pieces on the other mesh's surface read the same way: - Same normal (the two surfaces coincide and face one way): the probe outside the piece is outside the other mesh and the probe inside it is inside the other mesh. - Opposite normals (the surfaces coincide and face each other): the probe outside the piece is inside the other mesh and the probe inside it is outside. | Operation | Pieces of A kept | Pieces of B kept | |---|---|---| | union | outside probe outside B | outside probe outside A and inside probe outside A | | intersect | inside probe inside B | outside probe inside A and inside probe inside A | | subtract (A minus B) | inside probe outside B | outside probe inside A and inside probe inside A, flipped | A region shared with the same normal is kept once from A in a union and an intersection and dropped from both in a subtract; a region shared with opposite normals is dropped from both in a union and an intersection, and kept from A in a subtract.
▲ 0↑ born
·
procnode · born here
❒asset
# math.pow (procNode) `a` raised to the power `b`, element by element. The `pow` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
computeshader · born here
❒asset
# intersect_retri The triangles of one mesh retriangulated along the segments that cut them. One thread per triangle: a triangle no segment reaches is written as itself; a cut triangle is worked in its own plane, in world lengths, where its corners and the cut endpoints are the points and its sides and the cut segments are the constraints. The triangulation holds every constraint first (each cut out by the points that lie on it), then holds the remaining candidate edges shortest first, a length class at a time (the exponent and top three mantissa bits of the squared length), each when it crosses no held edge and has no point on it. The held edges are a planar graph: the neighbours of each point are sorted by direction, the faces are walked with their inside on the left, a face of three points is a triangle and a face of more is cut ear by ear (each ear a corner that turns left with no other point of the face inside it or on its sides), and the face outside the triangle is left out. Every triangle is wound the way the source triangle was, so the pieces of a triangle agree on the direction of every side they share however thin the sliver between two cut points. A triangle whose three points stand on one line in the world has no area and is left out. Every cut segment is a union of edges of the result, and the pieces of a triangle tile it. Points and distances are compared within a few steps of the coordinates' precision (`5e-7` of the largest coordinate of the triangle, and no less than `1e-7` of its longest side), the same measure for a triangle of either mesh, so cut points a few millionths apart are told apart by the triangles of both meshes alike. Param `side` names the mesh the pass cuts, 0 for A and 1 for B; a segment cuts the mesh when its endpoint records say so (`intersect_core`). A triangle takes every point of its entries, a segment of no length giving a point on an edge and nothing more. `STAGE count` writes how many triangles each triangle becomes; `STAGE write` writes them at the offsets of the prefix sum, four words each (three vertex references, the source triangle) from word 4 of `out`. A reference below `npts` is a point of the mesh; `npts + s` is the cut endpoint in slot `s`, which `intersect_unify`'s `rep` names. `out[0]` is 1 when a triangle ran out of working space, which the sizing below prevents. ## Working space A triangle takes a slice of working memory sized by its entries, so the number of points a triangle can carry follows the data: a run of `m` entries gives at most `3 + 2 m` points, `3 + m` segments and three edges a point, and the slice is `64 + 40 m` words. The slices of consecutive triangles follow one another and make the tail of `out`, after its 4 header words and the `4 * total` words of the triangles (`total` is 0 in the count stage): the slice of triangle `t` starts `64 * t + 40 * first(t)` words into the tail, `first(t)` the first entry of its run, so the runs' firsts are the exclusive prefix sum of their counts (the `start` of `intersect_pairs` for mesh A, the offsets of the per-b counts for mesh B). `out` is sized `4 + 4 * total + 64 * n + 40 * entries` words from the params `total` and `entries` (the number of entries of `intersect_pairs`). The triangles of mesh A are grouped by `a` already (`runs` is `start` and the counts of `intersect_pairs`, `order` the identity); for mesh B the entries are stably sorted by `b` first and `order` holds the sorted entry indices. Exact: each triangle is worked alone and in a fixed order.
▲ 0↑ born
·
procnode · born here
❒asset
# math.ceil (procNode) The least whole number not below the value, element by element. The `ceil` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
computeshader · born here
❒asset
# intersect_cuts The segment each entry of `intersect_pairs` is cut along, one thread per entry. The thread reads the entry's `a`, `b` and part and writes the part's two endpoints (`intersect_core` states the parts). An endpoint record is 8 words: the position as f32 bits, the kind word (the kind in the low byte, which meshes the segment cuts in bits 8 and 9), then `lo`, `hi`, `tri` and `extra`, which name the crossing; two endpoints, 16 words, an entry. The endpoint slot `s = 2 * entry + e` names an endpoint to `intersect_unify` and `intersect_retri`. The points on shared edges have both endpoints in one record. Exact.
▲ 0↑ born
·
computeshader · born here
❒asset
# intersect_unify The first slot of every cut point. Endpoint slots whose records name the same crossing (the kind word, `lo`, `hi`, `tri` and `extra`) are one point; `rep[s]` is the lowest slot among them, which is the point's identity. A slot with `rep[s] == s` is a new point's first slot, so a compaction of those slots numbers the cut points, and every endpoint reference maps through `rep`. Three dispatches (`STAGE claim`, `fold`, `resolve`) over a hash table of `cells` words (a power of two, at least twice the slot count) and a second table of the same size, both zero before the first stage. Bind `rep` (one word a slot) to the same buffer in all three. Exact: the cell a key takes depends on thread timing, the answer does not.
▲ 0↑ born
·
computeshader · born here
❒asset
# ray Casts rays and segments against the triangles of a geometry through the hierarchy the `bvh` kernel builds (closest hit, or any hit for occlusion), and computes the generalized winding number of the geometry about each origin, which is the inside test a boolean or a distance field needs. A hit is the parametric t, the primitive index and the barycentric weights. The shader's header states the ray and hit layouts. Exact: the same rays give the same hits, ties going to the lower index. `ray` is a family of three kernels, since one dispatch binds at most 8 storage buffers a stage: | Kernel | Answers | |---|---| | `ray` | `hit` (t, u, v, fan) and `hit_prim` (the primitive, the shape index of a mesh hit) for the `closest`, `any` and `winding` stages | | `ray_surface` | for the `closest` hits `ray` found: `hit_normal` (unit triangle normal facing the ray's origin side) and `hit_end` (the hit point pushed out along the normal by the `skin` param; a miss answers the ray's end) | | `ray_shapes` | the analytic shape table, bound as a `columns` binding (`shapeKind, center, extent, rotation, bounce, friction, index`; a plane's extent is its unit normal, a sphere's extent.x its radius): `hit`, `hit_prim` (the row of the shape table), `hit_normal`, `hit_end` | Chain for a mesh: dispatch `ray` with `STAGE = closest`, then `ray_surface` with the same `rays`, geometry bindings and the `hit` and `hit_prim` that `ray` wrote.
▲ 0↑ born
·
procnode · born here
❒asset
# math.log (procNode) The natural logarithm of a value, element by element. A value at or below 0 answers 0. The `log` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
computeshader · born here
❒asset
# bvh_codes A 30-bit Morton code for each primitive of a geometry: its bounds centre, normalized inside the scene bounds and interleaved bit by bit. Sorting primitives by this code puts primitives that are near in space near in the order, which is what the `bvh` kernel builds its tree from. The scene bounds are a min and a max reduce of the points. Exact.
▲ 0↑ born
·
computeshader · born here
❒asset
# bvh The tree of a bounding volume hierarchy over the primitives of a geometry (an LBVH): a leaf per primitive in Morton order with its bounds, and the internal nodes linked by the longest common prefix of the sorted codes, equal codes broken by place. `bvh_pyramid` and `bvh_internal` then fill in the internal bounds, and the `ray` kernel traverses the finished table. `bindings.yaml` states the node layout and the order the kernels run in. Exact: the same geometry gives the same tree.
▲ 0↑ born
·
computeshader · born here
❒asset
# bvh_internal The last step of a bounding volume hierarchy build: each internal node's bounds, the fold of the leaves its run of sorted places covers, read from the pyramid `bvh_pyramid` wrote. The node table is written in place, after which the `ray` kernel can traverse it. Exact.
▲ 0↑ born
·
computeshader · born here
❒asset
# bvh_pyramid One level of the pyramid of leaf bounds: each pyramid node folds the bounds of two nodes of the level below (leaves at the first level). Dispatched once per level, the levels let `bvh_internal` fold the bounds of any run of leaves in a few reads. The pyramid is written in place, each level reading what the level before it wrote. Exact.
▲ 0↑ born
·
procnode · born here
❒asset
# core.edges (procNode) The edge table of a geometry: every distinct side of its primitives as a point pair (a < b), the primitives along each side, whether the side is open (one primitive) or manifold (at most two), and the edge each vertex slot's side belongs to. Built from the `edges` kernel (the sides of every primitive), two stable `sort` passes (by the higher point, then the lower), `gather`, the `edges_runs` kernel (run heads, then the table), `compact` of the heads, an inclusive `scan` of the heads (the edge of each sorted place) and `scatter` through the slot order to give `sideEdge`, the edge id of every vertex slot. ## Inputs - `geometry` (Geometry). The geometry whose sides are listed. - `faces` (Bool). Default `true`. True for a geometry of faces, whose primitives wrap from their last vertex to their first; false for curves, which wrap only where their `closed` attribute says. ## Outputs - `edges`: Record `a`, `b`, `faceStart`, `faceCount`, `faces`, `open`, `manifold`, `reversed`, `sideEdge` (the edge each vertex slot's side belongs to, -1 for a slot with no side) and `count`.
▲ 0↑ born
·
computeshader · born here
❒asset
# topology Mesh edit operations over a geometry's linkage (`vertex.pt`, `primitive.vstart`, `primitive.vcount`), selected by the `OP` specialization (`split_edge`, `collapse_edge`, `insert_face`, `delete_face`, `split_vertex`), each stage by `STAGE` (`count`, `write`): split an edge (insert a point), collapse an edge (merge two points and drop the faces that degenerate), insert a face, delete a face, split a vertex (give a group of corners a point of their own). Each operation runs in two stages with an exclusive prefix sum between them. Stage 0 writes `cnt`, how many output vertices each primitive contributes (each vertex, for split vertex). The prefix sum of `cnt` is `off`. Stage 1 writes the output vertices' points (`o_pt`) and where each came from (`o_src`). The output primitives are `off` as run starts and `cnt` as run lengths; a primitive with an empty run is gone, and the select node removes it. The shader's header states the records each operation reads. The operation's records come from the edge table: for split edge, the point for each split edge gathered to the vertex slots through `edges`' side slots (both faces of an edge name the same point); for collapse, the point each point becomes, from an independent set of edges. The point an inserted vertex or a split vertex stands for is carried by the output maps: `o_src` names the vertex whose attributes an output vertex takes (negative when it is new, with ~v the vertex a split inserted after), and the compaction of the records names each new point's source. Integers are 32-bit words, exact.
▲ 0↑ born
·
procnode · born here
❒asset
# math.sqrt (procNode) The square root of a value, element by element. The `sqrt` operation of the authoring surface, called through the Luau node on `proc.fieldc.operators`. Operands are numbers, vectors, Columns or Fields. When every operand is a plain number or vector the answer is the plain result, folded where the node cooks; otherwise the answer is a Field: a template over its operands that whatever reads it evaluates in one dispatch together with the chain around it. Nothing dispatches for this node alone.
▲ 0↑ born
·
procnode · born here
❒asset
# attribute.number Number the elements of a class: a whole-number column holding `start` on the first element, start + 1 on the second, and so on, written under `name`. The number is stored with the element, so it stays with it when elements are dropped, reordered or duplicated around it. ## Inputs - `geometry`: What is numbered. A copy carrying the column comes out. - `class` (one of: detail, edge, point, primitive, vertex): Which elements are numbered. - `name`: What the column is called. A name the class already carries is replaced. - `start`: The number of the first element.
▲ 0↑ born
·
procnode · born here
❒asset
# core.stitch (procNode) Join consecutive rings of points with quads, and optionally close the ends with caps: the surface of a tube, a sweep or a loft. Built from `attribute.carry` (the geometry's vertices and primitives followed by the new ones, which read index -1 and so carry zeros, sized by `size`), `math` operators for the quad and cap layout, and `gather` of the ring points. `rings` holds every ring end to end, `size` points each; a quad runs ring `r` point `j`, point `j + 1`, then the same two points of ring `r + 1`. With `closed`, the last point of a ring joins the first. A cap is one primitive running round a whole end ring: the first ring wound the other way, the last ring in its own order. The ring count is read back from `rings.count`. ## Inputs - `geometry` (Geometry). The geometry holding the ring points; the quads and caps are added to it. - `rings` (Column<Int>). The point numbers of every ring, one ring after another, `size` points each. - `size` (Number). Default `3`. How many points each ring holds. - `closed` (Bool). Default `true`. Whether each ring is a loop: the last point joins the first, so a ring of k points makes k quads to the next ring instead of k - 1. - `caps` (one of none, start, end, both). Default `"none"`. Which ends are closed by a primitive running round the end ring. ## Outputs - `geometry`: the geometry with the quads and caps added.
▲ 0↑ born
·
procnode · born here
❒asset
# core.resample.curve (procNode) Redistribute the points of every curve evenly by arc length: a fixed number per curve, or as many as fit a spacing. Built from `core.arclength`, `core.points` (one row per new point, from the `expand` kernel over the per-curve counts), `core.sample_curve`, `core.interpolate`, and an exclusive `scan` for the new curves' starts. The result is curves again: `vertex.pt` runs through the new points in order and `primitive.vstart`, `vcount` and `closed` describe each curve; a curve's own attributes carry over. ## Inputs - `curve` (Curves). The curves resampled. - `count` (Number). Default `16`. Points per curve when `spacing` is 0. - `spacing` (Number). Default `0.0`. The distance between points; each curve takes as many as fit. 0 uses `count`. ## Outputs - `curve`: the resampled curves.
▲ 0↑ born
▣
module · born here
❒asset
▲ 0↑ born
backing path · systems/procgen.package/nodes/geometry.nodebox

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported on this asset or on the parts this page shows. Parts it leaves out are not counted here.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.