Skip to main content

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

LayerLives inUse for
Eventsan Events componenttrigger → list of steps; most interactivity
Patchesa patch graph or resourcelogic with values and conditions, built visually
Scriptsa script resourceanything genuinely programmatic
UIa DivKit cardall 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:

CallReturns
list_component_schemasevery component and its fields
list_event_typesevery trigger and step with parameters
list_patch_nodesevery patch node with its ports
describe_*_apiprose 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

Writing a nested value replaces all of it

update({ position: { y: 2 } }) sets x and z to zero. Always spread:

t.update({ position: { ...t.$data.position, y: 2 } });
A material is a list of slots

material.update({ color }) does nothing — color lives inside a slot:

material.update({
materials: [{ ...material.$data.materials[0], color: '#ff0000' }],
});
Never assign into $data

It appears to work and the change is dropped. Only update() and updateAt() write.

Setting a dynamic physics body's transform does nothing

Physics owns its position and overwrites it next step. Use ctx.physics.teleport to place and applyImpulse / applyForce to move.

An imported model has no collision shape

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 dt in ctx.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. Use ctx.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 channels for authoring and a baked keyframes list 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

QuantityIn dataWhere a human sees it
Positionmetresproject units
Rotationradiansdegrees
Animation timesecondsseconds (milliseconds on state switches)
Opacity0–10–100 in keyframes and the opacity step
Font sizepixels, 1000 px = 1 mpixels
Clip frames30 fpsframes

Answering a user well

  1. Ask which layer they want. "Without code" and "in a script" lead to completely different answers to the same question.
  2. Prefer the simplest layer that works. An event beats a patch; a patch beats a script.
  3. Say where to click. Panel names matter more than concepts to someone in the editor.
  4. Remind them to preview. Most "it does not work" reports are the editor not running logic.
  5. Do not invent names. If unsure, say so and point at the reference.