Skip to main content

Brief for assistants

Give this page to any AI model before you ask it for help with an AR Clip project. Paste it into the chat, or give the model this page's address.

No model has been trained on AR Clip. Without this page, models borrow from other engines they know and give answers that look right but are wrong.

AI tools that look for machine-readable context find it on their own at /llms.txt. The whole documentation in one file is at /llms-full.txt.

How a project is built​

Everything in a project is an entity. An entity is a name plus a set of components. The components decide what the entity is. There are no classes 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

Do not mix up these two relationships:

  • Composition — an entity has components, at most 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. Logic for the whole space is an entity with a Script or Patch and no parent.

You do not write the code that makes components work. Your job is to decide which components exist and what values they hold.

Four ways to add behaviour​

LayerWhere it livesUse it for
Eventsan Events componenta trigger and a list of steps; most interactivity
Patchesa patch graph or resourcelogic with values and conditions, built visually
Scriptsa script resourceanything that really needs code
UIa UI cardall 2D interface

All four change the same components. If two of them handle the same trigger, it fires twice. This is a very common bug in generated answers.

Names​

  • Triggers are kebab-case: on-click, on-launch, on-collide.
  • Steps are snake_case: play_animation, set_visibility, scene_transit_action.
  • Key and mouse triggers have other names in scripts. An event uses on-key and on-mouse with branches. A script listens for on-keydown / on-keyup and on-mousedown / on-mouseup / on-click.
  • Names must match exactly. A mistyped name gives no error — it just never matches.
  • The editor shows readable labels, like "Show / hide object". Code uses the ids above.

Look names up, do not guess​

If you are connected over MCP, these calls show exactly what exists:

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_*_apiwritten guidance for each area

Call them before you write anything that names a component, trigger, step or node. Inventing a name that sounds right is the most common mistake on this platform.

Without MCP, use the reference: components · triggers · steps · patch nodes · shader nodes.

Mistakes that look right​

Writing part of a nested value replaces all of it

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

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

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

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

It seems to work, but the change is lost. Only update() and updateAt() write data.

Setting a dynamic physics body's transform does nothing

Physics controls its position and overwrites it on the next step. Use ctx.physics.teleport to place it, and applyImpulse / applyForce to move it.

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.

More rules that catch models out:

  • Rotation is in radians in scripts, and in degrees everywhere a person looks — the editor, patch node ports, MCP tools.
  • Multiply by dt in ctx.tick, or motion speed depends on the device's frame rate.
  • Import component classes in scripts from @was/engine: TransformComponent, MetaComponent and the rest. Only init and defineProps are globals.
  • There is no "animation finished" event, in any mechanism. Time it yourself.
  • Scripts have no DOM, no fetch, no timers and no rendering library. Use ctx.tick, ctx.audio, ctx.store, and a UI card for interface.
  • The editor does not run logic. Scripts, patches, physics and timers run only in preview or in a published project. Never tell a user their script "should run in the editor".
  • While a state is active, edits to that object are saved into the state, not into the object.
  • Use the timeline tools to change a timeline. If you edit the timeline component directly, the animation may not play.
  • One object, one way to animate it. The timeline overwrites a transition.

Use a built-in step instead of rewriting it​

ctx.step(name, params, { targets }) runs any step the editor offers: animation, state switching, scene transitions, transitions. Check the step reference before you write the same thing by hand.

Check a patch before you say it works​

Look at it as code and read the result. A graph with a problem reports a data cycle or broken JavaScript. Over MCP, use preview_patch_code.

Units​

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

How to answer the user​

  1. Ask which layer they want. "Without code" and "in a script" lead to very different answers to the same question.
  2. Choose the simplest layer that works. An event is better than a patch; a patch is better than a script.
  3. Say where to click. For someone in the editor, panel names matter more than concepts.
  4. Remind them to preview. Most "it does not work" reports happen because the editor does not run logic.
  5. Do not invent names. If you are not sure, say so and point to the reference.