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 |
| 30 | Replace | V{…} | replace |
| 40 | Reroll | R{…} | reroll |
| 50 | Explode | ! | explode |
| 51 | Compound | !! | compound |
| 52 | Penetrate | !p | penetrate |
| 53 | Explode Sequence | !s{...}, !i, !r | explodeSequence |
| 55 | Wild Die | W | wildDie |
| 60 | Unique | U | unique |
| 65 | Drop | H, L, D{…} | drop |
| 66 | Keep | K, kl | keep |
| 80 | Count | #{…} | count |
| 85 | Pre-arithmetic multiply | *N | multiply |
| 90 | Plus | +N | plus |
| 91 | Minus | -N | minus |
| 93 | Integer divide | //N | integerDivide |
| 94 | Modulo | %N | modulo |
| 95 | Sort | sa/sd | sort |
| 100 | Total multiply | **N | multiplyTotal |
This means dice values are capped/constrained first, then values are replaced or rerolled, then explosive mechanics fire, pool size is adjusted (drop/keep), and finally arithmetic is applied.
ModifierOptions interface
Section titled “ModifierOptions interface”interface ModifierOptions {cap?: ComparisonOptionsreplace?: ReplaceOptions | ReplaceOptions[]reroll?: RerollOptionsexplode?: boolean | ComparisonOptionscompound?: boolean | number | ComparisonOptionspenetrate?: boolean | number | ComparisonOptionsexplodeSequence?: number[]wildDie?: booleanunique?: boolean | UniqueOptionsdrop?: DropOptionskeep?: KeepOptionscount?: CountOptionsmultiply?: numberplus?: numberminus?: numberintegerDivide?: numbermodulo?: numbersort?: 'asc' | 'desc'multiplyTotal?: 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.
Explode Sequence
Section titled “Explode Sequence”Roll additional dice using a custom die-size sequence when the current die shows its maximum value. Each step in the sequence uses the next die size; the last size repeats until max depth is reached.
Options key: explodeSequence
| Notation | Description |
|---|---|
!s{N1,N2,...} | Explode through die sizes in sequence |
!i | Sugar: inflate through standard die set (d4 → d6 → d8 → d10 → d12 → d20) |
!r | Sugar: reduce through standard die set (d20 → d12 → d10 → d8 → d6 → d4) |
import { roll } from '@randsum/roller'
roll('1d4!s{4,6,8,10}') // Explode d4 -> d6 -> d8 -> d10roll('1d6!s{8,12}') // Explode to d8, then d12roll('1d4!i') // Inflate through standard die setroll('1d20!r') // Reduce through standard die set
// Options objectroll({sides: 4,quantity: 1,modifiers: { explodeSequence: [4, 6, 8, 10] }})When the die shows its maximum, a new die of the next size in the sequence is rolled and added. The sequence steps forward with each successive explosion; once the last size is reached, it repeats (capped at the default explosion depth).
Example: 1d4!s{4,6,8,10} rolls a d4. On a 4 (max), rolls a d6. On a 6 (max), rolls a d8. On a 7 — not max — the sequence stops.
Wild Die
Section titled “Wild Die”The D6 System wild die modifier (West End Games). The last die in the pool is designated as the “wild die” with special behavior. All notation is case-insensitive.
Options key: wildDie
| Notation | Description |
|---|---|
W | Last die is the “wild die” |
import { roll } from '@randsum/roller'
roll('5d6W') // Last die is wild
// Options objectroll({sides: 6,quantity: 5,modifiers: { wildDie: true }})Wild die behavior:
- Wild die = max value (6): The wild die compound-explodes — keep rolling and adding while the maximum is rolled.
- Wild die = 1: Remove the wild die AND the highest non-wild die from the pool.
- Otherwise: No special effect, the wild die acts as a normal die.
Example: 5d6W rolls [4, 3, 5, 2, 6]. The wild die (6) compound-explodes: rolls 4, so wild die becomes 10. Result: [4, 3, 5, 2, 10] = 24.
Example (wild 1): 5d6W rolls [4, 3, 5, 2, 1]. The wild die (1) triggers removal: remove the 1 (wild) and the 5 (highest non-wild). Result: [4, 3, 2] = 9.
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 }})Sort dice results for display purposes without changing the total. All notation is case-insensitive.
Options key: sort
| Notation | Description |
|---|---|
sa | Sort ascending |
sd | Sort descending |
import { roll } from '@randsum/roller'
roll('4d6sa') // Sort results ascendingroll('4d6sd') // Sort results descending
// Options objectroll({sides: 6,quantity: 4,modifiers: { sort: 'asc' }})
roll({sides: 6,quantity: 4,modifiers: { sort: 'desc' }})Sort reorders the dice results for display without changing the total. Useful for readability when reviewing large pools.
Integer divide
Section titled “Integer divide”Integer divide the total, truncating toward zero. All notation is case-insensitive.
Options key: integerDivide
| Notation | Description |
|---|---|
//N | Integer divide total by N (truncates via Math.trunc) |
import { roll } from '@randsum/roller'
roll('4d6//2') // Integer divide total by 2roll('10d10//3') // Integer divide total by 3
// Options objectroll({sides: 6,quantity: 4,modifiers: { integerDivide: 2 }})Example: 4d6//2 rolls [3, 5, 4, 2] = 14. Integer divided by 2 = 7.
Use cases: Halving damage (e.g., resistance in D&D), averaging mechanics, systems that use integer math for resource calculation.
Modulo
Section titled “Modulo”Apply modulo to the total. All notation is case-insensitive.
Options key: modulo
| Notation | Description |
|---|---|
%N | Total modulo N |
import { roll } from '@randsum/roller'
roll('4d6%3') // Total modulo 3roll('1d20%5') // Total modulo 5
// Options objectroll({sides: 6,quantity: 4,modifiers: { modulo: 3 }})Example: 4d6%3 rolls [3, 5, 4, 2] = 14. 14 % 3 = 2.
Use cases: Wrapping values into ranges, clock mechanics, cyclic resource systems.
Count dice matching a condition instead of summing values. Used in dice pool systems (World of Darkness, Shadowrun, and similar). The total becomes the count of matching dice.
Options key: count
| Notation | Description |
|---|---|
#{condition} | Primary notation — count dice matching a comparison condition |
S{N} | Sugar: count dice >= N (successes) |
S{N,B} | Sugar: count dice >= N, subtract dice <= B (successes minus botches) |
F{N} | Sugar: count dice <= N (failures) |
S{...} and F{...} are convenience aliases parsed onto the same count modifier with CountOptions. The ModifierOptions interface has only one key: count.
import { roll } from '@randsum/roller'
// Primary notation: #{condition}roll('5d10#{>=7}') // Count dice >= 7roll('5d10#{>3,<1}') // Count >3, subtract <1 (deduct mode)
// Sugar: S{} for successesroll('5d10S{7}') // Count dice >= 7roll('5d10S{7,1}') // Count >= 7, subtract <= 1 (botches)
// Sugar: F{} for failuresroll('5d10F{3}') // Count dice <= 3
// Options object (always uses 'count' key, never countSuccesses/countFailures)roll({sides: 10,quantity: 5,modifiers: { count: { greaterThanOrEqual: 7 }}})
roll({sides: 10,quantity: 5,modifiers: { count: { greaterThanOrEqual: 7, lessThanOrEqual: 1, deduct: true }}})
roll({sides: 10,quantity: 5,modifiers: { count: { lessThanOrEqual: 3 }}})Example: 5d10#{>=7} rolls [8, 2, 10, 1, 9]. Dice >= 7: [8, 10, 9] = 3.
Example (deduct): 5d10#{>3,<1} counts dice > 3 and subtracts dice < 1. If above = 4 and below = 1, total = 3.
CountOptions:
interface CountOptions extends ComparisonOptions {/** If true, below-threshold count subtracts from above-threshold count */deduct?: boolean}
// ComparisonOptions fields used by CountOptions:interface ComparisonOptions {greaterThan?: numbergreaterThanOrEqual?: numberlessThan?: numberlessThanOrEqual?: numberexact?: 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 replace (swap values)
- Apply reroll (re-roll matching dice)
- Apply explode/compound/penetrate/explodeSequence (add dice or modify values)
- Apply wild die behavior (compound on max, remove on 1)
- Apply unique (ensure no duplicates)
- Apply drop/keep (adjust pool size — runs after explosions)
- Apply count if applicable (#{…}, or sugar S{…}/F{…})
- Sum remaining dice
- Apply pre-arithmetic multiply (
*) - Apply plus/minus (
+/-) - Apply integer divide (
//N) - Apply modulo (
%N) - Sort results if applicable (
sa/sd) - Apply total multiply (
**)
See the full Randsum Dice Notation Spec for the complete syntax reference.