# 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

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

| Priority | Modifier | Notation | Options key |
|---|---|---|---|
| 10 | Cap | C&#123;...&#125; | `cap` |
| 30 | Replace | V&#123;...&#125; | `replace` |
| 40 | Reroll | R&#123;...&#125; | `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&#123;...&#125; | `drop` |
| 66 | Keep | `K`, `kl` | `keep` |
| 80 | Count | #&#123;...&#125; | `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` |

:::note
`S{...}` (count successes) and `F{...}` (count failures) are sugar aliases parsed onto the `count` modifier — they are not independent `ModifierOptions` keys.
:::

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

<CodeExample code={`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
}`} />

## Cap

Limit roll values to a specific range. Values outside the range are clamped to the boundary.

**Options key:** `cap`

<CodeExample code={`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 }
  }
})`} />

<NotationRoller defaultNotation="4d20C{>18}" client:only="react" />

**ComparisonOptions:**

<CodeExample code={`interface ComparisonOptions {
  greaterThan?: number
  greaterThanOrEqual?: number
  lessThan?: number
  lessThanOrEqual?: number
  exact?: number[]
}`} />

## Drop

Remove dice from the result pool.

**Options key:** `drop`

<CodeExample code={`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 }
  }
})`} />

<NotationRoller defaultNotation="4d6L" client:only="react" />

**DropOptions:**

<CodeExample code={`interface DropOptions extends ComparisonOptions {
  highest?: number
  lowest?: number
  exact?: number[]
}`} />

## Keep

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

**Options key:** `keep`

<CodeExample code={`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 }
  }
})`} />

<NotationRoller defaultNotation="4d6K3" client:only="react" />

**KeepOptions:**

<CodeExample code={`interface KeepOptions {
  highest?: number
  lowest?: number
}`} />

:::note
Keeping N highest is equivalent to dropping (quantity - N) lowest. For example, `4d6K3` is the same as `4d6L`.
:::

## Replace

Replace specific results with new values.

**Options key:** `replace`

<CodeExample code={`// 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 }
  }
})`} />

<NotationRoller defaultNotation="4d20V{8=12}" client:only="react" />

**ReplaceOptions:**

<CodeExample code={`interface ReplaceOptions {
  from: number | ComparisonOptions
  to: number
}`} />

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

## Reroll

Reroll dice matching certain conditions.

**Options key:** `reroll`

<CodeExample code={`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 }
  }
})`} />

<NotationRoller defaultNotation="4d20R{<5}" client:only="react" />

**RerollOptions:**

<CodeExample code={`interface RerollOptions extends ComparisonOptions {
  exact?: number[]
  max?: number
}`} />

## Explode

Roll additional dice when a die shows its maximum value.

**Options key:** `explode`

<CodeExample code={`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)
})`} />

<NotationRoller defaultNotation="4d20!" client:only="react" />

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

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

**Options key:** `compound`

<CodeExample code={`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 }
})`} />

<NotationRoller defaultNotation="3d6!!" client:only="react" />

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

## Penetrate

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

**Options key:** `penetrate`

<CodeExample code={`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 }
})`} />

<NotationRoller defaultNotation="3d6!p" client:only="react" />

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

## 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 &rarr; d6 &rarr; d8 &rarr; d10 &rarr; d12 &rarr; d20) |
| `!r` | Sugar: reduce through standard die set (d20 &rarr; d12 &rarr; d10 &rarr; d8 &rarr; d6 &rarr; d4) |

<CodeExample code={`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] }
})`} />

<NotationRoller defaultNotation="1d4!s{4,6,8,10}" client:only="react" />

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

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" |

<CodeExample code={`roll('5d6W') // Last die is wild

// Options object
roll({
  sides: 6,
  quantity: 5,
  modifiers: { wildDie: true }
})`} />

<NotationRoller defaultNotation="5d6W" client:only="react" />

**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

Force all dice in a pool to show different values.

**Options key:** `unique`

<CodeExample code={`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] }
  }
})`} />

<NotationRoller defaultNotation="4d20U" client:only="react" />

**UniqueOptions:**

<CodeExample code={`interface UniqueOptions {
  notUnique: number[]
}`} />

## Pre-arithmetic multiply

Multiply the dice sum before +/- arithmetic.

**Options key:** `multiply`

<CodeExample code={`roll('2d6*2+3')  // (dice sum * 2) + 3

// Options object
roll({
  sides: 6,
  quantity: 2,
  modifiers: { multiply: 2, plus: 3 }
})`} />

<NotationRoller defaultNotation="2d6*2+3" client:only="react" />

## Plus / Minus

Add or subtract a fixed value from the total.

**Options keys:** `plus`, `minus`

<CodeExample code={`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 }
})`} />

<NotationRoller defaultNotation="4d6+5" client:only="react" />

<NotationRoller defaultNotation="2d8-2" client:only="react" />

## Sort

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 |

<CodeExample code={`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' }
})`} />

<NotationRoller defaultNotation="4d6sa" client:only="react" />

Sort reorders the dice results for display without changing the total. Useful for readability when reviewing large pools.

## 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`) |

<CodeExample code={`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 }
})`} />

<NotationRoller defaultNotation="4d6//2" client:only="react" />

**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

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

**Options key:** `modulo`

| Notation | Description |
|---|---|
| `%N` | Total modulo N |

<CodeExample code={`roll('4d6%3') // Total modulo 3
roll('1d20%5') // Total modulo 5

// Options object
roll({
  sides: 6,
  quantity: 4,
  modifiers: { modulo: 3 }
})`} />

<NotationRoller defaultNotation="4d6%3" client:only="react" />

**Example:** `4d6%3` rolls [3, 5, 4, 2] = 14. 14 % 3 = 2.

**Use cases:** Wrapping values into ranges, clock mechanics, cyclic resource systems.

## Count

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 &gt;= N (successes) |
| `S{N,B}` | Sugar: count dice &gt;= N, subtract dice &lt;= B (successes minus botches) |
| `F{N}` | Sugar: count dice &lt;= N (failures) |

`S{...}` and `F{...}` are convenience aliases parsed onto the same `count` modifier with `CountOptions`. The `ModifierOptions` interface has only one key: `count`.

:::note
`F` requires curly braces (`F{N}`) to avoid conflict with Fate dice notation (`dF`).
:::

<CodeExample code={`// 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 }
  }
})`} />

<NotationRoller defaultNotation="5d10#{>=7}" client:only="react" />

**Example:** `5d10#{>=7}` rolls [8, 2, 10, 1, 9]. Dice &gt;= 7: [8, 10, 9] = 3.

**Example (deduct):** `5d10#{>3,<1}` counts dice &gt; 3 and subtracts dice &lt; 1. If above = 4 and below = 1, total = 3.

**CountOptions:**

<CodeExample code={`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[]
}`} />

## Total multiply

Multiply the entire final total after all other modifiers.

**Options key:** `multiplyTotal`

<CodeExample code={`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 }
})`} />

<NotationRoller defaultNotation="2d6+3**2" client:only="react" />

## Order of operations

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 (#&#123;...&#125;, or sugar S&#123;...&#125;/F&#123;...&#125;)
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 (`**`)

<NotationRoller defaultNotation="4d6K3!*2+3**2" client:only="react" />

See the full [Randsum Dice Notation Spec](https://randsum.dev/notation/randsum-dice-notation/) for the complete syntax reference.