Log inGet started

assetType (asset type)

The assetType is the type of types — the meta-type that every <typename>.assetType/ folder is an instance of, including itself. A <typename>.assetType/ folder declares a new asset type called…

This type is self-hosting. assetType.assetType/ conforms to its own schema — a type.yaml plus a README.md — so the type system is defined in terms of itself rather than a hidden engine special-case (the same way a C compiler is written in C). Every asset in the engine, including each .assetType definition, now resolves to a registered assetType: a .material resolves to material.assetType, and material.assetType resolves to assetType.assetType, which resolves to itself.

Why it exists

Assets used to be mapped to their type implicitly, by matching the folder suffix against a name-keyed registry. That match carried no stored link: nothing on a Foo.material recorded which material.assetType it was written against, so two worlds shipping a same-named type could silently disagree. Making assetType a real, resolvable asset closes that gap — the link from an asset to its type is now an explicit reference pinned in the asset's .refs sidecar (via: "asset_type"), exactly like every other dependency.

Where it lives

  • Source: /zero/source/.../<typename>.assetType/
  • Identity: <typename> (the .assetType suffix strips from the identity; the folder retains the suffix on disk).
  • Folder shape:
    • type.yaml — the structural spec the validator reads. Required.
    • README.md — type-level documentation. Required.
    • behavior.luau / type.lua — optional behavior/scaffolding module.
    • template/ — optional canonical placeholder body the templater copies into a new asset of the type.
    • shared.module/ — optional shared code the type ships to every instance of it, reached via asset.type():shared() (see below).

Shared code its instances reach (asset.type() + :shared())

assetType.assetType/behavior.luau gives every AssetRef<assetType> the accessors that let a type expose shared code to its instances:

  • :shared() — load the type's shared.module/ exports.
  • :module(name) — load the type's <name>.module/ exports (nil if absent).
  • :modules() — list the <name>.module/ folders the type ships.

Combined with asset.type() (which resolves the calling asset's type by following its asset_type link — the explicit reference pinned in the instance's .refs sidecar), an instance reaches its type's shared code with one location-independent line:

local api = asset.type():shared()

The same line in an instance of a different type resolves to that type's shared module, so identically-named shared modules never collide. See assetTypes/README.md § "Shared code: type-owned modules" for the full mechanism and test.assetType for the worked example.

How to create one

Authoring a new asset type is just creating a <typename>.assetType/ folder under /zero/source/. The engine registers <typename> as a category the moment the folder is written — no engine restart, no Rust change, and it works for built-in (@builtin/assetTypes/) and user-defined types identically. Once <typename> is registered, <name>.<typename>/ folders are recognised as assets of that type and resolve their asset_type link back to this definition.

Creating a <name>.<typename>/ folder before its <typename>.assetType/ exists fails fast: the suffix has no registered type, so the folder can't be a valid asset. Author the type first.

The inspect hook (M.ref.inspect)

asset.inspect(ref) resolves ref, builds a common envelope shared by every asset (identity, name, guid, source, typeName, typeDefinitionPath, scope, origin, description, tags), then calls the resolved type's own inspect hook — M.ref.inspect(self) on behavior.luau — for the type-specific detail. The hook returns only detail; the framework fills in everything else:

-- behavior.luau
local M = {}

M.ref = {
  inspect = function(self)
    -- self.path is the asset's own folder. Parse the asset's OWN
    -- files — source text, a native payload header, its `.metadata`
    -- — never runtime state (a live component instance, a GPU
    -- upload, a compiled schema): inspect must work on an asset that
    -- hasn't been compiled, uploaded, or registered yet.
    return {
      -- type-specific fields, whatever this type wants to surface
    }
  end,
}

return M

A type that omits M.ref.inspect yields the generic record: the envelope alone, detail = nil. That's a valid, working state — not an error — but it means agents calling asset.inspect / tools.use("assets","describe", ...) on an instance of the type see only identity and scope, none of what makes an instance of it meaningful. Declaring the hook is what makes a type's instances inspectable for what they actually are.

A hook that raises leaves detail = nil and sets record.warning to the error text — asset.inspect itself never raises for a resolved ref.

Discovery

  • asset.list("assetType") — every registered asset type.
  • asset.inspect("<typename>") — the type's identity, source path, and this README.
  • asset.resolve("<typename>", "assetType") — the <typename>.assetType asset itself (used by the validator to find the type's type.yaml).
  • assetTypes/README.md — the full type.yaml schema and validator rules.
  • Every other *.assetType/ here — the concrete types built on this meta-type.
  • asset-type
  • reference