Integrate Dice Core and Dice View
Resolve rules in Core and present generic dice, systems, and event journals without changing outcomes.
This article documents the published v2. For the current physics preview, see the v3 guide. V3 ↗ Licenses ↗
Core parses notation, samples dice, and applies rules. View receives finished physical faces. Keep totals, modifiers, and rule explanations in the UI; the 3D stage shows the dice, including discarded ones.
Generic rolls
import { rollRpgDice } from '@erpg/dicecore'
import { type DiceSides } from '@erpg/dice3dview/external'
const supported = new Set<number>([2, 4, 6, 8, 10, 12, 20, 100])
const roll = rollRpgDice('4d6kh3+2', { seed: 'ability-42' })
const dice = roll.dice.flatMap(die => typeof die.sides === 'number' && supported.has(die.sides)
? [{
id: die.id,
sides: die.sides as DiceSides,
value: die.value,
discarded: !die.included
}]
: [])
if (dice.length) await viewer.display({ id: 'ability-42', dice })
showTotal(roll.total)
viewer is an instance created as in Getting started; showTotal stands for your own UI. Filtering matters because Core accepts formats without native 3D geometry. The +2 modifier affects the total but does not become a die. For effects whose semantic value cannot be a physical face, such as compound, prefer the journal with displayTimeline(). Try 4d6kh3+2.
Mixed rolls
rollMixedDice() returns a flat list. View’s pure adapter preserves order and physical faces, applies symbolic themes by profileId, and omits unsupported generic shapes by default.
import { rollMixedDice } from '@erpg/dicecore'
import { createMixedDisplayRequest } from '@erpg/dice3dview/adapters'
const mixed = rollMixedDice('2d20+5; v5(7,3,4); fate(4)', {
seed: 'session-42'
})
await viewer.display(createMixedDisplayRequest({
id: 'mixed-42',
seed: 'session-42',
dice: mixed.dice,
unsupportedDice: 'omit',
mode: 'physics'
}))
Use unsupportedDice: 'error' if silent omissions are unacceptable. The adapter reads physicalValue, then rawValue, then value. A generic d3 appears as d6 geometry with face 1–3; generic dF is omitted. For 3D Fate faces, use fate() in mixed notation. Try the mixed roll.
Systems with visual profiles
createSystemDisplayRequest() validates profileId, sides, and face. Pass the Core system’s dice list:
import { rollVampireV5 } from '@erpg/dicecore'
import { createSystemDisplayRequest } from '@erpg/dice3dview/adapters'
const roll = rollVampireV5(
{ pool: 7, hunger: 3, difficulty: 4 },
{ seed: 'v5-42' }
)
await viewer.display(createSystemDisplayRequest({
id: 'v5-42',
dice: roll.dice
}))
Built-in profiles cover Vampire V5, Assimilation, Fate, and Daggerheart Hope/Fear dice. For Assimilation, pass keptIds: selection.selectedIds after evaluateAssimilationSelection(); unselected dice are shown as discarded. See the profile table.
Event journal
When Core provides roll.events, displayTimeline() presents explosions, rerolls, drops, and classifications as visual phases. Keep the dice and corresponding events together; do not filter individual events if that leaves references to removed dice.
const journalRoll = rollRpgDice('4d6kh3+2')
const result = await viewer.displayTimeline({
id: 'journal-42',
dice: journalRoll.dice.map(die => ({
id: die.id,
sides: die.sides as DiceSides
})),
events: journalRoll.events.filter(event => event.subject === 'die')
})
console.log(result.eventCount, result.phaseCount, result.degraded)
This example uses a roll whose dice all have supported sides. Read Semantic timeline for event contracts, budgets, and progress.