---
title: "asset"
description: "The asset namespace — the engine's Luau API reference for asset."
section: "API Reference"
slug: "api-asset"
canonical: "https://origozero.ai/docs/api-asset"
updated: "2026-09-07T03:15:13.361117053+00:00"
tags: ["api", "reference"]
---

# asset

The `asset` namespace — 141 functions.

## asset/create {#asset-create}

```lua
asset.create(typeName: string, name: string, opts: table?) -> (table, table)
```

[Library] Instance a new asset of an existing registered type: runs that type's `onCreate(name, opts)` behavior hook (or clones its `template/` skeleton when it has none) and writes the produced files to `/zero/source/<name>.<type>/` — the one authored location, in every mode. While play runs, the play write lock takes that write onto the play shadow (live in the session, disk source untouched, listed by `vfs.playShadowPaths()`, promoted or discarded on a guarded play-exit), so the asset keeps the identity, path and require spelling it has in edit. A create made from a component or a scene entrypoint — output the world reproduces on every load — writes to the copy-on-write `/zero/runtime/assets/<identity>.<type>/` store instead, so the saved manifest never carries a second copy; `name` and `folder` spell the same identity in either store, so a reference written against that identity resolves the asset wherever the call filed it. Four framework `opts` keys steer placement and are consumed before the hook runs: `folder` (a relative subfolder under `/source`, and the dotted prefix of the asset's identity), `into` (a resolved container ref to author inside), `dest` (an absolute destination path) and `overwrite` (re-author in place, keeping the guid). Returns the created asset's AssetRef, and SECOND the durability of the bytes it landed: `{ durable = true }` when the files are where the call filed them, `{ durable = false, warning, playShadow, shadowed }` when the play shadow took them, with `playShadow` naming the routes that keep them and `shadowed` the paths. `durable` is present whatever the answer is, so its absence is never a reading, and one call answers once for every file it wrote — the same answer, off the same shadow set and in the same words, that the MCP `write_file` / `edit_file` / `capture` tools attach to their own results, and `vfs.durability` gives directly at any other write site. Raises on bad arguments or a failing onCreate.

**Parameters**

- `typeName` `string` — Registered asset type to instance (e.g. "material", "texture").
- `name` `string` — Destination asset name (becomes `/source/<name>.<typeName>`). A
bare identity — pass `opts.folder` to place it in a subfolder rather than
spelling a path here. The accepted shape is the type's to declare:
`^[A-Za-z][A-Za-z0-9_]*$` unless its `behavior.luau` exports a
`namePattern`, as `guide` does to take `getting-started` and `01-overview`.
This call names the category FIRST and the asset second. Every other
`asset.*` call taking both names them the other way round —
`asset.exists(name, category)`, `asset.tryResolve(ref, category)` — so a
create-then-check pair reads `if not asset.exists(n, t) then
asset.create(t, n, opts) end`. A call whose two arguments are read into
each other, at either end of that pair, is refused and told which way
round the call reads.
- `opts` `{ [string]: any }` _(optional)_ — Optional table forwarded to the type's `onCreate` hook, minus four
framework keys consumed here and never seen by the hook:
`folder` (a relative subfolder under `/source` to author the asset in, so
generated content groups instead of accumulating at the source root, and
the asset's identity carries that folder as its dotted prefix),
`into` (author INSIDE a resolved container ref), `dest` (an absolute
destination path), and `overwrite` (re-author in place, keeping the guid).

**Returns** `(AssetRef, { durable: boolean, warning: string?, playShadow: string?, shadowed: { string }? })` — The created asset's `AssetRef` — the SAME interned instance `asset.resolve` returns (guid/__ref/path + the type's ref methods: `:getBytes`, `:ensureHandle`, `:serialize`, …). Disk-only: nothing is uploaded to CPU/GPU. Where the bytes this call landed went, as a SECOND return value: `{ durable = true }` when the files are where the call filed them, and `{ durable = false, warning = …, playShadow = …, shadowed = { … } }` when the play shadow took them — live in this session, disk source untouched, discarded on a guarded play-exit unless kept, with `playShadow` naming the routes that keep them and `shadowed` the paths. `durable` is present whatever the answer is, so its absence is never a reading, and one call answers once for every file it wrote. It is the same answer, off the same shadow set and in the same words, that the MCP `write_file` / `edit_file` / `capture` tools attach to their own results — ask `vfs.durability` for it directly at any other write site.

```lua
local m = asset.create("material", "brick", { shader = "pbr" })
local mesh = asset.create("mesh", "tree", { positions = {...}, indices = {...} })
local mesh = asset.create("mesh", "rock", { positions = {...}, indices = {...}, folder = "terrain/props" })
local mesh = asset.create("mesh", name, { positions = p, indices = i, overwrite = true }) -- idempotent rebuild
local clip, d = asset.create("soundClip", "hit", { pcm = buf, sampleRate = 48000, channels = 1 })
if not d.durable then log.warn(d.warning .. " " .. d.playShadow) end
```

## globals/asset/add_tag {#globals-asset-add-tag}

```lua
asset.add_tag(ref: RefArg, tag: string)
```

Add a tag to the asset's `.metadata.tags`. Idempotent.
Creates the sidecar and the tags array if missing.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `tag` `string` — Tag to add.

```lua
asset.add_tag("brick", "wip")
```

## globals/asset/alias {#globals-asset-alias}

```lua
asset.alias(ref: RefArg, alias: string) -> boolean
```

Add a name the asset answers to. `asset.resolve`, leaf
shorthand, and the typed-argument coercion that content refs
travel through all reach the asset by the alias from here on,
exactly as they do by its identity — so a material naming a
shader by its alias resolves, and content written against an
older name keeps working after a rename. The name survives
writes to the asset's own files.
Raises when the name already resolves to a DIFFERENT asset — an
alias extends the identity namespace and never takes a name out
of another asset's hands — and when it is shaped like a guid or
a VFS path, forms that resolve before identity lookup, so an
alias in that shape could never answer.

**Parameters**

- `ref` `RefArg` — The asset gaining the name.
- `alias` `string` — The additional name. Any identity form: a bare leaf
(`standard`) or a scope-qualified path (`@builtin::shaders.legacy`).

**Returns** `boolean` — True when newly added, false when the asset already answered to it.

```lua
asset.alias("@builtin::shaders.pbr", "standard")
```

## globals/asset/aliases {#globals-asset-aliases}

```lua
asset.aliases(ref: RefArg) -> { string }
```

The additional names this asset answers to, beyond its own
identity — what `asset.alias` registered, plus the
package-relative `~pkg.tail` form when the asset lives inside a
package.

**Parameters**

- `ref` `RefArg` — Any name the asset has.

**Returns** `{ string }` — Array of alias names in canonical identity form.

```lua
for _, n in asset.aliases("pbr") do print(n) end
```

## globals/asset/canCreate {#globals-asset-cancreate}

```lua
asset.canCreate(typeName: string) -> boolean
```

Whether `asset.create` can instance `typeName`: the type declares
creation logic (a `behavior.luau` `onCreate` hook) or ships a
`template/` skeleton the hookless fallback clones. A type with neither
— one whose instances only arrive by import — answers false. The query
a creation UI derives its offering from, so what it offers is what
`asset.create` accepts.

**Parameters**

- `typeName` `string` — Registered asset type (e.g. "material", "scene").

**Returns** boolean — true when `asset.create(typeName, …)` can produce one.

```lua
if asset.canCreate(kind) then asset.create(kind, name) end
```

## globals/asset/categories {#globals-asset-categories}

```lua
asset.categories() -> { string }
```

List every asset category the engine currently recognises.
Use to discover valid `type` argument values for the rest of
`asset.*`.

**Returns** `{ string }` — Array of category names.

```lua
for _, c in asset.categories() do print(c) end
```

## globals/asset/containing {#globals-asset-containing}

```lua
asset.containing(path: string) -> AssetRef?
```

Walk `path`'s ancestors and return an `AssetRef` handle for
the OUTERMOST category-folder containing it (e.g. `main.scene`
for `"/source/scenes/main.scene/scene.json"`). Returns nil for
paths outside any registered asset type.

**Parameters**

- `path` `string` — VFS path to inspect.

**Returns** `AssetRef?` — AssetRef handle, or nil.

```lua
local a = asset.containing("/source/scenes/main.scene/scene.json")
```

## globals/asset/cpuResident {#globals-asset-cpuresident}

```lua
asset.cpuResident(ref: RefArg, typeName: string?) -> boolean
```

True when the asset is CPU-resident — a live script-component context
holds it (a component's assetRef field, or an imperative
`asset.resolve`/`ref` made while a component is the caller), which is what
warms its bytes into memory. The CPU pool is a different pool from the
device's: `asset.observe().cpu` lists it, `asset.observe().textures` /
`.meshes` list what the device holds, and an asset can be in one and not
the other.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.
- `typeName` `string` _(optional)_ — Category to restrict the match to. Omit to search every category.

**Returns** boolean — true when a live context holds it.

```lua
if asset.cpuResident(ref) then print("bytes are warm") end
```

## globals/asset/create {#globals-asset-create}

```lua
asset.create(typeName: string, name: string, opts: { [string]: any }?) -> (AssetRef, { durable: boolean, warning: string?, playShadow: string?, shadowed: { string }? })
```

Instance a new asset of an existing type. Runs the type's
`behavior.luau` `onCreate(name, opts)` hook to produce the asset's
files, then writes them under `/source/<name>.<type>/`. This is the
single generic asset-creation API. Refuses to clobber an existing
edit-mode asset unless `opts.overwrite = true`, which re-authors it in
place and keeps the existing guid (only the checksum changes). Pairs with
`asset.exists` for content generators that re-run over the same names.

A create made from a script component's callback or a scene entrypoint is
output the world reproduces on every load, so it is filed in the ephemeral
`/runtime/assets/` store instead, where the saved manifest never carries a
second copy of it. `name` and `folder` spell the same IDENTITY in either
store, so a reference written against that identity resolves the asset
wherever the call filed it, and one generator run from an `execute` and
from a component names one asset.

**Parameters**

- `typeName` `string` — Registered asset type to instance (e.g. "material", "texture").
- `name` `string` — Destination asset name (becomes `/source/<name>.<typeName>`). A
bare identity — pass `opts.folder` to place it in a subfolder rather than
spelling a path here. The accepted shape is the type's to declare:
`^[A-Za-z][A-Za-z0-9_]*$` unless its `behavior.luau` exports a
`namePattern`, as `guide` does to take `getting-started` and `01-overview`.
This call names the category FIRST and the asset second. Every other
`asset.*` call taking both names them the other way round —
`asset.exists(name, category)`, `asset.tryResolve(ref, category)` — so a
create-then-check pair reads `if not asset.exists(n, t) then
asset.create(t, n, opts) end`. A call whose two arguments are read into
each other, at either end of that pair, is refused and told which way
round the call reads.
- `opts` `{ [string]: any }` _(optional)_ — Optional table forwarded to the type's `onCreate` hook, minus four
framework keys consumed here and never seen by the hook:
`folder` (a relative subfolder under `/source` to author the asset in, so
generated content groups instead of accumulating at the source root, and
the asset's identity carries that folder as its dotted prefix),
`into` (author INSIDE a resolved container ref), `dest` (an absolute
destination path), and `overwrite` (re-author in place, keeping the guid).

**Returns** `(AssetRef, { durable: boolean, warning: string?, playShadow: string?, shadowed: { string }? })` — The created asset's `AssetRef` — the SAME interned instance `asset.resolve` returns (guid/__ref/path + the type's ref methods: `:getBytes`, `:ensureHandle`, `:serialize`, …). Disk-only: nothing is uploaded to CPU/GPU. Where the bytes this call landed went, as a SECOND return value: `{ durable = true }` when the files are where the call filed them, and `{ durable = false, warning = …, playShadow = …, shadowed = { … } }` when the play shadow took them — live in this session, disk source untouched, discarded on a guarded play-exit unless kept, with `playShadow` naming the routes that keep them and `shadowed` the paths. `durable` is present whatever the answer is, so its absence is never a reading, and one call answers once for every file it wrote. It is the same answer, off the same shadow set and in the same words, that the MCP `write_file` / `edit_file` / `capture` tools attach to their own results — ask `vfs.durability` for it directly at any other write site.

```lua
local m = asset.create("material", "brick", { shader = "pbr" })
local mesh = asset.create("mesh", "tree", { positions = {...}, indices = {...} })
local mesh = asset.create("mesh", "rock", { positions = {...}, indices = {...}, folder = "terrain/props" })
local mesh = asset.create("mesh", name, { positions = p, indices = i, overwrite = true }) -- idempotent rebuild
local clip, d = asset.create("soundClip", "hit", { pcm = buf, sampleRate = 48000, channels = 1 })
if not d.durable then log.warn(d.warning .. " " .. d.playShadow) end
```

## globals/asset/declareReferenceArg {#globals-asset-declarereferencearg}

```lua
asset.declareReferenceArg(call: string, position: number, assetType: string)
```

Declare that `call`'s argument at 1-based `position` names an
asset of `type`, so a string literal written there is recorded as a
reference. The positional counterpart of
`asset.declareReferenceField`, for a call that takes its asset as a
plain argument — including a world's own spawn helper, which is
where a name most often stops being visible to the reference graph.
A lookup whose asset is its FIRST argument (`asset.resolve` and its
siblings) is already read and needs no declaration. Only a literal —
or a name the file holds in a top-level string constant — is
recorded; anything computed is reported as a dynamic resolve.

**Parameters**

- `call` `string` — The callee as it is written at a call site.
- `position` `number` — Which argument holds the name, counting from 1.
- `assetType` `string`

```lua
asset.declareReferenceArg("spawnModel", 3, "mesh")
```

## globals/asset/declareReferenceField {#globals-asset-declarereferencefield}

```lua
asset.declareReferenceField(call: string, field: string, assetType: string)
```

Declare that `call`'s options table names an asset of `type` in
its `field`, so a string literal written there is recorded as a
reference by whatever writes the file. This is what puts an API
that takes an asset BY NAME into the reference graph: the named
asset becomes a dependency, travels with the content that names it
into a pack or a pull, and a name nothing answers to becomes an
unresolved dependency `worldValidation` reports and the push gate
refuses. A field holding a TABLE of names — a material's `textures`
— records every name in it. Declare once, beside the API; a call
taking its asset as the FIRST positional argument is already read
and needs no declaration. Only a literal is recorded; a computed
name resolves at runtime and is reported as a dynamic resolve.

**Parameters**

- `call` `string` — The callee as it is written at a call site.
- `field` `string` — The options-table field holding the name, read at the
table's own level.
- `assetType` `string`

```lua
asset.declareReferenceField("fx.beam", "material", "material")
```

## globals/asset/declareReferenceKey {#globals-asset-declarereferencekey}

```lua
asset.declareReferenceKey(assetType: string, key: string, refType: string)
```

Declare that, in a data file belonging to an `assetType` asset,
the top-level `key` names an asset of `type` — a `.material`'s
`mat.yaml` naming the shader it draws with and the textures it
binds. The names a format holds are references as surely as ones
written in code: recording them carries a material's shader along
with the material into a pack or a pull, and turns a name nothing
answers to into an unresolved dependency instead of a surface that
renders as the magenta error material. A key holding a table of
names records one per entry.

**Parameters**

- `assetType` `string` — The category owning the file, e.g. "material".
- `key` `string` — The top-level key holding the name(s).
- `refType` `string`

```lua
asset.declareReferenceKey("material", "shader", "shader")
```

## globals/asset/deps {#globals-asset-deps}

```lua
asset.deps(ref: RefArg, type: string?) -> DepsResult
```

Return the asset's outbound dependency graph — every other
asset recorded as a content dependency of it.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `DepsResult` — `{ deps = { { asset_guid, origin, literal, via, ... } } }`.

```lua
for _, d in asset.deps("main.scene").deps do print(d.asset_guid) end
```

## globals/asset/describe {#globals-asset-describe}

```lua
asset.describe(typeName: string) -> DescribeResult
```

The creation contract for an asset type: the parameters its
`onCreate(name, opts)` hook accepts, as data. `kind` is "schema"
(typed contract), "legacy" (untyped opts — anything passes), "none"
(template scaffold — takes no opts), or "error" (the type's schema
failed to parse; `error` says why). `contract` is the human-readable
rendering validation errors print.

**Parameters**

- `typeName` `string` — Registered asset type to describe (e.g. "texture").

**Returns** DescribeResult — the creation contract.

```lua
local contract = asset.describe("texture").contract
```

## globals/asset/diagnose {#globals-asset-diagnose}

```lua
asset.diagnose(ref: RefArg) -> any
```

Why one asset can or cannot be used, read from the engine rather than
from what the caller asked for. Always carries `usable`; when false,
`reason` is one of `asset.unusableReasons()` and `detail` is the engine's
own message. `primary` names the file the type's declared `primary` list
resolved to, so an asset that loaded a preview image instead of its
payload shows the wrong filename rather than a successful load. The
payload's bytes are read by the engine's own decoder wherever it has one
for that container, so `usable` is the verdict a load would reach and the
call costs that decode.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.

**Returns** `any` — DiagnoseRecord

```lua
local d = asset.diagnose("myTex") if not d.usable then print(d.reason, d.detail) end
```

## globals/asset/exists {#globals-asset-exists}

```lua
asset.exists(name: string, typeName: string) -> boolean
```

**Parameters**

- `name` `string`
- `typeName` `string`

**Returns** `boolean`

## globals/asset/get_field {#globals-asset-get-field}

```lua
asset.get_field(ref: RefArg, key: string) -> any
```

Read one top-level field from the asset's `.metadata`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.

**Returns** `any` — Field value or nil.

```lua
local author = asset.get_field("brick", "author")
local settings = asset.get_field("tree", "settings") -- → a Luau table
```

## globals/asset/gpuResident {#globals-asset-gpuresident}

```lua
asset.gpuResident(ref: RefArg) -> boolean
```

True when the device holds a texture or mesh under this asset's guid,
read off the inventory the renderer publishes.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.

**Returns** boolean — true when the device holds it.

```lua
print(asset.gpuResident("@builtin::models.Sample.DamagedHelmet"))
```

## globals/asset/guid {#globals-asset-guid}

```lua
asset.guid(ref: RefArg, type: string?) -> string
```

Return the guid for an asset.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `string` — Guid.

```lua
local g = asset.guid("@builtin::components.Camera")
```

## globals/asset/has_field {#globals-asset-has-field}

```lua
asset.has_field(ref: RefArg, key: string) -> boolean
```

True when the asset's `.metadata` carries the named field.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.

**Returns** `boolean` — True when present.

```lua
if asset.has_field("brick", "author") then end
```

## globals/asset/has_tag {#globals-asset-has-tag}

```lua
asset.has_tag(ref: RefArg, tag: string) -> boolean
```

True when the asset's `.metadata.tags` contains `tag`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `tag` `string` — Tag to check for.

**Returns** `boolean` — True when present.

```lua
if asset.has_tag("brick", "wip") then end
```

## globals/asset/identity {#globals-asset-identity}

```lua
asset.identity(ref: RefArg, type: string?) -> string
```

Return the canonical identity for an asset.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `string` — Canonical identity.

```lua
local id = asset.identity("brick")
```

## globals/asset/import {#globals-asset-import}

```lua
asset.import(path: string) -> string?
```

Import a raw source file NOW and return the produced asset path
(a `.bundle` for a model, `.texture` for an image, `.audio` for a
sound, …), or nil if no importer claims it. This is the deterministic,
on-demand counterpart to the engine's automatic import-on-write: it runs
in the calling task and returns only when the import is complete. Pair it
with a quiet write — `vfs.write(path, bytes, { quiet = true })` lands the
raw bytes without firing the automatic importer, then `asset.import(path)`
imports them under your control, so you can act on the result instead of
polling for the import to appear.

**Parameters**

- `path` `string` — The raw source VFS path to import (e.g. a just-written `.glb`).

**Returns** `string?` — The produced asset path, or nil when nothing claimed it.

```lua
local bundle = asset.import("/zero/source/generated/chest.glb")
```

## globals/asset/inspect {#globals-asset-inspect}

```lua
asset.inspect(ref: RefArg, type: string?) -> InspectRecord
```

Everything known about one asset in a single record: identity, guid,
source, type, scope and origin, its description and tags, the ref methods
its type exposes, and the type's own `inspect` detail when it declares one.
The read-everything counterpart to `asset.resolve`, which hands back a ref.

**Parameters**

- `ref` `RefArg` — An `AssetRef`, an identity string, or a path.
- `type` `string` _(optional)_ — Narrow the resolve to one asset type when assets of several
categories answer to the same bare name.

**Returns** `InspectRecord` — The inspect record.

```lua
local rec = asset.inspect("@builtin::materials.default")
local rec = asset.inspect(name, "mesh"); print(rec.guid, #rec.tags)
```

## globals/asset/list {#globals-asset-list}

```lua
asset.list(type_or_opts: (AssetCategory | ListOpts)?, scope: string?, opts: ListOpts?) -> ListResult
```

Query registered assets, returning each match as a resolved
`AssetRef` handle. Every filter narrows the same enumeration and
they compose: `path` selects a VFS subtree (the folder and
everything under it), `type` keeps only those asset types within
it, `scope` keeps only that scope, and `fields` keeps only assets
whose `.metadata` matches. `type` and `path` each take one value or
a list matching any of its entries, and `all` / `any` / `none`
group whole filters — `none` excludes what it matches. `order`,
`limit`, and `offset` shape the result: matches come back ordered
by identity unless `order` names another field
(`name` / `path` / `type` / `guid`).
Each entry is the same envelope `asset.resolve` returns (`__ref` /
`type` / `name` / `guid` / `identity` / `path`), so it can be passed
anywhere an AssetRef is accepted, and the result carries
`:first()` / `:random()` / `:filter()` / `:sort()` and friends.
`type` takes the same values `asset.categories()` lists. The first
positional argument is a path when it is absolute, a type
otherwise. An unknown key raises, as does a table setting both
`type` and its older spelling `category`.
A static (literal) type or path makes the enumeration part of the
calling file's content dependencies when it is saved — the set
travels with published content, so consumers get at-least the
authoring world's assets.

**Parameters**

- `type_or_opts` `(AssetCategory | ListOpts)` _(optional)_ — Type or VFS path filter (a static literal so the
enumeration can be captured for publish), or the full query table.
- `scope` `string` _(optional)_ — Scope filter (when first arg is a type).
- `opts` `ListOpts` _(optional)_ — The query table — see `ListOpts`.

**Returns** `ListResult` — The matched AssetRef handles, as a result carrying query methods.

```lua
local hero = asset.list({ path = "/zero/source/props", type = "mesh", fields = { tags = "hero" } }):first()
```

## globals/asset/list_field_values {#globals-asset-list-field-values}

```lua
asset.list_field_values(key: string) -> { any }
```

Distinct values seen for the named field across every
asset's `.metadata`.

**Parameters**

- `key` `string` — Field name.

**Returns** `{ any }` — Array of distinct values.

```lua
local authors = asset.list_field_values("author")
```

## globals/asset/list_fields {#globals-asset-list-fields}

```lua
asset.list_fields() -> { string }
```

Distinct top-level field keys observed across every asset's
`.metadata`. Useful for tooling discovering custom keys in use.

**Returns** `{ string }` — Array of field names.

```lua
for _, k in asset.list_fields() do print(k) end
```

## globals/asset/meta {#globals-asset-meta}

```lua
asset.meta(ref: RefArg, type: string?) -> AssetMeta
```

Read the asset's engine-owned identity record (guid /
checksum). Distinct from `.metadata` (agent-editable);
for that use `asset.metadata`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `AssetMeta` — Metadata table.

```lua
local m = asset.meta("brick") -- { guid = ..., checksum = ... }
```

## globals/asset/metadata {#globals-asset-metadata}

```lua
asset.metadata(ref: RefArg, type: string?) -> AssetMeta
```

Read the asset's agent-editable `.metadata` sidecar as a
Lua table. Missing sidecar returns `{}`. Distinct from
`asset.meta` (engine-owned).

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `AssetMeta` — JSON-shaped table; empty when no sidecar exists.

```lua
local md = asset.metadata("brick")
```

## globals/asset/observe {#globals-asset-observe}

```lua
asset.observe() -> any
```

What the engine is holding for content right now, in one reading:
`textures` and `meshes` (one row per resource the device holds, each with
the bytes it costs, its dimensions or buffer split, and where it came
from), `cpu` (one row per asset a live script-component context holds),
and `totals` — the aggregates those rows sum to, so the listing reconciles
against `renderer.textureMemory()` and `renderer.gpuMemory()`.

Each pool is named because they are different pools: an asset can be on
the device and not CPU-resident, or the reverse. `devicePublished` is
false when no renderer has published an inventory and `cpuPublished` when
the scripting VM has not published its pool — an engine that cannot answer
reads differently from one answering with nothing resident.

**Returns** `any` — ResidencyReading

```lua
local r = asset.observe() print(#r.textures, r.totals.textureBytes)
```

## globals/asset/preview {#globals-asset-preview}

```lua
asset.preview(ref: RefArg, opts: { [string]: any }?, type: string?) -> { [string]: any }
```

Render a preview of an asset. Resolves the ref and dispatches to its
type's `preview` ref-method when present; otherwise returns the
`{ available = false }` sentinel ("no preview available for this type").

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `opts` `{ [string]: any }` _(optional)_ — Optional `{ size = { width, height }, angle = { yaw, pitch } }`.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `{ [string]: any }` — `{ available, imageBase64?, width?, height?, bounds?, stats?, reason? }`.

```lua
local p = asset.preview("@builtin::materials.gold", { size = { width = 512, height = 512 } })
```

## globals/asset/primaryFile {#globals-asset-primaryfile}

```lua
asset.primaryFile(ref: RefArg) -> any
```

The file the asset type's declared `primary` list resolves to inside
this asset, as the loader itself resolves it. `resolved` is false when no
declaration matched and `path` is then absent; `declared` is the type's
own primary list in match order.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.

**Returns** `any` — `{ path: string?, resolved: boolean, isFolder: boolean, declared: { string } }`

```lua
print(asset.primaryFile("myTex").path)
```

## globals/asset/ref {#globals-asset-ref}

```lua
asset.ref(ref: RefArg, type: string?) -> AssetRef
```

Build a reference handle for an asset — the canonical ref
envelope constructor. Identical shape to `asset.resolve`;
preferred name for the author-side use case (embedding refs in
YAML / JSON / Luau output).

Naming the asset here reads exactly as naming it in `asset.resolve`,
down to raising on a miss: a literal is recorded as this source's
dependency, a computed name is a dynamic resolve — free in a tool,
refused on the gameplay path. `asset.exists(name, type)` is the probe a
computed name can ask.

**Parameters**

- `ref` `RefArg` — The asset to reference — an identity, a guid, a VFS path, or a handle.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `AssetRef` — Ref handle. Raises on a miss or ambiguity, as `asset.resolve` does.

```lua
local r = asset.ref("animations.idle", "animation")
```

## globals/asset/reloadPending {#globals-asset-reloadpending}

```lua
asset.reloadPending(ref: RefArg, typeName: string?) -> boolean
```

True while a write to this asset still owes it a reload — the write is
inside the settle window that collects one authoring step's writes, or its
reload is queued and the engine has not run it yet. False means every
content change written so far has reached its subscribers, so a consumer
bound to the asset now cannot be interrupted by a reload the earlier
writes already earned. The recording is synchronous with the write, so a
call made right after one already reads true.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.
- `typeName` `string` _(optional)_ — Category to restrict the match to. Omit to search every category.

**Returns** boolean — true while a content-change reload is still owed.

```lua
repeat task.wait() until not asset.reloadPending(ref)
```

## globals/asset/reloadSeq {#globals-asset-reloadseq}

```lua
asset.reloadSeq(ref: RefArg, typeName: string?) -> number
```

How many content-change reloads this asset has been through — the
count of `onAssetReload` dispatches the engine has RUN for it. A write to
a file inside an asset does not reload it on the spot: the writes of one
authoring step are collected for a settle window and the reload runs on a
later frame. Read this, write, then poll for a larger number to learn the
write's reload has actually reached subscribers. Monotonic per asset and
session-scoped; 0 for an asset whose content has not changed since boot.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.
- `typeName` `string` _(optional)_ — Category to restrict the match to. Omit to search every category.

**Returns** number — content-change reloads dispatched for this asset.

```lua
local at = asset.reloadSeq(ref)
vfs.write(asset.source(ref) .. "/graph.json", encoded)
repeat task.wait() until asset.reloadSeq(ref) > at
```

## globals/asset/remove_field {#globals-asset-remove-field}

```lua
asset.remove_field(ref: RefArg, key: string)
```

Remove one top-level field from the asset's `.metadata`.
No-op when the field isn't present.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.

```lua
asset.remove_field("brick", "author")
```

## globals/asset/remove_tag {#globals-asset-remove-tag}

```lua
asset.remove_tag(ref: RefArg, tag: string)
```

Remove a tag from the asset's `.metadata.tags`. No-op when
the tag isn't present.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `tag` `string` — Tag to remove.

```lua
asset.remove_tag("brick", "wip")
```

## globals/asset/resolve {#globals-asset-resolve}

```lua
asset.resolve(ref: RefArg, type: (C & string)?) -> AssetRef<C>
```

Find an asset. The returned handle carries every name form
the asset has (`guid`, `identity`, `path`, `type`) so
downstream code can read any one of them without calling
`resolve` again. Raises when `ref` resolves to no asset — or, with a
`type`, to no asset of that type — and when `ref` reaches more than one
asset, where it names the candidates for you to pick from instead of
picking one of them. A `<scope>::`-qualified identity reaches exactly one:
`@root::name` for the asset this world holds at its source root, the
library identity (`@builtin::…`) for a library's. For the same lookup
answering a miss with nil, use `asset.tryResolve(ref, type)`.

A name written as a string LITERAL is recorded as this source's
dependency on that asset, so the asset travels with the content and
still resolves once someone installs it in another world. A COMPUTED
name cannot be written down, so nothing pins what it reaches: that is a
dynamic resolve — free in a tool, refused on the gameplay path (a
component or scene entrypoint). `asset.tryResolve`, `asset.ref` and
`asset.source` read the name they are given exactly this way too, so
which of the four you reach for changes neither answer. To ask whether a
computed name has files without reaching a handle, use
`asset.exists(name, type)`.

**Parameters**

- `ref` `RefArg` — The asset to find — an identity, a guid, a VFS path, or a handle.
- `type` `(C & string)` _(optional)_ — Category to restrict the match to (optional). Separates a bare
name that assets of different categories share (`asset.resolve("cube",
"mesh")`); where several assets of the SAME category answer to it, the
scope-qualified identity is what separates them.
A reference naming a file an importer has since promoted (`wall.png`
after the texture importer turned it into `wall.texture`) resolves to the
promoted asset, and says so in the log once per reference.

**Returns** `AssetRef<C>` — Asset handle, carrying the category when one was named — so the methods that category defines are checked on the result. Raises (rather than returning nil) on a miss, and on a name that reaches more than one asset.

```lua
local a = asset.resolve("@builtin::components.Camera")
```

## globals/asset/set_field {#globals-asset-set-field}

```lua
asset.set_field(ref: RefArg, key: string, value: any?)
```

Set one field in the asset's `.metadata`, creating the sidecar if missing.
Sibling fields are preserved. When the new value AND the existing value are
both maps (objects), the new value DEEP-MERGES into the existing one, so
writing one sub-key never drops the others — `set_field(ref, "settings",
{ keepCpu = true })` keeps every other setting. Arrays and scalars replace.
Clear a whole field with `asset.remove_field`; replace the entire sidecar with
`asset.set_metadata`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.
- `value` `any` _(optional)_ — Field value (any JSON-serialisable Lua value).

```lua
asset.set_field("brick", "author", "me")
asset.set_field("tree", "settings", { keepCpu = true }) -- merges; sibling settings kept
```

## globals/asset/set_metadata {#globals-asset-set-metadata}

```lua
asset.set_metadata(ref: RefArg, data: AssetMeta)
```

Replace the asset's `.metadata` sidecar with the given
table. Pass an empty table to clear all fields.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `data` `AssetMeta` — Full JSON-shaped contents for the sidecar.

```lua
asset.set_metadata("brick", { author = "me", tags = { "wip" } })
```

## globals/asset/source {#globals-asset-source}

```lua
asset.source(ref: RefArg, type: string?) -> string
```

Return the VFS source path for an asset.

It reaches the asset, so the name carries the same reference contract
`asset.resolve`'s does: a literal is recorded as this source's
dependency, a computed name is a dynamic resolve — free in a tool,
refused on the gameplay path. `asset.exists(name, type)` is the probe a
computed name can ask.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `string` — VFS source path.

```lua
local p = asset.source("brick") -- "/source/brick.material"
```

## globals/asset/tags {#globals-asset-tags}

```lua
asset.tags(ref: RefArg) -> { string }
```

Convenience read of the `.metadata.tags` array.

**Parameters**

- `ref` `RefArg` — Any name the asset has.

**Returns** `{ string }` — Array of tag strings.

```lua
for _, t in asset.tags("brick") do print(t) end
```

## globals/asset/tryResolve {#globals-asset-tryresolve}

```lua
asset.tryResolve(ref: RefArg, type: string?, base: string?) -> AssetRef?
```

The same lookup `asset.resolve` performs, answering a miss with nil
instead of raising. Every name form, the same `type` narrowing, and the
same handle on success — so "use it if it is there" needs no `pcall`
around a call whose failure would otherwise be indistinguishable from a
real error.

This consults the asset REGISTRY, so it sees registered assets wherever
their files live, `@builtin::` ones included. `asset.exists(name, type)`
answers the narrower question of whether an asset's files are present in
the current mode's store.

A name that reaches more than one asset still raises — that is a question
about the reference, not about presence, and a nil there would report
absence for content that is present twice.

It reaches the asset, so the name carries the same reference contract
`asset.resolve`'s does: a literal is recorded as this source's
dependency, a computed name is a dynamic resolve — free in a tool,
refused on the gameplay path. `asset.exists(name, type)` is the probe a
computed name can ask.

**Parameters**

- `ref` `RefArg` — The asset to look up — an identity, a guid, a VFS path, or a handle.
- `type` `string` _(optional)_ — Category to restrict the match to (optional).
A reference naming a file an importer has since promoted resolves to the
promoted asset, the same as `asset.resolve`.
- `base` `string` _(optional)_ — Referring VFS path a `~` / `~.tail` ref expands against, the same
as `asset.resolve`'s — so the two answer the same question and differ only
in what a miss is.

**Returns** `AssetRef?` — Asset handle, or nil when the reference resolves to no asset.

```lua
local mat = asset.tryResolve(name, "material")
```

## globals/asset/typeRef {#globals-asset-typeref}

```lua
asset.typeRef(target: RefArg) -> string?
```

Return the pinned asset_type reference (the type's guid)
that the asset is an instance of. Resolve the full type with
`asset.resolve(asset.typeRef(target))`. Returns nil for loose
files / assets with no pinned type.

**Parameters**

- `target` `RefArg` — Asset handle / identity / guid / VFS path.

**Returns** `string?` — Pinned type's guid, or nil.

```lua
local t = asset.resolve(asset.typeRef("brick"))
```

## globals/asset/unusableReasons {#globals-asset-unusablereasons}

```lua
asset.unusableReasons() -> { string }
```

Every reason `asset.diagnose` can report an asset unusable for, sorted.

**Returns** `{ string }` — Array of reason names.

```lua
for _, r in ipairs(asset.unusableReasons()) do print(r) end
```

## globals/asset/validate {#globals-asset-validate}

```lua
asset.validate(ref: RefArg, type: string?) -> ValidateResult
```

Validate an asset folder against its type's `type.yaml`, plus
the type's own semantic validation. Structural problems come from
`type.yaml` — missing required files, unsatisfied `one_of_group`
alternatives, and (when `allow_unlisted: false`) unexpected
children. `validated = false` when no `type.yaml` is registered
— nothing structural to check. On top of that, when the asset's
type ships a `behavior.luau` exporting a top-level
`validate(assetRef) -> { { code, message, severity? } }`, its
reported problems (severity defaults to `"error"`) are appended to
`problems`; error-severity problems flip `ok` to false, warnings
leave it untouched. A hook that raises or returns a non-table is
itself reported as a `validate.hook_failed` error problem — a
broken hook blocks. A type with no `validate` export behaves
exactly as the structural check alone.
`world.push` calls this per user asset, so a type's semantic
validation is enforced at publish time with no further wiring.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `ValidateResult` — `{ ok, typeName, validated, problems }`.

```lua
local v = asset.validate("@builtin::components.Camera")
```

## globals/asset/warmup {#globals-asset-warmup}

```lua
asset.warmup(ref: RefArg, opts: WarmupOpts?) -> WarmupResult
```

Warm an asset's bytes into CPU memory and follow its declared
content dependencies to each referenced asset, deduped by
guid. Type-agnostic (reads the generic ref graph) and CPU-only —
never touches the GPU. Side-effect-free name resolution (uses
`asset.guid`/`asset.deps`, not `asset.resolve`).

**Parameters**

- `ref` `RefArg` — Any name the root asset has — handle, identity, guid, or path.
- `opts` `WarmupOpts` _(optional)_ — Optional `{ vias, max }` — restrict ref-edge kinds / cap closure size.

**Returns** `WarmupResult` — `{ closure, count }` — the deduped guid closure warmed and its size.

```lua
local w = asset.warmup("@builtin::scenes.test_arena")
```

## modules/asset/README {#modules-asset-readme}

```lua
require("@builtin/modules/api/engine/asset") -- asset (also available as global 'asset')
```

Asset resolver, ref envelope builder, sidecar metadata. Public Luau surface over the `__asset` Internal FFI namespace.

Usage: local asset = require("@builtin/modules/api/engine/asset")
Also available as global: asset

## modules/asset/add_tag {#modules-asset-add-tag}

```lua
add_tag(ref: RefArg, tag: string)
```

Add a tag to the asset's `.metadata.tags`. Idempotent.
Creates the sidecar and the tags array if missing.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `tag` `string` — Tag to add.

```lua
asset.add_tag("brick", "wip")
```

## modules/asset/alias {#modules-asset-alias}

```lua
alias(ref: RefArg, alias: string): boolean
```

Add a name the asset answers to. `asset.resolve`, leaf
shorthand, and the typed-argument coercion that content refs
travel through all reach the asset by the alias from here on,
exactly as they do by its identity — so a material naming a
shader by its alias resolves, and content written against an
older name keeps working after a rename. The name survives
writes to the asset's own files.
Raises when the name already resolves to a DIFFERENT asset — an
alias extends the identity namespace and never takes a name out
of another asset's hands — and when it is shaped like a guid or
a VFS path, forms that resolve before identity lookup, so an
alias in that shape could never answer.

**Parameters**

- `ref` `RefArg` — The asset gaining the name.
- `alias` `string` — The additional name. Any identity form: a bare leaf
(`standard`) or a scope-qualified path (`@builtin::shaders.legacy`).

```lua
asset.alias("@builtin::shaders.pbr", "standard")
```

## modules/asset/aliases {#modules-asset-aliases}

```lua
aliases(ref: RefArg): { string }
```

The additional names this asset answers to, beyond its own
identity — what `asset.alias` registered, plus the
package-relative `~pkg.tail` form when the asset lives inside a
package.

**Parameters**

- `ref` `RefArg` — Any name the asset has.

```lua
for _, n in asset.aliases("pbr") do print(n) end
```

## modules/asset/canCreate {#modules-asset-cancreate}

```lua
canCreate(typeName: string): boolean
```

Whether `asset.create` can instance `typeName`: the type declares
creation logic (a `behavior.luau` `onCreate` hook) or ships a
`template/` skeleton the hookless fallback clones. A type with neither
— one whose instances only arrive by import — answers false. The query
a creation UI derives its offering from, so what it offers is what
`asset.create` accepts.

**Parameters**

- `typeName` `string` — Registered asset type (e.g. "material", "scene").

```lua
if asset.canCreate(kind) then asset.create(kind, name) end
```

## modules/asset/categories {#modules-asset-categories}

```lua
categories(): { string }
```

List every asset category the engine currently recognises.
Use to discover valid `type` argument values for the rest of
`asset.*`.

```lua
for _, c in asset.categories() do print(c) end
```

## modules/asset/containing {#modules-asset-containing}

```lua
containing(path: string): AssetRef?
```

Walk `path`'s ancestors and return an `AssetRef` handle for
the OUTERMOST category-folder containing it (e.g. `main.scene`
for `"/source/scenes/main.scene/scene.json"`). Returns nil for
paths outside any registered asset type.

**Parameters**

- `path` `string` — VFS path to inspect.

```lua
local a = asset.containing("/source/scenes/main.scene/scene.json")
```

## modules/asset/cpuResident {#modules-asset-cpuresident}

```lua
cpuResident(ref: RefArg, typeName: string?): boolean
```

True when the asset is CPU-resident — a live script-component context
holds it (a component's assetRef field, or an imperative
`asset.resolve`/`ref` made while a component is the caller), which is what
warms its bytes into memory. The CPU pool is a different pool from the
device's: `asset.observe().cpu` lists it, `asset.observe().textures` /
`.meshes` list what the device holds, and an asset can be in one and not
the other.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.
- `typeName` `string?` _(optional)_ — Category to restrict the match to. Omit to search every category.

```lua
if asset.cpuResident(ref) then print("bytes are warm") end
```

## modules/asset/create {#modules-asset-create}

```lua
create(typeName: string, name: string, opts: { [string]: any }?): (AssetRef, { durable: boolean, warning: string?, playShadow: string?, shadowed: { string }? })
```

Instance a new asset of an existing type. Runs the type's
`behavior.luau` `onCreate(name, opts)` hook to produce the asset's
files, then writes them under `/source/<name>.<type>/`. This is the
single generic asset-creation API. Refuses to clobber an existing
edit-mode asset unless `opts.overwrite = true`, which re-authors it in
place and keeps the existing guid (only the checksum changes). Pairs with
`asset.exists` for content generators that re-run over the same names.

A create made from a script component's callback or a scene entrypoint is
output the world reproduces on every load, so it is filed in the ephemeral
`/runtime/assets/` store instead, where the saved manifest never carries a
second copy of it. `name` and `folder` spell the same IDENTITY in either
store, so a reference written against that identity resolves the asset
wherever the call filed it, and one generator run from an `execute` and
from a component names one asset.

**Parameters**

- `typeName` `string` — The asset category to instance — one of `asset.categories()`.
- `name` `string` — The new asset's name.
- `opts` `{ [string]: any }?` _(optional)_ — Optional table forwarded to the type's `onCreate` hook, minus four
framework keys consumed here and never seen by the hook:
`folder` (a relative subfolder under `/source` to author the asset in, so
generated content groups instead of accumulating at the source root, and
the asset's identity carries that folder as its dotted prefix),
`into` (author INSIDE a resolved container ref), `dest` (an absolute
destination path), and `overwrite` (re-author in place, keeping the guid).

```lua
local m = asset.create("material", "brick", { shader = "pbr" })
local mesh = asset.create("mesh", "tree", { positions = {...}, indices = {...} })
local mesh = asset.create("mesh", "rock", { positions = {...}, indices = {...}, folder = "terrain/props" })
local mesh = asset.create("mesh", name, { positions = p, indices = i, overwrite = true }) -- idempotent rebuild
local clip, d = asset.create("soundClip", "hit", { pcm = buf, sampleRate = 48000, channels = 1 })
if not d.durable then log.warn(d.warning .. " " .. d.playShadow) end
```

## modules/asset/declareReferenceArg {#modules-asset-declarereferencearg}

```lua
declareReferenceArg(call: string, position: number, assetType: string)
```

Declare that `call`'s argument at 1-based `position` names an
asset of `type`, so a string literal written there is recorded as a
reference. The positional counterpart of
`asset.declareReferenceField`, for a call that takes its asset as a
plain argument — including a world's own spawn helper, which is
where a name most often stops being visible to the reference graph.
A lookup whose asset is its FIRST argument (`asset.resolve` and its
siblings) is already read and needs no declaration. Only a literal —
or a name the file holds in a top-level string constant — is
recorded; anything computed is reported as a dynamic resolve.

**Parameters**

- `call` `string` — The callee as it is written at a call site.
- `position` `number` — Which argument holds the name, counting from 1.
- `assetType` `string`

```lua
asset.declareReferenceArg("spawnModel", 3, "mesh")
```

## modules/asset/declareReferenceField {#modules-asset-declarereferencefield}

```lua
declareReferenceField(call: string, field: string, assetType: string)
```

Declare that `call`'s options table names an asset of `type` in
its `field`, so a string literal written there is recorded as a
reference by whatever writes the file. This is what puts an API
that takes an asset BY NAME into the reference graph: the named
asset becomes a dependency, travels with the content that names it
into a pack or a pull, and a name nothing answers to becomes an
unresolved dependency `worldValidation` reports and the push gate
refuses. A field holding a TABLE of names — a material's `textures`
— records every name in it. Declare once, beside the API; a call
taking its asset as the FIRST positional argument is already read
and needs no declaration. Only a literal is recorded; a computed
name resolves at runtime and is reported as a dynamic resolve.

**Parameters**

- `call` `string` — The callee as it is written at a call site.
- `field` `string` — The options-table field holding the name, read at the
table's own level.
- `assetType` `string`

```lua
asset.declareReferenceField("fx.beam", "material", "material")
```

## modules/asset/declareReferenceKey {#modules-asset-declarereferencekey}

```lua
declareReferenceKey(assetType: string, key: string, refType: string)
```

Declare that, in a data file belonging to an `assetType` asset,
the top-level `key` names an asset of `type` — a `.material`'s
`mat.yaml` naming the shader it draws with and the textures it
binds. The names a format holds are references as surely as ones
written in code: recording them carries a material's shader along
with the material into a pack or a pull, and turns a name nothing
answers to into an unresolved dependency instead of a surface that
renders as the magenta error material. A key holding a table of
names records one per entry.

**Parameters**

- `assetType` `string` — The category owning the file, e.g. "material".
- `key` `string` — The top-level key holding the name(s).
- `refType` `string`

```lua
asset.declareReferenceKey("material", "shader", "shader")
```

## modules/asset/deps {#modules-asset-deps}

```lua
deps(ref: RefArg, type: string?): DepsResult
```

Return the asset's outbound dependency graph — every other
asset recorded as a content dependency of it.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string?` _(optional)_ — Category hint (optional).

```lua
for _, d in asset.deps("main.scene").deps do print(d.asset_guid) end
```

## modules/asset/describe {#modules-asset-describe}

```lua
describe(typeName: string): DescribeResult
```

The creation contract for an asset type: the parameters its
`onCreate(name, opts)` hook accepts, as data. `kind` is "schema"
(typed contract), "legacy" (untyped opts — anything passes), "none"
(template scaffold — takes no opts), or "error" (the type's schema
failed to parse; `error` says why). `contract` is the human-readable
rendering validation errors print.

**Parameters**

- `typeName` `string` — Registered asset type to describe (e.g. "texture").

```lua
local contract = asset.describe("texture").contract
```

## modules/asset/diagnose {#modules-asset-diagnose}

```lua
diagnose(ref: RefArg): any
```

Why one asset can or cannot be used, read from the engine rather than
from what the caller asked for. Always carries `usable`; when false,
`reason` is one of `asset.unusableReasons()` and `detail` is the engine's
own message. `primary` names the file the type's declared `primary` list
resolved to, so an asset that loaded a preview image instead of its
payload shows the wrong filename rather than a successful load. The
payload's bytes are read by the engine's own decoder wherever it has one
for that container, so `usable` is the verdict a load would reach and the
call costs that decode.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.

```lua
local d = asset.diagnose("myTex") if not d.usable then print(d.reason, d.detail) end
```

## modules/asset/exists {#modules-asset-exists}

```lua
exists(name: string, typeName: string): boolean
```

**Parameters**

- `name` `string`
- `typeName` `string`

## modules/asset/get_field {#modules-asset-get-field}

```lua
get_field(ref: RefArg, key: string): any
```

Read one top-level field from the asset's `.metadata`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.

```lua
local author = asset.get_field("brick", "author")
local settings = asset.get_field("tree", "settings") -- → a Luau table
```

## modules/asset/gpuResident {#modules-asset-gpuresident}

```lua
gpuResident(ref: RefArg): boolean
```

True when the device holds a texture or mesh under this asset's guid,
read off the inventory the renderer publishes.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.

```lua
print(asset.gpuResident("@builtin::models.Sample.DamagedHelmet"))
```

## modules/asset/guid {#modules-asset-guid}

```lua
guid(ref: RefArg, type: string?): string
```

Return the guid for an asset.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string?` _(optional)_ — Category hint (optional).

```lua
local g = asset.guid("@builtin::components.Camera")
```

## modules/asset/has_field {#modules-asset-has-field}

```lua
has_field(ref: RefArg, key: string): boolean
```

True when the asset's `.metadata` carries the named field.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.

```lua
if asset.has_field("brick", "author") then end
```

## modules/asset/has_tag {#modules-asset-has-tag}

```lua
has_tag(ref: RefArg, tag: string): boolean
```

True when the asset's `.metadata.tags` contains `tag`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `tag` `string` — Tag to check for.

```lua
if asset.has_tag("brick", "wip") then end
```

## modules/asset/identity {#modules-asset-identity}

```lua
identity(ref: RefArg, type: string?): string
```

Return the canonical identity for an asset.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string?` _(optional)_ — Category hint (optional).

```lua
local id = asset.identity("brick")
```

## modules/asset/import {#modules-asset-import}

```lua
import(path: string): string?
```

Import a raw source file NOW and return the produced asset path
(a `.bundle` for a model, `.texture` for an image, `.audio` for a
sound, …), or nil if no importer claims it. This is the deterministic,
on-demand counterpart to the engine's automatic import-on-write: it runs
in the calling task and returns only when the import is complete. Pair it
with a quiet write — `vfs.write(path, bytes, { quiet = true })` lands the
raw bytes without firing the automatic importer, then `asset.import(path)`
imports them under your control, so you can act on the result instead of
polling for the import to appear.

**Parameters**

- `path` `string` — The raw source VFS path to import (e.g. a just-written `.glb`).

```lua
local bundle = asset.import("/zero/source/generated/chest.glb")
```

## modules/asset/inspect {#modules-asset-inspect}

```lua
inspect(ref: RefArg, type: string?): InspectRecord
```

Everything known about one asset in a single record: identity, guid,
source, type, scope and origin, its description and tags, the ref methods
its type exposes, and the type's own `inspect` detail when it declares one.
The read-everything counterpart to `asset.resolve`, which hands back a ref.

**Parameters**

- `ref` `RefArg` — An `AssetRef`, an identity string, or a path.
- `type` `string?` _(optional)_ — Narrow the resolve to one asset type when assets of several
categories answer to the same bare name.

```lua
local rec = asset.inspect("@builtin::materials.default")
local rec = asset.inspect(name, "mesh"); print(rec.guid, #rec.tags)
```

## modules/asset/list {#modules-asset-list}

```lua
list(type_or_opts: (AssetCategory | ListOpts)?, scope: string?, opts: ListOpts?): ListResult
```

Query registered assets, returning each match as a resolved
`AssetRef` handle. Every filter narrows the same enumeration and
they compose: `path` selects a VFS subtree (the folder and
everything under it), `type` keeps only those asset types within
it, `scope` keeps only that scope, and `fields` keeps only assets
whose `.metadata` matches. `type` and `path` each take one value or
a list matching any of its entries, and `all` / `any` / `none`
group whole filters — `none` excludes what it matches. `order`,
`limit`, and `offset` shape the result: matches come back ordered
by identity unless `order` names another field
(`name` / `path` / `type` / `guid`).
Each entry is the same envelope `asset.resolve` returns (`__ref` /
`type` / `name` / `guid` / `identity` / `path`), so it can be passed
anywhere an AssetRef is accepted, and the result carries
`:first()` / `:random()` / `:filter()` / `:sort()` and friends.
`type` takes the same values `asset.categories()` lists. The first
positional argument is a path when it is absolute, a type
otherwise. An unknown key raises, as does a table setting both
`type` and its older spelling `category`.
A static (literal) type or path makes the enumeration part of the
calling file's content dependencies when it is saved — the set
travels with published content, so consumers get at-least the
authoring world's assets.

**Parameters**

- `type_or_opts` `(AssetCategory | ListOpts)?` _(optional)_ — Type or VFS path filter (a static literal so the
enumeration can be captured for publish), or the full query table.
- `scope` `string?` _(optional)_ — Scope filter (when first arg is a type).
- `opts` `ListOpts?` _(optional)_ — The query table — see `ListOpts`.

```lua
local hero = asset.list({ path = "/zero/source/props", type = "mesh", fields = { tags = "hero" } }):first()
```

## modules/asset/list_field_values {#modules-asset-list-field-values}

```lua
list_field_values(key: string): { any }
```

Distinct values seen for the named field across every
asset's `.metadata`.

**Parameters**

- `key` `string` — Field name.

```lua
local authors = asset.list_field_values("author")
```

## modules/asset/list_fields {#modules-asset-list-fields}

```lua
list_fields(): { string }
```

Distinct top-level field keys observed across every asset's
`.metadata`. Useful for tooling discovering custom keys in use.

```lua
for _, k in asset.list_fields() do print(k) end
```

## modules/asset/meta {#modules-asset-meta}

```lua
meta(ref: RefArg, type: string?): AssetMeta
```

Read the asset's engine-owned identity record (guid /
checksum). Distinct from `.metadata` (agent-editable);
for that use `asset.metadata`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string?` _(optional)_ — Category hint (optional).

```lua
local m = asset.meta("brick") -- { guid = ..., checksum = ... }
```

## modules/asset/metadata {#modules-asset-metadata}

```lua
metadata(ref: RefArg, type: string?): AssetMeta
```

Read the asset's agent-editable `.metadata` sidecar as a
Lua table. Missing sidecar returns `{}`. Distinct from
`asset.meta` (engine-owned).

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string?` _(optional)_ — Category hint (optional).

```lua
local md = asset.metadata("brick")
```

## modules/asset/observe {#modules-asset-observe}

```lua
observe(): any
```

What the engine is holding for content right now, in one reading:
`textures` and `meshes` (one row per resource the device holds, each with
the bytes it costs, its dimensions or buffer split, and where it came
from), `cpu` (one row per asset a live script-component context holds),
and `totals` — the aggregates those rows sum to, so the listing reconciles
against `renderer.textureMemory()` and `renderer.gpuMemory()`.

Each pool is named because they are different pools: an asset can be on
the device and not CPU-resident, or the reverse. `devicePublished` is
false when no renderer has published an inventory and `cpuPublished` when
the scripting VM has not published its pool — an engine that cannot answer
reads differently from one answering with nothing resident.

```lua
local r = asset.observe() print(#r.textures, r.totals.textureBytes)
```

## modules/asset/preview {#modules-asset-preview}

```lua
preview(ref: RefArg, opts: { [string]: any }?, type: string?): { [string]: any }
```

Render a preview of an asset. Resolves the ref and dispatches to its
type's `preview` ref-method when present; otherwise returns the
`{ available = false }` sentinel ("no preview available for this type").

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `opts` `{ [string]: any }?` _(optional)_ — Optional `{ size = { width, height }, angle = { yaw, pitch } }`.
- `type` `string?` _(optional)_ — Category hint (optional).

```lua
local p = asset.preview("@builtin::materials.gold", { size = { width = 512, height = 512 } })
```

## modules/asset/primaryFile {#modules-asset-primaryfile}

```lua
primaryFile(ref: RefArg): any
```

The file the asset type's declared `primary` list resolves to inside
this asset, as the loader itself resolves it. `resolved` is false when no
declaration matched and `path` is then absent; `declared` is the type's
own primary list in match order.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.

```lua
print(asset.primaryFile("myTex").path)
```

## modules/asset/ref {#modules-asset-ref}

```lua
ref(ref: RefArg, type: string?): AssetRef
```

Build a reference handle for an asset — the canonical ref
envelope constructor. Identical shape to `asset.resolve`;
preferred name for the author-side use case (embedding refs in
YAML / JSON / Luau output).

Naming the asset here reads exactly as naming it in `asset.resolve`,
down to raising on a miss: a literal is recorded as this source's
dependency, a computed name is a dynamic resolve — free in a tool,
refused on the gameplay path. `asset.exists(name, type)` is the probe a
computed name can ask.

**Parameters**

- `ref` `RefArg` — The asset to reference — an identity, a guid, a VFS path, or a handle.
- `type` `string?` _(optional)_ — Category hint (optional).

```lua
local r = asset.ref("animations.idle", "animation")
```

## modules/asset/reloadPending {#modules-asset-reloadpending}

```lua
reloadPending(ref: RefArg, typeName: string?): boolean
```

True while a write to this asset still owes it a reload — the write is
inside the settle window that collects one authoring step's writes, or its
reload is queued and the engine has not run it yet. False means every
content change written so far has reached its subscribers, so a consumer
bound to the asset now cannot be interrupted by a reload the earlier
writes already earned. The recording is synchronous with the write, so a
call made right after one already reads true.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.
- `typeName` `string?` _(optional)_ — Category to restrict the match to. Omit to search every category.

```lua
repeat task.wait() until not asset.reloadPending(ref)
```

## modules/asset/reloadSeq {#modules-asset-reloadseq}

```lua
reloadSeq(ref: RefArg, typeName: string?): number
```

How many content-change reloads this asset has been through — the
count of `onAssetReload` dispatches the engine has RUN for it. A write to
a file inside an asset does not reload it on the spot: the writes of one
authoring step are collected for a settle window and the reload runs on a
later frame. Read this, write, then poll for a larger number to learn the
write's reload has actually reached subscribers. Monotonic per asset and
session-scoped; 0 for an asset whose content has not changed since boot.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.
- `typeName` `string?` _(optional)_ — Category to restrict the match to. Omit to search every category.

```lua
local at = asset.reloadSeq(ref)
vfs.write(asset.source(ref) .. "/graph.json", encoded)
repeat task.wait() until asset.reloadSeq(ref) > at
```

## modules/asset/remove_field {#modules-asset-remove-field}

```lua
remove_field(ref: RefArg, key: string)
```

Remove one top-level field from the asset's `.metadata`.
No-op when the field isn't present.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.

```lua
asset.remove_field("brick", "author")
```

## modules/asset/remove_tag {#modules-asset-remove-tag}

```lua
remove_tag(ref: RefArg, tag: string)
```

Remove a tag from the asset's `.metadata.tags`. No-op when
the tag isn't present.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `tag` `string` — Tag to remove.

```lua
asset.remove_tag("brick", "wip")
```

## modules/asset/resolve<C> {#modules-asset-resolve-c}

```lua
resolve<C>(ref: RefArg, type: (C & string)?): AssetRef<C>
```

Find an asset. The returned handle carries every name form
the asset has (`guid`, `identity`, `path`, `type`) so
downstream code can read any one of them without calling
`resolve` again. Raises when `ref` resolves to no asset — or, with a
`type`, to no asset of that type — and when `ref` reaches more than one
asset, where it names the candidates for you to pick from instead of
picking one of them. A `<scope>::`-qualified identity reaches exactly one:
`@root::name` for the asset this world holds at its source root, the
library identity (`@builtin::…`) for a library's. For the same lookup
answering a miss with nil, use `asset.tryResolve(ref, type)`.

A name written as a string LITERAL is recorded as this source's
dependency on that asset, so the asset travels with the content and
still resolves once someone installs it in another world. A COMPUTED
name cannot be written down, so nothing pins what it reaches: that is a
dynamic resolve — free in a tool, refused on the gameplay path (a
component or scene entrypoint). `asset.tryResolve`, `asset.ref` and
`asset.source` read the name they are given exactly this way too, so
which of the four you reach for changes neither answer. To ask whether a
computed name has files without reaching a handle, use
`asset.exists(name, type)`.

**Parameters**

- `ref` `RefArg` — The asset to find — an identity, a guid, a VFS path, or a handle.
- `type` `(C & string)?` _(optional)_ — Category to restrict the match to (optional). Separates a bare
name that assets of different categories share (`asset.resolve("cube",
"mesh")`); where several assets of the SAME category answer to it, the
scope-qualified identity is what separates them.
A reference naming a file an importer has since promoted (`wall.png`
after the texture importer turned it into `wall.texture`) resolves to the
promoted asset, and says so in the log once per reference.

```lua
local a = asset.resolve("@builtin::components.Camera")
```

## modules/asset/set_field {#modules-asset-set-field}

```lua
set_field(ref: RefArg, key: string, value: any)
```

Set one field in the asset's `.metadata`, creating the sidecar if missing.
Sibling fields are preserved. When the new value AND the existing value are
both maps (objects), the new value DEEP-MERGES into the existing one, so
writing one sub-key never drops the others — `set_field(ref, "settings",
{ keepCpu = true })` keeps every other setting. Arrays and scalars replace.
Clear a whole field with `asset.remove_field`; replace the entire sidecar with
`asset.set_metadata`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.
- `value` `any` _(optional)_ — Field value (any JSON-serialisable Lua value).

```lua
asset.set_field("brick", "author", "me")
asset.set_field("tree", "settings", { keepCpu = true }) -- merges; sibling settings kept
```

## modules/asset/set_metadata {#modules-asset-set-metadata}

```lua
set_metadata(ref: RefArg, data: AssetMeta)
```

Replace the asset's `.metadata` sidecar with the given
table. Pass an empty table to clear all fields.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `data` `AssetMeta` — Full JSON-shaped contents for the sidecar.

```lua
asset.set_metadata("brick", { author = "me", tags = { "wip" } })
```

## modules/asset/source {#modules-asset-source}

```lua
source(ref: RefArg, type: string?): string
```

Return the VFS source path for an asset.

It reaches the asset, so the name carries the same reference contract
`asset.resolve`'s does: a literal is recorded as this source's
dependency, a computed name is a dynamic resolve — free in a tool,
refused on the gameplay path. `asset.exists(name, type)` is the probe a
computed name can ask.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string?` _(optional)_ — Category hint (optional).

```lua
local p = asset.source("brick") -- "/source/brick.material"
```

## modules/asset/storeHoldsIdentity {#modules-asset-storeholdsidentity}

```lua
storeHoldsIdentity(identity: string, typeName: string, reproduced: boolean): boolean
```

Whether an asset of type `typeName` that `name` reaches has files in
the store `asset.create` writes from this context — the authored `/source`
tree, plus the ephemeral `/runtime/assets/` store when the caller is a
script component or a scene entrypoint, which is where a create there
files it. `name` is any name the registry answers to for that asset: its
canonical identity (`"a.b.thing"`), a registered alias, or the bare leaf
(`"thing"`) — the spelling `asset.list` publishes as an entry's `name` and
the spelling `asset.create` was given alongside `opts.folder`. So the
`if not asset.exists(n, t) then asset.create(t, n, opts) end` pairing is
satisfied by its own create, spelled the way the create was, in either
store and at any folder depth. A plain existence probe — it does NOT
resolve a handle or pin a content dependency, so it is safe to call with a
COMPUTED name (unlike `asset.resolve`, whose handle would become a
static-pinned dependency).

The name is read the way `asset.resolve` reads one; the ANSWER comes from
the store, so a registered asset whose files live outside the store this
context writes — every `@builtin::` asset among them — is `false`.
`asset.tryResolve(name, typeName)` asks the registry the wider question
and hands back the handle — and because it reaches the asset, the name it
is given follows the reference rule every lookup follows: a literal is
pinned, a computed one is a dynamic resolve. So a name this source
computed is what `asset.exists` is for.
This call names the asset FIRST and its category second, as every
`asset.*` call taking both does except `asset.create(category, name,
opts)`. A call carrying a category where the asset goes and a non-category
where the category goes is refused, naming which way round the call reads,
rather than reporting the asset absent.

**Parameters**

- `identity` `string`
- `typeName` `string` — The asset type (e.g. "mesh", "texture", "material") — one of
`asset.categories()`.
- `reproduced` `boolean`

```lua
if not asset.exists(meshName, "mesh") then asset.create("mesh", meshName, geo) end
if not asset.exists(n, "mesh") then asset.create("mesh", n, { folder = "props", positions = p, indices = i }) end
```

## modules/asset/tags {#modules-asset-tags}

```lua
tags(ref: RefArg): { string }
```

Convenience read of the `.metadata.tags` array.

**Parameters**

- `ref` `RefArg` — Any name the asset has.

```lua
for _, t in asset.tags("brick") do print(t) end
```

## modules/asset/tryResolve {#modules-asset-tryresolve}

```lua
tryResolve(ref: RefArg, type: string?, base: string?): AssetRef?
```

The same lookup `asset.resolve` performs, answering a miss with nil
instead of raising. Every name form, the same `type` narrowing, and the
same handle on success — so "use it if it is there" needs no `pcall`
around a call whose failure would otherwise be indistinguishable from a
real error.

This consults the asset REGISTRY, so it sees registered assets wherever
their files live, `@builtin::` ones included. `asset.exists(name, type)`
answers the narrower question of whether an asset's files are present in
the current mode's store.

A name that reaches more than one asset still raises — that is a question
about the reference, not about presence, and a nil there would report
absence for content that is present twice.

It reaches the asset, so the name carries the same reference contract
`asset.resolve`'s does: a literal is recorded as this source's
dependency, a computed name is a dynamic resolve — free in a tool,
refused on the gameplay path. `asset.exists(name, type)` is the probe a
computed name can ask.

**Parameters**

- `ref` `RefArg` — The asset to look up — an identity, a guid, a VFS path, or a handle.
- `type` `string?` _(optional)_ — Category to restrict the match to (optional).
A reference naming a file an importer has since promoted resolves to the
promoted asset, the same as `asset.resolve`.
- `base` `string?` _(optional)_ — Referring VFS path a `~` / `~.tail` ref expands against, the same
as `asset.resolve`'s — so the two answer the same question and differ only
in what a miss is.

```lua
local mat = asset.tryResolve(name, "material")
```

## modules/asset/typeRef {#modules-asset-typeref}

```lua
typeRef(target: RefArg): string?
```

Return the pinned asset_type reference (the type's guid)
that the asset is an instance of. Resolve the full type with
`asset.resolve(asset.typeRef(target))`. Returns nil for loose
files / assets with no pinned type.

**Parameters**

- `target` `RefArg` — Asset handle / identity / guid / VFS path.

```lua
local t = asset.resolve(asset.typeRef("brick"))
```

## modules/asset/unusableReasons {#modules-asset-unusablereasons}

```lua
unusableReasons(): { string }
```

Every reason `asset.diagnose` can report an asset unusable for, sorted.

```lua
for _, r in ipairs(asset.unusableReasons()) do print(r) end
```

## modules/asset/validate {#modules-asset-validate}

```lua
validate(ref: RefArg, type: string?): ValidateResult
```

Validate an asset folder against its type's `type.yaml`, plus
the type's own semantic validation. Structural problems come from
`type.yaml` — missing required files, unsatisfied `one_of_group`
alternatives, and (when `allow_unlisted: false`) unexpected
children. `validated = false` when no `type.yaml` is registered
— nothing structural to check. On top of that, when the asset's
type ships a `behavior.luau` exporting a top-level
`validate(assetRef) -> { { code, message, severity? } }`, its
reported problems (severity defaults to `"error"`) are appended to
`problems`; error-severity problems flip `ok` to false, warnings
leave it untouched. A hook that raises or returns a non-table is
itself reported as a `validate.hook_failed` error problem — a
broken hook blocks. A type with no `validate` export behaves
exactly as the structural check alone.
`world.push` calls this per user asset, so a type's semantic
validation is enforced at publish time with no further wiring.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string?` _(optional)_ — Category hint (optional).

```lua
local v = asset.validate("@builtin::components.Camera")
```

## modules/asset/warmup {#modules-asset-warmup}

```lua
warmup(ref: RefArg, opts: WarmupOpts?): WarmupResult
```

Warm an asset's bytes into CPU memory and follow its declared
content dependencies to each referenced asset, deduped by
guid. Type-agnostic (reads the generic ref graph) and CPU-only —
never touches the GPU. Side-effect-free name resolution (uses
`asset.guid`/`asset.deps`, not `asset.resolve`).

**Parameters**

- `ref` `RefArg` — Any name the root asset has — handle, identity, guid, or path.
- `opts` `WarmupOpts?` _(optional)_ — Optional `{ vias, max }` — restrict ref-edge kinds / cap closure size.

```lua
local w = asset.warmup("@builtin::scenes.test_arena")
```

## typed/builtin//modules/api/engine/asset/asset/add_tag {#typed-builtin-modules-api-engine-asset-asset-add-tag}

```lua
asset.add_tag(ref: RefArg, tag: string)
```

Add a tag to the asset's `.metadata.tags`. Idempotent.
Creates the sidecar and the tags array if missing.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `tag` `string` — Tag to add.

```lua
asset.add_tag("brick", "wip")
```

## typed/builtin//modules/api/engine/asset/asset/alias {#typed-builtin-modules-api-engine-asset-asset-alias}

```lua
asset.alias(ref: RefArg, alias: string) -> boolean
```

Add a name the asset answers to. `asset.resolve`, leaf
shorthand, and the typed-argument coercion that content refs
travel through all reach the asset by the alias from here on,
exactly as they do by its identity — so a material naming a
shader by its alias resolves, and content written against an
older name keeps working after a rename. The name survives
writes to the asset's own files.
Raises when the name already resolves to a DIFFERENT asset — an
alias extends the identity namespace and never takes a name out
of another asset's hands — and when it is shaped like a guid or
a VFS path, forms that resolve before identity lookup, so an
alias in that shape could never answer.

**Parameters**

- `ref` `RefArg` — The asset gaining the name.
- `alias` `string` — The additional name. Any identity form: a bare leaf
(`standard`) or a scope-qualified path (`@builtin::shaders.legacy`).

**Returns** `boolean` — True when newly added, false when the asset already answered to it.

```lua
asset.alias("@builtin::shaders.pbr", "standard")
```

## typed/builtin//modules/api/engine/asset/asset/aliases {#typed-builtin-modules-api-engine-asset-asset-aliases}

```lua
asset.aliases(ref: RefArg) -> { string }
```

The additional names this asset answers to, beyond its own
identity — what `asset.alias` registered, plus the
package-relative `~pkg.tail` form when the asset lives inside a
package.

**Parameters**

- `ref` `RefArg` — Any name the asset has.

**Returns** `{ string }` — Array of alias names in canonical identity form.

```lua
for _, n in asset.aliases("pbr") do print(n) end
```

## typed/builtin//modules/api/engine/asset/asset/canCreate {#typed-builtin-modules-api-engine-asset-asset-cancreate}

```lua
asset.canCreate(typeName: string) -> boolean
```

Whether `asset.create` can instance `typeName`: the type declares
creation logic (a `behavior.luau` `onCreate` hook) or ships a
`template/` skeleton the hookless fallback clones. A type with neither
— one whose instances only arrive by import — answers false. The query
a creation UI derives its offering from, so what it offers is what
`asset.create` accepts.

**Parameters**

- `typeName` `string` — Registered asset type (e.g. "material", "scene").

**Returns** boolean — true when `asset.create(typeName, …)` can produce one.

```lua
if asset.canCreate(kind) then asset.create(kind, name) end
```

## typed/builtin//modules/api/engine/asset/asset/categories {#typed-builtin-modules-api-engine-asset-asset-categories}

```lua
asset.categories() -> { string }
```

List every asset category the engine currently recognises.
Use to discover valid `type` argument values for the rest of
`asset.*`.

**Returns** `{ string }` — Array of category names.

```lua
for _, c in asset.categories() do print(c) end
```

## typed/builtin//modules/api/engine/asset/asset/containing {#typed-builtin-modules-api-engine-asset-asset-containing}

```lua
asset.containing(path: string) -> AssetRef?
```

Walk `path`'s ancestors and return an `AssetRef` handle for
the OUTERMOST category-folder containing it (e.g. `main.scene`
for `"/source/scenes/main.scene/scene.json"`). Returns nil for
paths outside any registered asset type.

**Parameters**

- `path` `string` — VFS path to inspect.

**Returns** `AssetRef?` — AssetRef handle, or nil.

```lua
local a = asset.containing("/source/scenes/main.scene/scene.json")
```

## typed/builtin//modules/api/engine/asset/asset/cpuResident {#typed-builtin-modules-api-engine-asset-asset-cpuresident}

```lua
asset.cpuResident(ref: RefArg, typeName: string?) -> boolean
```

True when the asset is CPU-resident — a live script-component context
holds it (a component's assetRef field, or an imperative
`asset.resolve`/`ref` made while a component is the caller), which is what
warms its bytes into memory. The CPU pool is a different pool from the
device's: `asset.observe().cpu` lists it, `asset.observe().textures` /
`.meshes` list what the device holds, and an asset can be in one and not
the other.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.
- `typeName` `string` _(optional)_ — Category to restrict the match to. Omit to search every category.

**Returns** boolean — true when a live context holds it.

```lua
if asset.cpuResident(ref) then print("bytes are warm") end
```

## typed/builtin//modules/api/engine/asset/asset/create {#typed-builtin-modules-api-engine-asset-asset-create}

```lua
asset.create(typeName: string, name: string, opts: { [string]: any }?) -> (AssetRef, { durable: boolean, warning: string?, playShadow: string?, shadowed: { string }? })
```

Instance a new asset of an existing type. Runs the type's
`behavior.luau` `onCreate(name, opts)` hook to produce the asset's
files, then writes them under `/source/<name>.<type>/`. This is the
single generic asset-creation API. Refuses to clobber an existing
edit-mode asset unless `opts.overwrite = true`, which re-authors it in
place and keeps the existing guid (only the checksum changes). Pairs with
`asset.exists` for content generators that re-run over the same names.

A create made from a script component's callback or a scene entrypoint is
output the world reproduces on every load, so it is filed in the ephemeral
`/runtime/assets/` store instead, where the saved manifest never carries a
second copy of it. `name` and `folder` spell the same IDENTITY in either
store, so a reference written against that identity resolves the asset
wherever the call filed it, and one generator run from an `execute` and
from a component names one asset.

**Parameters**

- `typeName` `string` — Registered asset type to instance (e.g. "material", "texture").
- `name` `string` — Destination asset name (becomes `/source/<name>.<typeName>`). A
bare identity — pass `opts.folder` to place it in a subfolder rather than
spelling a path here. The accepted shape is the type's to declare:
`^[A-Za-z][A-Za-z0-9_]*$` unless its `behavior.luau` exports a
`namePattern`, as `guide` does to take `getting-started` and `01-overview`.
This call names the category FIRST and the asset second. Every other
`asset.*` call taking both names them the other way round —
`asset.exists(name, category)`, `asset.tryResolve(ref, category)` — so a
create-then-check pair reads `if not asset.exists(n, t) then
asset.create(t, n, opts) end`. A call whose two arguments are read into
each other, at either end of that pair, is refused and told which way
round the call reads.
- `opts` `{ [string]: any }` _(optional)_ — Optional table forwarded to the type's `onCreate` hook, minus four
framework keys consumed here and never seen by the hook:
`folder` (a relative subfolder under `/source` to author the asset in, so
generated content groups instead of accumulating at the source root, and
the asset's identity carries that folder as its dotted prefix),
`into` (author INSIDE a resolved container ref), `dest` (an absolute
destination path), and `overwrite` (re-author in place, keeping the guid).

**Returns** `(AssetRef, { durable: boolean, warning: string?, playShadow: string?, shadowed: { string }? })` — The created asset's `AssetRef` — the SAME interned instance `asset.resolve` returns (guid/__ref/path + the type's ref methods: `:getBytes`, `:ensureHandle`, `:serialize`, …). Disk-only: nothing is uploaded to CPU/GPU. Where the bytes this call landed went, as a SECOND return value: `{ durable = true }` when the files are where the call filed them, and `{ durable = false, warning = …, playShadow = …, shadowed = { … } }` when the play shadow took them — live in this session, disk source untouched, discarded on a guarded play-exit unless kept, with `playShadow` naming the routes that keep them and `shadowed` the paths. `durable` is present whatever the answer is, so its absence is never a reading, and one call answers once for every file it wrote. It is the same answer, off the same shadow set and in the same words, that the MCP `write_file` / `edit_file` / `capture` tools attach to their own results — ask `vfs.durability` for it directly at any other write site.

```lua
local m = asset.create("material", "brick", { shader = "pbr" })
local mesh = asset.create("mesh", "tree", { positions = {...}, indices = {...} })
local mesh = asset.create("mesh", "rock", { positions = {...}, indices = {...}, folder = "terrain/props" })
local mesh = asset.create("mesh", name, { positions = p, indices = i, overwrite = true }) -- idempotent rebuild
local clip, d = asset.create("soundClip", "hit", { pcm = buf, sampleRate = 48000, channels = 1 })
if not d.durable then log.warn(d.warning .. " " .. d.playShadow) end
```

## typed/builtin//modules/api/engine/asset/asset/declareReferenceArg {#typed-builtin-modules-api-engine-asset-asset-declarereferencearg}

```lua
asset.declareReferenceArg(call: string, position: number, assetType: string)
```

Declare that `call`'s argument at 1-based `position` names an
asset of `type`, so a string literal written there is recorded as a
reference. The positional counterpart of
`asset.declareReferenceField`, for a call that takes its asset as a
plain argument — including a world's own spawn helper, which is
where a name most often stops being visible to the reference graph.
A lookup whose asset is its FIRST argument (`asset.resolve` and its
siblings) is already read and needs no declaration. Only a literal —
or a name the file holds in a top-level string constant — is
recorded; anything computed is reported as a dynamic resolve.

**Parameters**

- `call` `string` — The callee as it is written at a call site.
- `position` `number` — Which argument holds the name, counting from 1.
- `assetType` `string`

```lua
asset.declareReferenceArg("spawnModel", 3, "mesh")
```

## typed/builtin//modules/api/engine/asset/asset/declareReferenceField {#typed-builtin-modules-api-engine-asset-asset-declarereferencefield}

```lua
asset.declareReferenceField(call: string, field: string, assetType: string)
```

Declare that `call`'s options table names an asset of `type` in
its `field`, so a string literal written there is recorded as a
reference by whatever writes the file. This is what puts an API
that takes an asset BY NAME into the reference graph: the named
asset becomes a dependency, travels with the content that names it
into a pack or a pull, and a name nothing answers to becomes an
unresolved dependency `worldValidation` reports and the push gate
refuses. A field holding a TABLE of names — a material's `textures`
— records every name in it. Declare once, beside the API; a call
taking its asset as the FIRST positional argument is already read
and needs no declaration. Only a literal is recorded; a computed
name resolves at runtime and is reported as a dynamic resolve.

**Parameters**

- `call` `string` — The callee as it is written at a call site.
- `field` `string` — The options-table field holding the name, read at the
table's own level.
- `assetType` `string`

```lua
asset.declareReferenceField("fx.beam", "material", "material")
```

## typed/builtin//modules/api/engine/asset/asset/declareReferenceKey {#typed-builtin-modules-api-engine-asset-asset-declarereferencekey}

```lua
asset.declareReferenceKey(assetType: string, key: string, refType: string)
```

Declare that, in a data file belonging to an `assetType` asset,
the top-level `key` names an asset of `type` — a `.material`'s
`mat.yaml` naming the shader it draws with and the textures it
binds. The names a format holds are references as surely as ones
written in code: recording them carries a material's shader along
with the material into a pack or a pull, and turns a name nothing
answers to into an unresolved dependency instead of a surface that
renders as the magenta error material. A key holding a table of
names records one per entry.

**Parameters**

- `assetType` `string` — The category owning the file, e.g. "material".
- `key` `string` — The top-level key holding the name(s).
- `refType` `string`

```lua
asset.declareReferenceKey("material", "shader", "shader")
```

## typed/builtin//modules/api/engine/asset/asset/deps {#typed-builtin-modules-api-engine-asset-asset-deps}

```lua
asset.deps(ref: RefArg, type: string?) -> DepsResult
```

Return the asset's outbound dependency graph — every other
asset recorded as a content dependency of it.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `DepsResult` — `{ deps = { { asset_guid, origin, literal, via, ... } } }`.

```lua
for _, d in asset.deps("main.scene").deps do print(d.asset_guid) end
```

## typed/builtin//modules/api/engine/asset/asset/describe {#typed-builtin-modules-api-engine-asset-asset-describe}

```lua
asset.describe(typeName: string) -> DescribeResult
```

The creation contract for an asset type: the parameters its
`onCreate(name, opts)` hook accepts, as data. `kind` is "schema"
(typed contract), "legacy" (untyped opts — anything passes), "none"
(template scaffold — takes no opts), or "error" (the type's schema
failed to parse; `error` says why). `contract` is the human-readable
rendering validation errors print.

**Parameters**

- `typeName` `string` — Registered asset type to describe (e.g. "texture").

**Returns** DescribeResult — the creation contract.

```lua
local contract = asset.describe("texture").contract
```

## typed/builtin//modules/api/engine/asset/asset/diagnose {#typed-builtin-modules-api-engine-asset-asset-diagnose}

```lua
asset.diagnose(ref: RefArg) -> any
```

Why one asset can or cannot be used, read from the engine rather than
from what the caller asked for. Always carries `usable`; when false,
`reason` is one of `asset.unusableReasons()` and `detail` is the engine's
own message. `primary` names the file the type's declared `primary` list
resolved to, so an asset that loaded a preview image instead of its
payload shows the wrong filename rather than a successful load. The
payload's bytes are read by the engine's own decoder wherever it has one
for that container, so `usable` is the verdict a load would reach and the
call costs that decode.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.

**Returns** `any` — DiagnoseRecord

```lua
local d = asset.diagnose("myTex") if not d.usable then print(d.reason, d.detail) end
```

## typed/builtin//modules/api/engine/asset/asset/exists {#typed-builtin-modules-api-engine-asset-asset-exists}

```lua
asset.exists(name: string, typeName: string) -> boolean
```

**Parameters**

- `name` `string`
- `typeName` `string`

**Returns** `boolean`

## typed/builtin//modules/api/engine/asset/asset/get_field {#typed-builtin-modules-api-engine-asset-asset-get-field}

```lua
asset.get_field(ref: RefArg, key: string) -> any
```

Read one top-level field from the asset's `.metadata`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.

**Returns** `any` — Field value or nil.

```lua
local author = asset.get_field("brick", "author")
local settings = asset.get_field("tree", "settings") -- → a Luau table
```

## typed/builtin//modules/api/engine/asset/asset/gpuResident {#typed-builtin-modules-api-engine-asset-asset-gpuresident}

```lua
asset.gpuResident(ref: RefArg) -> boolean
```

True when the device holds a texture or mesh under this asset's guid,
read off the inventory the renderer publishes.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.

**Returns** boolean — true when the device holds it.

```lua
print(asset.gpuResident("@builtin::models.Sample.DamagedHelmet"))
```

## typed/builtin//modules/api/engine/asset/asset/guid {#typed-builtin-modules-api-engine-asset-asset-guid}

```lua
asset.guid(ref: RefArg, type: string?) -> string
```

Return the guid for an asset.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `string` — Guid.

```lua
local g = asset.guid("@builtin::components.Camera")
```

## typed/builtin//modules/api/engine/asset/asset/has_field {#typed-builtin-modules-api-engine-asset-asset-has-field}

```lua
asset.has_field(ref: RefArg, key: string) -> boolean
```

True when the asset's `.metadata` carries the named field.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.

**Returns** `boolean` — True when present.

```lua
if asset.has_field("brick", "author") then end
```

## typed/builtin//modules/api/engine/asset/asset/has_tag {#typed-builtin-modules-api-engine-asset-asset-has-tag}

```lua
asset.has_tag(ref: RefArg, tag: string) -> boolean
```

True when the asset's `.metadata.tags` contains `tag`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `tag` `string` — Tag to check for.

**Returns** `boolean` — True when present.

```lua
if asset.has_tag("brick", "wip") then end
```

## typed/builtin//modules/api/engine/asset/asset/identity {#typed-builtin-modules-api-engine-asset-asset-identity}

```lua
asset.identity(ref: RefArg, type: string?) -> string
```

Return the canonical identity for an asset.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `string` — Canonical identity.

```lua
local id = asset.identity("brick")
```

## typed/builtin//modules/api/engine/asset/asset/import {#typed-builtin-modules-api-engine-asset-asset-import}

```lua
asset.import(path: string) -> string?
```

Import a raw source file NOW and return the produced asset path
(a `.bundle` for a model, `.texture` for an image, `.audio` for a
sound, …), or nil if no importer claims it. This is the deterministic,
on-demand counterpart to the engine's automatic import-on-write: it runs
in the calling task and returns only when the import is complete. Pair it
with a quiet write — `vfs.write(path, bytes, { quiet = true })` lands the
raw bytes without firing the automatic importer, then `asset.import(path)`
imports them under your control, so you can act on the result instead of
polling for the import to appear.

**Parameters**

- `path` `string` — The raw source VFS path to import (e.g. a just-written `.glb`).

**Returns** `string?` — The produced asset path, or nil when nothing claimed it.

```lua
local bundle = asset.import("/zero/source/generated/chest.glb")
```

## typed/builtin//modules/api/engine/asset/asset/inspect {#typed-builtin-modules-api-engine-asset-asset-inspect}

```lua
asset.inspect(ref: RefArg, type: string?) -> InspectRecord
```

Everything known about one asset in a single record: identity, guid,
source, type, scope and origin, its description and tags, the ref methods
its type exposes, and the type's own `inspect` detail when it declares one.
The read-everything counterpart to `asset.resolve`, which hands back a ref.

**Parameters**

- `ref` `RefArg` — An `AssetRef`, an identity string, or a path.
- `type` `string` _(optional)_ — Narrow the resolve to one asset type when assets of several
categories answer to the same bare name.

**Returns** `InspectRecord` — The inspect record.

```lua
local rec = asset.inspect("@builtin::materials.default")
local rec = asset.inspect(name, "mesh"); print(rec.guid, #rec.tags)
```

## typed/builtin//modules/api/engine/asset/asset/list {#typed-builtin-modules-api-engine-asset-asset-list}

```lua
asset.list(type_or_opts: (AssetCategory | ListOpts)?, scope: string?, opts: ListOpts?) -> ListResult
```

Query registered assets, returning each match as a resolved
`AssetRef` handle. Every filter narrows the same enumeration and
they compose: `path` selects a VFS subtree (the folder and
everything under it), `type` keeps only those asset types within
it, `scope` keeps only that scope, and `fields` keeps only assets
whose `.metadata` matches. `type` and `path` each take one value or
a list matching any of its entries, and `all` / `any` / `none`
group whole filters — `none` excludes what it matches. `order`,
`limit`, and `offset` shape the result: matches come back ordered
by identity unless `order` names another field
(`name` / `path` / `type` / `guid`).
Each entry is the same envelope `asset.resolve` returns (`__ref` /
`type` / `name` / `guid` / `identity` / `path`), so it can be passed
anywhere an AssetRef is accepted, and the result carries
`:first()` / `:random()` / `:filter()` / `:sort()` and friends.
`type` takes the same values `asset.categories()` lists. The first
positional argument is a path when it is absolute, a type
otherwise. An unknown key raises, as does a table setting both
`type` and its older spelling `category`.
A static (literal) type or path makes the enumeration part of the
calling file's content dependencies when it is saved — the set
travels with published content, so consumers get at-least the
authoring world's assets.

**Parameters**

- `type_or_opts` `(AssetCategory | ListOpts)` _(optional)_ — Type or VFS path filter (a static literal so the
enumeration can be captured for publish), or the full query table.
- `scope` `string` _(optional)_ — Scope filter (when first arg is a type).
- `opts` `ListOpts` _(optional)_ — The query table — see `ListOpts`.

**Returns** `ListResult` — The matched AssetRef handles, as a result carrying query methods.

```lua
local hero = asset.list({ path = "/zero/source/props", type = "mesh", fields = { tags = "hero" } }):first()
```

## typed/builtin//modules/api/engine/asset/asset/list_field_values {#typed-builtin-modules-api-engine-asset-asset-list-field-values}

```lua
asset.list_field_values(key: string) -> { any }
```

Distinct values seen for the named field across every
asset's `.metadata`.

**Parameters**

- `key` `string` — Field name.

**Returns** `{ any }` — Array of distinct values.

```lua
local authors = asset.list_field_values("author")
```

## typed/builtin//modules/api/engine/asset/asset/list_fields {#typed-builtin-modules-api-engine-asset-asset-list-fields}

```lua
asset.list_fields() -> { string }
```

Distinct top-level field keys observed across every asset's
`.metadata`. Useful for tooling discovering custom keys in use.

**Returns** `{ string }` — Array of field names.

```lua
for _, k in asset.list_fields() do print(k) end
```

## typed/builtin//modules/api/engine/asset/asset/meta {#typed-builtin-modules-api-engine-asset-asset-meta}

```lua
asset.meta(ref: RefArg, type: string?) -> AssetMeta
```

Read the asset's engine-owned identity record (guid /
checksum). Distinct from `.metadata` (agent-editable);
for that use `asset.metadata`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `AssetMeta` — Metadata table.

```lua
local m = asset.meta("brick") -- { guid = ..., checksum = ... }
```

## typed/builtin//modules/api/engine/asset/asset/metadata {#typed-builtin-modules-api-engine-asset-asset-metadata}

```lua
asset.metadata(ref: RefArg, type: string?) -> AssetMeta
```

Read the asset's agent-editable `.metadata` sidecar as a
Lua table. Missing sidecar returns `{}`. Distinct from
`asset.meta` (engine-owned).

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `AssetMeta` — JSON-shaped table; empty when no sidecar exists.

```lua
local md = asset.metadata("brick")
```

## typed/builtin//modules/api/engine/asset/asset/observe {#typed-builtin-modules-api-engine-asset-asset-observe}

```lua
asset.observe() -> any
```

What the engine is holding for content right now, in one reading:
`textures` and `meshes` (one row per resource the device holds, each with
the bytes it costs, its dimensions or buffer split, and where it came
from), `cpu` (one row per asset a live script-component context holds),
and `totals` — the aggregates those rows sum to, so the listing reconciles
against `renderer.textureMemory()` and `renderer.gpuMemory()`.

Each pool is named because they are different pools: an asset can be on
the device and not CPU-resident, or the reverse. `devicePublished` is
false when no renderer has published an inventory and `cpuPublished` when
the scripting VM has not published its pool — an engine that cannot answer
reads differently from one answering with nothing resident.

**Returns** `any` — ResidencyReading

```lua
local r = asset.observe() print(#r.textures, r.totals.textureBytes)
```

## typed/builtin//modules/api/engine/asset/asset/preview {#typed-builtin-modules-api-engine-asset-asset-preview}

```lua
asset.preview(ref: RefArg, opts: { [string]: any }?, type: string?) -> { [string]: any }
```

Render a preview of an asset. Resolves the ref and dispatches to its
type's `preview` ref-method when present; otherwise returns the
`{ available = false }` sentinel ("no preview available for this type").

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `opts` `{ [string]: any }` _(optional)_ — Optional `{ size = { width, height }, angle = { yaw, pitch } }`.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `{ [string]: any }` — `{ available, imageBase64?, width?, height?, bounds?, stats?, reason? }`.

```lua
local p = asset.preview("@builtin::materials.gold", { size = { width = 512, height = 512 } })
```

## typed/builtin//modules/api/engine/asset/asset/primaryFile {#typed-builtin-modules-api-engine-asset-asset-primaryfile}

```lua
asset.primaryFile(ref: RefArg) -> any
```

The file the asset type's declared `primary` list resolves to inside
this asset, as the loader itself resolves it. `resolved` is false when no
declaration matched and `path` is then absent; `declared` is the type's
own primary list in match order.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.

**Returns** `any` — `{ path: string?, resolved: boolean, isFolder: boolean, declared: { string } }`

```lua
print(asset.primaryFile("myTex").path)
```

## typed/builtin//modules/api/engine/asset/asset/ref {#typed-builtin-modules-api-engine-asset-asset-ref}

```lua
asset.ref(ref: RefArg, type: string?) -> AssetRef
```

Build a reference handle for an asset — the canonical ref
envelope constructor. Identical shape to `asset.resolve`;
preferred name for the author-side use case (embedding refs in
YAML / JSON / Luau output).

Naming the asset here reads exactly as naming it in `asset.resolve`,
down to raising on a miss: a literal is recorded as this source's
dependency, a computed name is a dynamic resolve — free in a tool,
refused on the gameplay path. `asset.exists(name, type)` is the probe a
computed name can ask.

## typed/builtin//modules/api/engine/asset/asset/reloadPending {#typed-builtin-modules-api-engine-asset-asset-reloadpending}

```lua
asset.reloadPending(ref: RefArg, typeName: string?) -> boolean
```

True while a write to this asset still owes it a reload — the write is
inside the settle window that collects one authoring step's writes, or its
reload is queued and the engine has not run it yet. False means every
content change written so far has reached its subscribers, so a consumer
bound to the asset now cannot be interrupted by a reload the earlier
writes already earned. The recording is synchronous with the write, so a
call made right after one already reads true.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.
- `typeName` `string` _(optional)_ — Category to restrict the match to. Omit to search every category.

**Returns** boolean — true while a content-change reload is still owed.

```lua
repeat task.wait() until not asset.reloadPending(ref)
```

## typed/builtin//modules/api/engine/asset/asset/reloadSeq {#typed-builtin-modules-api-engine-asset-asset-reloadseq}

```lua
asset.reloadSeq(ref: RefArg, typeName: string?) -> number
```

How many content-change reloads this asset has been through — the
count of `onAssetReload` dispatches the engine has RUN for it. A write to
a file inside an asset does not reload it on the spot: the writes of one
authoring step are collected for a settle window and the reload runs on a
later frame. Read this, write, then poll for a larger number to learn the
write's reload has actually reached subscribers. Monotonic per asset and
session-scoped; 0 for an asset whose content has not changed since boot.

**Parameters**

- `ref` `RefArg` — Any name the asset has — handle, identity, guid, name or path.
- `typeName` `string` _(optional)_ — Category to restrict the match to. Omit to search every category.

**Returns** number — content-change reloads dispatched for this asset.

```lua
local at = asset.reloadSeq(ref)
vfs.write(asset.source(ref) .. "/graph.json", encoded)
repeat task.wait() until asset.reloadSeq(ref) > at
```

## typed/builtin//modules/api/engine/asset/asset/remove_field {#typed-builtin-modules-api-engine-asset-asset-remove-field}

```lua
asset.remove_field(ref: RefArg, key: string)
```

Remove one top-level field from the asset's `.metadata`.
No-op when the field isn't present.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.

```lua
asset.remove_field("brick", "author")
```

## typed/builtin//modules/api/engine/asset/asset/remove_tag {#typed-builtin-modules-api-engine-asset-asset-remove-tag}

```lua
asset.remove_tag(ref: RefArg, tag: string)
```

Remove a tag from the asset's `.metadata.tags`. No-op when
the tag isn't present.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `tag` `string` — Tag to remove.

```lua
asset.remove_tag("brick", "wip")
```

## typed/builtin//modules/api/engine/asset/asset/resolve {#typed-builtin-modules-api-engine-asset-asset-resolve}

```lua
asset.resolve(ref: RefArg, type: (C & string)?) -> AssetRef<C>
```

Find an asset. The returned handle carries every name form
the asset has (`guid`, `identity`, `path`, `type`) so
downstream code can read any one of them without calling
`resolve` again. Raises when `ref` resolves to no asset — or, with a
`type`, to no asset of that type — and when `ref` reaches more than one
asset, where it names the candidates for you to pick from instead of
picking one of them. A `<scope>::`-qualified identity reaches exactly one:
`@root::name` for the asset this world holds at its source root, the
library identity (`@builtin::…`) for a library's. For the same lookup
answering a miss with nil, use `asset.tryResolve(ref, type)`.

A name written as a string LITERAL is recorded as this source's
dependency on that asset, so the asset travels with the content and
still resolves once someone installs it in another world. A COMPUTED
name cannot be written down, so nothing pins what it reaches: that is a
dynamic resolve — free in a tool, refused on the gameplay path (a
component or scene entrypoint). `asset.tryResolve`, `asset.ref` and
`asset.source` read the name they are given exactly this way too, so
which of the four you reach for changes neither answer. To ask whether a
computed name has files without reaching a handle, use
`asset.exists(name, type)`.

**Parameters**

- `ref` `RefArg` — The asset to find — an identity, a guid, a VFS path, or a handle.
- `type` `(C & string)` _(optional)_ — Category to restrict the match to (optional). Separates a bare
name that assets of different categories share (`asset.resolve("cube",
"mesh")`); where several assets of the SAME category answer to it, the
scope-qualified identity is what separates them.
A reference naming a file an importer has since promoted (`wall.png`
after the texture importer turned it into `wall.texture`) resolves to the
promoted asset, and says so in the log once per reference.

**Returns** `AssetRef<C>` — Asset handle, carrying the category when one was named — so the methods that category defines are checked on the result. Raises (rather than returning nil) on a miss, and on a name that reaches more than one asset.

```lua
local a = asset.resolve("@builtin::components.Camera")
```

## typed/builtin//modules/api/engine/asset/asset/set_field {#typed-builtin-modules-api-engine-asset-asset-set-field}

```lua
asset.set_field(ref: RefArg, key: string, value: any?)
```

Set one field in the asset's `.metadata`, creating the sidecar if missing.
Sibling fields are preserved. When the new value AND the existing value are
both maps (objects), the new value DEEP-MERGES into the existing one, so
writing one sub-key never drops the others — `set_field(ref, "settings",
{ keepCpu = true })` keeps every other setting. Arrays and scalars replace.
Clear a whole field with `asset.remove_field`; replace the entire sidecar with
`asset.set_metadata`.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `key` `string` — Field name.
- `value` `any` _(optional)_ — Field value (any JSON-serialisable Lua value).

```lua
asset.set_field("brick", "author", "me")
asset.set_field("tree", "settings", { keepCpu = true }) -- merges; sibling settings kept
```

## typed/builtin//modules/api/engine/asset/asset/set_metadata {#typed-builtin-modules-api-engine-asset-asset-set-metadata}

```lua
asset.set_metadata(ref: RefArg, data: AssetMeta)
```

Replace the asset's `.metadata` sidecar with the given
table. Pass an empty table to clear all fields.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `data` `AssetMeta` — Full JSON-shaped contents for the sidecar.

```lua
asset.set_metadata("brick", { author = "me", tags = { "wip" } })
```

## typed/builtin//modules/api/engine/asset/asset/source {#typed-builtin-modules-api-engine-asset-asset-source}

```lua
asset.source(ref: RefArg, type: string?) -> string
```

Return the VFS source path for an asset.

It reaches the asset, so the name carries the same reference contract
`asset.resolve`'s does: a literal is recorded as this source's
dependency, a computed name is a dynamic resolve — free in a tool,
refused on the gameplay path. `asset.exists(name, type)` is the probe a
computed name can ask.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `string` — VFS source path.

```lua
local p = asset.source("brick") -- "/source/brick.material"
```

## typed/builtin//modules/api/engine/asset/asset/tags {#typed-builtin-modules-api-engine-asset-asset-tags}

```lua
asset.tags(ref: RefArg) -> { string }
```

Convenience read of the `.metadata.tags` array.

**Parameters**

- `ref` `RefArg` — Any name the asset has.

**Returns** `{ string }` — Array of tag strings.

```lua
for _, t in asset.tags("brick") do print(t) end
```

## typed/builtin//modules/api/engine/asset/asset/tryResolve {#typed-builtin-modules-api-engine-asset-asset-tryresolve}

```lua
asset.tryResolve(ref: RefArg, type: string?, base: string?) -> AssetRef?
```

The same lookup `asset.resolve` performs, answering a miss with nil
instead of raising. Every name form, the same `type` narrowing, and the
same handle on success — so "use it if it is there" needs no `pcall`
around a call whose failure would otherwise be indistinguishable from a
real error.

This consults the asset REGISTRY, so it sees registered assets wherever
their files live, `@builtin::` ones included. `asset.exists(name, type)`
answers the narrower question of whether an asset's files are present in
the current mode's store.

A name that reaches more than one asset still raises — that is a question
about the reference, not about presence, and a nil there would report
absence for content that is present twice.

It reaches the asset, so the name carries the same reference contract
`asset.resolve`'s does: a literal is recorded as this source's
dependency, a computed name is a dynamic resolve — free in a tool,
refused on the gameplay path. `asset.exists(name, type)` is the probe a
computed name can ask.

**Parameters**

- `ref` `RefArg` — The asset to look up — an identity, a guid, a VFS path, or a handle.
- `type` `string` _(optional)_ — Category to restrict the match to (optional).
A reference naming a file an importer has since promoted resolves to the
promoted asset, the same as `asset.resolve`.
- `base` `string` _(optional)_ — Referring VFS path a `~` / `~.tail` ref expands against, the same
as `asset.resolve`'s — so the two answer the same question and differ only
in what a miss is.

**Returns** `AssetRef?` — Asset handle, or nil when the reference resolves to no asset.

```lua
local mat = asset.tryResolve(name, "material")
```

## typed/builtin//modules/api/engine/asset/asset/typeRef {#typed-builtin-modules-api-engine-asset-asset-typeref}

```lua
asset.typeRef(target: RefArg) -> string?
```

Return the pinned asset_type reference (the type's guid)
that the asset is an instance of. Resolve the full type with
`asset.resolve(asset.typeRef(target))`. Returns nil for loose
files / assets with no pinned type.

## typed/builtin//modules/api/engine/asset/asset/unusableReasons {#typed-builtin-modules-api-engine-asset-asset-unusablereasons}

```lua
asset.unusableReasons() -> { string }
```

Every reason `asset.diagnose` can report an asset unusable for, sorted.

**Returns** `{ string }` — Array of reason names.

```lua
for _, r in ipairs(asset.unusableReasons()) do print(r) end
```

## typed/builtin//modules/api/engine/asset/asset/validate {#typed-builtin-modules-api-engine-asset-asset-validate}

```lua
asset.validate(ref: RefArg, type: string?) -> ValidateResult
```

Validate an asset folder against its type's `type.yaml`, plus
the type's own semantic validation. Structural problems come from
`type.yaml` — missing required files, unsatisfied `one_of_group`
alternatives, and (when `allow_unlisted: false`) unexpected
children. `validated = false` when no `type.yaml` is registered
— nothing structural to check. On top of that, when the asset's
type ships a `behavior.luau` exporting a top-level
`validate(assetRef) -> { { code, message, severity? } }`, its
reported problems (severity defaults to `"error"`) are appended to
`problems`; error-severity problems flip `ok` to false, warnings
leave it untouched. A hook that raises or returns a non-table is
itself reported as a `validate.hook_failed` error problem — a
broken hook blocks. A type with no `validate` export behaves
exactly as the structural check alone.
`world.push` calls this per user asset, so a type's semantic
validation is enforced at publish time with no further wiring.

**Parameters**

- `ref` `RefArg` — Any name the asset has.
- `type` `string` _(optional)_ — Category hint (optional).

**Returns** `ValidateResult` — `{ ok, typeName, validated, problems }`.

```lua
local v = asset.validate("@builtin::components.Camera")
```

## typed/builtin//modules/api/engine/asset/asset/warmup {#typed-builtin-modules-api-engine-asset-asset-warmup}

```lua
asset.warmup(ref: RefArg, opts: WarmupOpts?) -> WarmupResult
```

Warm an asset's bytes into CPU memory and follow its declared
content dependencies to each referenced asset, deduped by
guid. Type-agnostic (reads the generic ref graph) and CPU-only —
never touches the GPU. Side-effect-free name resolution (uses
`asset.guid`/`asset.deps`, not `asset.resolve`).

**Parameters**

- `ref` `RefArg` — Any name the root asset has — handle, identity, guid, or path.
- `opts` `WarmupOpts` _(optional)_ — Optional `{ vias, max }` — restrict ref-edge kinds / cap closure size.

**Returns** `WarmupResult` — `{ closure, count }` — the deduped guid closure warmed and its size.

```lua
local w = asset.warmup("@builtin::scenes.test_arena")
```
