Dicey Table

SANDBOX_SAFE_OBJECT_ACTIONS

The ten action names a mod may pass to api.objectAction(objectId, action). It is declared as ModObjectAction in the mod scripting declarations and enforced at three points, independently, against one list:

  1. In the sandbox frameSAFE_OBJECT_ACTIONS in apps/web/src/mods/sandbox/modSandbox.html throws Unsupported object action from sandbox: <action> synchronously for anything else.
  2. On the host, when the message arrivesSANDBOX_SAFE_OBJECT_ACTIONS in apps/web/src/mods/SandboxedModRunner.ts, checked by isSandboxSafeObjectAction. A message posted straight at the host without going through the frame is rejected here and reported as a mod diagnostic.
  3. On the host, at dispatch — the runner's objectAction implementation (apps/web/src/ui/App.tsx) checks the same isSandboxSafeObjectAction before building the intent, and throws Unsupported script object action: <action> otherwise. The innermost gate is deliberately no laxer than the outer ones.

The object-action capability is not this gate. The capability decides whether you may call the method at all; this list decides what you may ask for, and it is re-checked host-side whatever the manifest says.

The values#

Value What the host does Kinds it is meaningful for Also available to a table script
flip Toggles faceDown and rotates 180° about local X. On a deck it also flips every card entry. card, deck. On any other kind the rotation and the flag both change anyway. yes, handle.flip()
rotate Rotates 90° about local Y. every kind. yes, handle.rotate()
lock Marks the entity locked and switches its rigidbody to static. every kind. yes, handle.lock()
unlock Clears the lock and restores the authored body type. every kind. yes, handle.unlock()
shuffle Shuffles a deck's contents. Refused outright on anything that is not a deck, even from a host-side mod. deck. yes, handle.shuffle()
draw Draws the first item off the container. Guards itself: does nothing on any other kind. deck, bag. yes, handle.draw()
deal Deals from the container to the seated players. Guards itself. deck, bag. yes, handle.deal()
split Splits a stacked deck in two. Guards itself: does nothing unless the deck holds more than one card. deck. no — in the union, but ObjectHandle has no method
combine Merges the entity into a stack. card, deck. no — same reason
roll Throws a die with a randomized impulse and lets physics settle it. Does not guard itself. die. On a card it throws the card. yes, handle.roll()

What a mod cannot ask for, and why#

Thirteen of the engine's 23 actions are unreachable from a mod under any capability:

Withheld Reason
delete A mod is untrusted code fetched from a repository. One that can destroy entities can quietly dismantle a table it did not create.
tap, untap No participant path sends them either — the per-kind gate refuses both for every kind — so a mod is not being singled out.
lift, flick Drag mechanics: the physical result of a pointer gesture, meaningless without the gesture.
press A button's own click. It is raised by a player pressing the button, not requested — no surface, table script or mod, can synthesize one.
reveal-all, reveal-team-a, reveal-team-b Hidden-information reveals. An action that can flip who sees a card is an action that can be used to cheat, so the host owns them outright.
peek, search, search-pull, search-close Host-authoritative private reveals of a pile, to one player. A mod that could open one would manufacture a reveal for itself; it hears about them instead, through public, identity-free table events.

By design. A mod and a table script get different budgets because they carry different trust: a table script is authored in the editor by whoever built the scene, while a mod is code from a GitHub repository gated by a declared capability list. Neither list is expected to widen. Build a mod around api.createObject and the ten actions it does have; a game that genuinely needs to remove pieces belongs in a table script.

split and combine are more reachable from a mod than from a table script, because a mod passes the action name as a string and the typed table-script API has no method for either.

See also#