Assets and prefabs¶
The project folder is the asset store. Every file the engine understands (meshes, textures, materials, prefabs, scenes, audio, scripts) becomes an asset record with a stable GUID, a type, tags, a description and its provenance, so you and your agents can search, preview, reuse and credit it. Linked prefabs package entity trees for reuse, materials package surfaces, and the placement tools put assets on real geometry.

.meta and in the project's CREDITS.md.Concepts¶
Asset records¶
| Field | Meaning |
|---|---|
guid |
A stable id stored in a <file>.meta sidecar next to the file. It survives renames and moves. Tools accept "guid:3f2a…" wherever they take an asset. |
path |
Project-relative path with / separators. Scenes reference assets by path ("asset:models/boat.glb"). |
type |
One of mesh, texture, material, prefab, scene, audio, video, script, agent, animation, controller, sequence. |
tags, description |
Free-form search metadata. Write them: they are how the next agent finds the asset. |
source |
Provenance, for example {"generator": "texgen", "kind": "grass", "seed": 1} or the license and author of a download. |
import |
Importer settings and output, for example normalize and z_up for a mesh. |
A .meta file is small JSON:
{
"guid": "8c612c6ac5452d7cfac912192eaf1aed",
"type": "texture",
"tags": ["generated", "grass"],
"description": "",
"source": {"generator": "texgen", "kind": "grass", "seed": 1, "by": "mcp:Pixel"},
"import": {}
}
The database rescans the project every two seconds while you edit, and on asset_refresh. The scan skips
dot-folders, build/, node_modules/ and DerivedData/. Commit .meta files with your assets so GUIDs stay stable
for everyone on the team.
Types by file name¶
| Type | Files |
|---|---|
| Mesh | .glb, .gltf, .obj (with .mtl), .ply, .stl |
| Texture | .png, .jpg, .jpeg, .hdr (sky panoramas for skyMode: hdri) |
| Material | .mat.json |
| Prefab | .prefab.json |
| Scene | .sky.json |
| Script | .wander |
| Agent | .agent.json |
| Animation, controller, sequence | .anim, .animctl.json, .sequence.json |
| Audio | .wav, .mp3, .ogg, .m4a, .flac |
| Video | .mp4, .mov, .webm |
Meshes¶
| Format | What is imported |
|---|---|
| glTF 2.0 / GLB | The node hierarchy (flattened), normals, UVs, vertex colors and the materials: base color, metal/roughness, emissive, normal, ORM and emissive maps, alpha mask or blend, double-sided. A model with several materials becomes parts (asset:model.gltf#<material index>), one entity each, saved together as <model>.prefab.json. Embedded, data-URI and external buffers all load; external textures are referenced in place. |
| OBJ + MTL | Polygons (triangulated), normals, UVs, v x y z r g b vertex colors and the first .mtl material (Kd, d, Ns/Pr to roughness, Pm, Ke, map_Kd, map_Bump/norm). |
| PLY | ASCII and binary (both byte orders): positions, normals, UVs, vertex colors, polygon faces. Point clouds without faces are rejected with a hint. |
| STL | ASCII and binary, flat-shaded facets (CAD and 3D-printing models). |
Import options, stored in the asset's .meta so the model loads the same way every time:
normalize(defaulttrue) scales the model to fit a 1 m cube, centered on the ground, so generated models arrive at a predictable size. Usefalsefor assets already in real-world meters (photoscans). The result reports the model'sbounds, so you can place it precisely.z_uprotates Z-up sources (CAD, scans, some exporters) to Y-up.
Rigged or animated glTF characters also get an animation library (<name>.anim: skeleton plus clips) and a prefab
with an animator; they keep their real size unless you pass normalize, and are turned to face −Z. Animation-only
glTF files become clip libraries. See Animation.
Meshes of 3,000 triangles or more get an automatic level-of-detail chain at load. The engine also keeps a CPU copy of
every mesh, which makes raycast, place_on_surface and scatter triangle-accurate.
Materials¶
A material asset (.mat.json) holds the same surface fields as the mesh component: color, metallic, roughness,
emissive, maps, shading model, triplanar projection, clearcoat, subsurface, rim and outline. When mesh.material is
set, the material overrides the entity's inline color, PBR values, emissive and texture. Editing a material updates
every entity that uses it, live.
{
"format": "skywalker.material",
"version": 1,
"color": "#e0b040",
"metallic": 1,
"roughness": 0.25,
"emissive": "#000000",
"shading": "pbr",
"texture": "",
"tilingU": 1,
"tilingV": 1
}
material_create {"path": "materials/brass.mat.json", "preset": "gold", "roughness": 0.35}
material_assign {"entities": ["Bell", "Door Handle"], "material": "materials/brass.mat.json"}
material_update {"path": "materials/brass.mat.json", "color": "#c8a050"}
texture_generate {"kind": "cobblestone", "name": "textures/plaza_stones", "color1": "#8a8580"}
texture_generate writes a seamless albedo, normal and ORM set and, by default, a triplanar material that uses them.
The field-by-field guide, every preset and every texture kind are on Materials.
Prefabs¶
A prefab (.prefab.json) is a saved entity tree: components, behaviors (intent and Wander), vars, tags and children.
The root's position is stored at zero, so instances can go anywhere.
{
"format": "skywalker.prefab",
"version": 1,
"name": "Bamboo",
"root": {
"name": "Bamboo",
"enabled": true,
"tags": [],
"vars": {},
"components": {"transform": {"position": [0, 0, 0], "rotation": [0, 0, 0], "scale": [1, 1, 1]}},
"behaviors": [
{"name": "Sway", "intent": "Sway gently in the breeze, each cluster on its own phase.", "source": "on tick\n self.rotation = (sin(time * 0.7 + self.id) * 2.5, 0, cos(time * 0.55 + self.id * 1.3) * 2.5)\nend", "enabled": true}
],
"children": [
{"name": "Bamboo Cane 1", "enabled": true, "tags": [], "vars": {},
"components": {"transform": {"position": [0.35, 3, 0], "scale": [0.16, 6, 0.16]},
"mesh": {"mesh": "cylinder", "color": "#6a9a3a", "roughness": 0.45}}}
]
}
}
(An abridged copy of examples/zen_garden/prefabs/bamboo.prefab.json.)
| To | Use |
|---|---|
| Create | prefab_create, or right-click an entity in the Outliner and choose Save as Prefab |
| Place one | prefab_instantiate (with on_surface to drop it onto the geometry below), or drag it from the asset browser |
| Place many | scatter with prefab |
| Spawn at run time | Wander spawn("prefab:prefabs/coin.prefab.json", position) |
Linked prefabs and overrides¶
A placed prefab stays linked to its .prefab.json. prefab_instantiate, scatter with a prefab, the asset browser
and Wander's spawn("prefab:...") all create linked instances, and prefab_create links the source entity to the new
prefab (link: false opts out). Instances are ordinary entities to rendering, physics and Wander; the scene file
stores only what each instance changes, its overrides:
| Override | Example |
|---|---|
| One component field | light.intensity |
| A component added or removed | light (the whole component, or null) |
| A record field | name, enabled, tags, unique, behaviors (behaviors compare as a whole) |
| One variable | vars.hp |
The instance root's name and transform are its placement and never count as overrides. Every way of editing (tools, the editor, the gizmo, Wander in edit mode) produces overrides, field by field.
When the prefab file changes (an edit on disk, or prefab_apply), every instance in the open scene is rebuilt from the
new version and keeps its overrides; other scenes pick the change up when they load. Entity ids of existing members
stay the same, and while the game is playing the update waits until play stops. A missing prefab file loads as a
placeholder that keeps its saved data, and scene_load warns about it.
| To | Use |
|---|---|
| See what an instance changes | prefab_overrides {"entity": ...}; without entity, every instance with its override count |
| Undo an override | prefab_revert (one property, all of an entity's overrides, or the whole instance) |
| Make an override the default | prefab_apply pushes it into the prefab file; other instances update and keep their own overrides |
| Detach an instance | prefab_unpack keeps the entities as they are, unlinked |
| Adopt copies in an old scene | prefab_relink links existing copies whose hierarchy matches the prefab node for node |
prefab_instantiate {"prefab": "prefabs/lamp.prefab.json", "position": [4, 0, 2]}
prefab_overrides {}
prefab_revert {"entity": "Lamp", "property": "light.intensity"}
prefab_apply {"entity": "Lamp", "property": "light.intensity"}
prefab_relink {"prefab": "prefabs/tree.prefab.json", "dry_run": true}
Entity links and unique names¶
Component fields that point at another entity (a joint's target, a 2D camera's follow, an animator's lookAt,
bone attachments, IK, hair and particle colliders) store the target's id plus its name, so renames never break
them. Duplicating or pasting a group (entity_duplicate, entity_copy / entity_paste) keeps the copies wired to
each other. entity_refs {"entity": "Door"} lists what points at an entity; entity_refs {} is a scene health check
that lists dangling links, name-only links, duplicate unique names and broken prefab instances, each with a fix.
Mark a part unique (entity_update {"entity": "Muzzle", "unique": true}) and Wander's find("%Muzzle") finds the
copy inside the caller's own prefab instance, so every spawned enemy finds its own muzzle.
Downloading assets¶
asset_download fetches an openly licensed model, texture, .hdr panorama, sound or .zip pack from a URL:
- It saves into
downloads/<name>/(orfolder), fetches multi-file glTF and OBJ+MTL dependencies automatically, and takesinclude({"textures/a.jpg": "https://…"}) for files hosted elsewhere. .zippacks are extracted with path-traversal and size guards.- It records the license, author, URL and retrieval date in every file's
.meta, and appends a credit line to the project'sCREDITS.md. - It imports models (
normalizeandz_upas above) and can place one as a new entity (create_entity,position).
The license rules are part of the tool:
- Only download assets whose license allows the project's use: CC0, CC-BY with attribution, MIT, public domain, or terms the human accepted. Give the license exactly as the source page states it.
- Unknown or "all rights reserved" licenses are refused. Non-commercial and no-derivatives licenses produce warnings.
- Never bypass logins, paywalls or download limits.
asset_download is an open-world tool: MCP clients show it as a network action that needs approval, and in the editor
the Network permission asks before every download unless you allow it for an agent. See
Permissions and approvals.
Generated assets¶
For content from an image, 3D or audio generator, an agent queues asset_request {kind, prompt, target}. A connected
generator (or another agent) fulfills it with asset_complete {id, path}: a mesh is imported and assigned to the
target, a texture becomes its texture, a sprite turns it into a camera-facing textured quad. asset_requests lists
pending, done and failed requests. Provenance (the prompt, who made it) lands in the .meta.
Spatial placement¶
Placement tools work on real triangles, not bounding boxes:
| Tool | Use |
|---|---|
raycast |
The first mesh hit along a ray: entity, point, normal and distance. Give origin + direction, or a pixel x, y of an editor capture. Ground height, line of sight, aiming. |
place_on_surface |
Moves entities straight down (or up) until the bottom of their bounds rests on the geometry below, with an optional offset. One undo step. |
scatter |
Places 1 to 2000 copies of an entity or prefab in a rectangle (size) or circle (radius), with min_distance, random yaw and scale ranges, optional snapping to a surface, and a seed. All copies go under one group entity in one undo step; the same seed gives the same layout. |
prefab_instantiate |
One instance with position, yaw or rotation, scale, parent and on_surface. |
viewport_multi |
Perspective plus top, front and side orthographic views in one image, to check layout and spacing. |

viewport_multi on hello_sky: perspective (top left), top, front and side views in one image.How to find, inspect and place an asset¶
asset_info lists everything about one asset, including which entities use it, and type-specific data such as a
material's fields or a prefab's contents. asset_preview renders an isolated thumbnail: a mesh, a material on a
sphere, a texture or a prefab.
How to import a file you placed in the project¶
asset_import {"path": "models/lighthouse.glb", "create_entity": "Lighthouse", "position": [12, 0, -30], "normalize": false, "tags": ["landmark"], "description": "Stone lighthouse with a glass lantern room"}
asset_tag {"asset": "models/lighthouse.glb", "tags": ["landmark", "coast"], "description": "Stone lighthouse, real-world scale"}
asset_move {"asset": "models/lighthouse.glb", "to": "models/coast/lighthouse.glb"}
asset_move moves the file together with its .meta and GUID and rewrites references in the open scene. The file move
itself is not part of the undo history; move it back with another asset_move.
Recipe: a pine forest on an island¶
asset_list {"query": "pine"}
asset_preview {"asset": "prefabs/pine.prefab.json"}
scatter {"prefab": "prefabs/pine.prefab.json", "count": 40, "radius": 12, "min_distance": 1.5, "yaw": [0, 360], "scale": [0.8, 1.3], "surface": "Island", "seed": 7}
viewport_multi {"size": 384}
If the layout is wrong, undo once (the scatter is one step), change seed, min_distance or radius, and run it
again. For dense ground cover over terrain (grass, flowers, pebbles) use foliage_add, which instances on the GPU; see
Foliage.
The editor's asset browser¶
The Assets dock shows every record with type filters and counts, search over paths, tags and descriptions, and thumbnails rendered on demand and cached until the file changes. The inspector shows provenance and usage.
- Drag a mesh or prefab into the viewport: it lands on the surface under the cursor.
- Drag a material or texture onto an object in the viewport: it applies to that object.
- Drop an asset on the Material, Texture or Mesh field in Details, or use the field's asset menu.
Pitfalls¶
- Normalization changes size. By default meshes are scaled to 1 m. Pass
"normalize": falsefor scans and kits authored in meters, and read the reportedbounds. - Materials override inline values. An entity with
mesh.materialignores its own color and PBR fields. - Overrides win over prefab edits. A field you changed on an instance keeps its value when the prefab changes; use
prefab_revertto follow the prefab again. Behaviors and tags compare as whole values, so editing one script overrides the instance's whole behavior list. - No nested prefabs or variants. A prefab made from a tree that contains instances flattens them; the
prefab_createresult says so. - Unknown licenses are refused.
asset_downloadneeds the license string from the source page; it will not guess. - Commit
.metafiles. Without them, GUIDs are regenerated on another machine and GUID references break.
For agents
Search before you make, look before you place, and describe what you add:
asset_list {"query": "rock", "type": "mesh"} # reuse what the project already has
asset_preview {"asset": "models/rock_a.glb"} # check it is the right thing
scatter {"source": "Rock A", "count": 25, "radius": 8, "surface": "Ground", "seed": 3}
viewport_multi {} # verify layout from four views
asset_tag {"asset": "models/rock_a.glb", "tags": ["rock", "granite"], "description": "Mossy granite boulder, 1 m"}
Downloads need the human's approval and a stated license; prefer assets already in the project.
Reference¶
- Tools: Asset tools (
asset_list,asset_info,asset_preview,asset_import,asset_tag,asset_move,material_create,prefab_create,prefab_instantiate,prefab_overrides,prefab_revert,prefab_apply,prefab_unpack,prefab_relink),entity_refs,asset_download,raycast,place_on_surface,scatter,viewport_multi - Wander:
spawn - Design: docs/ASSETS.md