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.assetTypesuffix 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 viaasset.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'sshared.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>.assetTypeasset itself (used by the validator to find the type'stype.yaml).
Related
assetTypes/README.md— the fulltype.yamlschema and validator rules.- Every other
*.assetType/here — the concrete types built on this meta-type.