Brief for assistants
This page is written to be given to an AI model. Paste it into a conversation, or point the model at this URL, before asking it to help with an AR Clip project.
Assistants that look for machine-readable context will find it by themselves at
/llms.txt, with the whole documentation in one file at
/llms-full.txt.
It exists because no model has been trained on this platform. Without it they reach for whatever engine they do know (Unity, three.js, A-Frame) and produce answers that look right and are not.
The mental model
Everything in a project is an entity. An entity is a name plus a set of components, and the components decide what it is. There is no class hierarchy and no object types to choose from.
Project
└── Space shared background, lighting, grid, units
└── Scene an entity carrying an Anchor — the trigger that makes it appear
└── Entity
└── Entity entities nest
Two relationships that must not be confused:
- Composition — an entity has components. One of each kind. Components are not children.
- Containment — an entity contains other entities. Moving a parent moves its children.
A scene is an entity with an Anchor and no Transform. Space-level logic is an entity with a Script or Patch and no parent.
You never write systems. The engine reacts to components; your job is to decide which components exist and what their values are.
Four ways to add behaviour
| Layer | Lives in | Use for |
|---|---|---|
| Events | an Events component | trigger → list of steps; most interactivity |
| Patches | a patch graph or resource | logic with values and conditions, built visually |
| Scripts | a script resource | anything genuinely programmatic |
| UI | a DivKit card | all 2D interface |
All four write into the same components. The same trigger handled in two of them fires twice — a very common generated bug.
Naming rules
- Triggers are kebab-case:
on-click,on-launch,on-collide. - Steps are snake_case:
play_animation,set_visibility,scene_transit_action. - Resolution is exact. A mistyped name does not error — it silently never matches.
- The editor shows human labels ("Show / hide object"); the ids above are what code uses.
Do not guess — look it up
If you are connected over MCP, these answer from the live engine:
| Call | Returns |
|---|---|
list_component_schemas | every component and its fields |
list_event_types | every trigger and step with parameters |
list_patch_nodes | every patch node with its ports |
describe_*_api | prose guidance per area |
Call them before writing anything that names a component, a trigger, a step or a node. Inventing a plausible name is the single most common failure mode here.
Without MCP, use the generated reference: components · triggers · steps · patch nodes · shader nodes.
Traps that produce confidently wrong code
update({ position: { y: 2 } }) sets x and z to zero. Always spread:
t.update({ position: { ...t.$data.position, y: 2 } });
material.update({ color }) does nothing — color lives inside a slot:
material.update({
materials: [{ ...material.$data.materials[0], color: '#ff0000' }],
});
$dataIt appears to work and the change is dropped. Only update() and updateAt() write.
Physics owns its position and overwrites it next step. Use ctx.physics.teleport to place and
applyImpulse / applyForce to move.
A GLB is not solid until you give it a collider. A dynamic one falls through the world.
Further rules that catch generators out:
- Rotation is radians in scripts, degrees everywhere a human looks — the editor, patch node ports, MCP tooling.
- Multiply by
dtinctx.tick, or motion runs at the device's frame rate. - There is no "animation finished" event in any mechanism. Time it yourself.
- Scripts have no DOM, no
fetch, no timers, no rendering library. Usectx.tick,ctx.audio,ctx.store, and a UI card for interface. - The editor does not execute logic. Scripts, patches, physics and timers only run in preview or a publication. Never tell a user their script "should run in the editor".
- While a state is active, edits to that object are recorded into the state, not the object.
- The timeline stores
channelsfor authoring and a bakedkeyframeslist for playback. Writing channels without re-baking means nothing plays. - One object, one animation mechanism. The timeline overwrites a transition every frame.
Prefer the built-in step to reimplementing it
ctx.step(name, params, { targets }) runs any step the editor offers — animation, state
switching, scene transitions, transitions. Check the step reference before writing code by hand.
Validate a patch before claiming it works
Compile it and read the result. A graph that cannot compile reports a data cycle or broken
JavaScript, and the compiled source is exactly what will run. Over MCP that is
preview_patch_code.
Units
| Quantity | In data | Where a human sees it |
|---|---|---|
| Position | metres | project units |
| Rotation | radians | degrees |
| Animation time | seconds | seconds (milliseconds on state switches) |
| Opacity | 0–1 | 0–100 in keyframes and the opacity step |
| Font size | pixels, 1000 px = 1 m | pixels |
| Clip frames | 30 fps | frames |
Answering a user well
- Ask which layer they want. "Without code" and "in a script" lead to completely different answers to the same question.
- Prefer the simplest layer that works. An event beats a patch; a patch beats a script.
- Say where to click. Panel names matter more than concepts to someone in the editor.
- Remind them to preview. Most "it does not work" reports are the editor not running logic.
- Do not invent names. If unsure, say so and point at the reference.