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. | card→pickup · deck→pickup · die→pickup · token→pickup · custom→pickup · bag→box-pickup |
no — the table raises it |
release |
A piece is let go. | card→place · deck→place · die→drop · token→drop · custom→drop · board→place · bag→box-place · card-holder→place |
no |
settle |
A piece stops moving. | card→place · deck→place · die→place · token→place · custom→place · board→place · bag→box-place · card-holder→place |
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. | die→roll · token→place · custom→place |
no |
roll |
A die is thrown. | die→roll |
indirectly: api.objectAction(id, "roll") |
shuffle |
A deck is shuffled. | card→shuffle · deck→shuffle |
indirectly: api.objectAction(id, "shuffle") |
flip |
A card is turned over. | card→place |
indirectly: api.objectAction(id, "flip") |
deal |
Cards are dealt out. | card→withdraw · deck→withdraw |
indirectly: api.objectAction(id, "deal") |
draw |
A card is taken off a pile. | card→withdraw · deck→withdraw |
indirectly: api.objectAction(id, "draw") |
insert |
A card or deck is absorbed into a deck — drop one onto another. Plays on the surviving deck. | deck→return-to-box |
indirectly: api.objectAction(id, "combine") |
collect |
Several pieces are gathered up. Never raised — no such operation exists. | die→collect |
no |
board-clear |
A board is swept clear. Never raised — no such operation exists. | board→board-clear |
no |
rummage |
A hand searches inside a bag. Never raised — the gesture exists, the loop protocol does not. | bag→bag-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 (analyzeShake → dispatchShakeGesture) 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 draw → withdraw 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#
- SoundAction — the 17 actions this table maps into, and the three no moment binds.
- SoundMaterial — what resolves an action to a clip.
api.setObjectSound— overriding one action on one entity.api.playSound— a one-shot that changes nothing.- Object kinds — the kinds in the second column.
