Log inGet started
·
agentskill · drop-in viewer
asset⌬ agentskillagentSkillprimary: skill.yaml·originates fromworld 07158574-5…

authoring-assets.agentSkill

The asset-authoring skill: search before creating, author through `asset.create` rather than hand-writing folders, add a whole new asset type when no existing one fits, and generate recognizable content instead of blocking it out.

by◐lumi·posted 3mo ago
What it does

authoring-assets

The asset-authoring skill: search before creating, author through asset.create rather than hand-writing folders, add a whole new asset type when no existing one fits, and generate recognizable content instead of blocking it out.

Carries the generating subskill, which is the services-toolbox procedure for turning a prompt into a real mesh, texture, or sound.

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.

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 up to 3 levels deep. Open a part to see what it holds further down.

103+items
·
agentskill · born here
❒asset
# generating The services-toolbox procedure: discover what can be generated, start a generation from a prompt, track the job across turns, and put the produced asset into the slot a placeholder was holding. A subskill of `authoring-assets` — open it with `agent_skill { name = "authoring-assets/generating" }`.
▲ 0↑ born
·
assettype · born here
❒asset
# agentSkill A skill packages what an agent needs to do one job well: the instructions, the assets and toolboxes the job runs through, and — for jobs with distinct branches — subskills that go deeper. Skills advertise themselves. Every registered skill's name and one-line description rides the agent's tool responses, so an agent discovers what this world knows how to do without being told to look. Invoking one returns the instructions plus the exact identities and tool names the job needs. ## Shape ``` <name>.agentSkill/ skill.yaml # the manifest: description, when, checks, dependencies, order instructions.md # the prose an agent receives on invoke README.md # what this skill is, for a human reading the tree .metadata # tags <sub>.agentSkill/ # optional subskills, reachable as <name>/<sub> ``` ## The manifest ```yaml $schema: zero/agent-skill/v1 description: Making a character a person can actually play. when: > Reach for this when the player should look like something else, or when you are about to build a camera and a body and wire them together by hand. checks: - check: Holding a movement key in play moves the body across the ground. observe: Read its position before and after holding the key; the numbers differ. - check: The body animates while it moves. observe: A time-sampled collage from a camera outside the player, while the key is held. dependencies: - "@builtin::tools.scene" - "@builtin::tools.scene.player" - "@builtin::components.PlayerPrototype" - "@builtin::docs.core.multiplayer" order: [spawn-points, camera-rig] ``` `description` is the line the roster carries, and the whole basis on which a skill is chosen. Name the job the skill makes a reader competent at, in the outcome they are after — "getting a scene's player and view right", "making content the engine will load". A reader scanning the roster is matching it against the job in front of them, so the closer it reads to their own sentence about that job, the sooner they open it. `when` is the longer trigger — the situations that should send a reader here, in the words they would use for their own task. `kind` says what the skill is to its reader, and `job` is assumed when it is left out. A `job` is work taken on and finished: opening it holds it, so it rides the reader's tool responses until it is released, and its `checks` say when it is done. A `reference` is knowledge for the whole session, such as how the engine fits together: opening it does not hold it, it is never released, and it states no `checks`, `stages` or `verdicts`, because it describes no job whose end they would mark. A check on a reference turns it into a step that one pass completes, after which its reader stops applying it. `checks` is what the reader of a job is answerable for at the end, and it leads the rendered skill, ahead of the procedure. Each entry pairs a **condition that must hold** with **where it is seen**, and both halves are required — a condition alone is satisfied by any glance, and the wrong vantage is how a job passes while every one of its faults sits outside the frame. Write them as observations someone else could repeat and disagree with: a number that changed, a thing visible in a named shot, a count that came back as one. A job publishes only once it carries at least one. `stages` models a skill whose passes run in order, when the order is load-bearing. Each stage is `{ name, detail?, checks? }`, and a check that sits in a stage is answered after that pass has run — which is the difference between a criterion that means something and one that misleads. A skill with no meaningful order leaves `stages` out and lists its checks flat; a skill that has one may do both, and the flat checks then read as the final gate. `verdicts` names the fixed set of outcomes a reader reports — at the end of each stage when the skill has stages, and once at the end when it does not. Each is `{ verdict, means }`: the word, and what reporting it asserts. Both halves are required for the reason both halves of a check are. A verdict is a check one level up, deciding a whole pass rather than one condition, and a bare `refine` is a label each reader fills in privately — so two runs report the same word about different states and nothing downstream can tell them apart. Saying what it means settles the boundary in the skill instead of in whoever happens to be reading it. A check marked `perRun: true` is one whose content is written for the job at hand: the manifest can say *keep a list of every feature this object must have and answer against it*, but not what is on that list, because that is per run. It renders with that instruction attached. ```yaml verdicts: - verdict: continue means: Every check in the stage held, and the next stage can build on it. - verdict: refine means: A check failed on something this stage can still fix. Run it again. - verdict: stop means: A check failed on something an earlier stage decided. Go back to it. stages: - name: blockout detail: Mass and proportion only. checks: - check: Every part on the identity list has a stand-in at roughly the right size. observe: A capture beside the reference, at the same angle. perRun: true - name: material checks: - check: Metal reads as metal and cloth as cloth. observe: The metallic and roughness passes, after the lighting stage has run. ``` `dependencies` is one flat list of asset **identities**: a toolbox, a single tool, a guide, a component, a module, another skill. Each entry is resolved when the skill is opened and reported with what it turned out to be, so a skill never declares "this one is a toolbox" — the asset says so, and the reader groups by that. Identities are what make a skill portable. They pin like any other reference, so installing a skill into another world brings the toolboxes and guides it runs through along with it. A bare name (`scene`) or a guide path (`core/multiplayer`) resolves to nothing and is refused before publish. A toolbox entry lists the tools it carries, and a tool entry carries its own signature, so the instructions never restate a call contract that can drift. An identity this world lacks is reported as missing rather than dropped. Write instructions that name the toolboxes and tools a job runs through, and leave the call form to the reader — an agent may be holding the MCP tool surface, the shell, or Luau, and the signatures are listed for it either way. ## Subskills A `<sub>.agentSkill/` folder nested inside a skill is a subskill. It has the same shape as its parent and is invoked as `parent/sub`. Subskills are reachable only through their parent: they are absent from the roster and from `skills.list`, and appear in the parent's invoke result. A skill can therefore carry a dozen narrow procedures while costing the roster one line. `order` in the manifest names the subskills that lead. Any subskill it omits still appears, sorted by name, after the ordered ones — so adding a subskill folder is the only step needed to publish it. ## Publishing one Make a skill and it is live: it joins the roster agents read and it opens by name from the moment it exists. Edit its description and that is the line agents see next. A skill lives anywhere under `/zero/source/`. `asset.create` hands you a valid one to fill in: ```luau asset.create("agentSkill", "waveSpawning", { folder = "skills" }) ``` A skill's scope — `builtin`, `library`, or `world` — comes from where it lives rather than from anything it declares, so a world's own skill always reads as the world's. ## Opening one ``` agent_skill {} -- the roster agent_skill { name = "scenes" } -- one skill's instructions + deps agent_skill { name = "scenes/player-setup" } -- a subskill ``` The same three through the toolbox — `skills.list`, `skills.invoke` — and from Luau via `tools.use("skills", "invoke", "scenes")`. `asset.inspect` on a skill ref returns the structured record all of them render. Skills authored in a world are the world's own, and are labelled as world content wherever they surface beside built-in ones. ## In the Inspector Skill: what the skill is for and when it applies, how many checks and verdicts it declares, its stages, a link to each asset it depends on (and a problem for each that does not resolve), its subskills, and **Open** for instructions.md.
▲ 0↑ born
·
toolbox · born here
❒asset
# services Generate real content — 3D meshes, textures, audio — from a prompt, by spending credits. This is the whole surface for it: discover what you can make, start a generation, track it, and find what you've made. You never touch the underlying asset type or task plumbing. When a build needs a recognizable thing — a treasure chest, a snowboard, a character, a sound — you **generate** it. You don't assemble it from boxes and spheres. ## The loop Discover what you can make: each service lists its operations, and each operation is self-describing: its inputs, the kind of asset it produces, and its credit cost. Check your credit balance if you want. Start a generation with a service and a prompt; this returns immediately with a handle and runs in the background, because a generation takes minutes and blocking would trip the execute time limit. When the run ends it posts a notice, `generation finished` naming its id and the job's `asset` (the spawnable asset path, which you can drop straight into a blocked-out slot) or `generation failed` naming its id and the error, and the notice reaches you on your next tool call; `services.status` reads a run's progress by its id meanwhile. A run files its output where your writes landed when you started it: one started in edit keeps its asset on source even when it finishes after you entered play, and one started inside your play session leaves it on the play shadow, where a play-exit discard takes it back; `durable` in the input states it outright. The notice and `services.status` both say where the output stands (`durable`, and the shadowed paths when the play shadow holds any of it). Alongside the live per-session job view, a durable outputs record survives restarts by reading the provenance stamped on each generated asset. ## Describing your assets `describe` is the one tool here that makes text, not content. It finds the assets in your world that lack the description their type asks for (for the built-in types that ask, a README, or the `about.yaml` texts of a type) and fills them in: `{ mode = "find" }` lists what is missing and what filling it costs, `{ mode = "all" }` fills it, and `{ assets = { ... } }` fills the assets you name. A call costs 1 credit and covers many assets, and `find` reports the number of calls before anything is spent. What it writes is an ordinary file in the asset, there for you to edit.
▲ 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. The body runs in an environment of the module's own: a name it declares without `local` is readable by the module's own functions and by no other script, so an export reaches its callers through the returned table and nothing else. Requiring a component's folder runs its chunk the same way, once, and its `update`, `start` and `public` stay in that environment; a component runs on an entity through `entity(id).component.add(name)`. 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()`. A name the body declares without `local` lives in the module's environment, which every run of the module shares: a function the previous run left in place reads what the reload assigned. 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
·
assettype · born here
❒asset
# Material (asset type) A material is a named, configured instance of a `.shader`. The shader defines the surface look (lighting model, available property fields, texture slots); the material picks values for those fields and a shader to bind to. Renderable entities reference materials, not shaders, so the same geometry can wear different materials and the same material can be reused across many entities. Entities sharing one material can still differ in a value: every drawn object carries four `vec4` lanes of its own that a surface shader reads as `input.shader_data[lane]` and `renderer.instanceData.set` writes. Use that for a dissolve at its own progress per subject, a hit flash at its own age, a fill level per instance — a material per entity is for when the LOOK differs, not when a number does. > New to how a material relates to its shader, its textures, and the live GPU > material behind it? Read `core/resource-model` first. It also covers the > difference between an authored `.material` asset (this type) and a runtime GPU > material made with `renderer.material.create`. ## When to use one - You want a named surface configuration an entity wears by referencing it from its `Model` component (`material = "@.../<Name>"`). - You want runtime property tweaks (`asset.resolve(ref):setProperty(...)`) to affect every entity using the material at once. - You want to publish a curated look (`Gold`, `RedGlow`, `BrickWall`) other authors can reuse without re-typing shader bindings. If you need a brand-new lighting model or a custom visual effect, you need a `.shader` first; the material wraps it. ## Where it lives - Source: `/zero/source/.../<Name>.material/` - Identity: `<Name>` (the `.material` suffix strips). - Folder shape: - `mat.yaml` — canonical material spec (shader + property values). **Required.** - `README.md` / `.metadata` — optional prose + tags, indexed when an author writes real ones. - `textures/` — optional folder for embedded textures. Validator allows the folder; its interior is unrestricted. - `shaders/` — optional folder for a co-located custom shader. Same rule. ## How to create one ```luau asset.create("material", "<Name>") -- Creates: /zero/source/<Name>.material/ -- mat.yaml (shader binding + property scaffold) -- `folder` places it in a subfolder of /source instead of the root: asset.create("material", "<Name>", { folder = "materials" }) ``` Pass the shader and initial values through `asset.create`, then tune with `setProperty` and keep what you tuned with `saveDefinition`: ```luau local mat = asset.create("material", "<Name>", { shader = "@builtin::shaders.pbr" }) mat:setProperty("base_color", { 0.8, 0.6, 0.1, 1 }) mat:setProperty("roughness", 0.3) mat:saveDefinition() -- writes the tuned values into mat.yaml ``` This mints the `.material` asset and returns a handle you tune with `setProperty`. A set is a runtime change: it reaches the GPU material and every entity wearing it, and `saveDefinition` is the call that writes it into `mat.yaml`, so a property driven every frame costs no file write and leaves the authored file as its author wrote it. A purely runtime `asset.create` lives only for the session. For an ephemeral, runtime-only GPU material that is not an asset at all, use `renderer.material.create` instead (see `core/resource-model`). ## How it operates 1. **Property reflection.** The bound shader's uniform struct fields become the material's configurable properties — no manual property declarations needed. 2. **Property values.** `mat.yaml` groups values by type, each block keyed by the shader's field names: `properties:` holds scalars and colors/vectors, `ints:` holds integer-typed fields, `bools:` holds boolean-typed fields, and `textures:` holds texture slots. Omitted fields take the shader's default. A scalar is a bare number (`roughness: 0.5`); a color or vector is an array (`base_color: [1, 1, 1, 1]`). `shader:` is the shader's identity string. This is the shape every material the engine writes uses. A `saveDefinition` writes the changed values into the file where they already stand and leaves the rest of it — its comments, the order of its keys and blocks, the spelling of its literals, untouched. 3. **Render state.** `render:` holds how the mesh is drawn rather than what the shader reads: `blend` (the equation the fragment composites with, also spelled `type`), `cull`, `depth_write`, `depth_compare`, `topology` and `queue` (the draw order, a whole number: `2000` opaque, `3000` transparent, `4000` overlay). The `.shader` type README's "The render block" section lists every value each key takes, and `asset.create`'s `render` argument takes the same keys and values. 4. **Application.** An entity wears a material by referencing it from its `Model` component (`material = "@.../<Name>"`). Materials affect every entity that references them; runtime `asset.resolve(ref):setProperty(...)` changes propagate to all of them immediately, and last until the next mode change unless `saveDefinition` writes them into `mat.yaml`. 5. **Texture binding.** Texture slots accept `color:r,g,b,a` for solid colors, `default:white` / `default:black` / `default:normal` for the engine's own fallback texels, `@builtin::textures.<name>` for builtins, `path/to/image.png` for file textures, or a render-target handle for camera renders. The GPU texture cache reads the `color:` and `default:` forms from the string itself, so a slot written in one names no asset and carries nothing with the material when it travels; every other form names a texture asset the slot depends on. A texture in `base_color_texture` is multiplied by `base_color`: every built-in surface shader declaring both draws the texture's colour times `base_color`, channel by channel. Under the default white `base_color` the texture shows as itself, and a coloured `base_color` tints it. The two read a colour differently: `base_color` is linear, and the slot reads its texels as sRGB-encoded, a `color:` value's included, the way it reads an image. A colour stated in both places is therefore multiplied by a darker copy of itself: `color:0.62,0.14,0.05,1` in the slot decodes to `0.34, 0.017, 0.0039`, so beside `base_color` `0.62, 0.14, 0.05` the surface draws `0.21, 0.0024, 0.0002`, far darker than either. State a solid colour once, as `base_color`, and leave the slot at its default white. 6. **Hot reload.** Editing `mat.yaml` repacks the GPU uniform buffer on the next frame; visible immediately. **Properties that follow a named value.** `values:` binds a property to a named value of the entity wearing the material (`values.set(entity, name, v)`, the `topics/values` guide): `property: valueName`, or `property: { value: "name", scale: s, offset: o }`, and the property draws `value * scale + offset` on each entity holding the value, read on the draw. A boolean reads as 1 or 0, and a number fills every channel of a colour or vector property. The wearer's own value is read, else its nearest ancestor's, so a lamp mesh under a car lights from the car's `brake`; a wearer holding none draws the material's own value. A scalar, vector, colour or bool property binds (at most 32 per material); a texture slot does not. `asset.create("material", name, { values = { ... } })` and `renderer.material.create({ ..., values = { ... } }, key)` take the same map. ```yaml values: emissive_intensity: { value: "brake", scale: 3 } ``` 7. **Inspector.** `type.yaml` names `inspector: { type: "inspector.luau" }`, and `inspector.luau` declares the material's sections beside the generic ones: `shader` (the bound shader, swappable), `properties` (each shader field as its typed control) and `textures` (each texture slot as an asset chip). They read the material through `mat:getShaderRef()` (the bound shader's ref, `~` expanded) and `mat:getTextures()` (slot name → `{ ref, name }`: the bound texture's guid and the id the material names it by). An edit there is an authoring write: `mat:writeProperty(name, value)` and `mat:writeTexture(slot, texture)` write the value into `mat.yaml` (and drop the session's own value for it, so the material reads what the file holds), and `setShader` rewrites the file; inside an undo operation each file write is recorded, so undo puts `mat.yaml` back. `setProperty` / `setTexture` stay runtime writes that last until the next mode change. ## Discovery - `asset.list("material")` — every registered material. - `asset.inspect("<name>")` — shader binding, property values, source path, this type README. - `ls /zero/source/<Name>.material` lists the files the folder holds. - `asset.resolve("<name>"):getProperties()` — the property fields exposed by the bound shader, each at the value it currently holds: what `mat.yaml` authored until something writes to the material, and the written value from then on, whichever call wrote it. That is the value the surface is drawn with. Read them back rather than guessing names; which fields exist comes from the shader. - `asset.resolve("<name>"):getDefinition()` — the authored `mat.yaml` bytes, the value the material rests at. - `asset.resolve("<name>"):getShader()` — the shader the material binds. ## Authoring conventions - Use lowercase, descriptive names (`brushed_steel`, `red_glow`, `tarmac_wet`). Avoid shader-name suffixes in the material name — the binding lives in `mat.yaml::shader`. - Drive every property the shader exposes; defaults are a fallback, not a contract for which fields exist. - Embed textures alongside the material (`textures/` subfolder) when they're used only by this material; reference shared textures from `@builtin::textures.*` or a library. ## Common pitfalls - **Property names must match the shader's uniform struct exactly** (case-sensitive). Misnamed properties silently get the shader's default. - **`base_color_texture` multiplies `base_color`.** A `color:` texture holding the same value as `base_color` multiplies the colour by its own sRGB-decoded value, so the surface renders far darker than the value written. Write a solid colour as `base_color` alone. - **Broken shaders break the material.** A material whose shader has a parse error renders as the magenta checkerboard. Inspect the shader first if the material looks wrong. - **`setProperty` targets the material, not the entity.** Changes affect every entity that references the material. - **`setProperty` is a runtime change.** It reaches the GPU, not the file: call `saveDefinition` to write the current values into `mat.yaml`. For a per-entity override that no other entity sees, give the entity its own copy with `Model:applySessionMaterial(renderer.material.create(...))`. - **Renaming the folder changes the identity.** A `Model` component or other reference to the material is recorded against the material itself, so renaming or moving it does not break them. ## Related types - `.shader` — defines what properties exist; required before a material can bind to it. - `.preset` — captures a component configuration; conceptually similar to a material, but for components instead of shaders.
▲ 0↑ born
·
toolbox · born here
❒asset
# assets Find content and trace how it's used — search the project's assets, see what an entity is made of, and find who uses a given asset. Search the whole asset index by substring and get every match grouped by category, without knowing where anything lives. Point at one entity, a list, or the whole scene and get back the unique set of asset references in use — materials, meshes, textures, bundles, and anything else a component field holds — each enriched with its identity, path, and the entities that use it. Or go the other way: name an asset and find every entity in the active layer that references it, down to the component and field.
▲ 0↑ born
·
guide · born here
❒asset
# asset-system guide Reference docs surfaced through `guides { path: "..." }` and `man asset-system`. The content lives in `guide.md`; this README is just the asset summary.
▲ 0↑ born
·
assettype · born here
❒asset
# assetType (asset type) An asset type says what a kind of asset is: the files an asset of the kind is made of, the code that gives it behaviour, the interface a person uses to work with it, and how it is found, pictured and checked. A `<typename>.assetType/` folder declares a type called `<typename>`, and every `<name>.<typename>/` folder is an asset of it. `assetType` is itself a type, so a type is an asset like any other: it is created, versioned, installed and contributed the same way. ## Making a type `asset.create("assetType", "<typename>")` scaffolds a type. The type registers the moment its folder is written, anywhere under `/zero/source/`, and from then on `<name>.<typename>/` folders are its assets. An asset of a type that is not registered yet is not an asset, so make the type first. A type folder is a root: `~.x` in any file inside it names the type's own `x`, wherever the type is installed. Keep the type's whole implementation inside its folder and reach it that way. What a type folder must hold is set by this type's own `type.yaml`, the same way every type's `type.yaml` sets what its assets hold. `asset.validate` on a type reports anything missing, and the scaffold produces a folder that passes. ## What sets your type's schema (`type.yaml`) `type.yaml` is the structural contract every asset of the type is checked against. The keys below are the ones the engine reads; a key not listed here is ignored. | Key | What it controls | |---|---| | `suffix` | The folder suffix of the type's assets. Defaults to `.<typename>`. | | `primary` | The file or files that are the asset's content: what the asset resolves to when it is used. | | `entry_files`, `primary_aliases` | Further files that load as the asset or share its identity. | | `is_container` | Whether the suffix stays in the identities of what the asset holds. | | `is_opaque_walker` | Whether a walk of the tree stops at the asset and registers only its primary. | | `plural_dir`, `classify_exts` | Loose files in a folder of this name, or with these extensions, are assets of the type. | | `is_text_default` | Whether the asset's files are text unless stated otherwise. | | `broadcasts_subassets` | Whether assets nested inside an asset of the type are found by their own short name. | | `root` | `true` makes every asset of the type a root, so `~.x` inside one names that asset's own `x`. | | `contract_ref: { field, category }` | A key of the primary file that names another asset the asset is built on. | | `files: { required, optional }` | The files an asset holds (below). | | `allow_unlisted` | `false` reports every file the type does not declare. | | `aliases_from: [{ file, key }]` | Further names an asset answers to, read from its own files. | | `settings:` | Typed per-asset settings (below). | | `inspector:` | The interface people use for the type and its assets (below). | | `preview:` | How an asset is pictured (below). | | `references:` | Names of other assets the type's files carry (below). | | `indexing:` | What search reads for each asset (below). | | `describe:` | How a missing description is generated (below). | ### The files an asset holds Each entry under `files.required` or `files.optional` is `{ path, kind: file | folder, one_of_group?, content? }`. `*` in a path matches within one segment. Entries that share a `one_of_group` are satisfied by any one of them. `content` lists checks on the file's bytes: | Check | Passes when | |---|---| | `non_empty` | the file has content | | `json_field { key, type?, non_empty?, accept? }` | the JSON holds the key (a dotted path) with that shape | | `yaml_field { key, type?, non_empty?, accept? }` | the same for YAML | | `described { keys?, headings?, message? }` | the file says what the asset is (see "Ask your assets to say what they are") | Each check takes a `message`, which is what the author is told when it fails. ### Settings `settings: { values, schema: { type | instance, export? } }` gives every asset typed settings. `values` names the file an asset's values live in; `schema.type` declares one schema for the whole type, and `schema.instance` names a file each asset declares its own in. `asset.create` writes the resolved values, and a write to the values file reaches `onChange` with what changed. ## The human side (`inspector.luau`) A type is used by people through its Inspector, and without one the type is usable only from code. The Inspector is the type's interface for a person: for a procedural graph it is the graph editor, for a kit it is the tracking view of what the kit holds and what is still missing. Writing it is part of writing the type. `inspector: { type, instance? }` in `type.yaml` names the files. `type` is the interface for the type itself and for its assets; `instance` names a file an asset may ship to add its own. Each returns: ```luau return { sections = { { id = "graph", title = "Graph", build = function(ctx) ... end } }, replace = { contents = { ... } }, -- replace a default section by id order = { "graph", "contents" }, -- section order } ``` `build(ctx)` receives the asset and builds the section. A section's `title`, and the text its rows carry, is a string or an `i18n.text` value read from a catalog (`i18n.text("inspectors.mesh.title")`), which follows the editor's language. `asset.create("assetType", ...)` scaffolds a working `inspector.luau` to start from. `asset.validate` on a type that names no inspector, or names one that does not load, reports it. ## Say what your type is for (`about.yaml`) Every type carries an `about.yaml` that says what the type is for, in three plain-language texts: ```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, in the words a game maker uses. Up to 800 bytes. - **`function`** is how it works today: what an asset of it is made of and what the engine does with it. Up to 1500 bytes. - **`usage`** is what a person or an agent does with one. Up to 1200 bytes. Write about the type as a whole, not one asset of it, and say what it does now. Agents read these texts to learn when to reach for the type and how; people searching find the type by them. The scaffold starts each text with `[[describe]]` and a note on what to write; a text that still starts that way counts as not written. `services describe` can write them. They show up in `asset.inspect` as `detail.about`, in `assetTypeRef:getAbout()`, at the end of the type's `types/<typename>` guide, and at the top of the type's Inspector. ## Ask your assets to say what they are A type can require each asset to carry a description: a file, written by the author or generated, saying what the asset is and what it is for. Declare it with a `described` check: ```yaml files: required: - path: "README.md" kind: "file" content: - { check: "described", message: "Say what this widget is for and where it goes." } ``` A file that is missing, empty or still has a line starting with `[[describe]]` fails the check, and so does a YAML file whose named `keys` are missing or over their `max_bytes`, or a Markdown file missing a named heading. `asset.validate` names the asset, and a push that carries it is refused with `message`. `services describe` finds such assets and writes their descriptions. `describe:` steers what is generated: ```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>" ``` An example is an asset of your type whose description is a true, written one, never a placeholder and never the asset being described; with none, leave `examples` empty. A type whose assets the model cannot perceive (a sound, a motion) says so in `guidance`, and the description is drawn only from what the asset's own files state. Start the file unwritten in your `template/`: a heading and one line starting with `[[describe]]`, never example content, which every new asset would carry verbatim. A type whose assets say what they are through their own content (a mesh, a texture) declares no check. ## The type's code (`behavior.luau`) `behavior.luau` returns one table. These are the keys the engine reads from it; anything else on the table is the type's own. ### What the type's assets can do | Key | Shape | What the engine does with it | |---|---|---| | `ref` | `{ [name] = function(self, ...) }` | Every function becomes a method on every asset of the type: `ref:name(...)`. | | `modules` | `{ [name] = require(".x") }` | Code the type's assets reach with `asset.containing(__FILE__).modules.<name>`. | | `events` | an events schema | Events an asset fires. Readers subscribe through `ref.events.<name>:connect(...)`; the type fires them. | | `refShapes` | `{ [method] = function(self) -> (typeExpr, source?) }` | The result type of a `ref` method for one particular asset (below). | | `namePattern` | a Lua pattern | The names `asset.create` accepts for the type's assets. Default `^[A-Za-z][A-Za-z0-9_]*$`. | | `derive` | `{ output, module, compute, fingerprint, matches?, required?, timeout? }` | A file computed from the asset when it is committed; a `required` one holds the commit until it lands. | Some `ref` names mean the same for every type, and the engine calls them: - **`instantiate(self, target?, opts?) -> (root, idMap)`**: puts the asset in a scene. Defining it is what makes the type scene-instantiable. Its return is a contract (below). - **`inspect(self) -> detail`**: what `asset.inspect` reports for the asset beyond the common record (below). - **`preview(self, opts)`**: renders the asset's picture; it wins over the type's `preview:` recipe. - **`previewMaterial(self) -> { material }`**: the material a `surface` preview paints. - **`saveDefinition(self)`**: writes the asset's in-memory edits back to its files in edit mode. Some names are answered for every asset before the type is asked, so a `ref` entry of the same name is never reached: `getSource`, `getBytes`, `getText`, `exists`, `deps`, `canInstantiate`, `canPreview`, `preview` (which calls yours when you define one), `meta`, `runtime`, `events`, `typeRef`, `modules`, and the residency flags `has_backing_asset`, `has_runtime_changes`, `cpu_resident`, `gpu_resident`. ### When the engine calls the type | Hook | Signature | When it runs | |---|---|---| | `onCreate` | `(name, opts) -> { [path] = contents }` | On `asset.create`. Returns the files of the new asset, laid over the type's `template/`. Annotate `opts`: its type is the schema `asset.create` checks the caller's arguments against. A `""` key writes a single loose file. | | `onReplace` | `(dest, files) -> files` | On an `asset.create` over an asset already at `dest`, after `onCreate`, so what the author added can be carried into the replacement. | | `onRegister` | `(self)` | Once per asset, when it first registers: when the world loads, on a live `asset.create`, and when an asset arrives later (installed, pulled, synced). This is what makes an asset take effect the moment it is written. | | `onChange` | `(ref, change)` | On every write to a file inside an asset of the type. `change` is `{ path, asset, type, kind, origin }`, plus `settings` and `settingsChanged` for a write to the settings values file; `kind` is `edited` or `seeded`, `origin` is `local` or `remote`. Filter on `change.path`. Writes to one asset arrive in order, each after the hook for the one before has finished, and the hook may wait on what it reads. A write the hook makes inside its own asset arrives later like any other, so keep the hook convergent. | | `onDependencyChange` | `(ref, change)` | When a module the asset's code requires is re-run, or a name the asset carries starts resolving. `change.kind` is `dependency`. | | `onDelete` | `(ref)` | When an asset of the type leaves the world: deleted, or the world it is in is swapped away. | | `validate` | `(ref) -> { { code, message, severity?, path? } }` | On `asset.validate`, after the `type.yaml` checks, for the type's own rules. `severity` defaults to `error`. Runs on every push, so a type's rules hold at publish. A hook that fails reports `validate.hook_failed`. | ### Settings hooks | Hook | When it runs | |---|---| | `settingsDefaults(name, opts, asked) -> table` | Once per `asset.create`, for values the schema's defaults do not give. | | `settingsCorrections(values, name, opts) -> (corrections?, reasons?)` | On every settings write. A correction to a field the writer named refuses the write, giving the reason. | | `settingsDerivedDefaults(values, explicit, changed)` | On a settings write, for fields derived from others. | | `normalizeSettings(values, stored?)` | On every read and write, to map an earlier spelling of a field to the current one. | | `settingsHealed(values, stored)` | On a read, to repair stored values. | | `settingsSchemaProblem(schema, ref) -> string?` | When an asset's own schema is read; a returned string refuses the schema. | `creationInputs` (`{ [opt] = true | selectorOpt }`) names `asset.create` options that are inputs to the type rather than settings. ### Reloading An edit to `behavior.luau` reaches existing assets with no restart. ## How an asset is pictured (`preview:`) | `kind` | The picture is | The type provides | |---|---|---| | `instantiate` | the asset placed in the preview studio through its `instantiate` | `ref.instantiate` | | `image` | a file the asset holds, fitted to the frame | `source: { files }`, the candidate files in order | | `surface` | the asset painted on a mesh | `mesh`, and `ref.previewMaterial` | | `custom` | whatever `ref.preview` returns | `ref.preview` | ```yaml preview: kind: surface mesh: sphere ``` `angle: [yaw, pitch]` turns the camera. The studio owns the light, the backdrop and the framing, so an asset pictures the same in every world. A type with no `preview:` that defines `instantiate` still answers `ref:preview()`. A picture is rendered when something asks to view it (the editor showing the asset, `ref:preview()`), when previews are generated on request (`assets.generatePreviews`), and when the asset is staged. An edit alone renders nothing. The picture records what it was made from, so a stale one is told from a current one. ## The names your type's assets hold (`references:`) Where your type's own format or API carries the name of another asset, declare it here: ```yaml references: keys: # a top-level key of the type's files that holds a name, or a table of them - { key: "shader", type: "shader" } args: # an argument of a call, counting from 1 - { call: "g:node", position: 2, type: "procNode" } fields: # a field of the options table a call takes - { call: "fx.beam", field: "material", type: "material" } ``` `type` is the category the name resolves in. `asset.declareReferenceKey`, `asset.declareReferenceArg` and `asset.declareReferenceField` declare the same from a script. ## What search reads (`indexing:`) `indexing:` is how assets are found. Search reaches an asset only through what its type's `indexing:` names, and adds nothing of its own. That includes you: an asset indexed badly is one you cannot find later, when you are making something else and need it. A type whose assets are never looked for says `indexing: none`. ```yaml indexing: data: # text, read from the asset's files - role: identity extractor: verbatim source: { file: "README.md" } - role: interface extractor: symbols source: { files: ["init.luau"] } - role: implementation extractor: verbatim source: { files: ["init.luau"] } content: # the picture - role: identity modality: image source: { file: "preview.png" } facets: # what a query filters and re-ranks by file: ".metadata" rigged: { type: bool, from: { components: { contains: SkinnedModel } } } roughness: { type: number, filter: range, from: { file: "mat.yaml", field: "floats.roughness" } } ``` **Roles.** Each `data` and `content` entry has a `role`, and a query matches in two slots: - **`identity`**: what an asset is. For anything you look at, the picture; for anything else, the text that says what it is. - **`capability`**: what it does and how it is made. `interface` (the signatures or shape a caller uses) and `implementation` (the body) both count as capability, and a type with code usually declares both. Ask what a person would recognise an asset of the type by, and what it does, and point each role at the file that says it. For text, that is the asset's description: the file your template starts with a `[[describe]]` block, which the author writes or `services describe` fills in (see "Ask your assets to say what they are"). **`data`.** `extractor: verbatim` embeds a file's text as it is; `symbols` embeds the declared signatures of code. `source` is `{ file }` or `{ files: [...] }`. Never index a file name (`source: { field: name }`): one or two words carry nothing to match. **`content`.** `modality: image` embeds the picture itself, so a text query matches what the asset looks like. A type whose assets are looked at makes the picture its identity. **`facets`.** What a query narrows and re-ranks the matches by. `file` is embedded whole, so keys nobody declared in it are still found. Each further key is computed for every asset and states a value for every one, including `false`: a file can imply "not rigged" but never say it, and a query separates rigged from unrigged only because the unrigged ones say `rigged: false`. A key is `{ type: bool | number | enum, filter?: range, from }`, where `from` is `{ file, field }` (a field of one of the asset's files, dotted for nesting), `{ metadata: key }`, or `{ components: { contains | any_outside } }` (what the asset's entities carry). ## What blocks a push A push checks every asset it carries against its type. These refuse it: - a required file that is missing, a `content` check that fails, a `described` file not written yet; - an error from the type's `validate`. Unlisted files and warnings do not. A `described` file blocks only a push that changes the asset. When a push is refused, the message names each asset and what it lacks. `world.publishBlockers()` and `zm status` read the same list without pushing. ## Typing a result from the asset (`refShapes`) A `ref` method whose result depends on the asset (one entry per child folder, per row of a config, per control a map declares) can only be annotated as `{ [string]: Thing }`, which accepts any key. `refShapes` states the result for each asset instead: a function returning a Luau type expression for that asset, and the module whose types the expression uses. Read the asset; never run it. ```lua -- inputMap.assetType/behavior.luau 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)` is checked and `map.noexisting` is reported. A per-asset type applies where the call site names which asset: a component field whose default names it, or an annotation `AssetRef<"inputMap", "<identity>">`. `asset.resolve(name, category)` carries only the category, and a name computed at runtime carries nothing, so calls on those keep the declared return. The type belongs to the call's result: index it there, or bind what you need from it there, and the names stay checked. `require("@builtin::assetTypes.assetType.shared.refShapes").published()` shows what was published: `categories` (each type's method surface) and `returns` (per-asset results by identity, then method). An identity missing from `returns` was never published. Results are recomputed when an asset is written; at most 64 assets of one type are read. ## Putting an asset in a scene (`ref.instantiate`) A caller writes one piece of code against every instantiable type, so what `instantiate` returns is fixed: ```lua local Instantiable = require("@builtin::assetTypes.assetType.shared.instantiable") M.ref = { instantiate = function(self, target, opts) local root = Instantiable.root(self, target, opts) -- compose the asset under `root`, now return Instantiable.result(self, root, idMap) end, } ``` **In.** `target` is the entity the asset lands under, or onto for a hierarchy type; with none, the type spawns a new root. `position`, `rotation` (three numbers in degrees, or a quaternion), `scale`, `name` and `temporary` mean the same for every type: `Instantiable.root` applies them to a root you make, `Instantiable.place` to one you adopt. Document any options of your own in the hook's `--!arg` docs. **Out.** `(root, idMap)`. `root` is the composed root, live when you return: build synchronously, never defer to a component's `awake`. `idMap` maps each original id to the spawned one, so a component that composes the asset again on every load can pass it back in and keep references into the composition. Every call goes through `Instantiable.result`, which refuses a root that is not live and turns a missing `idMap` into `{}`. ## Inspecting an asset (`ref.inspect`) `asset.inspect(ref)` builds a record every asset shares (`identity`, `name`, `guid`, `source`, `typeName`, `typeDefinitionPath`, `scope`, `origin`, `description`, `tags`), then calls `ref.inspect(self)` for the type's own `detail`: ```lua M.ref = { inspect = function(self) -- read the asset's own files, never runtime state: inspect works on an asset -- that has not been compiled, uploaded or registered return { --[[ what makes this asset what it is ]] } end, } ``` Without it, the record is the shared part alone, and an agent inspecting the asset learns nothing of what it is. A hook that raises leaves `detail` empty and sets `record.warning`. ## Discovery - `asset.list("assetType")`: every registered type. - `asset.inspect("<typename>")`: the type, its README and its `about.yaml` texts. - `asset.resolve("<typename>", "assetType")`: the type itself.
▲ 0↑ born
·
assettype · born here
❒asset
# Component (asset type) A component is a reusable behavior + data unit attached to an entity. The component file declares public fields, methods, and lifecycle callbacks; the engine instantiates one per attachment and drives its hooks every frame. Components are the primary unit of gameplay code — movement, AI, animations, UI panels, network sync, and per-entity state all live here. ## When to use one - You need per-entity state that survives across frames. - You need per-entity behavior driven by engine lifecycle hooks (`start`, `update`, `onDestroy`). - You want to expose configurable fields to designers / `entity.spawn` callers / scenes without code changes. If you need a stateless pure-function library, use a `.module`. If you need to generate content declaratively, use a `.service`; compute work belongs in a `.computeShader`. ## Where it lives - Source: `/zero/source/.../<Name>.component/` - Identity: `<Name>` (the `.component` suffix strips from the identity, but the folder retains the suffix on disk). - Folder shape: - `init.luau` (or `init.lua`) — declares the component. **Required.** - `README.md` — instance-level documentation. **Required.** - `.metadata` — agent-editable tags + free-form fields. **Required.** The structural contract is enforced by `asset.validate` against this type's `type.yaml`. ## How to create one Always start with the scaffold — it guarantees the folder shape matches `type.yaml` from the moment it's on disk: ```luau asset.create("component", "<Name>") -- Creates: /zero/source/<Name>.component/ -- init.luau (canonical declare + public block + hooks stubbed) -- README.md (instance README template) ``` After scaffolding, edit `init.luau` to fill in the actual fields and behaviour. The component registers as soon as the file is written — hot-reload is automatic; no engine restart required. ## How it operates 1. **Registration.** Writing `init.luau` into a `.component/` folder indexes the asset, mints a guid (in the `.meta` sidecar), and surfaces the type to the engine's component registry. Same-frame visible to `entity.component.add`. 2. **Attachment.** `entity(id).component.add(name, data)` (or the batch form `tools.use("entityOps", "addComponents", targets, { [name] = data })`) attaches an instance. The `data` table's fields override the component's declared defaults; omitted fields take the declared default. The `data` values are copied into `public.*` BEFORE `awake()` runs. 3. **Lifecycle.** The hooks run in this order: - `awake()` — same frame as attach, immediately after the data copy. Initialise state, set up native bridges, register with other systems. Other components on the same entity may not have awoken yet. - `start()` — the next frame. Every component on the entity has completed `awake()` by then, so cross-component setup is safe here. - `update(dt)` — same frame as `start()`, then every frame after, for as long as the gameplay clock is running. `fixedUpdate(dt)` runs once per physics step, straight after it, with the step's `dt`, so a frame can run it several times or not at all; `editorUpdate(dt)` runs while the clock is held. Which of them ticks is decided by `engine.mode` AND `engine.paused` together — see **Per-frame hooks and the two axes** below, and `man modes`. - `onDestroy()` — when the component is removed via `component.remove` or when the owning entity is despawned. Release resources, drop native ECS components. For the full list (`onEnable`, `onDisable`, `fixedUpdate`, `editorUpdate`, `onPropertyChanged`, `onAssetReload`, `onModuleReload`, `onBeforeSave`, …) and the timing details, **read `man components` — that is the canonical lifecycle reference. Generic component patterns (declare blocks, public fields, sync, ECS bindings, typed methods) all live there.** 4. **Public surface.** Fields declared in `public` are inspectable, editable in the inspector, and persist with the entity. Methods are declared on that same table with a colon — `typed function public:method(args)` — and every one of them takes the component ref as `self`, so a caller invokes it with a colon too: `entity(id).component.get(name):method(args)`. That is the one calling convention a component ref answers to: the reflection methods `ref:fields()` / `ref:getField(name)` / `ref:setField(name, value)` take the same spelling. A function the chunk declares at file scope (`function helper()`) is the component's own, reachable from its hooks under its bare name; `public` is what the ref carries. 5. **Settings and the Inspector card.** `type.yaml` names the component's settings schema as `settings: { schema: { instance: "init.luau", export: "public" } }`: the `public` table IS the schema, and its values live on each entity the component is on, so `asset.schema(componentRef)` answers the Field descriptors and no values file exists. On an entity the Inspector shows the component as a card whose generic `settings` section edits those fields as typed controls, each edit checked against the schema and written to the live component as one undoable operation. `type.yaml` also names `inspector: { instance: "inspector.luau" }`: a component that ships an `inspector.luau` beside its `init.luau` shapes its card — the file returns `{ sections, replace, order }` (see `assetTypes/README.md`, "`inspector:`"), each section's `build(ctx)` reading `ctx.component` (the live component) and `ctx.selection.id` (the entity). `SceneModule.component/inspector.luau` replaces `settings` with the bound module's declared inputs. 6. **Events.** An `events` block declares typed moments the component fires from inside — `events = { onHit = Event({ dmg = Field.number(0, NoSync) }) }`. The owner fires with `events.onHit:fire({ dmg = 10 })`; outside code subscribes through the proxy's subscribe-only facade, `entity(id).component.get(name).events.onHit:connect(fn)` (`:once` / `:wait` too). Pass `Sync` as the event's second argument to replicate a fire to other peers. Events let one component react to another's moments without polling. Full reference: **`man components`**, Events section. 7. **Hot reload.** Editing `init.luau` reloads the component definition; live instances are reconciled with the new declaration in-place, and each crosses one lifecycle boundary: `onDestroy()` ends the running life, then `awake()` and `start()` construct the one that replaces it, and `update()` resumes on the next frame. `public` and `private` values carry across; module-level `local`s belong to the chunk, so the new life starts with them at their initial values and its `awake`/`start` are what fill them in. Whatever the old life handed elsewhere — a subscription, a shared registration, a spawned entity — is released in `onDestroy`, the call that pairs with the construction about to run. An edit that leaves the source byte-identical crosses no boundary at all. A field carries its value across when something stated it: a key of the `data` table the instance was attached with (a scene record's data is that table), or a write made since. Every other field takes the edited source's declared default, so changing a default reaches every instance that left the field to it. An instance that states its own value for a field whose default the edit changed keeps that value, and the reload logs a `[HotReload]` warning naming the field, the entity, the value kept and both defaults. ## Discovery - `asset.list("component")` — every registered component (engine + world + libraries). - `asset.inspect("<name>")` — identity, scope, public fields, lifecycle methods, source path, and this type README. `detail` carries the component's declared `fields`, `methods`, and `events`, parsed from its source — `tools.use("assets","describe", "<name>")` renders it as markdown, so a component's events are visible without opening its source. - `ls /zero/source/<Name>.component` lists the files the folder holds. - `entity(id).component.list()` — components attached to an entity. ## Per-frame hooks and the two axes `engine.mode` (`"edit"` / `"play"`) and `engine.paused` (the gameplay clock) are independent, and the per-frame hooks read them separately: - `update(dt)` / `fixedUpdate(dt)` tick while **the gameplay clock is running** — whichever mode the engine is in. - `editorUpdate(dt)` ticks while **the clock is held** — because the mode is `edit`, or because play is paused — and also alongside `update` whenever edit is running. Which gives four states, and this table is the contract: | `engine.mode` | `engine.paused` | hooks that tick | |---|---|---| | `edit` | `true` | `editorUpdate` | | `edit` | `false` | `update`, `fixedUpdate`, `editorUpdate` | | `play` | `false` | `update`, `fixedUpdate` | | `play` | `true` | `editorUpdate` | Entering edit sets `paused = true` and entering play sets it back to `false`, so a session that never writes `engine.paused` only ever sees rows 1 and 3 — which is where the shorthand "`update` is the play tick, `editorUpdate` is the edit tick" comes from. It is the two default rows of a four-row table, not the rule. **Row 2 is the one that surprises.** With `engine.paused = false` in edit — set by hand, or by a tool that advances the gameplay clock to sample it, such as a `capture.collage` given a `duration` — `update` and `editorUpdate` both fire, once each, every frame. A component that declares both and routes them to one body therefore runs that body **twice per frame**: ```luau local function drive(dt) ... end function update(dt) drive(dt) end function editorUpdate(dt) drive(dt) end -- twice per frame in row 2 ``` Nothing reports the doubling; it reads as an animation running at twice its intended speed. To get exactly one tick per frame in every row, ask which axis you meant and answer it in the body: ```luau function update(dt) drive(dt) end function editorUpdate(dt) if engine.paused then drive(dt) end end ``` A scene `entrypoint.luau` declaring the same two hook names is dispatched by the scene loader, not by this table: there `update` needs play AND running, `editorUpdate` needs edit, and play-paused ticks neither. Read `man scenes` before carrying a component's rule to an entrypoint. ## Authoring conventions - Annotate public fields with `--!desc` so the inspector + LSP show documentation alongside the field name. - Declare lifecycle hooks (`start`, `update`, `onDestroy`) explicitly rather than relying on engine defaults — readers can see at a glance what runs when. - Keep `update(dt)` cheap — it runs every frame per instance. Move heavy work to `start`, to a `.computeShader`, or behind a job (`man jobs`). - For multi-entity batching, prefer `tools.use("entityOps", "addComponents", targets, components)` over a loop of `entity(id).component.add` — it takes many targets and many components in one call. ## Common pitfalls - **Naming.** The component identity is the folder stem with `.component` stripped (e.g. `<Spinner>.component/` → identity `Spinner`). Don't repeat the suffix in the identity passed to `component.add`. - **`init.lua` vs `init.luau`.** Either is accepted (it's a `one_of_group: "entrypoint"`), but `.luau` is canonical. Mixing both in one folder is invalid. - **`update` in edit mode.** It runs whenever the gameplay clock is running, and entering edit stops that clock — so `update` is idle in a freshly-entered edit session and ticks again the moment anything sets `engine.paused = false`. Use `tools.use("wld", "play")` to test update logic under the mode it ships in; `tools.use("wld", "edit")` to return. See **Per-frame hooks and the two axes** above. - **Renaming.** Renaming the folder changes the identity. References to the component are recorded against the component itself, so renaming or moving it does not break them. ## Related types - `.module` — pure-function library, no per-entity state. - `.service`: a declarative content generator, no per-entity state. - `.preset` — a captured component configuration, replayable into `component.add` without re-typing fields. - `.bundle` — entity hierarchies that may carry component attachments as part of the bundle. ## In the Inspector Fields: its execution order, hooks, and how many methods and events it declares, then the fields its `public` table declares as read-only typed controls holding their defaults, with **Check** and **Open** for its entry file. Instances: a link to every entity carrying it. What its card shows on an entity comes from the component's own `inspector.luau`.
▲ 0↑ born
·
tool · born here
❒asset
# assets.describe Agent-facing inspect surface. Resolves `assetName` the same way a search hit's name resolves, calls `asset.inspect` for the structured record, and renders it to markdown via `modules/asset_inspect_render`. Pairs with `assets.search` (search -> describe). Named `describe` — never `inspect` — so it is never confused with the `asset.inspect` code API (one letter apart is a trap).
▲ 0↑ born
·
toolbox · born here
❒asset
# capture Render a screenshot of the scene from anywhere — an explicit position, a named camera station around an entity, a specific camera, or a mirror of the active viewport — and a grid of them in one image; or read back the frame the engine actually presented, exactly as the player saw it. The picture of the machine's own camera (a webcam, a phone camera) is `cameraInput`, covered by the `topics/camera-input` guide; this toolbox photographs the scene. Every source but `presented` and `window` (see [The presented frame](#the-presented-frame)) spawns an ephemeral offscreen camera, triggers one render, and returns the picture as an image value — returned from a tool, a probe or `execute`, it reaches you as a picture, with what the capture has to say about it (its size, the frame it is of, the cells of a grid) beside it. A PNG is written as well when you pass a `path`. The convenience wrappers render from a world-space position (with optional look-at), frame a target entity, mirror the active viewport camera including its layers, or render a specific camera by name from its exact authored pose and settings (`fromCamera`, with optional per-capture overrides that never touch the camera); each waits for the render and the GPU readback, cleans up its camera, and returns only once the picture is read back. The low-level oneshot spawns the camera, triggers one render, and returns the handles at once without waiting, leaving the readback and cleanup to the caller; the `capture` MCP tool is built on it. A cleanup tool clears the capture output directory. ## Where a shot lands A capture given a `path` writes the PNG there and says where those bytes stand, beside the path it wrote: `durable` is true when the PNG is on the path it names and the next session reads it back, and false when the play shadow took it (live in this session and discarded on a guarded play-exit unless kept, with `survivesRestart` saying whether a restart keeps it and `playShadow` naming the routes that keep it) or when the path is in `/source/tmp/` scratch (gone when the engine stops or restarts, with a `warning` opening `SCRATCH`). Every capture carries it on the picture it returns as `durability`. Shooting gameplay means shooting in play, which is exactly when a `/source` destination is the shadow's, so this is the field that says whether a shot survives leaving play — `vfs.promotePlayShadow(path)` keeps it with play still running. ## The presented frame `source = "presented"` reads back the frame the engine presented to the player instead of rendering one. Nothing is drawn for it: it is the frame the viewport drew, copied out as it was finished, after the post chain, the upscale, the display transform, the content UI and every post-scene pass, at the size it was composed at. So it is the one capture whose temporal antialiasing, motion blur, camera motion, auto exposure and render scale are the real ones, and the source to verify any of them with. `source = "window"` is the whole presented surface (the window, the browser canvas or the headless framebuffer) with everything painted on the window: the editor's chrome and every window-level UI pass. Where no Scene panel shows the viewport, the two are the same image: the scene fills the window and the editor's chrome is painted over it, and each such frame says so in its `note` (a cell of a `presented` capture, a frame `renderer.presentedFrames` returns) for as long as that chrome is on the window. Show the Scene panel (the editor toolbox's `layout` tool) to read the game view alone. `frames = n` (1-32) takes n presented frames, one every `interval` frames (default 1: consecutive), at real gameplay speed, and lays them out as a grid in the order they were presented. Each cell states what its frame was drawn with: `frame` (the renderer's count, so consecutive frames carry consecutive numbers), `rasterWidth` / `rasterHeight` (the size the scene rasterized at), `compositeWidth` / `compositeHeight`, `renderScale` and `scaleMode`, `jitter` (the projection's sub-pixel offset in raster pixels), `exposure` (what the display transform multiplied the scene by) and `historyReset` (the viewport camera's first frame, a cut, or a hand-over to another camera). A single frame comes back at the size it was presented at; `width` / `height` resample it, or size the sheet a grid is laid into. A presented frame is drawn through the camera, layers, lens and chain that drew the screen, so the options that aim, light or shape a rendered frame (`camera`, `entity`, `position`, `viewpoint`, `fov`, `pass`, `renderLayers`, `postProcessing`, `environment`, `mode = "collage"` and the collage axes) are refused for it; aim the camera holding the viewport instead, or take an offscreen capture. A source that renders refuses `frames` and `interval` the same way, since they count presented frames. `deterministic` and `clearAir` hold the clock and the air for the whole engine, so they reach the presented frame too. A diagnostic buffer of the viewport itself, under its real camera motion and history, is read the same way: put the camera holding the viewport on that pass (the camera toolbox's `set` with `debugPass = "motion_vectors"`) and take `source = "presented"`. The viewport's velocity buffer is the frame's travel against the frame before it, so a turning camera reads in it, where an offscreen `pass = "motion_vectors"` holds the objects' own motion alone. The `motion_vectors` picture is a visualization: each channel is the square root of 100 times the stored displacement's size on its axis (R horizontal, G vertical, B the length), the sign is dropped, and R reads 255 from `width / 200` px of horizontal travel, G from `height / 200` px of vertical travel (raster pixels). The rendering guide's part on motion (`topics/rendering-motion`), "What the `motion_vectors` picture encodes", states it in full. The same frames, with their pixels as `TextureCpuHandle`s, come from `renderer.presentedFrames({ scope = "viewport" | "window", frames, interval })`, and `velocity = true` adds the viewport's velocity buffer from each frame as numbers (`frame.velocity`, a `VelocityField`): each pixel's travel in pixels per frame, x right and y down, signed and unclamped, read with `field:at(x, y)`, `field:row(y)` and `field:region(rect)`. ## The feed Every capture publishes the frame it made to `@builtin::tools.capture.feed`, so a viewer can show the newest one without taking a shot of its own. `Feed.latest()` is that frame: a `src` an image widget draws (the render target's guid while the engine still holds it, the written path when the capture produced a file), with the size, the source and the pass beside it. `Feed.watch(fn)` calls `fn` as each new frame arrives. The editor's Capture panel is built on it, and the `watch` layout puts that panel under the scene. A viewer calls `Feed.shown()` to say it is drawing frames from the feed. From then on the feed holds the newest capture's render target so the picture stays up after the capture that made it is finished with it, and lets it go when a later frame replaces it; `Feed.shown(false)` stops. With nobody drawing, a capture's target is released the moment its capture is done, and nothing is held. ## Viewpoints A **viewpoint** names where the camera stands and which way is up in the image: `front`, `back`, `left`, `right`, `top`, `bottom`, `iso`. It replaces reverse-engineering a `{yaw, pitch}` pair, and names the seven stations that answer "is this built correctly". `basis` says which axes the name is measured against, and it is **required** alongside a viewpoint when a subject is being framed: - **`"local"`** — the subject's own axes. `front` is the side it faces however it is turned, and the frame is fitted to its own extents rather than to the world-axis box around them. - **`"world"`** — the world axes. `front` is whatever faces world +Z. For a crate turned 40 degrees those are different pictures, so the call says which one it wants. Framing a world position rather than an entity, both values mean the world axes and either is accepted. ## Passes `pass` names **what** the frame holds, by name. `"final"` is the lit picture; a built-in diagnostic pass (`albedo`, `normal`, `depth`, `motion_vectors`, …) replaces the shaded image with the buffer behind it; and any capture **view** a render feature has published (`renderer.captureView.list()` enumerates them) is selectable by its own name. A name that is neither is refused against the list of what is registered. The name carries two decisions: which channel the camera renders, and which render layers that channel is legible under. A diagnostic buffer is read flat, so the sky behind it and the post-process chain over it come off; a view that draws its own geometry declares the layers it needs and is rendered under them. Stating `renderLayers` answers that second question yourself, for the one call. `passes` spreads the same vocabulary across the cells of a collage, and each cell reports the layers it was read under. ## Projection `projection = "orthographic"` covers a fixed world-space height at every distance. Parallel edges stay parallel and two equal-size objects at different depths cover equal pixels, which is what makes it the projection to check a shape or compare sizes under — a perspective frame's convergence hides both. `orthoHeight` sets that height; omit it and it is fitted to what is being framed. Any camera can be orthographic, not only a capture: it is a field on the `Camera` component. A capture through a camera the scene holds (`fromCamera`, `viewport`, and `source = "camera"`, `"screen"` or the scene camera) takes that camera's `projection` and `orthoHeight` with the rest of its lens, and its lighting `environment` with an `isolated` camera's background and ambient; a `projection` or `orthoHeight` stated for the call stands over the camera's. ## Which entity a capture framed A capture names an entity, or a camera to render from, by its id or by its name. A name exactly one entity carries means that entity. A name several entities carry means none of them on its own, so the capture is refused, and the refusal says how many entities carry it and names each by name and id; pass the id of the one you mean. A player is drawn as its body, so a capture of a player identity (an entity carrying `UserIdentity`, named `user`) frames the body its `avatar` names. A player prototype is absent from the live scene while it plays, so its name reaches no entity there: a capture of that name is refused, and the refusal names each player spawned from the prototype, by its identity and its body, with their ids. Every cell of a capture aimed at an entity reports what it framed under `subjects`: one `{ id, name }` per subject, carrying `playerIdentity = { id, name }` when the picture is the body of the identity the capture named. ## Isolate Framing a subject fits the **frame** to it; it does not decide what is drawn in that frame. Everything else standing there is still rendered — and a scene's default player spawn sits at the world **origin**, which is exactly where a prop usually gets built, so a tight shot of a new prop at the origin can come back showing the player instead. `isolate = true` draws the subject without the other geometry. It changes exactly that — lighting, sky and post-process are untouched, so a final frame stays a final frame, and a flat read of the geometry is a `pass`. Excluded geometry still casts shadows onto the subject and still bounces light into it, the same as any render-layer exclusion. In a `setups` collage each cell shows the entity its own set-up frames alone, and a set-up can state `isolate` for its own cell; a set-up aimed from a position or a camera frames no entity and is refused when it is to be isolated. ## The tone the frame carries Every capture reports `luma` on its cell record: the tone of the pixels it just encoded, in code values on the 0-255 scale the image is delivered at. ``` luma = { pixels, min, max, mean, p1, p5, p50, p95, p99, span, spread, crushed, clipped } ``` `span` is `max - min` — the whole range, a single stray pixel included. `spread` is `p95 - p5` — the range the body of the picture occupies, which is the reading that answers whether a shot is legible. `crushed` and `clipped` are the shares of the picture sitting at code 0 and at code 255. A frame's mean cannot answer that question: half a picture at 40 and half at 200 carries the same mean as a flat field at 120, and only one of them has anything in it. A subject can be modelled, shaded, lit and drawn and still arrive inside a handful of code values, in which case the image looks like a wash and every other field here reports success. `spread` is the number that says so. The same reading is available anywhere pixels are: `cpu:tone()` on a `TextureCpuHandle` — from `renderer.texture.readback(rtHandle)` on a capture handle, from a render-to-texture camera's target, from a CPU canvas — and `cpu:histogram()` under it, the per-code-value counts for luminance and for each of the three channels, which every reading above is a sum over. `cpu:histogram(x, y, w, h)` counts one rectangle of the picture alone, so a reading per region of the frame — how much of each cell is dark, lit or a given colour — is one call per region. `cpu:flip(reference)` holds the picture against another of its size by NVIDIA FLIP, the comparison a golden is judged by; `@builtin::modules.postMetrics` carries it with the golden runner, the pixel metrics (banding, temporal variance, halo overshoot, ring energy, hue drift) and the scopes, each reading a presented frame or a capture result as it comes. ## The grade an offscreen frame carries A capture spawns its own camera and renders offscreen, and the post-process chain runs over that frame the way it runs over the viewport's — for a station, for an orbit around an entity, for a named camera, for a render-to-texture camera alike. Inside an effect, `engine.view_proj` / `engine.prev_view_proj` / `engine.inv_view_proj` are the camera THAT render was drawn from and `engine.resolution` is that target's own size, so a grade that reconstructs world space from scene depth reconstructs against the station and lens the capture asked for. That makes an offscreen capture an oracle for an authored look: it photographs a chosen station and lens without taking the on-screen camera away from whoever else is driving the scene, and moving that camera somewhere else does not move the frame this one returns. An offscreen render keeps no view history of its own, so `engine.prev_view_proj` there holds that same matrix rather than the frame before it, and a pass taking camera motion from the two reads none. On the viewport the pair is a frame apart, so a camera's motion (and the blur and temporal reconstruction built on it) is read from the presented frame: `source = "presented"`. A render feature's passes reach an offscreen render the same way, against the same camera — the `renderFeature` assetType README carries that half, including `@frame.camera`, its own per-target camera input. `render(ctx)` itself runs once a frame for the main viewport, so `ctx.viewport` is the window's size rather than the captured target's; a feature sizes its scratch with `screen = true` and lets the engine track the drawn target. `postProcessing = false` takes the chain off the frame a capture returns, and with it the film a render feature draws — the flare and grain, a short focus's bokeh, the shutter's smear. It is a `Camera` property rather than a render layer, so `renderLayers` does not reach it. A capture naming a diagnostic `pass` drops the chain by default, so the buffer comes back as the value it encodes. ## Collage `collage` renders a grid of frames into one image. What varies across its cells is what you pass — `setups` for a cell per whole camera set-up, `viewpoints` for a cell per view of one subject (or `"sides"` for all six), `passes` for a cell per render pass, `duration` for a cell per sample over time. A collage with no axis is refused rather than quietly sampling time. `setups` is the shot list: each entry is written the way a single capture is aimed (`source`, `camera`, `entity`/`entities`, `position`, `lookAt`, `rotation`, `distance`, `angle`, `margin`, `viewpoint`, `basis`, `projection`, `orthoHeight`, `fov`, `near`, `far`), plus `label` to name the shot on the result, and a field an entry leaves out comes from the collage's own options. `setups` and `viewpoints` are one axis — both say where the camera stands — so a request names one of them; either crosses with `passes` into a 2D grid, a row per station and a column per pass. The shot most of this exists for is one call: six sides of one object, each an orthographic elevation, nothing else in frame. Authoring the cameras themselves — creating, listing, aiming, framing, and setting their parameters — is the `camera` toolbox.
▲ 0↑ born
·
tool · born here
❒asset
# services.attach Reads a generation's gateway job by the `jobId` a run recorded, and returns its state and, once succeeded, its result — the provider's response, the URL or bytes the run would have downloaded. A submitted job is kept on the gateway with its result for hours after it finishes, longer than the engine that started it is guaranteed to last, so this reaches work that was already paid for once the run that started it is gone. A job whose provider refused the request (a 429 while it rate limits) reads `failed`, with the provider's HTTP status in `upstreamStatus`, its reason in `error`, and its answer in `result`. Costs nothing; the charge happened at submit.
▲ 0↑ born
·
tool · born here
❒asset
# services.balance Reads your current credit balance — the pool every generation spends from. Check it against an operation's cost before starting a generation. Returns the credit count when signed in, or a reason when not.
▲ 0↑ born
·
tool · born here
❒asset
▲ 0↑ born
·
tool · born here
❒asset
# services.describe Describe your assets: finds the descriptions (READMEs) they are missing and writes them, many at a call. A description is the text an asset's type asks it to carry: for the built-in types that ask, its README, or the `intent`, `function` and `usage` texts of a type's `about.yaml`. Each call costs 1 credit and covers many assets. What it writes is an ordinary file in the asset, there for you to edit. ```luau tools.use("services", "describe", { mode = "find" }) -- what is missing, how many calls, what they cost tools.use("services", "describe", { mode = "all" }) -- fill what that listing names tools.use("services", "describe", { assets = { "Spinner", "Door" } }) ``` ## Modes - `find` (the default) lists the assets that lack the description their type asks for, the file and keys each needs, the number of calls filling them makes and the total price. It writes nothing. - `all` fills every asset `find` lists. - `assets` fills the assets you name. By default the assets are the ones that block the current push. `scope = "world"` takes every asset your world holds that a type asks to describe. ## What it does The assets are packed whole into as few calls as fit, in the order of their names, and the calls run several at once. Each asset comes back `written`, `failed` (with the reason) or `skipped` (with the reason). An answer that is missing or unusable for one asset fails that asset and no other. The text is written into the description file or keys the type declares. Text you wrote is never replaced: it fills only what is missing or still starts with `[[describe]]`. Library and installed assets, `/tmp` and `/local` are never touched, and a sub-asset is left to its enclosing asset unless that asset's type makes its sub-assets discoverable. Add `instructions` to say more to the model, and `examples` to name assets whose descriptions show the shape you want. The model also reads the type's own guidance and example descriptions, and each asset's files, whole. ## Price A call costs 1 credit, whatever it carries, and a call covers many assets. `find` reports the number of calls the fill would make, which is also the total in credits, before anything is spent. The credits for the whole fill are checked before the first call, and a fill you cannot cover starts nothing. An asset whose files alone are more than one call carries is answered `failed`, since the files are never cut down.
▲ 0↑ born
·
tool · born here
❒asset
# services.generate Starts a generation on a service: the step that turns a prompt into a real asset (a 3D mesh, a texture, a sound) by spending credits. You name a service and pass that operation's inputs, including the prompt. It returns immediately with a handle because a generation takes minutes and runs in the background; when the run ends it posts a notice, `generation finished` with the handle's id and the asset to spawn, or `generation failed` with the id and the error, which reaches you on your next tool call. `services.status` reads the run's progress in the meantime.
▲ 0↑ born
·
tool · born here
❒asset
# services.jobs Lists the generation jobs this session has started, active and finished, newest first: the live view of what is running and where the finished ones landed. The answer is bounded. It carries at most `limit` rows (20 by default; `limit = 0` answers the counts alone) and says how many jobs matched (`total`), whether rows were left out (`truncated`) and how to read them (`more`). `status` keeps the jobs in a status (`"active"` for the ones still running, `"finished"` for completed and failed, or an exact status) and `service` keeps one service's jobs (a name that is no service and ran no job is refused, naming the services); `byStatus` and `byService` count the matching jobs. Each row carries the job's id, service, operation, status, progress, resulting asset path and any error, with `promptPreview` (the prompt's first 60 characters) in place of the full prompt. `prompts = true` carries the full prompt on every row, and the status tool reads one job with its full prompt. For the durable record that survives restarts, use the outputs listing instead.
▲ 0↑ born
·
tool · born here
❒asset
# services.list Lists the generation services you can use — the things you can generate (3D meshes, textures, audio) by spending credits — and is the entry point to the whole flow. Each service is self-describing: its operations, and for each operation the inputs it takes, the kind of asset it produces, and its credit cost. Call this first, then start a generation with what you learn here.
▲ 0↑ born
·
tool · born here
❒asset
# services.outputs Lists the assets generated by services in this world — the durable record that survives restarts, read from the provenance stamped on each produced asset (the in-flight job list does not persist). Each row carries the spawnable asset path alongside the service, operation, prompt, and when it was generated. Use it to find what you've generated across sessions.
▲ 0↑ born
▣
module · born here
❒asset
# services.shared The implementations behind the `services.*` tools. They wrap the `service` asset type (`asset.resolve(name, "service")`) so the tools stay thin and an agent never has to resolve or invoke a service directly. - **discover** — `list` enumerates available services with fully-described operations; `balance` reads credits. - **start** — `generate` resolves the service and invokes it, returning a handle. - **track** — `status` / `jobs` read the in-flight run records a generation mirrors into `/zero/source/tmp/services/<id>.json`; `outputs` reads the durable provenance stamped on each produced asset (the run records aren't synced and vanish on restart — the asset stamp is what survives).
▲ 0↑ born
·
tool · born here
❒asset
# services.status Reports one generation job's current status by the handle id returned when it was started. The generation runs in the background regardless, and posts a `generation finished` or `generation failed` notice when it ends; this reads the same outcome on demand, and each row carries progress and, once completed, the spawnable asset path. A call the provider refused for the moment (a 429 while it rate limits) is sent again after a wait; while it waits, `status` names the refusal and the attempt coming next (`generate: retrying after upstream 429: ... (attempt 2 of 6)`), and `jobId` names the gateway job of the latest attempt. A run whose attempts all end refused reads `failed`, with the provider's status and reason in `error`. Returns nil for an unknown id.
▲ 0↑ born
·
assettype · born here
❒asset
# toolbox (asset type) A toolbox is an asset grouping set of `<tool>.tool` assets around one area of work. Each tool in it becomes callable under the toolbox `<toolbox>.<tool>`, and is a shell command, `zero <toolbox> <tool> <args>`; `guides core/tools`, "A tool's command line", covers its flags, paths, `render.luau` and exit status. ## When to group tools together When creating tools for the engine, a common theme should be identified and and a toolbox created to house them, this allows each individual operation to be easily accessible instead of being scattered in a million places. ## Sharing functionality between tools A toolbox features ability to define functions that code inside each individual tool inside it can access, this is usefull when establishing helpers etc so you dont have to duplicate code for each new tool you write. A `shared.module` inside the toolbox root is where this code lives, each tool inside a toolbox can access its functions by requiring it with `require(".shared")`. ```luau -- <toolbox>.toolbox/shared.module/init.luau local M = {} -- One step a tool reports in its result. export type Step = { name: string, ok: boolean, detail: string? } --!desc Check that a numeric argument lies within bounds. --!arg arg The argument's name, for the error. --!arg value The value passed. --!arg min The lowest value allowed. --!arg max The highest value allowed. --!return The value; raises naming the argument and the range when it lies outside. --!example "count", 12, 1, 100 typed function M.range(arg: string, value: number, min: number, max: number): number if value < min or value > max then error(string.format("%s must be between %s and %s, got %s", arg, tostring(min), tostring(max), tostring(value))) end return value end --!desc A byte count as a readable size. --!arg bytes The count. --!return The size in B, KB, MB or GB. --!example 2681107 typed function M.size(bytes: number): string local units = { "B", "KB", "MB", "GB" } local i = 1 while bytes >= 1024 and i < #units do bytes /= 1024 i += 1 end return if i == 1 then string.format("%d %s", bytes, units[i]) else string.format("%.1f %s", bytes, units[i]) end --!desc A step for a tool's result. --!arg name What the step did. --!arg ok Whether it succeeded. --!arg detail What happened, when there is more to say. --!return The step. --!example "copy textures", true typed function M.step(name: string, ok: boolean, detail: string?): Step return { name = name, ok = ok, detail = detail } end --!desc The asset of `kind` that a caller named, looked up by that name. --!arg kind The asset type to look in. --!arg name The name the caller passed. --!return The asset; raises naming the kind and name when nothing matches. --!example "material", "gold" typed function M.named(kind: string, name: string): AssetRef local ref = asset.tryResolve(name, kind) if ref == nil then error(string.format("no %s named '%s'", kind, name)) end return ref end return M ``` This shared code should be limited to being used by the tools inside it, dont put general functionality you might need otuside the toolbox into its shared module. Instead reference those in the shared module should you wish to utilize something. A benefit the shared module shares with the tools own code is that it allows the author to write code that has computed references, meaning eg resolving an asset from a computed variable `asset.resolve(<type>,name)` that is normally not allowed in other parts of the engine surface, due to said references not being recorded. This is usefully for utilizing tools to handle operations that are called by users/agents and are dynamic in nature, such as finding/inspecting/testing assets, or content based on an input etc. When writing a shared module, you should follow standard module practices, please see `module.assetType` readme.md for more indepth information. ## Describing the toolbox When writing the readme for your toolbox, ensure you focus on the overall area what the toolbox focuses on, rather than listing individual tools inside it, discovery surfcaces already group tools and toolboxes together so a toolbox can be discoverd by the tools it hosts and doesnt require separately maintaining a list of the tools in it's readme. ## What are tools and when do i create ones? Tools are typed operations with a known serialized output format, made to enable efficient operation in the engine and provide an easily callable interface for operations around a topic. They are also useful in that they can be called from a multitude of surfaces, from `execute`, to `bash` to `tool_exec` to users Tools portion in the edui. A toolbox and its tools differ from eg a module that has multiple callable functionm in that they might return anything, and come with no guarantee that the output is meant to be inspected directly, where as tools are meant to perform an self contained operation and return results/info/relevant data in a readable format, including eg. image data. Whenever you identify an operation that requires specific orchestration and or is something that could be a repeated operation and makes it easier to manage the project/task or when you want to expose logic written in `luau` to the other call surfaces like `bash` under its `zero` command,you should reach for creating a toolbox. Toolboxes also come in handy for limiting control/inputs, exposing a limited set of functionality in a clean manner or ensuring the caller doesnt have to understand the entire code. For comparison, you could think of toolboxes as programs, and the tools inside them as commands. A package might contain tens of tousands of lines of code, dozens of modules, components etc, a toolbox turns that surface into a system that any agent/user can then utilize without having to read or understand the code inside. Its the UI equilevant to operations. For the indepth explanation of what `tool.assetType` is and does, read it's readme.
▲ 0↑ born
·
tool · born here
❒asset
# backfillSearchMeta Re-derive the search `.metadata` for existing assets whose type records facet data there — a bundle's component set and humanoid classification, an animation's humanoid classification. New and edited assets get this from their type's `onChange` hook. Assets with no recorded `.metadata.components` / `.metadata.humanoid` have search facets that read a false negative (an unrecorded asset looks the same as one that genuinely lacks the trait). Run this to populate them. ```lua tools.use("assets", "backfillSearchMeta", {}) -- bundle + animation tools.use("assets", "backfillSearchMeta", { types = { "bundle" } }) ``` Idempotent: re-running recomputes the same values. Only assets whose type exposes a `reindexSearchMeta` ref method are touched; everything else is counted as scanned and left alone. Returns `{ total, byType = { <type> = { scanned, reindexed } } }`.
▲ 0↑ born
·
tool · born here
❒asset
# assets.create Create an asset of a type from that type's own template, and answer where it went: its identity, guid, folder and primary file, and whether what it wrote is kept. The type decides which files the asset holds, and `assets.typeDoc <type>` reads what its creation takes. An asset that already exists is refused unless `overwrite` re-authors it in place, keeping its guid.
▲ 0↑ born
·
tool · born here
❒asset
# assets.fetchInUse Fetch the unique set of asset references in use on the targeted entities — materials, meshes, textures, bundles, and every other category a component field can hold. It scans each entity's components, walking their public data recursively for asset references, dedupes by identity, and reports each reference enriched with its identity and on-disk path plus the entities that use it. Target one entity, a list of entities, or the whole scene; roots are scanned through their children. The one-call inventory of what a piece of content is made of.
▲ 0↑ born
·
tool · born here
❒asset
# assets.fromEntity Capture everything an entity is carrying at **runtime** as durable assets, and repoint the entity at them. A procedural mesh (`renderer.mesh.create`) has no asset behind it, so anything keyed to that geometry — a GI bake, a saved scene — is session-only: reload and the mesh regenerates with a fresh identity, and the bake is gone. `assets.fromEntity` freezes every live runtime resource an entity's components hold — across the whole hierarchy, every resource field — into persistent assets in one call, and rewrites each field to point at the durable asset. Persistent references (already assets) are left untouched. Resource fields are discovered generically from each component's registered schema, so any component carrying runtime content participates — it reifies **all** of an entity's runtime content, not one hard-coded type. A runtime resource shared across several fields is reified once and every field is repointed at the one asset. Reify in **edit mode** to make content durable — in play mode the new assets land in the ephemeral runtime store. ``` assets.fromEntity() -- freeze every runtime resource in the scene assets.fromEntity("cornellRoom") -- freeze the cornellRoom hierarchy assets.fromEntity(nil, { dryRun = true }) -- report what would be reified, write nothing ``` Returns `{ reified, skipped, failed }`. The single-resource primitive is `assets.new`.
▲ 0↑ born
·
tool · born here
❒asset
# assets.generatePreviews Queue a render of the missing or stale `preview.png` for every world asset whose type declares a `preview:` recipe in its `type.yaml` — the batch pass that brings an existing world up to the previews-carry-the-description contract. Each asset is pictured by its type's recipe (a material on a sphere, a shader on the shader ball, a texture as its own pixels, anything scene-instantiable stood in the preview studio) and the picture is persisted inside the asset folder, where it syncs, publishes, drives image-based search, and gives browsers a thumbnail. The call itself only scans and enqueues, so it returns in seconds regardless of world size; the renders drain serially on the preview work queue in the background — poll `assets.previewStatus` until `pending` reaches 0. Re-running the tool is safe at any point: assets whose preview is current, one that records the content the asset holds now, are skipped unless `force` re-renders them, so an interrupted backfill resumes where it left off. `root` narrows the pass to a path prefix. Empty leftover `README.md` files inside visited assets are removed — an empty file can never sync, so each one otherwise re-enters the outgoing queue on every boot.
▲ 0↑ born
·
tool · born here
❒asset
# assets.installedTypes The asset types installed in this world, the engine's own included. Types are content: a world has the ones the engine ships and the ones it installed, and every other type is published content like any asset, found by searching for what you are making and installed with it. This lists the first group only, which is why its name says so. Each row carries the type's name, the lead line of its own README, and whether it declares `instantiate`, the contract `entityOps.fromAsset` dispatches through, and therefore whether the type can become an entity in a scene.
▲ 0↑ born
·
tool · born here
❒asset
# assets.new Persist a **live runtime GPU resource** — a mesh or texture handle from `renderer.mesh.create` / `renderer.texture.create`, which has no asset behind it — into a durable, guid-anchored asset. A runtime resource lives only on the GPU: nothing references it by a stable identity, so anything keyed to it (a GI bake, a saved scene, a material slot) is session-only and regenerates with a fresh identity on the next reload. `assets.new` reads the resource back from the GPU, encodes it, and writes a real asset via `asset.create`, so it survives a reload. `asset.create` itself is disk-only — it writes supplied bytes or pixels and never reads the GPU back. So for raw in-hand geometry or pixels, call `asset.create(<type>, name, data)` directly; `assets.new` is the tool for freezing a resource that only exists as a live handle. Reify in **edit mode** to make content durable — in play mode the asset lands in the ephemeral runtime store. ``` local h = renderer.mesh.create({ positions = p, indices = i }) assets.new(h, "tree") -- persist the runtime mesh assets.new(meshGuid, "terrain", { category = "mesh" }) -- a bare guid string needs opts.category ``` Reifiable categories: `mesh`, `texture`. See `assets.fromEntity` to reify every runtime resource an entity carries in one call.
▲ 0↑ born
·
tool · born here
❒asset
# assets.preview Render one asset through its type's own preview — a mesh, material, texture or scene seen on its own, without placing it in the world — and hand the picture back as an image. An asset whose type renders no preview answers `available = false` with the reason.
▲ 0↑ born
·
tool · born here
❒asset
# assets.previewStatus Read the preview work queue — the serial drain that renders every queued `preview.png` (from `generatePreviews` backfills, from staging an asset whose preview is missing or stale, and from views that ask for a picture). Returns how many renders are still pending, which asset is rendering right now, the session's completed/failed totals, and the most recent failure records. Poll it after `generatePreviews` until `pending` reaches 0 and `active` is empty; a non-empty `failures` list names each asset that could not render and why. While `pending` is above 0 and nothing renders, `held` names what the queue is waiting for: the engine in play mode (the queue renders in edit) or a render device being rebuilt after a loss. A render that cannot finish does not wait: when the render device is abandoned, a subject draws nothing, or the drain rendering it ends part way, the entry moves to `failures` with the reason, and the `zm push` refusal for an asset that needs its preview.png says where the queue stands on it. `activeFrames` counts the engine frames the batch under way has run, which tells a slow batch from one that is not moving, and `drainer` is the handle of the task draining the queue (`task.status(drainer)` reads it). Reading the status starts a drain when work is queued and none is running.
▲ 0↑ born
·
tool · born here
❒asset
# assets.reconcileReferenceNames Write the current name of every moved reference into the world sources that still spell an old one. A source records the guid of each asset it references, so when an asset sits at another path the reference keeps resolving to it and nothing breaks; the source's stored text still spells the old name, and a notice names the source each time the world becomes ready. This call writes the name the asset has now. It changes no behaviour: every reference resolves to the same asset before and after. Each write is an edit of that source, synced to the world like any other. Only the author's sources are written: the world's own files and installed assets the author has modified, never `/source/deps/` or an installed asset still as it was published. `dryRun` lists what would change and writes nothing; `paths` narrows the call to those sources.
▲ 0↑ born
▣
module · born here
❒asset
# reify Shared helper for the assets toolbox: turn a **live runtime GPU resource** into a persistent asset. A resource made with `renderer.mesh.create` / `renderer.texture.create` lives only on the GPU — no asset, no stable identity. This module reads it back from the GPU, encodes it into the engine-native payload, and writes a real asset via `asset.create` (the GPU→CPU→encode→create path `asset.create` does not do on its own, since it is disk-only). The reify recipes are a table keyed by asset category, so a new resource category is one entry rather than a code fork. Current recipes: `mesh` (ZMSH via `renderer.mesh.readback`) and `texture` (ZTEX via `renderer.texture.readback`). Consumed by `assets.new` (one resource) and `assets.fromEntity` (every runtime resource an entity carries). Not an agent-facing tool.
▲ 0↑ born
·
tool · born here
❒asset
# search Searches for assets by what they are or do, across everything published, this world's own and installed content included, and says for each hit whether this world has it installed.
▲ 0↑ born
·
tool · born here
❒asset
# assets.typeBehavior What an asset of this TYPE can do: the operations every ref of it answers to (`ref:method(...)`), the lifecycle hooks it runs, and whether it can become an entity in a scene. Knowing a type exists is not the same as knowing what it gives you. A type that already evaluates, instantiates, validates and recompiles itself is scaffolding to build inside. This is how you see that before deciding to write your own. Pairs with `assets.typeDoc` for the prose; `assets.installedTypes` lists the installed types.
▲ 0↑ born
·
tool · born here
❒asset
# assets.typeDoc One asset type's own documentation — what it IS, the shape of its folder, how it is authored, and what it does for you. A type's README says things no signature can: which file is the source, what regenerates when you write it, what the type handles so you do not have to. Read it before building anything that resembles what the type describes; that is the moment a project turns into a few lines. It reads an installed type: `assets.installedTypes` lists those, and a type not installed yet is found by searching. `assets.typeBehavior` gives the operations the type answers to.
▲ 0↑ born
·
tool · born here
❒asset
# assets.users Find every entity in the active layer that uses a given asset. Name the asset as a word — the project is combed case-insensitively and the first match is used — or pass an asset reference you already hold. Every entity's component data is scanned recursively, and each use site is reported with the entity, component, and field it lives in.
▲ 0↑ born
·
assettype · born here
❒asset
# guide A guide is a single-file documentation asset: a `<name>.guide/` folder whose body is `guide.md`, surfaced through the `guides` MCP tool, the `man` shell builtin and the Docs panel. Drop one anywhere under `/zero/source/`, a `.package` included, and it shows beside the built-in guides. ## Linking guides together A markdown link in a guide is followed when it is clicked in the Docs panel: `[Bindings](controls-guide)` opens another guide by any name `guides read` takes, `[Slots](#save-slots)` a heading of the same guide (its text in lower case, punctuation removed, spaces turned into `-`), `[Slots](save-kit#save-slots)` a heading of another, and `[the module](../saveKit.module)` a file or asset, relative to the guide's own folder. A link the panel cannot follow says why above the guide. ## In the Inspector Guide: the guide's word count, its headings, and **Open in Docs**, which opens it in the Docs panel.
▲ 0↑ born
▣
module · born here
❒asset
# assetType runtime The machinery that reads a `<typename>.assetType/behavior.luau` and turns its declarations into live behaviour. A `behavior.luau` is a table of declarations — `ref` methods, `modules` shared code, an `events` schema, a `namePattern`, `refShapes`, and the `onCreate` / `onRegister` / `onChange` / `onDelete` / `validate` / `settingsDefaults` / `settingsCorrections` hooks. None of it runs itself. This is the code that runs it, and it lives here because reading a type's declarations is the **assetType type's own behaviour** — the same way `material.assetType` owns what a `.material` does. ## Sub-modules | Module | Reads | Responsibility | |---|---|---| | `ref.module` | `ref`, `modules` | Builds every `AssetRef` envelope, attaches the shared metatable, and dispatches per-type methods and shared modules onto it. Also owns edit-mode write-behind persistence. | | `create.module` | `onCreate`, `namePattern`, `validate` | Backs `asset.create`: resolves the type, checks the name against its pattern, runs `onCreate` (or clones `template/`), and writes the instance. `validate.module` beside it checks a creation `opts` table against the type's `createSchema`. Before running `onCreate`, resolves the type's settings chain (below) and hands the result to the hook as `opts.settings`. | | `settings.module` | `settings`, `settingsDefaults`, `settingsCorrections` | A type's declared settings schema and where its values live (`type.yaml`'s `settings:` block). `Settings.resolveForCreate` layers the schema's own defaults, the type's own `settingsDefaults(name, opts, asked)` hook (a DEFAULT the schema can't express — a name-derived convention, a fact read off a payload the caller handed it, a value following a field `asked` shows a higher layer naming; any later layer overrides it), the Preset Manager, an importer's settings, the caller's `settings` table, then the type's own `settingsCorrections(values, name, opts)` hook (a CROSS-FIELD CONSTRAINT the schema can't express, applied last over the fully merged values). A correction over a default/preset/importer value lands silently; a correction that would override what the caller's own `settings` table explicitly asked for refuses the creation instead, naming the field and why. Neither hook is ever handed a table to mutate; each returns what it wants changed (`settingsCorrections` may also return a `{ [field]: reason }` table for that refusal message). `Settings.cloneValues` deep-copies a values table — `create.module` hands `onCreate` a copy, never the table it stamps. `Settings.valuesOf` / `writeValues` read and write an asset's own values file. | | `typeYaml.module` | `type.yaml` | The folder a type lives in and its parsed `type.yaml`, decoded once per content version of the file. `settings.module` and `inspector.module` read their blocks through it. `about` / `aboutAt` read a type's `about.yaml` (`intent`, `function`, `usage`), leaving out any key that is empty or still starts with `[[describe]]`; `aboutRows` and `aboutMarkdown` lay the texts out for the Inspector and the `types/<name>` guide. | | `inspector.module` | `type.yaml`'s `inspector:` block | Which inspector a type declares for its assets (`inspector: { type: ... }` or `{ instance: ... }`), and the declaration that file returns — the sections it adds, the generic ones it replaces, and their order — or the problem that kept it from loading. | | `onRegister.module` | `onRegister` | Fires an instance's once-only registration hook — on arrival, on world load, and for `@builtin` library instances. | | `changeDispatch.module` | `onChange`, `onDelete`, `refShapes` | Routes a VFS source write or a folder delete to the enclosing typed asset's type hooks. | | `refShapes.module` | `refShapes`, `ref` | Publishes what every `AssetRef<category>` answers to, so a member read on an asset-typed value is checked against that category's real surface. | | `events.module` | `events` | The per-asset event runtime behind `ref.events`. | | `instantiable.module` | `ref.instantiate` | The instantiation contract an asset type opts into: `root` stands the root entity, `place` applies the base placement opts to one the type adopted, and `result` shapes the `(root, idMap)` every `instantiate` returns. Also registers the `sceneInstantiable` field-constraint validator. | | `declaration.module` | `type.yaml` | The type's decoded declaration, read once and held until a write inside an `.assetType` folder; what every question about a type as a whole reads. | | `previewRecipe.module` | `type.yaml` `preview:` | How an instance's `preview.png` is produced: which of the four recipes (`instantiate`, `image`, `surface`, `custom`) the type declares, and what that recipe needs. | ## Reaching it ```luau local rt = require("@builtin::assetTypes.assetType.shared") rt.ref.loadTypeBehavior("material") ``` Fields resolve lazily. The prelude requires the sub-modules directly, in dependency order (`ref`, then `changeDispatch`, `onRegister.install()`, `refShapes.install()`) — a table that required all six on load would take that ordering away from it.
▲ 0↑ born
▣
module · born here
❒asset
# yaml.module YAML decode + encode for authored engine content. Pure Luau, no engine dependencies — a general parsing library, not data-system plumbing. ```luau local Yaml = require("@builtin::modules.yaml") local doc = Yaml.decode(vfs.read(path)) vfs.write(path, Yaml.encode(doc)) ``` ## Supported subset - Block mappings (`key: value`) and block sequences (`- item`), nested to any depth. - Compact sequence-of-mapping entries (`- path: x` / ` kind: y`). - Flow collections — `{ a: 1, b: 2 }` and `[1, 2, 3]` — single line. - Plain scalars with type inference: `~` / `null` → nil, `true` / `false` → boolean, numeric literals → number, everything else → string. - Single- and double-quoted strings (`''` escapes a literal quote inside a single-quoted string; `\n` `\t` `\r` `\\` `\"` inside a double-quoted one). A quote opens a quoted scalar where a scalar begins — after `key:`, after a `- ` entry marker, or inside a flow collection — so a plain scalar carries an apostrophe as data (`note: the world's rules`). Quoted keys are read in flow mappings. - `#` comments — honored outside quotes, so a `#` inside a quoted string is kept as data. - Literal (`|`) and folded (`>`) block scalars, with a chomping indicator (`-` strips the final newline, `+` keeps every trailing blank line, none keeps one newline) and an indentation indicator (`1`-`9`: the content's indent past the parent's), in either order (`|-`, `>+`, `|2`, `>1-`). A block scalar's content is literal text: quotes (balanced or not), `#`, `key: value` and `---` inside it are data. - An optional leading `---` document marker. ## Loud-error contract Constructs outside the subset above raise instead of misparsing: anchors/aliases (`&`, `*`), tags (`!`), directives (`%`), multi-document streams (a second `---`), a block-scalar header it cannot read (`|x`, `|-+`, `|0`), tab indentation, and inconsistent indentation (both block structure and block-scalar bodies). Every error carries the offending line number (`yaml: line N: ...`), so a bad config points straight at the line to fix. Tab indentation is rejected everywhere — including block-scalar body lines whose leading whitespace contains a tab. A tab after the first non-space character of a line is content and passes through. ## API - `Yaml.decode(text: string): any` — decode a YAML document into a Luau value (table, scalar, or nil for an empty document). Raises on malformed input or an unsupported construct. - `Yaml.decodeWithComments(text: string): (any, boolean)` — decode as `decode` does, and report whether the text carries a comment by the decoder's own rule: a `#` at a line's start or after whitespace, outside a quoted scalar and outside a block scalar. Re-encoding the value drops such comments, so an editor that writes a document back reads this first. - `Yaml.encode(value: table): string` — encode a Luau table as YAML (block style, two-space indent, alphabetically sorted keys). Raises on values YAML can't represent (functions, userdata, non-string mapping keys). A top-level empty table encodes to an empty document, which decodes back to `nil` (the YAML empty-document ambiguity).
▲ 0↑ born
·
agentskill · born here
❒asset
# interfaces The screen-building skill: author a UI as a styled widget tree the way you write HTML and CSS, register it, and then see it and drive it through the `gui` toolbox rather than reasoning about the tree. Covers the layout and paint model, entrance and idle animation, callbacks that fire in play mode, and the build → capture → play → iterate loop that gets a screen from blank to built.
▲ 0↑ born
·
agentskill · born here
❒asset
# debugging Finding out **when** something went wrong in a running world. The job this skill covers is not "what is the current value" — the current value is downstream of the moment that matters. It is: attach an instrument to running code, keep working, and be told the instant the thing happens. It exists because the reflex it replaces does not work. Running something, waiting, and taking a capture aims at a moment you cannot see: seconds pass between the call and the frame, and adding a wait only moves the guess. Polling is the same guess with more steps. Covers: probing a function call, a source line, or a value; using a condition to name the moment rather than every step toward it; reading the state a value was taken in before trusting the value; and cancelling what you armed. The trap it spends the most words on is the expensive one — a right value read in the wrong world, where the match already ended or the scene already swapped, nothing announced it, and every number afterwards means something else.
▲ 0↑ born
backing path · skills/authoring-assets.agentSkill

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.