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:
material === "silent"resolves to nothing, always — the sentinel opt-out.rollkeys off the struck surface (opts.surface, defaulting towood) andopts.count, not the rolling piece's own material — a plastic die thrown onto a cardboard box sounds like cardboard, not plastic.slidekeys off the sliding piece's own material, falling back towoodif that material has no slide variant.- Every other action tries, in order:
material.action.intensity(if an intensity was given), thenmaterial.action, then — forplasticonly — adice.actionalternative, thengeneric.actionas the last resort. - 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 sinceshufflebecame deck-only it does nothing. What blocks the sound is thatbag-rummageis a looping set:soundEventSchemahas no loop flag and there is no stop message, so a peer would play a loop-mastered clip as a one-shot.slideis stuck behind the identical wall, and the two are one decision.collectandboard-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#
api.playSound— a one-shot sound, ephemeral and never persisted.api.setObjectSound— a permanent per-entity, per-action override.- Object kinds — the per-kind default sound material.
- Manifest reference — the
soundSetsfield in the wider manifest schema. - Known limitations — documented gaps across both script surfaces.
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.
