Dicey Table

RuntimeSoundEvent

A runtime sound event is a moment the table notices: a piece was grabbed, a piece came to rest, two pieces collided. The runtime maps each moment to a sound action using a per-kind table, then resolves that action against the entity's material to pick a clip.

15 values, declared as runtimeSoundEventSchema in packages/shared/src/soundSets.ts.

This enum is not part of any script surface. A mod never names a runtime sound event: it names a sound action directly with api.playSound. The list is here because it is what decides, without you doing anything, what your pieces sound like — and because a mod that plays its own sound on top of one of these gets two sounds.

The values#

Value The moment Kinds that bind it, and the action it plays Reachable from a script
grab A piece is picked up. cardpickup · deckpickup · diepickup · tokenpickup · custompickup · bagbox-pickup no — the table raises it
release A piece is let go. cardplace · deckplace · diedrop · tokendrop · customdrop · boardplace · bagbox-place · card-holderplace no
settle A piece stops moving. cardplace · deckplace · dieplace · tokenplace · customplace · boardplace · bagbox-place · card-holderplace no
fall A piece stops moving after a real descent: over 2.5 ft/s straight down, roughly a 6 in drop. every kind→fall no
topple A piece stops moving while still spinning over 6 rad/s — it went over rather than being placed. every kind→topple no
collide A piece strikes something. dieroll · tokenplace · customplace no
roll A die is thrown. dieroll indirectly: api.objectAction(id, "roll")
shuffle A deck is shuffled. cardshuffle · deckshuffle indirectly: api.objectAction(id, "shuffle")
flip A card is turned over. cardplace indirectly: api.objectAction(id, "flip")
deal Cards are dealt out. cardwithdraw · deckwithdraw indirectly: api.objectAction(id, "deal")
draw A card is taken off a pile. cardwithdraw · deckwithdraw indirectly: api.objectAction(id, "draw")
insert A card or deck is absorbed into a deck — drop one onto another. Plays on the surviving deck. deckreturn-to-box indirectly: api.objectAction(id, "combine")
collect Several pieces are gathered up. Never raised — no such operation exists. diecollect no
board-clear A board is swept clear. Never raised — no such operation exists. boardboard-clear no
rummage A hand searches inside a bag. Never raised — the gesture exists, the loop protocol does not. bagbag-rummage no

A kind with no binding for a moment makes no sound at that moment. board binds no grab, which is why a board is quiet when picked up and a bag is not.

Three of the 15 have a binding and are never raised, for two different reasons. rummage is the one that is nearly there: shaking a held bag is already detected (analyzeShakedispatchShakeGesture) and, now that shuffle is deck-only, does nothing — but bag-rummage is a looping set and the sound-event protocol has no loop flag and no stop message, the same wall that keeps slide unbound. collect and board-clear are a different case entirely: the table has no gather-the-dice and no sweep-the-board operation at all, so there is no moment to raise. Their clip sets still preload. If your game needs one of these, play it yourself with api.playSound.

settle, fall and topple are the same moment, resolved once. A body's moving→rest transition picks exactly one of the three — fall first, then topple, then the ordinary settle — so a landing never plays two clips, and binding the two new ones cannot make a settling pile any noisier than it was. A candidate is taken only if the kind binds it and the entity's material resolves a clip for it; the catalog carries wood.fall, wood.topple and plastic.topple and nothing else in that family, so most entities keep their place. The choice lives in apps/web/src/playcanvas/audio/settleSoundAction.ts if you want to read the thresholds.

How this affects a mod#

Do not duplicate what the table already plays. Calling api.playSound in an onCardDrawn handler adds a second sound on top of the drawwithdraw the runtime already played. Use playSound for moments the table has no concept of — a bid accepted, a round scored — and let the physical ones alone.

To change what a moment sounds like, change the entity, not the moment. There is no way to remap a runtime event. What you can do is override the action it resolves to, per entity, with api.setObjectSound(objectId, action, ref) — so a coin that should clink when it lands gets a metal override on place, and every settle and release on that coin picks it up.

Sounds are ephemeral. They ride the unreliable channel, are never persisted, and never appear in a snapshot. Do not treat a sound as evidence that anything happened.

See also#