Dice View v3 API

Reference for the physics API, requests, options, timeline, and looks in version 3.0.0-alpha.0.

This article covers the v3 alpha preview, which uses physics only and is not yet published on npm. V2 ↗ Licenses ↗

This reference describes the ae00d9f v3 revision, identified as 3.0.0-alpha.0. The alpha is not on npm yet; see installing v3. The published 2.6.1 release has a separate reference.

View receives resolved values and presents them. It neither parses notation nor draws random results. In v3, DisplayMode is only 'physics'; cinematic mode has been removed.

Entrypoints

Import Contents
@erpg/dice3dview DiceResultViewer, helpers, types, and looks; WebGL module with no runtime dependencies
@erpg/dice3dview/external Alias for the same module, retained for v2 integrations
@erpg/dice3dview/adapters System dice converters without loading the renderer
@erpg/dice3dview/style.css Canvas styles

Construction requires an element in the DOM. init() requires WebGL2 or WebGL1; construct only on the client.

DiceResultViewer

Member Contract Purpose
canvas HTMLCanvasElement Canvas created and attached by the constructor
constructor(options?) ViewerOptions Validates options and mounts the canvas in container
init() Promise<this> Initializes WebGL, themes, and resize observation; idempotent
display(request) Promise<DisplayResult> Simulates and draws supplied faces; calls init() when needed
displayTimeline(request) Promise<DisplayTimelineResult> Runs a semantic journal using physics
clear() void Cancels the presentation and immediately clears the scene
updateOptions(options) Promise<void> Merges options; timeline effects are merged individually
applyLook(look) Promise<void> Validates and applies a complete versioned look
playParticles(moment, options?) void Triggers particles on dice already on the table
resize() void Recalculates the stage; ResizeObserver also calls it
dispose() void Releases resources and removes the canvas; do not reuse the instance

A new presentation or clear() cancels the previous Promise with DisplayCancelledError. Detect it with isDisplayCancelledError(error).

display(request)

type DiceSides = 2 | 4 | 6 | 8 | 10 | 12 | 20 | 100
type DisplayMode = 'physics'

interface ResolvedDie {
  readonly id: string
  readonly sides: DiceSides
  readonly value: number
  readonly discarded?: boolean
  readonly theme?: string
  readonly themeColor?: string
}

interface DisplayRequest {
  readonly id: string
  readonly dice: readonly ResolvedDie[]
  readonly seed?: string
  readonly mode?: DisplayMode | 'kinematic' // legacy; runs physics
}

interface DisplayResult {
  readonly id: string
  readonly dice: readonly ResolvedDie[]
  readonly durationMs: number
}

id and dice must be nonempty. value must be an integer from 1 to sides. seed defaults to id and affects only choreography. Supported shapes are d2, d4, d6, d8, d10, d12, d20, and d100; each d100 uses two bodies against the maxDice limit. mode: 'kinematic' is accepted for compatibility, warns once in the console, and runs physics.

display() returns normalized, frozen dice. After input validation, a WebGL or asset failure is logged and the result is still returned. If visual presentation is essential to your workflow, call await viewer.init() explicitly and inspect the stage.

displayTimeline(request)

interface DisplayTimelineRequest {
  readonly id: string
  readonly dice: readonly TimelineDieDefinition[]
  readonly events: readonly DiceTimelineEvent[]
  readonly seed?: string
  readonly mode?: 'physics' | 'kinematic' // legacy
}

interface DisplayTimelineResult extends DisplayResult {
  readonly eventCount: number
  readonly phaseCount: number
  readonly degraded: boolean
}

Each TimelineDieDefinition identifies a die and its sides; roll and reroll events supply the faces. Explosion, reroll, discard, and classification events retain the v2 journal format. V3 spawns and relaunches dice physically. onTimelineProgress receives initial, phase, and complete snapshots for synchronizing the total in your UI. Unlike display(), displayTimeline() propagates rendering and asset failures as well as validation errors.

System adapters

createMixedDisplayRequest({ id, dice, seed?, unsupportedDice?, theme?, themeColor?, keptIds?, themeColors? }) accepts rollMixedDice().dice from Core, preserves order, and selects physicalValue ?? rawValue ?? value. Vampire V5, Assimilation, Fate, and Daggerheart profiles automatically apply their themes. Generic dice with no 3D shape are omitted by default; unsupportedDice: 'error' makes that an error. createSystemDisplayRequest() accepts dice already associated with a profileId and applies each system profile.

import { rollMixedDice } from '@erpg/dicecore'
import { createMixedDisplayRequest } from '@erpg/dice3dview/adapters'

const mixed = rollMixedDice('2d20+5; v5(7,3,4); fate(4)', { seed: 'table-42' })
await viewer.display(createMixedDisplayRequest({
  id: 'table-42',
  seed: 'table-42',
  dice: mixed.dice
}))

ViewerOptions

Core and scene

Option Default Use
container null Selector or element; required in practice
assetPath /assets/dice-box/ Public root for themes/
origin page origin Origin for internal assets
theme / themeColor default / #2e8555 Default theme and color
preloadThemes / externalThemes [] / {} Preloading and external theme URL map
maxDice 120 Body limit; d100 counts as two
enableShadows / shadowTransparency true / 0.8 Soft contact shadows
lightIntensity / antialias / scale 1 / true / 5 Lighting, WebGL, and scale
delay / wallPadding 10 ms / 0.25 Sequential release and stage boundary
spawnSpacing / spawnHeightStep / spawnOverscan 1.72 / 0 / 0.15 Throw distribution
reducedMotion auto auto, always, or never; auto follows the system preference

mode defaults to physics and is optional. shadowResolution and duration remain accepted to compile v2 clients but are ignored.

Physics

Option Default Use
gravity 1.3 Gravity multiplier
mass 1.08 Base mass
startingHeight 7.6 Starting height
spinForce 5.8 Initial spin
throwForce 6.4 Throw energy
aggressiveThrowChance 0.12 Seeded chance of a higher-energy throw
colliderScale 1.02 Polyhedron collider scale
friction / restitution 0.54 / 0.29 Friction and bounce
linearDamping / angularDamping 0.10 / 0.08 Damping
settleTimeout 4200 ms Settling window; never decides the result

wallBounceChance remains a deprecated alias for aggressiveThrowChance. physicsWasmUrl is ignored: the v3 engine is JavaScript and uses no Havok.

Timeline and callbacks

timeline.enabled defaults to true, maxEvents to 500, maxDurationMs to 12000, and phaseGapMs to 180. Effects include explode, reroll, unique, compound, penetrate, keep, drop, success, failure, neutral, and criticals. explode.origin is source or edge; reroll.style and unique.style are hop, edge, or spin. When the budget is exceeded, the result marks degraded: true and presents the final state as a flat roll.

Callbacks: onCollision, onThemeConfigLoaded, onThemeLoaded, and onTimelineProgress. onCollision receives optional IDs for the two bodies and force; onTimelineProgress receives revealed dice and completed event sequences at each stage.

Skins, particles, and glow

const viewer = new DiceResultViewer({
  container: '#dice-stage',
  skin: {
    texture: '/skins/marble.webp',
    scale: 1,
    blend: 'multiply',
    opacity: 0.8,
    labels: 'auto'
  },
  particles: { preset: 'sparkle', intensity: 1 },
  glow: { color: '#ff4f93', intensity: 0.7, light: true }
})

skin projects an image over color materials; numbers and symbols remain above it. blend accepts normal, multiply, screen, and overlay. Particles can use one of 15 presets or a custom effect with emitters for trail, ground, impact, collision, settle, aura, explode, and critical. glow controls color, intensity, light on the table, and pulsing. All three are optional (null by default) and can be changed with updateOptions().

A DiceLook combines color, skin, particles, and glow in a versioned object that can be saved and applied:

import { createDiceLook } from '@erpg/dice3dview'

const look = createDiceLook({
  name: 'Pink',
  themeColor: '#e60049',
  particles: { preset: 'sparkle' },
  glow: { intensity: 0.7 }
})

await viewer.applyLook(look)
viewer.playParticles('critical')

The format uses format: 'dice3dview-look' and version: 1. applyLook() validates before changing the viewer; omitted parts are turned off. playParticles() accepts impact, collision, settle, aura, explode, and critical, optionally with { dice: ['id'] }.

Assets, lifecycle, and cancellation

Publish dist/assets/dice-box/ at /assets/dice-box/; v3 needs themes, models, and textures but no havok/. For external themes, configure externalThemes and the asset server’s CORS policy. Changing container, id, antialias, or the root of already-loaded assets requires a new instance.

import { isDisplayCancelledError } from '@erpg/dice3dview'

try {
  await viewer.display(request)
} catch (error) {
  if (isDisplayCancelledError(error)) return
  throw error
}

Technical sources: official v3 API and public types.