Skip to content

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.

Modifiers are applied in priority order (lower number = earlier execution):

PriorityModifierNotationOptions key
10CapC{…}cap
30ReplaceV{…}replace
40RerollR{…}reroll
50Explode!explode
51Compound!!compound
52Penetrate!ppenetrate
53Explode Sequence!s{...}, !i, !rexplodeSequence
55Wild DieWwildDie
60UniqueUunique
65DropH, L, D{…}drop
66KeepK, klkeep
80Count#{…}count
85Pre-arithmetic multiply*Nmultiply
90Plus+Nplus
91Minus-Nminus
93Integer divide//NintegerDivide
94Modulo%Nmodulo
95Sortsa/sdsort
100Total multiply**NmultiplyTotal

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.

interface ModifierOptions {
cap?: ComparisonOptions
replace?: ReplaceOptions | ReplaceOptions[]
reroll?: RerollOptions
explode?: boolean | ComparisonOptions
compound?: boolean | number | ComparisonOptions
penetrate?: boolean | number | ComparisonOptions
explodeSequence?: number[]
wildDie?: boolean
unique?: boolean | UniqueOptions
drop?: DropOptions
keep?: KeepOptions
count?: CountOptions
multiply?: number
plus?: number
minus?: number
integerDivide?: number
modulo?: number
sort?: '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 18
roll('4d20C{<3}') // Cap under 3 to 3
roll('4d20C{<2,>19}') // Cap both ends
// Options object
roll({
sides: 20,
quantity: 4,
modifiers: {
cap: { greaterThan: 18 }
}
})
roll({
sides: 20,
quantity: 4,
modifiers: {
cap: { greaterThan: 19, lessThan: 2 }
}
})

ComparisonOptions:

interface ComparisonOptions {
greaterThan?: number
greaterThanOrEqual?: number
lessThan?: number
lessThanOrEqual?: number
exact?: number[]
}

Remove dice from the result pool.

Options key: drop

import { roll } from '@randsum/roller'
roll('4d6L') // 3-18
roll('4d6L2') // Drop lowest 2
roll('4d6H') // Drop highest 1
roll('4d6H2') // Drop highest 2
// Drop by value
roll('4d20D{>17}') // Drop results over 17
roll('4d20D{<5}') // Drop results under 5
roll('4d20D{8,12}') // Drop exact values
// Options object
roll({
sides: 6,
quantity: 4,
modifiers: {
drop: { lowest: 1 }
}
})
roll({
sides: 20,
quantity: 4,
modifiers: {
drop: { greaterThan: 17 }
}
})

DropOptions:

interface DropOptions extends ComparisonOptions {
highest?: number
lowest?: number
exact?: number[]
}

Keep specific dice from the result (complement of drop).

Options key: keep

import { roll } from '@randsum/roller'
roll('4d6K3') // 3-18
roll('4d6K') // Keep highest 1
// Keep lowest
roll('4d6kl2') // Keep lowest 2
roll('4d6kl') // Keep lowest 1
// Options object
roll({
sides: 6,
quantity: 4,
modifiers: {
keep: { highest: 3 }
}
})

KeepOptions:

interface KeepOptions {
highest?: number
lowest?: number
}

Replace specific results with new values.

Options key: replace

import { roll } from '@randsum/roller'
// Replace exact values
roll('4d20V{8=12}') // Replace 8s with 12s
// Replace by comparison
roll('4d20V{>17=20}') // Replace results over 17 with 20
roll('4d20V{<5=1}') // Replace results under 5 with 1
// Options object
roll({
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 | ComparisonOptions
to: number
}

You can pass an array of ReplaceOptions to perform multiple replacements.

Reroll dice matching certain conditions.

Options key: reroll

import { roll } from '@randsum/roller'
roll('4d20R{<5}') // 4-20
roll('4d20R{>17}') // Reroll results over 17
// Reroll exact values
roll('4d20R{8,12}') // Reroll 8s and 12s
// With max attempts
roll('4d20R{<5}3') // Reroll under 5, max 3 attempts
// Options object
roll({
sides: 20,
quantity: 4,
modifiers: {
reroll: { lessThan: 5, max: 3 }
}
})

RerollOptions:

interface RerollOptions extends ComparisonOptions {
exact?: number[]
max?: number
}

Roll additional dice when a die shows its maximum value.

Options key: explode

import { roll } from '@randsum/roller'
roll('4d20!') // Exploding d20s
// Options object
roll({
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.

Compounding exploding dice add to the triggering die instead of creating new dice.

Options key: compound

import { roll } from '@randsum/roller'
roll('3d6!!') // Compound once
roll('3d6!!5') // Max depth 5
roll('3d6!!0') // Unlimited (capped at 100)
// Options object
roll({
sides: 6,
quantity: 3,
modifiers: { compound: true }
})

Unlike regular exploding, compound does not create new dice — it modifies the existing die value.

Penetrating exploding dice subtract 1 from each subsequent explosion (Hackmaster-style).

Options key: penetrate

import { roll } from '@randsum/roller'
roll('3d6!p') // Penetrate once
roll('3d6!p5') // Max depth 5
roll('3d6!p0') // Unlimited (capped at 100)
// Options object
roll({
sides: 6,
quantity: 3,
modifiers: { penetrate: true }
})

Each subsequent penetration subtracts 1 from the result before adding, creating diminishing returns.

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

NotationDescription
!s{N1,N2,...}Explode through die sizes in sequence
!iSugar: inflate through standard die set (d4 → d6 → d8 → d10 → d12 → d20)
!rSugar: 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 -> d10
roll('1d6!s{8,12}') // Explode to d8, then d12
roll('1d4!i') // Inflate through standard die set
roll('1d20!r') // Reduce through standard die set
// Options object
roll({
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.

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

NotationDescription
WLast die is the “wild die”
import { roll } from '@randsum/roller'
roll('5d6W') // Last die is wild
// Options object
roll({
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.

Force all dice in a pool to show different values.

Options key: unique

import { roll } from '@randsum/roller'
roll('4d20U') // 4-80
roll('4d20U{5,10}') // Unique except 5s and 10s
// Options object
roll({
sides: 20,
quantity: 4,
modifiers: { unique: true }
})
roll({
sides: 20,
quantity: 4,
modifiers: {
unique: { notUnique: [5, 10] }
}
})

UniqueOptions:

interface UniqueOptions {
notUnique: number[]
}

Multiply the dice sum before +/- arithmetic.

Options key: multiply

import { roll } from '@randsum/roller'
roll('2d6*2+3') // (dice sum * 2) + 3
// Options object
roll({
sides: 6,
quantity: 2,
modifiers: { multiply: 2, plus: 3 }
})

Add or subtract a fixed value from the total.

Options keys: plus, minus

import { roll } from '@randsum/roller'
roll('4d6+5') // Notation
roll('2d8-2') // Notation
// Options object
roll({
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

NotationDescription
saSort ascending
sdSort descending
import { roll } from '@randsum/roller'
roll('4d6sa') // Sort results ascending
roll('4d6sd') // Sort results descending
// Options object
roll({
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 the total, truncating toward zero. All notation is case-insensitive.

Options key: integerDivide

NotationDescription
//NInteger divide total by N (truncates via Math.trunc)
import { roll } from '@randsum/roller'
roll('4d6//2') // Integer divide total by 2
roll('10d10//3') // Integer divide total by 3
// Options object
roll({
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.

Apply modulo to the total. All notation is case-insensitive.

Options key: modulo

NotationDescription
%NTotal modulo N
import { roll } from '@randsum/roller'
roll('4d6%3') // Total modulo 3
roll('1d20%5') // Total modulo 5
// Options object
roll({
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

NotationDescription
#{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 >= 7
roll('5d10#{>3,<1}') // Count >3, subtract <1 (deduct mode)
// Sugar: S{} for successes
roll('5d10S{7}') // Count dice >= 7
roll('5d10S{7,1}') // Count >= 7, subtract <= 1 (botches)
// Sugar: F{} for failures
roll('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?: number
greaterThanOrEqual?: number
lessThan?: number
lessThanOrEqual?: number
exact?: number[]
}

Multiply the entire final total after all other modifiers.

Options key: multiplyTotal

import { roll } from '@randsum/roller'
roll('2d6+3**2') // (dice + 3) * 2
roll('4d6L+2**3') // ((drop lowest) + 2) * 3
// Options object
roll({
sides: 6,
quantity: 2,
modifiers: { plus: 3, multiplyTotal: 2 }
})

The full calculation order is:

  1. Roll all dice
  2. Apply cap (clamp values)
  3. Apply replace (swap values)
  4. Apply reroll (re-roll matching dice)
  5. Apply explode/compound/penetrate/explodeSequence (add dice or modify values)
  6. Apply wild die behavior (compound on max, remove on 1)
  7. Apply unique (ensure no duplicates)
  8. Apply drop/keep (adjust pool size — runs after explosions)
  9. Apply count if applicable (#{…}, or sugar S{…}/F{…})
  10. Sum remaining dice
  11. Apply pre-arithmetic multiply (*)
  12. Apply plus/minus (+/-)
  13. Apply integer divide (//N)
  14. Apply modulo (%N)
  15. Sort results if applicable (sa/sd)
  16. Apply total multiply (**)

See the full Randsum Dice Notation Spec for the complete syntax reference.