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?: ComparisonOptions drop?: DropOptions keep?: KeepOptions replace?: ReplaceOptions | ReplaceOptions[] reroll?: RerollOptions unique?: boolean | UniqueOptions explode?: boolean | number compound?: boolean | number penetrate?: boolean | number countSuccesses?: SuccessCountOptions multiply?: number plus?: number minus?: number multiplyTotal?: number}Limit roll values to a specific range. Values outside the range are clamped to the boundary.
Options key: cap
// Notationroll('4d20C{>18}') // Cap over 18 to 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?: number lessThan?: number}Remove dice from the result pool.
Options key: drop
// Drop lowest / highestroll('4d6L') // Drop lowest 1roll('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?: number lowest?: number exact?: number[]}Keep specific dice from the result (complement of drop).
Options key: keep
// Keep highestroll('4d6K3') // Keep highest 3roll('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?: number lowest?: number}Replace
Section titled “Replace”Replace specific results with new values.
Options key: replace
// 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 | ComparisonOptions to: number}You can pass an array of ReplaceOptions to perform multiple replacements.
Reroll
Section titled “Reroll”Reroll dice matching certain conditions.
Options key: reroll
// Reroll by comparisonroll('4d20R{>17}') // Reroll results over 17roll('4d20R{<5}') // Reroll results under 5
// 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
roll('4d20!') // Explode once per dieroll('3d6!5') // Max depth of 5roll('3d6!0') // Unlimited (capped at 100)
// Options objectroll({ sides: 20, quantity: 4, modifiers: { explode: true } // Once per die})
roll({ sides: 6, quantity: 3, modifiers: { explode: 5 } // Max depth 5})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
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
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
roll('4d20U') // All must be uniqueroll('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
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
roll('4d6+5') // Add 5roll('2d8-2') // Subtract 2
// Options objectroll({ sides: 6, quantity: 4, modifiers: { plus: 5 }})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
roll('5d10S{7}') // Count dice >= 7roll('5d10S{7,1}') // Successes >= 7, minus botches <= 1
// Options objectroll({ sides: 10, quantity: 5, modifiers: { countSuccesses: { threshold: 7, botchThreshold: 1 } }})SuccessCountOptions:
interface SuccessCountOptions { threshold: number botchThreshold?: number}Total multiply
Section titled “Total multiply”Multiply the entire final total after all other modifiers.
Options key: multiplyTotal
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 }})Complete order of operations
Section titled “Complete 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 (
**)
// Full example: ((sum of kept dice) * 2) + 3) * 2roll('4d6K3!*2+3**2')