Dicey Table

ObjectAction (table script)

ObjectAction is the union a table script may pass through the script host — 13 of the engine's 19 actions. It is declared inside DICEYTABLE_SCRIPT_API_DTS and re-checked at run time against SCRIPT_SAFE_OBJECT_ACTIONS in apps/web/src/scripting/TableScriptHost.ts, and the two lists match exactly, so a compromised sandbox frame cannot broaden the set.

This union does not exist on the mod surface. A mod's ten-action list is SANDBOX_SAFE_OBJECT_ACTIONS, which is a different list with a different enforcement path.

The values#

The How a script calls it column is the practical limit: an action with no method is in the union and would pass the allowlist, but the typed API offers no way to request it.

Value What the host does Kinds it is meaningful for How a script calls it
flip Toggles faceDown and rotates 180° about local X. card, deck; on any other kind the rotation and the flag both still change. handle.flip()
rotate Rotates 90° about local Y. every kind. handle.rotate()
lock Marks the entity locked and switches its rigidbody to static. every kind. handle.lock()
unlock Clears the lock and restores the authored body type. every kind. handle.unlock()
tap Sets tapped to true. every kind. no method
untap Sets tapped to false. every kind. no method
shuffle Shuffles a deck's contents. Refused outright on every other kind — the runtime returns before it changes anything, so a bag makes no sound and does not move. deck. handle.shuffle()
draw Draws the first item off the container. Does nothing on any other kind. deck, bag. handle.draw()
deal Deals from the container to the seated players. Does nothing on any other kind. deck, bag. handle.deal()
split Splits a stacked deck in two. Does nothing unless the deck holds more than one card. deck. no method
combine Merges the entity into a stack. card, deck. no method
roll Throws the die with a randomized impulse. On a card it throws the card. die. handle.roll()
delete Destroys the entity, re-parents its children, removes its joints. every kind. handle.destroy() — the method name differs from the action name

9 of the 13 have a method. tap, untap, split and combine do not.

Known gap. tap, untap, split and combine are declared in ObjectAction and are on the script host's allowlist (apps/web/src/scripting/TableScriptHost.ts, SCRIPT_SAFE_OBJECT_ACTIONS), but ObjectHandle exposes no method for any of them (packages/shared/src/scripting.ts). Every layer below the typed API is ready for them — the allowlist passes them and the runtime applies them correctly — and only the calling surface is missing. delete looks like the same problem and is not: it is reachable, as destroy(). Until a method exists, model tapping as your own state — a tag, or saved data — and read it back with handle.refresh(). A mod can call split and combine, because mod scripting passes the action name as a string. See Known limitations.

What the engine has and this union does not#

lift, flick, press, reveal-all, reveal-team-a and reveal-team-b are the six engine actions a table script cannot request.

By design. lift and flick are drag mechanics — the physical result of a pointer gesture, meaningless without the gesture that produced it. press is a button's own click — a script observes it through ButtonObject.onPressed and reacts, rather than synthesizing one. The three reveal-* actions are hidden-information reveals, and hidden information is the one part of the table the host must own outright: an action that can flip who sees a card is an action that can be used to cheat. Scripts do not get them, and that is not expected to change. Model a reveal as your own state — a tag, or saved data — and let the host's reveal path stay the only thing that changes card visibility.

Receiving an action is wider than requesting one#

globalEvents.onObjectAction and ObjectHandle.onAction are not typed with this union. They are typed with ObservedObjectAction, which is this union plus the six engine actions above — the event is raised for all 19, so a handler can be handed a lift, a flick, a press or a reveal-* it could never have asked for.

That is why there are two types rather than one: collapsing them would either hide six real events or advertise six calls the host's allowlist refuses. Annotate with ObjectAction what you are about to request and with ObservedObjectAction what an event gave you, and keep a default branch in the switch — the engine's list and this union are maintained by hand in two files.

See also#