Dicey Table

Sound sets

DiceyTable's built-in sound effects are licensed for embedded gameplay use only — the EULA forbids exposing them as an addressable clip library (see Object actions for the parallel gating pattern on the physical side of the table). Every mod- and script-facing surface reflects that: nothing in this reference names a clip id, a file, or an internal catalog key. A sound is always requested semantically — by material and action — or as one of the mod's own uploaded files.

The five enums#

Five small enums, all declared in packages/shared/src/soundSets.ts, combine to select a clip. Three already have their own full reference page; this page owns the two that don't, plus the parts of the system that combine all five.

Enum Values What it is Full reference
SoundMaterial 8 The physical surface a piece is made of. SoundMaterial
SoundAction 17 The logical interaction that emits a sound. SoundAction
RuntimeSoundEvent 15 The physics/interaction moment the table notices on its own. RuntimeSoundEvent
SoundCount 3 How many pieces the action involves. below
SoundIntensity 2 Soft vs. hard impact. below

SoundCount — 3 values#

single · double · multi. Only meaningful for roll sounds: resolveBuiltinSetId looks up roll.<surface>.<count> first and falls back to roll.<surface>.single if the exact count has no dedicated variant. Nothing sets this from object state directly — the runtime derives it from how many dice are rolling together in one gesture at the moment of the roll.

SoundIntensity — 2 values#

gentle · firm. An optional modifier on place-type impacts (dropping a piece softly onto the table vs. slamming it down); the runtime derives it from the impact velocity at the moment of collision. Most (material, action) pairs have no firm variant, in which case the resolver falls back to the plain material.action set — a firm drop and a gentle drop sound the same unless a dedicated variant exists.

The generated field tables for all five enums — including the full SoundMaterial, SoundAction and RuntimeSoundEvent value lists — are appended below; their individual reference pages linked above go deeper on each one's per-kind bindings and defaults.

How a material becomes a clip: resolveBuiltinSetId#

The runtime's resolveBuiltinSetId(material, action, { count?, intensity?, surface? }) is the one function that turns a semantic request into an internal catalog key, and it is worth understanding conceptually even though the catalog itself is not part of the mod-facing contract (see the licensing note above — this reference does not enumerate it). In order:

  1. material === "silent" resolves to nothing, always — the sentinel opt-out.
  2. roll keys off the struck surface (opts.surface, defaulting to wood) and opts.count, not the rolling piece's own material — a plastic die thrown onto a cardboard box sounds like cardboard, not plastic.
  3. slide keys off the sliding piece's own material, falling back to wood if that material has no slide variant.
  4. Every other action tries, in order: material.action.intensity (if an intensity was given), then material.action, then — for plastic only — a dice.action alternative, then generic.action as the last resort.
  5. Nothing matching, at any step: the sound is silently dropped. This is not an error condition — an unusual (material, action) combination (e.g. board + roll) legitimately has no built-in set.

What binds a RuntimeSoundEvent to a SoundAction#

OBJECT_SOUND_EVENT_MAP (packages/shared/src/soundSets.ts) is the per-kind table that decides which SoundAction plays for each RuntimeSoundEvent — a card's grab plays pickup, a bag's grab plays box-pickup. The full table, one row per event, lives on RuntimeSoundEvent rather than being duplicated here.

A binding here means "this kind has this moment", not "this kind always makes a noise then." Whether anything is audible is settled a step later, when the action is resolved against the entity's material — so every kind binds topple, but only wood and plastic have a topple clip, and the rest quietly fall back to their ordinary place. Binding broadly and resolving narrowly is deliberate, because this map is also what the loading screen walks to decide which clips to preload. An action the runtime can trigger but that is missing from this map is fetched cold on first use, and if it decodes too late it is dropped — so the first one you hear is silence. That is why fall and topple are in the map even though the runtime, not the map, decides when they happen.

14 of the 17 sound actions have a binding; 11 of them actually fire today. slide, counter-land and counter-fall are bound to nothing at all — a mod-only vocabulary, deliberately, and the reasons are on Known limitations. Separately, collect, board-clear and bag-rummage are in the map and still never play — but for two different reasons, and the difference decides whether waiting is worth anything:

  • bag-rummage — the moment exists, the protocol does not. Shaking a held bag is already a detected gesture, and since shuffle became deck-only it does nothing. What blocks the sound is that bag-rummage is a looping set: soundEventSchema has no loop flag and there is no stop message, so a peer would play a loop-mastered clip as a one-shot. slide is stuck behind the identical wall, and the two are one decision.
  • collect and board-clear — the operation does not exist. There is no gather-the-dice and no sweep-the-board gesture anywhere in the runtime, so there is nothing to bind a sound to. These are speculative bindings for features that were never built, not sounds waiting on plumbing.

Do not assume a bound action is an audible one; if your game needs a rummage or a board sweep, play it yourself with api.playSound.

Every kind also has a default material it assumes when an entity has no material field set — DEFAULT_SOUND_MATERIAL_BY_KIND, reproduced on SoundMaterial and on Object kinds's physics table (same values, different page, kept in sync by hand — flag a mismatch as a doc bug if you find one, not a data bug).

SoundRef — the license-safe pointer#

A SoundRef (packages/shared/src/soundSets.ts) is how anything outside the runtime — a mod, an entity's soundSetOverrides, the editor's sound-override panel — names a sound. It is a two-shape discriminated union and nothing else is representable:

Shape Meaning
{ kind: "builtin", material } Semantic: the engine resolves material × whatever action is being played internally. No SoundSetId, clip file or path is ever present.
{ kind: "mod", modId, name } A sound the named mod declared in its own manifest soundSets. The host enforces modId matches the calling mod and name is one it actually declared — a mod cannot point at another mod's sound.

api.setObjectSound(objectId, action, ref) is the mod-facing way to set one, permanently, in an entity's soundSetOverrides; see its reference page for the full parameter treatment and the ownership-check rejection message.

A mod's own sounds: soundSets in the manifest#

A mod that ships its own audio — a game-specific sound effect no built-in material captures — declares each one as a ModSoundSet in the manifest's soundSets array (max 64 entries; see Manifest reference § soundSets for the field-level schema and a worked JSON example). The generated ModSoundSet field table is appended below.

Once declared, a mod plays its own sound with api.playSound({ modSound: "<name>" }) (reference) or attaches it permanently to an entity with api.setObjectSound(objectId, action, { kind: "mod", modId: manifest.id, name: "<name>" }). Both reject a name the mod never declared, or a modId that isn't the calling mod's own.

See also#

modSoundSetSchema#

Exported from @diceytable/shared as modSoundSetSchema. 5 fields across 1 table.

Field Type Required Default Min / Max Pattern Rule Description
name string yes 1–80 chars ^[a-z0-9][a-z0-9._-]*$
variants string[] yes 1–16 items; each 1–180 chars
material soundMaterialSchema no
action soundActionSchema no
loop boolean no

name#

The logical name a mod's own script and soundSetOverrides refer to it by — api.playSound({ modSound: "<name>" }) and { kind: "mod", modId, name }. Scoped to the declaring mod; another mod's script cannot reference it by name even if it knows it.

variants#

Repo-relative asset paths (subject to the same 18-extension allowlist as any other declared asset — in practice .mp3/.ogg/.wav). One is chosen at random per play, the same variant-rotation model the built-in catalog uses.

material#

Optional semantic tag for your own bookkeeping — does not blend this sound with the built-in catalog or let anything resolve to it automatically. A mod's sound only ever plays when named explicitly by modSound or a { kind: "mod" } ref.

action#

Optional semantic tag, same caveat as material above — informational only, not a hook into the runtime's own event → action bindings.

loop#

Marks this as a continuous sound (a rummage, a slide) rather than a one-shot. A mod still has to pass loop: true again on the playSound/setObjectSound call that uses it — declaring it here does not make every play of this sound loop automatically.

soundMaterialSchema#

8 values. The physical material an object sounds like. silent is the opt-out sentinel.

Value
wood
cardboard
metal
plastic
card
tile
generic
silent

Full treatment, including the per-kind default table and the "where a material comes from" precedence order, lives on SoundMaterial — this table is the raw value list for quick reference alongside the other four sound enums.

soundActionSchema#

18 values. The logical interaction that emits a sound. A superset of the gameplay TableObjectAction union.

Value
place
pickup
drop
slide
shuffle
roll
fall
topple
withdraw
collect
return-to-box
board-clear
box-pickup
box-place
bag-rummage
counter-land
counter-fall
press

Full treatment lives on SoundAction. It is a separate vocabulary from the 19-value gameplay TableObjectAction union on Object actions — neither a subset nor a superset of it. A sound action names why a sound plays; an object action names a thing the engine did to an entity. The two lists share exactly two spellings, roll and shuffle. Every other value here (place, pickup, drop, slide, fall, topple, withdraw, collect, return-to-box, board-clear, box-pickup, box-place, bag-rummage, counter-land, counter-fall) has no object action of the same name, and the sixteen other object actions have no sound action of the same name. Passing one where the other belongs fails loudly: api.objectAction throws on a sound action, and api.setObjectSound drops an object action with a diagnostic.

runtimeSoundEventSchema#

16 values. Runtime moments that can emit a sound; mapped to a sound action per object kind.

Value
grab
release
settle
fall
topple
collide
roll
shuffle
flip
deal
draw
insert
collect
board-clear
rummage
press

Full treatment, including the per-kind event → action binding table, lives on RuntimeSoundEvent. Not part of any script surface — a mod never names one of these directly.

soundCountSchema#

3 values. How many pieces the action involves — selects single/double/multi clip variants.

Value
single
double
multi

Only meaningful on a roll sound; see "How a material becomes a clip" above for exactly how resolveBuiltinSetId uses it.

soundIntensitySchema#

2 values. Soft vs hard impact. Also derived from impact velocity at runtime.

Value
gentle
firm

Derived from impact velocity at runtime — nothing on the mod or table-script surface sets this directly. See "How a material becomes a clip" above.