Modifiers
Modifiers transform dice results after rolling. They can drop dice, reroll values, cap ranges, make dice explode, and more. Modifiers are applied in a fixed priority order to ensure consistent, predictable results.
Application order
Section titled “Application order”Modifiers are applied in priority order (lower number = earlier execution):
| Priority | Modifier | Notation | Options key |
|---|---|---|---|
| 10 | Cap | C{…} | cap |
| 20 | Drop | H, L, D{…} | drop |
| 21 | Keep | K, kl | keep |
| 30 | Replace | V{…} | replace |
| 40 | Reroll | R{…} | reroll |
| 50 | Explode | ! | explode |
| 51 | Compound | !! | compound |
| 52 | Penetrate | !p | penetrate |
| 60 | Unique | U | unique |
| 85 | Pre-arithmetic multiply | *N | multiply |
| 90 | Plus | +N | plus |
| 91 | Minus | -N | minus |
| 95 | Count successes | S{…} | countSuccesses |
| 100 | Total multiply | **N | multiplyTotal |
This means dice values are capped/constrained first, then pool size is adjusted (drop/keep), then values are replaced or rerolled, then explosive mechanics fire, and finally arithmetic is applied.
ModifierOptions interface
Section titled “ModifierOptions interface”interface ModifierOptions {cap?: ComparisonOptionsdrop?: DropOptionskeep?: KeepOptionsreplace?: ReplaceOptions | ReplaceOptions[]reroll?: RerollOptionsunique?: boolean | UniqueOptionsexplode?: boolean | numbercompound?: boolean | numberpenetrate?: boolean | numbercountSuccesses?: SuccessCountOptionsmultiply?: numberplus?: numberminus?: numbermultiplyTotal?: number}Limit roll values to a specific range. Values outside the range are clamped to the boundary.
Options key: cap
import { roll } from '@randsum/roller'
roll('4d20C{>18}') // caps at 18roll('4d20C{<3}') // Cap under 3 to 3roll('4d20C{<2,>19}') // Cap both ends
// Options objectroll({sides: 20,quantity: 4,modifiers: { cap: { greaterThan: 18 }}})
roll({sides: 20,quantity: 4,modifiers: { cap: { greaterThan: 19, lessThan: 2 }}})ComparisonOptions:
interface ComparisonOptions {greaterThan?: numbergreaterThanOrEqual?: numberlessThan?: numberlessThanOrEqual?: numberexact?: number[]}Remove dice from the result pool.
Options key: drop
import { roll } from '@randsum/roller'
roll('4d6L') // 3-18roll('4d6L2') // Drop lowest 2roll('4d6H') // Drop highest 1roll('4d6H2') // Drop highest 2
// Drop by valueroll('4d20D{>17}') // Drop results over 17roll('4d20D{<5}') // Drop results under 5roll('4d20D{8,12}') // Drop exact values
// Options objectroll({sides: 6,quantity: 4,modifiers: { drop: { lowest: 1 }}})
roll({sides: 20,quantity: 4,modifiers: { drop: { greaterThan: 17 }}})DropOptions:
interface DropOptions extends ComparisonOptions {highest?: numberlowest?: numberexact?: number[]}Keep specific dice from the result (complement of drop).
Options key: keep
import { roll } from '@randsum/roller'
roll('4d6K3') // 3-18roll('4d6K') // Keep highest 1
// Keep lowestroll('4d6kl2') // Keep lowest 2roll('4d6kl') // Keep lowest 1
// Options objectroll({sides: 6,quantity: 4,modifiers: { keep: { highest: 3 }}})KeepOptions:
interface KeepOptions {highest?: numberlowest?: number}Replace
Section titled “Replace”Replace specific results with new values.
Options key: replace
import { roll } from '@randsum/roller'
// Replace exact valuesroll('4d20V{8=12}') // Replace 8s with 12s
// Replace by comparisonroll('4d20V{>17=20}') // Replace results over 17 with 20roll('4d20V{<5=1}') // Replace results under 5 with 1
// Options objectroll({sides: 20,quantity: 4,modifiers: { replace: { from: 8, to: 12 }}})
roll({sides: 20,quantity: 4,modifiers: { replace: { from: { greaterThan: 17 }, to: 20 }}})ReplaceOptions:
interface ReplaceOptions {from: number | ComparisonOptionsto: number}You can pass an array of ReplaceOptions to perform multiple replacements.
Reroll
Section titled “Reroll”Reroll dice matching certain conditions.
Options key: reroll
import { roll } from '@randsum/roller'
roll('4d20R{<5}') // 4-20roll('4d20R{>17}') // Reroll results over 17
// Reroll exact valuesroll('4d20R{8,12}') // Reroll 8s and 12s
// With max attemptsroll('4d20R{<5}3') // Reroll under 5, max 3 attempts
// Options objectroll({sides: 20,quantity: 4,modifiers: { reroll: { lessThan: 5, max: 3 }}})RerollOptions:
interface RerollOptions extends ComparisonOptions {exact?: number[]max?: number}Explode
Section titled “Explode”Roll additional dice when a die shows its maximum value.
Options key: explode
import { roll } from '@randsum/roller'
roll('4d20!') // Exploding d20s
// Options objectroll({sides: 20,quantity: 4,modifiers: { explode: true }})
roll({sides: 6,quantity: 3,modifiers: { explode: 5 } // Max depth 5 (options only)})When a die shows its maximum value, a new die is rolled and added as a separate result. Each new maximum continues the chain.
Compound
Section titled “Compound”Compounding exploding dice add to the triggering die instead of creating new dice.
Options key: compound
import { roll } from '@randsum/roller'
roll('3d6!!') // Compound onceroll('3d6!!5') // Max depth 5roll('3d6!!0') // Unlimited (capped at 100)
// Options objectroll({sides: 6,quantity: 3,modifiers: { compound: true }})Unlike regular exploding, compound does not create new dice — it modifies the existing die value.
Penetrate
Section titled “Penetrate”Penetrating exploding dice subtract 1 from each subsequent explosion (Hackmaster-style).
Options key: penetrate
import { roll } from '@randsum/roller'
roll('3d6!p') // Penetrate onceroll('3d6!p5') // Max depth 5roll('3d6!p0') // Unlimited (capped at 100)
// Options objectroll({sides: 6,quantity: 3,modifiers: { penetrate: true }})Each subsequent penetration subtracts 1 from the result before adding, creating diminishing returns.
Unique
Section titled “Unique”Force all dice in a pool to show different values.
Options key: unique
import { roll } from '@randsum/roller'
roll('4d20U') // 4-80roll('4d20U{5,10}') // Unique except 5s and 10s
// Options objectroll({sides: 20,quantity: 4,modifiers: { unique: true }})
roll({sides: 20,quantity: 4,modifiers: { unique: { notUnique: [5, 10] }}})UniqueOptions:
interface UniqueOptions {notUnique: number[]}Pre-arithmetic multiply
Section titled “Pre-arithmetic multiply”Multiply the dice sum before +/- arithmetic.
Options key: multiply
import { roll } from '@randsum/roller'
roll('2d6*2+3') // (dice sum * 2) + 3
// Options objectroll({sides: 6,quantity: 2,modifiers: { multiply: 2, plus: 3 }})Plus / Minus
Section titled “Plus / Minus”Add or subtract a fixed value from the total.
Options keys: plus, minus
import { roll } from '@randsum/roller'
roll('4d6+5') // Notationroll('2d8-2') // Notation
// Options objectroll({sides: 6,quantity: 4,modifiers: { plus: 5 }})
roll({sides: 8,quantity: 2,modifiers: { minus: 2 }})Count successes
Section titled “Count successes”Count dice meeting a threshold instead of summing. Used in dice pool systems (World of Darkness, Shadowrun).
Options key: countSuccesses
import { roll } from '@randsum/roller'
roll('5d10S{7}') // 0-5roll('5d10S{7,1}') // Successes >= 7, minus botches <= 1
// Options objectroll({sides: 10,quantity: 5,modifiers: { countSuccesses: { threshold: 7, botchThreshold: 1 }}})SuccessCountOptions:
interface SuccessCountOptions {threshold: numberbotchThreshold?: number}Total multiply
Section titled “Total multiply”Multiply the entire final total after all other modifiers.
Options key: multiplyTotal
import { roll } from '@randsum/roller'
roll('2d6+3**2') // (dice + 3) * 2roll('4d6L+2**3') // ((drop lowest) + 2) * 3
// Options objectroll({sides: 6,quantity: 2,modifiers: { plus: 3, multiplyTotal: 2 }})Order of operations
Section titled “Order of operations”The full calculation order is:
- Roll all dice
- Apply cap (clamp values)
- Apply drop/keep (adjust pool)
- Apply replace (swap values)
- Apply reroll (re-roll matching dice)
- Apply explode/compound/penetrate (add dice or modify values)
- Apply unique (ensure no duplicates)
- Sum remaining dice
- Apply pre-arithmetic multiply (
*) - Apply plus/minus (
+/-) - Count successes if applicable (S{…})
- Apply total multiply (
**)
See the full Randsum Dice Notation Spec for the complete syntax reference.