Dicey Table

Attaching and Running Scripts

This page is about attaching and running Table Scripting inside Edit Mode. For the editor Monaco surface itself — autocomplete, transpile-on-save, the Problems panel — see Item editors: Script. For the language and API, see Scripting API and Scripting concepts.

The SCRIPT section on an object showing the script picker set to None, with Edit and + New controls

script is not a component — there is one attachment story#

⚠ Unlike light and camera, script was deliberately not migrated into an entity's components[] array. An entity's script lives on metadata.scriptId (OBJECT_SCRIPT_METADATA_KEY, packages/shared/src/scripting.ts), with its own dedicated SCRIPT Inspector section — not the + ADD COMPONENT flow. There is no legacy-vs-current split to reason about here: this is how a script attaches to an entity, full stop. See The Scene Model for why "component" is reserved for engine components and never means this.

Two flavours, and where each one comes from#

Where you press the button decides what you get. The two buttons produce different starter scripts on purpose:

Created from Flavour Starter body refObject
An entity's SCRIPT section (+ New) Object script, for that entity's kind Handlers for the delegates that kind raises That kind's handle — DeckObject, CardObject, …
The asset bar or SCENE SCRIPTS (New ▾ ▸ Script) Global (scene) script A globalEvents.onObjectDropped handler The generic ObjectHandle

The kind is recorded on the script itself (SceneScript.refKind), so it survives a save and still types the editor the next time the script is opened. It is an authoring hint, never a gate — the host still attaches on metadata.scriptId alone.

Object scripts: one per entity#

Select an entity with a transform and open its SCRIPT Inspector section:

  • The Script dropdown lists every project script, with the kind each was written for in brackets; picking one writes its id to metadata.scriptId. An entity holds at most one object script.
  • + New creates a script for this entity's kind — a deck gets a deck starter, a die a die starter — and attaches it in the same action.
  • Edit opens the attached script in its own tab (see Item editors: Script).

An object script runs with refObject bound to the entity it's attached to, alongside the usual world and globalEvents globals. In the editor, refObject is typed for the kind the script was created for, so a deck script completes on refObject.cards and refObject.onDepleted while a die script does not offer them at all. Object Types documents what each kind adds.

Attaching a script to an entity of a different kind is allowed; the section warns, and the script runs exactly as before.

Global (scene) scripts: attached to the scene, not one entity#

Select the Room folder in the Hierarchy and find the SCENE SCRIPTS section. Scene scripts are attached by id, in a list (sceneScriptIds on the scene document, packages/shared/src/sceneEditor.ts, capped at SCENE_SCRIPT_MAX_COUNT = 64 total project scripts) — a scene can carry several at once, unlike the one-per-entity object-script slot. A scene script runs with world and globalEvents but no refObject — there is no single entity it belongs to. Use the same Add script dropdown or New ▾ ▸ Script to attach one; each attached script gets its own row with Edit and a × to detach.

Running scripts in the editor#

The viewport toolbar's Play Scripts button compiles and runs every attached script (object and scene) against the live scene, right inside Edit Mode — no separate preview environment. Starting it automatically switches Frozen to Live, because a script that reads or drives physics needs the simulation actually stepping to do anything useful. Restart re-runs with the latest compiled output and clears session saved-data; Stop halts every running script and leaves the scene as it was.

The Script Console panel above the viewport (auto-shown once anything is running or has logged) reports diagnostics per script with a timestamp and phase, plus broadcast messages — this is where a runtime error or a console.log from your script actually surfaces, since there's no browser devtools access into the sandboxed script host.

Common mistakes#

  • Looking for script in + ADD COMPONENT. It isn't there on purpose — use the dedicated SCRIPT section (object) or the SCENE SCRIPTS section under Room (scene).
  • Expecting an object script's refObject to work in a scene script. It's undefined there — a scene script has no single owning entity. Use world.getObjectById / world.getAllObjects instead. A kind filter narrows the latter, so world.getAllObjects({ kind: "deck" }) gives you DeckObjects with no cast.
  • Expecting a global script to complete on kind-specific members. It cannot: its refObject is the generic handle, because it has no entity. Create the script from the entity's SCRIPT section if you want the narrowed typing.
  • Expecting an entity restored from a snapshot to pick up its object script. An entity spawned while scripts are running does attach the script its metadata.scriptId names, and its onCreated fires — but a table rebuilt from a snapshot raises no creation event, so nothing attaches there. Restart the scripts after a rebuild. See Known limitations.
  • Forgetting Play Scripts flips Frozen to Live. If pieces you didn't expect start moving the moment you press Play Scripts, that's this, not a bug in your script.

See also#