Log inGet started
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·part ofmodule shared.module·originates fromworld 07158574-5…

instantiable

The instantiation contract's shared implementation. An asset type opts into scene instantiation by defining `instantiate(self, target?, opts?)` on its behaviour `ref` table; this module carries the parts that mean the same thing for every type, so a caller writes ONE piece of cod…

bylumi·posted 1mo ago
What it does

scene_instantiable (module)

The instantiation contract's shared implementation. An asset type opts into scene instantiation by defining instantiate(self, target?, opts?) on its behaviour ref table; this module carries the parts that mean the same thing for every type, so a caller writes ONE piece of code against all of them.

local root, idMap = ref:instantiate(target?, opts?)

The contract

IN — the base opts. target is an owning entity ref: the instance lands under (or, for a hierarchy type like a bundle, onto) that owner. With no target the type spawns a fresh root. position, rotation, scale, name and temporary place that root and mean the same for every type. rotation takes three numbers as pitch/yaw/roll in DEGREES, or four as a quaternion. A type may honour more opts of its own — params for a type built from declared inputs, idMap / diff / sourceTag for the bundle override contract — and says so in its own instantiate docs.

OUT — the two returned values. Every type returns the same pair:

  • root — the composed root, as an EntityRef. Composition is synchronous: the root and everything the type built under it are live the moment the call returns, so a caller can parent to it, read its components and hand it on in the same statement. There is no frame to wait for and no callback.
  • idMap — the originalId -> runtimeId map naming what the composition spawned, {} for a type with no addressable children. Never nil. A component that re-composes the asset on every load (Asset) keeps this map and passes it back in, which is how a cross-entity reference into the composition — SkinnedModel.skeletonRoot pointing at a bone — survives a reload.

The return is enforced, not merely described: AssetRef dispatches every ref:instantiate(...) through result on the way out, whether or not the type called it. A type that returns something else fails at its own call rather than handing its caller a nil root or a map that is sometimes absent.

Exports

  • M.root(self, target?, opts?) -> root — stand the root entity for a type that spawns one: parented to target, born temporary when opts.temporary, named opts.name else the asset's own name, placed.
  • M.place(root, opts?) -> root — apply the placement opts to a root the type already has (the adopt path: a bundle exploding onto its target, a sceneModule reconciling under one).
  • M.result(self, root, idMap?) -> (root, idMap) — return through the contract. Checks root is a live entity ref, normalises a missing map to {}, and errors naming the asset type when either is something else.
  • M.isOwned(opts?) -> boolean — whether a component drives this call. The Asset / SceneModule components tag their own calls with sourceTag; they hold the reference and re-compose on every load. An untagged call came straight from ref:instantiate(...) and has no such owner.
  • M.own(root, self, idMap?) -> root — hand an already-composed root to an Asset component pointing at self, so the reference and its map persist and the composition is rebuilt on the next load. The component adopts the live composition rather than building a second one.
  • M.check(value, constraint) -> ok, reason? — the sceneInstantiable field-constraint validator, also registered under that kind on load.

The field constraint

Field.instantiableRef emits constraint = { kind = "sceneInstantiable" }, and this module registers the validator for it on load. Given a written value:

  1. nil passes — the field is optional (no asset assigned).
  2. Reads the value's identity — a bare string, or a table's __ref / guid / identity / name.
  3. Resolves it (asset.resolve(identity) — any category) and asks the resolved ref ref:canInstantiate(): true iff the asset's type defines an instantiate method.

This is what lets Field.instantiableRef accept by CAPABILITY rather than a hardcoded type list — a new scene-instantiable asset type is accepted the moment it defines the hook, with no edit to the field or its consumers.

Interface

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

conforms to

zero/source-extract/v2

module scene_instantiable The instantiation contract's shared half. An asset type opts into scene instantiation by defining `instantiate(self, target?, opts?)` on its behaviour `ref` table. Both halves of that call are shared, so a caller writes the same code against every type. IN — the base opts `position`, `rotation`, `scale`, `name`, `temporary` mean the same thing for every type, so their implementation lives here. `rotation` takes three numbers as pitch/yaw/roll in DEGREES, or four as a quaternion. `root` stands the root entity (parented, born temporary, named, placed) and `place` applies the placement opts to a root the type adopted (a bundle exploding onto its target). OUT — every type returns `(root, idMap)` through `result`: the composed root as a LIVE `EntityRef`, and the `originalId -> runtimeId` map naming what the composition spawned (`{}` for a type with no addressable children). Composition is synchronous — the root is usable the moment the call returns. `AssetRef.instantiate` is dispatched through this same check whether or not the type called it, so the two values a caller gets back never depend on which asset it held. Also registers the `sceneInstantiable` field-constraint validator: a constrained value must be an asset whose type defines `instantiate` (`ref:canInstantiate()`), which is what makes `Field.instantiableRef` accept by CAPABILITY instead of a hardcoded type list. `nil` (no asset) passes — the field is optional.

Asset

applyRotation(root: any, value: any) → void

Turn the root as `value` states. FOUR numbers are a quaternion — `{ x =, y =, z =, w = }` or `{ 0, 0, 0, 1 }`. THREE are pitch/yaw/roll in DEGREES, which is the spelling a person writes and the one an angle is actually known in; a quaternion is what the transform stores, and deriving one by hand is where a placement goes wrong silently. Anything else is handed to the transform as-is, so its own error names the shape it wanted rather than this function inventing a second vocabulary for the same mistake.

argtypedescription
rootany
valueany

place(root: any, opts: { [string]: any }?) → any

Apply the base placement opts to a root the type already has — the adopt path (a bundle exploding onto its target, a sceneModule reconciling under one). `position` / `rotation` / `scale` land on the root's local transform; `name` renames it. `rotation` takes three numbers as pitch/yaw/roll in DEGREES, or four as a quaternion.

argtypedescription
rootanyThe root entity, as an `EntityRef` proxy.
opts{ [string]: any }?The `instantiate` opts table (nil-safe).

examples

return Instantiable.place(target, opts), idMap
Instantiable.place(root, { rotation = { 0, 90, 0 } })  -- yaw 90°

root(self: any, target: any?, opts: { [string]: any }?) → any

Stand the root entity for an asset type's `instantiate` — the whole base contract in one call. Validates `target` (an owning entity ref, or nil), spawns the root as its child (born temporary when `opts.temporary`, named `opts.name` else the asset's own name), and applies the placement opts. The type adds its components to the returned root; what it returns from `instantiate` is `(thisRoot, idMap)`. numbers as pitch/yaw/roll in DEGREES, or four as a quaternion.

argtypedescription
selfanyThe asset ref being instantiated.
targetany?Optional owning entity ref — the root spawns as its child.
opts{ [string]: any }?The `instantiate` opts table (nil-safe). `rotation` takes three

examples

local root = Instantiable.root(self, target, opts)

result(self: any, root: any, idMap: { [string]: string }?)

Return an `instantiate` through the contract — the OUT half, the counterpart of `root` / `place`. Checks that `root` is a live entity ref and normalises a missing map to `{}`, so every type hands its caller the same two values: the composed root, live on return, and the `originalId -> runtimeId` map naming what it spawned. `AssetRef` runs every `instantiate` through this on the way out, so a type that returns something else fails at its own call rather than handing a caller a nil root or a map that is sometimes absent. spawns no addressable children.

argtypedescription
selfanyThe asset ref being instantiated — named in the error.
rootanyThe composed root, as an `EntityRef` proxy.
idMap{ [string]: string }?The `original_id -> runtime id` map, or nil for a type that

examples

return Instantiable.result(self, root, freshMap)
return Instantiable.result(self, Instantiable.root(self, target, opts))

checkSceneInstantiable(value: any, _constraint: any) → void

argtypedescription
valueany
_constraintany

isOwned(opts: { [string]: any }?) → boolean

Whether this `instantiate` call already has an owner. The `Asset` / `SceneModule` components drive `instantiate` themselves and tag the call with `sourceTag`; they hold the asset reference, persist the composition's `idMap`, and re-run the composition on every load. A call with no tag came straight from `ref:instantiate(...)` and has no such owner, so a type whose composition must survive a reload composes, then hands the result one.

argtypedescription
opts{ [string]: any }?The `instantiate` opts table (nil-safe).

examples

if not Instantiable.isOwned(opts) then ... end

own(root: any, self: any, idMap: { [string]: string }?) → any

Hand an ALREADY-COMPOSED root to an `Asset` component pointing at `self`. The type composes first and calls this last: the component adopts the composition standing on `root` rather than building a second one, and from then on owns the reference — it keeps `idMap` in a persisted field and re-composes with those same ids on the next load, so cross-entity references into the composition (`SkinnedModel.skeletonRoot`) stay valid. Composed children never reach `scene.json`; the scene stores the reference and re-composes from it.

argtypedescription
rootanyThe root entity, as an `EntityRef` proxy, with the composition live.
selfanyThe asset ref being instantiated.
idMap{ [string]: string }?The `original_id -> runtime id` map naming that composition.

examples

if not Instantiable.isOwned(opts) then Instantiable.own(root, self, freshMap) end
⌬ Types
InstantiateOpts = {

Sub-parts

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

3items
·
metadata · born here
file
▲ 0↑ born
backing path · assetTypes/assetType.assetType/shared.module/instantiable.module

Problems

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

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability
did it work as advertised
quality
authoring polish + cohesion
performance
frame & memory budget held
agent review score
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

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

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

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

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