Skip to content

RANDSUM provides a Claude Code plugin with three skills that give AI agents specialized knowledge for tabletop RPG dice mechanics.

Dice Rolling

Roll dice, interpret results, and answer questions about RANDSUM notation and supported game systems. Covers all 19+ modifiers and 6 game systems.

Dice Probability

Analyze probability distributions, expected values, and outcome comparisons. Compare strategies like “advantage vs. flat +2” with exact math or Monte Carlo simulation.

Game Spec Creator

Create .randsum.json specs for custom TTRPG dice mechanics. Translate tabletop game rules into declarative specs that generate typed TypeScript roll() functions via codegen.

A skill is a markdown document that gives Claude specialized knowledge. When Claude loads a RANDSUM skill, it learns domain-specific concepts — dice notation syntax, probability math, or the .randsum.json spec format — and can apply them to your questions without you needing to explain the domain.

Skills activate automatically based on context. Ask naturally:

Roll 4d6 drop lowest for my strength score
What are the odds of rolling 15+ on 3d6?
Create a .randsum.json spec for Ironsworn

Or invoke explicitly with /dice-rolling, /dice-probability, or /game-spec-creator.

All skill files live in the skills/ directory.

Place skill files in your project’s .claude/skills/ directory:

Terminal window
# Download all three skills
for skill in dice-rolling dice-probability game-spec-creator; do
mkdir -p .claude/skills/$skill/references
curl -o .claude/skills/$skill/SKILL.md \
https://raw.githubusercontent.com/RANDSUM/randsum/main/skills/$skill/SKILL.md
done
# Download reference files
curl -o .claude/skills/dice-rolling/references/NOTATION.md \
https://raw.githubusercontent.com/RANDSUM/randsum/main/skills/dice-rolling/references/NOTATION.md
curl -o .claude/skills/dice-rolling/references/GAME_SYSTEMS.md \
https://raw.githubusercontent.com/RANDSUM/randsum/main/skills/dice-rolling/references/GAME_SYSTEMS.md
curl -o .claude/skills/dice-probability/references/PROBABILITY_TABLES.md \
https://raw.githubusercontent.com/RANDSUM/randsum/main/skills/dice-probability/references/PROBABILITY_TABLES.md
curl -o .claude/skills/game-spec-creator/references/SPEC_EXAMPLES.md \
https://raw.githubusercontent.com/RANDSUM/randsum/main/skills/game-spec-creator/references/SPEC_EXAMPLES.md

The core skill. Teaches an agent RANDSUM dice notation — a compact syntax for any roll — and how to interpret results for six supported game systems: D&D 5e, Blades in the Dark, Daggerheart, PbtA, Root RPG, and Salvage Union.

What the agent learns:

  • All RANDSUM notation syntax (4d6L, 2d20H+5, 3d6!, d%, 4dF, etc.)
  • Game-specific result interpretation (critical hits, partial success, desperation rolls)
  • When to validate input and how to explain results clearly

Files:

Teaches an agent to analyze probability distributions for dice rolls. Handles exact enumeration for small outcome spaces and Monte Carlo simulation for complex rolls.

What the agent learns:

  • Expected values, variance, and standard deviation for any notation
  • Probability of hitting specific thresholds (e.g., “odds of 15+ on 3d6”)
  • Comparative analysis (“advantage vs. flat +2 bonus”)
  • Game-system outcome probabilities (e.g., “chance of critical success in Blades”)

Files:

Teaches an agent the .randsum.json spec format — a declarative JSON Schema for describing tabletop RPG dice mechanics. Specs define a four-stage pipeline (Dice → Modify → Resolve → Outcome) and generate typed TypeScript roll() functions via codegen.

What the agent learns:

  • The full .randsum.json schema (pools, modifiers, conditions, outcome tables)
  • How to translate natural-language game rules into declarative specs
  • Reference resolution ($ref for reusable components)
  • Validation rules and common patterns across game systems

Files:

If you’re building a custom agent, you can also use @randsum/roller directly:

Terminal window
bun add @randsum/roller
import { roll } from '@randsum/roller'
const result = roll('4d6L')
console.log(result.total) // Sum after dropping lowest
console.log(result.rolls) // Individual die results

See the Roller documentation for the full API.

The full skill file that agents receive:

Dice Rolling Skill

Executing Rolls

When a user asks you to roll dice, produce an actual result — don’t just show notation or code. If you have shell access, use the CLI:

Terminal window
bunx @randsum/cli <notation> # in bun projects (preferred)
npx @randsum/cli <notation> # anywhere else

Examples:

Terminal window
bunx @randsum/cli 4d6L # D&D ability score
bunx @randsum/cli 2d20L+7 # advantage attack
bunx @randsum/cli 3d6 # Blades in the Dark pool
bunx @randsum/cli 5d10S{7} # World of Darkness successes

If you don’t have shell access, simulate the roll yourself — mentally generate random numbers as you would genuinely roll dice, and present them clearly. Don’t just describe how the roll would work without giving a result.

Response Format

After rolling, always show:

  1. What you rolled — the notation and what it represents
  2. The dice — raw values (and which were dropped/kept if relevant)
  3. The total — the final number
  4. The meaning — what it means in the game context (hit/miss, success tier, stat value, etc.)
Rolling 4d6L (4 six-sided dice, drop lowest) for Strength:
Dice: [5, 3, 4, 2] → dropped 2 → kept [5, 3, 4]
Total: 12
→ Strength score: 12 (modifier: +1)

Keep it compact — one block, no lengthy preamble. For game-system rolls, add one sentence of flavor or consequence after the result (e.g. “You pull it off cleanly.” or “Brace for a complication.”).

Notation Quick Reference

All notation is case-insensitive. Basic syntax: NdS (N dice, S sides).

Core Modifiers

NotationDescription
4d6LDrop lowest die
4d6HDrop highest die
4d6L2Drop 2 lowest
4d6K3Keep highest 3
4d6kl2Keep lowest 2
1d20+5Add 5 to total
4d6-1Subtract 1
2d6*2Multiply dice sum (before +/-)
2d6+3**2Multiply final total (after +/-)

Conditional Modifiers

Conditions use comparison operators: >N (greater than), <N (less than), >=N (at or over), <=N (at or under), =N or bare N (exact match).

NotationDescription
4d6R{1}Reroll 1s (bare number = exact match)
4d6R{=1}Same — explicit = form
4d20R{<5}Reroll under 5
4d20R{<=5}Reroll 5 or under
4d20R{>=17}Reroll 17 or over
4d20R{<5}3Reroll under 5, max 3 attempts
4d20V{8=12}Replace 8s with 12s
4d20V{>17=20}Replace results over 17 with 20
4d20V{>=17=20}Replace results at or over 17 with 20
4d20V{<=3=1}Replace results at or under 3 with 1
4d20C{>18}Cap rolls over 18 down to 18
4d20C{5}Cap rolls above 5 to 5 (bare number = max cap)
4d20C{=5}Same — explicit = form
4d20C{>=5}Cap rolls at or over 5 to 5
4d20C{<2,>19}Floor 2, ceiling 19
4d20UAll results must be unique
4d20D{>17}Drop dice over 17
4d20D{>=17}Drop dice at or over 17
4d20D{1,2}Drop 1s and 2s
4d20D{=1,=2}Same — explicit = form

Ultra-complex example — roll 8d10, cap to [3,8] range, keep top 5, reroll floor values, add 4:

8d10C{>8,<3}K5R{=3}+4

Exploding Dice

NotationDescription
3d6!Explode: new die on max result
3d6!!Compound: add to same die on max
3d6!pPenetrate: explode minus 1 each time
3d6!5Explode up to 5 times per die

Dice Pools

NotationDescription
5d10S{7}Count dice >= 7 (successes)
5d10S{7,1}Successes >= 7, subtract botches <= 1
5d10F{3}Count dice <= 3 (failures)

Wild Die, Integer Division, Modulo

NotationDescription
5d6WD6 System wild die (last die is wild)
4d6//2Integer divide total by 2
4d6%3Total modulo 3

Special Dice

NotationDescription
g6Geometric die: roll d6 until 1, return count
3g6Three independent geometric rolls
DD6Draw die: sample without replacement from d6
3DD6Draw 3 unique values from d6 pool

Annotations and Repeat

NotationDescription
2d6+3[fire]Label a roll group (no mechanical effect)
4d6Lx6Repeat notation 6 times (ability score gen)

Multiple Groups

1d20+2d6+5 # attack (d20) + damage (2d6) + mod
2d12-1d6 # roll 2d12, subtract 1d6

Modifier order (lower = earlier): Cap (10) → Drop/Keep (20/21) → Replace (30) → Reroll (40) → Explode (50-52) → Wild Die (55) → Unique (60) → Multiply (85) → Plus/Minus (90/91) → Sort (92) → Integer Divide (93) → Modulo (94) → Count Successes (95) → Count Failures (96) → Total Multiply (100)

Full notation reference: references/NOTATION.md

Game Systems

See references/GAME_SYSTEMS.md for full mechanics.

GameRollInterpretation
D&D 5E advantage2d20L+modKeep highest + modifier
D&D 5E disadvantage2d20H+modKeep lowest + modifier
D&D 5E ability score4d6LDrop lowest of 4d6
D&D 5E crit2×damage dice + modDouble the dice
Blades in the DarkNd6, keep highest6 = success, 4–5 = partial, 1–3 = failure; two 6s = critical
Daggerheart2d12 (Hope/Fear)Higher die = narrative control; sum = mechanical success
PbtA / Root RPG2d6+stat10+ = strong hit, 7–9 = weak hit, 6– = miss
Salvage Uniond20 vs targetRoll under; 1 = critical success, 20 = critical failure

Programmatic API

import { roll } from "@randsum/roller"
roll(20) // 1d20
roll("4d6L") // notation string
roll({ sides: 6, quantity: 4, modifiers: { drop: { lowest: 1 } } })
roll("1d20+5", "2d6") // multiple args, combined total
const result = roll("2d6+3")
result.total // final sum
result.rolls // RollRecord[] — full roll data per dice group
result.values // string[] — rolled values as strings

Randsum Dice Notation

Table of Contents


Overview

Dice notation is a compact way to represent dice rolls and their modifications. For example, 4d20+2 means “roll four twenty-sided dice, then add two”.

Randsum extends standard dice notation with powerful modifiers like dropping lowest rolls, rerolling specific values, and ensuring unique results.

The core roll() function accepts several argument types: a number (sides for a single die, e.g. roll(20) for 1d20), a notation string (e.g. roll("4d6L")), an options object (e.g. roll({ sides: 6, quantity: 4, modifiers: { drop: { lowest: 1 } } })), or multiple arguments combined into one total (e.g. roll("1d20+5", "2d6")).

Basic Syntax

All notation in randsum is case-insensitive (2d8 = 2D8).

Standard Rolls

// Roll one d20
roll(20) // number argument
roll("1d20") // notation string
roll({
sides: 20,
quantity: 1
})
// Roll four d6
roll("4d6")
roll({
sides: 6,
quantity: 4
})

Special Dice

Geometric Die (gN)

Roll dN until a 1 appears, result = attempt count. Safety cap at 1000 iterations.

roll("g6") // Roll d6 until 1 appears (average: 6 rolls)
roll("3g6") // Three independent geometric rolls

Draw Die (DDN)

Sampling without replacement — like drawing from a deck. Uses Fisher-Yates shuffle.

roll("DD6") // Draw one unique value from [1..6]
roll("3DD6") // Draw 3 unique values
roll("6DD6") // Full permutation of [1,2,3,4,5,6]
roll("8DD6") // Full permutation + 2 more (reshuffles after exhaustion)

Modifiers

Basic Arithmetic

roll("4d6+2") // Add 2 to total
roll({
sides: 6,
quantity: 4,
modifiers: { plus: 2 }
})
roll("4d6-1") // Subtract 1 from total
roll({
sides: 6,
quantity: 4,
modifiers: { minus: 1 }
})

Cap Modifiers

Limit roll values to specific ranges:

roll("4d20C{>18}") // Cap rolls over 18 to 18
roll({
sides: 20,
quantity: 4,
modifiers: {
cap: { greaterThan: 18 }
}
})
roll("4d20C{<3}") // Cap rolls under 3 to 3
roll({
sides: 20,
quantity: 4,
modifiers: {
cap: { lessThan: 3 }
}
})
roll("4d20C{<2,>19}") // Cap rolls under 2 to 2 and over 19 to 19
roll({
sides: 20,
quantity: 4,
modifiers: {
cap: {
greaterThan: 19,
lessThan: 2
}
}
})

Drop Modifiers

Drop specific dice from the results:

roll("4d6L") // Drop lowest
roll({
sides: 6,
quantity: 4,
modifiers: { drop: { lowest: 1 } }
})
roll("4d6L2") // Drop 2 lowest
roll({
sides: 6,
quantity: 4,
modifiers: { drop: { lowest: 2 } }
})
roll("4d6H") // Drop highest
roll({
sides: 6,
quantity: 4,
modifiers: { drop: { highest: 1 } }
})
roll("4d6H2") // Drop 2 highest
roll({
sides: 6,
quantity: 4,
modifiers: { drop: { highest: 2 } }
})
roll("4d6LH") // Drop both lowest and highest
roll({
sides: 6,
quantity: 4,
modifiers: { drop: { lowest: 1, highest: 1 } }
})
// Drop by value
roll("4d20D{>17}") // Drop rolls over 17
roll({
sides: 20,
quantity: 4,
modifiers: { drop: { greaterThan: 17 } }
})
roll("4d20D{<5}") // Drop rolls under 5
roll({
sides: 20,
quantity: 4,
modifiers: { drop: { lessThan: 5 } }
})
roll("4d20D{8,12}") // Drop 8s and 12s
roll({
sides: 20,
quantity: 4,
modifiers: { drop: { exact: [8, 12] } }
})

Reroll Modifiers

Reroll dice matching certain conditions:

roll("4d20R{>17}") // Reroll results over 17
roll({
sides: 20,
quantity: 4,
modifiers: { reroll: { greaterThan: 17 } }
})
roll("4d20R{<5}") // Reroll results under 5
roll({
sides: 20,
quantity: 4,
modifiers: { reroll: { lessThan: 5 } }
})
roll("4d20R{8,12}") // Reroll 8s and 12s
roll({
sides: 20,
quantity: 4,
modifiers: { reroll: { exact: [8, 12] } }
})
roll("4d20R{<5}3") // Reroll under 5, max 3 attempts
roll({
sides: 20,
quantity: 4,
modifiers: {
reroll: {
lessThan: 5,
max: 3
}
}
})

Replace Modifiers

Replace specific results with new values:

roll("4d20V{8=12}") // Replace 8s with 12s
roll({
sides: 20,
quantity: 4,
modifiers: {
replace: {
from: 8,
to: 12
}
}
})
roll("4d20V{>17=20}") // Replace results over 17 with 20
roll({
sides: 20,
quantity: 4,
modifiers: {
replace: {
from: { greaterThan: 17 },
to: 20
}
}
})
roll("4d20V{<5=1}") // Replace results under 5 with 1
roll({
sides: 20,
quantity: 4,
modifiers: {
replace: {
from: { lessThan: 5 },
to: 1
}
}
})

Unique Results

Force unique rolls within a pool:

roll("4d20U") // All results must be unique
roll({
sides: 20,
quantity: 4,
modifiers: { unique: true }
})
roll("4d20U{5,10}") // Unique except 5s and 10s can repeat
roll({
sides: 20,
quantity: 4,
modifiers: {
unique: { notUnique: [5, 10] }
}
})

Keep Modifiers

Keep specific dice from the result (complement to drop):

roll("4d6K3") // Keep highest 3
roll({
sides: 6,
quantity: 4,
modifiers: { keep: { highest: 3 } }
})
roll("4d6K") // Keep highest 1
roll({
sides: 6,
quantity: 4,
modifiers: { keep: { highest: 1 } }
})
roll("4d6kl2") // Keep lowest 2
roll({
sides: 6,
quantity: 4,
modifiers: { keep: { lowest: 2 } }
})
roll("4d6kl") // Keep lowest 1
roll({
sides: 6,
quantity: 4,
modifiers: { keep: { lowest: 1 } }
})

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

Exploding Dice

Roll additional dice on maximum results:

roll("4d20!") // Roll an extra d20 for each 20 rolled
roll({
sides: 20,
quantity: 4,
modifiers: { explode: true }
})
roll("3d6!5") // Explode with max depth of 5
roll({
sides: 6,
quantity: 3,
modifiers: { explode: 5 }
})
roll("3d6!0") // Explode unlimited (capped at 1000 for safety)
roll({
sides: 6,
quantity: 3,
modifiers: { explode: 0 }
})

How it works: When a die shows its maximum value, it “explodes” - a new die is rolled and added to the result. This continues for each new maximum value rolled, creating additional dice in the result.

Example: 3d6! rolls [6, 4, 6]. The two 6s explode, adding [5, 3]. Final result: [6, 4, 6, 5, 3] = 24.

Compounding Exploding (!!)

Exploding dice that add to the triggering die instead of creating new dice:

roll("3d6!!") // Compound explode: add to die instead of new dice
roll({
sides: 6,
quantity: 3,
modifiers: { compound: true }
})
roll("3d6!!5") // Compound explode with max depth of 5
roll({
sides: 6,
quantity: 3,
modifiers: { compound: 5 }
})
roll("3d6!!0") // Compound explode unlimited (capped at 1000)
roll({
sides: 6,
quantity: 3,
modifiers: { compound: 0 }
})

How it works: When a die shows its maximum value, it compounds - a new roll is added directly to that die’s value. Unlike regular exploding, this doesn’t create new dice; it modifies the existing die.

Example: 1d6!! rolls 6. This compounds, rolling 4. The die value becomes 6 + 4 = 10. If that 4 had been a 6, it would compound again: 6 + 6 + 3 = 15.

Use cases: Compounding is useful in systems where you want explosive growth on a single die value rather than multiple dice. Common in damage systems where a critical hit adds to the base damage value.

Differences from Explode:

  • Explode (!): Creates new dice → [6, 4, 6] becomes [6, 4, 6, 5, 3] (5 dice)
  • Compound (!!): Modifies existing die → [6, 4, 6] becomes [15, 4, 12] (still 3 dice)

Penetrating Exploding (!p)

Exploding dice where each subsequent explosion subtracts 1 (Hackmaster-style):

roll("3d6!p") // Penetrate explode: subtract 1 from subsequent rolls
roll({
sides: 6,
quantity: 3,
modifiers: { penetrate: true }
})
roll("3d6!p5") // Penetrate with max depth of 5
roll({
sides: 6,
quantity: 3,
modifiers: { penetrate: 5 }
})
roll("3d6!p0") // Penetrate unlimited (capped at 1000)
roll({
sides: 6,
quantity: 3,
modifiers: { penetrate: 0 }
})

How it works: When a die shows its maximum value, it penetrates - a new roll is made, but 1 is subtracted from the result before adding. Each subsequent penetration also subtracts 1. This creates a diminishing return effect.

Example: 1d6!p rolls 6. This penetrates, rolling 5. The value added is 5 - 1 = 4, so the die becomes 6 + 4 = 10. If that roll had been a 6, it would penetrate again: roll 3, subtract 1 = 2, so the die becomes 6 + 4 + 2 = 12.

Use cases: Penetrating dice are used in Hackmaster and similar systems where you want explosive results but with diminishing returns to prevent unlimited growth.

Comparison:

  • Explode (!): [6][6, 6, 4] = 16 (new dice added)
  • Compound (!!): [6][16] = 16 (die value modified)
  • Penetrate (!p): [6][12] = 12 (6 + (6-1) + (3-1) if it keeps penetrating, die value modified with -1 on each subsequent roll)

Pre-Arithmetic Multiplier (*)

Multiply the dice sum before adding/subtracting arithmetic modifiers:

roll("2d6*2+3") // (dice sum * 2) + 3
roll({
sides: 6,
quantity: 2,
modifiers: {
multiply: 2,
plus: 3
}
})
roll("4d6*3") // Multiply dice sum by 3
roll({
sides: 6,
quantity: 4,
modifiers: { multiply: 3 }
})

How it works: The multiplier is applied to the sum of all dice after other modifiers (drop, keep, explode, etc.) but before plus/minus arithmetic modifiers are applied.

Order of operations: (dice sum × multiply) ± plus/minus

Example: 2d6*2+3 rolls [4, 5] = 9. Multiplied by 2 = 18. Plus 3 = 21.

Use cases: Critical hits that double or triple base damage before modifiers. Or systems where dice are multiplied before bonuses are added.

Total Multiplier (**)

Multiply the entire final total after all other modifiers:

roll("2d6+3**2") // (dice + 3) * 2
roll({
sides: 6,
quantity: 2,
modifiers: {
plus: 3,
multiplyTotal: 2
}
})
roll("4d6L+2**3") // ((drop lowest) + 2) * 3
roll({
sides: 6,
quantity: 4,
modifiers: {
drop: { lowest: 1 },
plus: 2,
multiplyTotal: 3
}
})

How it works: The total multiplier is applied last, after all other modifiers including plus/minus. It multiplies the complete final result.

Order of operations: ((dice sum × multiply) ± plus/minus) × multiplyTotal

Example: 2d6+3**2 rolls [4, 5] = 9. Plus 3 = 12. Multiplied by 2 = 24.

Use cases: Final multipliers like area effect multipliers, critical hit total multipliers, or system-wide bonuses that apply to the entire result.

Difference from Pre-Arithmetic Multiplier:

  • Pre-Arithmetic (*): 2d6*2+3 = (9 × 2) + 3 = 21
  • Total (**): 2d6+3\*\*2 = (9 + 3) × 2 = 24

Count Successes (S{…})

Count dice meeting a threshold instead of summing values. Used in dice pool systems like World of Darkness and Shadowrun:

roll("5d10S{7}") // Count how many dice rolled >= 7
roll({
sides: 10,
quantity: 5,
modifiers: {
countSuccesses: { threshold: 7 }
}
})
// With botch threshold (successes - botches)
roll("5d10S{7,1}") // Count successes >= 7, subtract botches <= 1
roll({
sides: 10,
quantity: 5,
modifiers: {
countSuccesses: {
threshold: 7,
botchThreshold: 1
}
}
})

How it works: Instead of summing dice values, the total becomes a count of dice that meet or exceed the threshold. If a botch threshold is specified, dice at or below that value are subtracted from the success count.

Example: 5d10S{7} rolls [8, 3, 10, 6, 9]. Successes >= 7: [8, 10, 9] = 3.

Example with botch: 5d10S{7,1} rolls [8, 1, 10, 1, 9]. Successes: 3, Botches: 2. Result = 1.

Integer Division (//N)

Integer divide the total (truncates toward zero via Math.trunc):

roll("4d6//2") // Integer divide total by 2
roll({
sides: 6,
quantity: 4,
modifiers: { integerDivide: 2 }
})

Modulo (%N)

Apply modulo to the total:

roll("4d6%3") // Total modulo 3
roll({
sides: 6,
quantity: 4,
modifiers: { modulo: 3 }
})

Count Failures (F{N})

Count how many dice rolled at or below a threshold. Requires curly braces to avoid conflict with Fate dice:

roll("5d10F{3}") // Count dice that rolled <= 3
roll({
sides: 10,
quantity: 5,
modifiers: {
countFailures: { threshold: 3 }
}
})

Wild Die (W)

D6 System wild die (West End Games). Last die is “wild”:

  • Wild = max (6): compound explode (keep rolling and adding while max)
  • Wild = 1: remove wild die AND highest non-wild die
  • Otherwise: no change
roll("5d6W") // Last die is wild
roll({
sides: 6,
quantity: 5,
modifiers: { wildDie: true }
})

Annotations/Labels ([text])

Attach metadata labels to dice terms — flavor text with no mechanical effect:

roll("2d6+3[fire]+1d4[cold]") // Labels on roll groups
roll("4d6L[strength]") // Label the roll purpose

Repeat Operator (xN)

Repeat a roll expression N times (notation sugar):

roll("4d6Lx6") // Equivalent to six separate 4d6L rolls
roll("2d6+3x4") // Roll 2d6+3 four times

Combining Modifiers

Modifiers can be chained together. They are applied in a specific order to ensure consistent results:

Modifier Application Order:

  1. Cap
  2. Drop / Keep
  3. Replace
  4. Reroll
  5. Explode / Compound / Penetrate
  6. Wild Die
  7. Unique
  8. Pre-Arithmetic Multiply (*)
  9. Plus / Minus
  10. Sort
  11. Integer Divide / Modulo
  12. Count Successes / Count Failures
  13. Total Multiply (**)
roll("4d6L+2") // Drop lowest, add 2
roll("2d20H!+1") // Drop highest, explode, add 1
roll("4d6R{<3}L") // Reroll under 3, then drop lowest
roll("3d6!!*2+3") // Compound explode, multiply by 2, add 3
roll("2d6*2+3**2") // Multiply dice by 2, add 3, multiply total by 2
roll("4d6K3!+2") // Keep highest 3, explode, add 2
roll("3d6!pL+1") // Penetrate explode, drop lowest, add 1
roll("5d6W+2") // Wild die with modifier
roll("4d6Lx6") // Six ability score rolls

Important Notes:

  • Pre-arithmetic multiply (*) applies before plus/minus: 2d6*2+3 = (sum × 2) + 3
  • Total multiply (**) applies after everything: 2d6+3**2 = (sum + 3) × 2
  • You can use both multipliers together: 2d6*2+3**2 = ((sum × 2) + 3) × 2
  • Keep is processed before explode/compound/penetrate, so explosions only happen on kept dice
  • Drop/Keep happen after reroll but before explode, so you reroll first, then keep/drop

Multiple Dice Sides in a Single Roll

You can roll multiple dice sides in a single by passing multiple arguments:

roll("1d20", "-2d6", "10d8+2") // Roll 1d20, 2d6, and 10d8 + 2.
roll("1d20-2d6+10d8+2") // Same as above, but in a single string

Adding or Subtracting Rolls from the Total

You can add or subtract rolls from the total by using the arithmetic option, or by adding a + or - to the notation:

roll("2d12-1d6") // Roll 2d12, add them, then subtract 1d6

Common Use Cases

D&D 5e Critical Hits

  • Double base damage: 2d6+3*2 → (9 × 2) + 3 = 21
  • Double total damage: 2d6+3**2 → (9 + 3) × 2 = 24

D&D 5e Ability Score Generation

roll("4d6K3") // Roll 4d6, keep highest 3
roll("4d6L") // Equivalent: Roll 4d6, drop lowest 1

D&D 5e Advantage/Disadvantage

roll("2d20K") // Advantage: keep highest
roll("2d20kl") // Disadvantage: keep lowest

Hackmaster Penetrating Dice

roll("1d6!p") // Standard penetrate
roll("2d6!p+3") // Penetrate with modifier

Area Effect Spells

roll("8d6*2") // 8d6 damage, doubled for area effect
roll("8d6+5**2") // 8d6+5 damage, doubled for area effect

Performance Considerations

Depth Limits

All explosive modifiers (explode, compound, penetrate) have built-in depth limits:

  • Explicit depth: !N, !!N, !pN - Limited to N depth
  • Unlimited (0): !0, !!0, !p0 - Capped at 1000 for safety
  • Default: !, !!, !p - Limited to 1 explosion per die

Best Practices

  1. Use depth limits for explosive modifiers in production code
  2. Keep dice quantities reasonable (< 100 dice for best performance)
  3. Prefer compound over explode if you don’t need separate dice tracking
  4. Use multipliers sparingly - they’re cheap but unnecessary if not needed

Attribution

The extended notation syntax was inspired by Sophie’s Dice.

RANDSUM Game Systems

Supported Game Packages

@randsum/games/blades - Blades in the Dark

  • Mechanics: d6 dice pools, keep highest result
  • Outcomes: 6 = success, 4-5 = partial, 1-3 = failure; multiple 6s = critical
  • 0 dice pool: rolls 2d6, drops highest (desperate position)
  • Result type: 'critical' | 'success' | 'partial' | 'failure'
  • Usage: roll(rating?: number) — 0 = desperate (2d6, keep lowest), 1-4 = roll that many d6, keep highest
const result = roll(3)
result.result // 'critical' | 'success' | 'partial' | 'failure'
result.total // highest die value

@randsum/games/daggerheart - Daggerheart RPG

  • Mechanics: Hope/Fear d12 system
  • Roll: 2d12 (one Hope die, one Fear die) + optional advantage/disadvantage d6
  • Outcome: Higher die determines narrative (Hope/Fear/Critical Hope); sum determines mechanical success
  • Result type: 'hope' | 'fear' | 'critical hope'
  • Usage: roll(arg?: DaggerheartRollArgument)
interface DaggerheartRollArgument {
modifier?: number // added to total
rollingWith?: 'Advantage' | 'Disadvantage' // adds/subtracts a d6
amplifyHope?: boolean // use d20 instead of d12 for Hope
amplifyFear?: boolean // use d20 instead of d12 for Fear
}
const result = roll({ modifier: 3, rollingWith: 'Advantage' })
result.result // 'hope' | 'fear' | 'critical hope'
result.details // { hope, fear, advantage, modifier }

@randsum/games/fifth - D&D 5th Edition

  • Mechanics: d20 with advantage/disadvantage
  • Advantage: Roll 2d20, keep highest (2d20L — drop lowest)
  • Disadvantage: Roll 2d20, keep lowest (2d20H — drop highest)
  • Ability Scores: use roll("4d6L") from @randsum/roller directly
  • Result type: number (d20 + modifier)
  • Usage: roll(arg?: FifthRollArgument)
interface FifthRollArgument {
modifier?: number // range -30 to +30
rollingWith?: 'Advantage' | 'Disadvantage'
}
roll({ modifier: 5 })
roll({ modifier: 3, rollingWith: 'Advantage' })
roll({ modifier: -2, rollingWith: 'Disadvantage' })

@randsum/games/root-rpg - Root RPG

  • Mechanics: 2d6 + stat modifier
  • Outcomes: 10+ = strong hit, 7-9 = weak hit, 6- = miss
  • Result type: 'Strong Hit' | 'Weak Hit' | 'Miss'
  • Usage: roll(bonus: number) — range -20 to +20
const result = roll(2)
result.result // 'Strong Hit' | 'Weak Hit' | 'Miss'
result.total // 2d6 + bonus

@randsum/games/pbta - Powered by the Apocalypse

  • Mechanics: 2d6 + stat modifier (generic PbtA)
  • Outcomes: 10+ = strong hit, 7-9 = weak hit, 6- = miss
  • Advantage: roll 3d6, keep 2 highest; Disadvantage: roll 3d6, keep 2 lowest
  • Result type: 'strong_hit' | 'weak_hit' | 'miss'
  • Usage: roll(arg: PbtARollArgument)stat is required
interface PbtARollArgument {
stat: number // required; range -3 to +5
forward?: number // one-time bonus; range -5 to +5
ongoing?: number // persistent bonus; range -5 to +5
rollingWith?: 'Advantage' | 'Disadvantage' // 3d6 keep highest/lowest 2
}
const result = roll({ stat: 2, forward: 1 })
result.result // 'strong_hit' | 'weak_hit' | 'miss'

Games: Dungeon World, Monster of the Week, Apocalypse World, Masks, and more.

@randsum/games/salvageunion - Salvage Union

  • Mechanics: d20 roll-under system; lower is better
  • Outcome: 1 = critical success, 20 = critical failure; results looked up in reference tables
  • Result type: SalvageUnionRollRecord (label, description, table metadata)
  • Usage: roll(tableName?: SalvageUnionTableName) — defaults to 'Core Mechanic'
const result = roll() // defaults to Core Mechanic table
const result = roll('Morale') // specific table
result.result.label // human-readable result name
result.result.description // result description
result.result.roll // d20 value (1-20)
result.result.tableName // table used

Common Patterns

All game packages:

  • Depend on @randsum/roller core package
  • Return a result object with result, total, and rolls fields
  • Export roll function and result types

Quick Reference by System

GameNotation / PatternOutcome
D&D 5Eroll({ modifier: 5 })d20 + modifier
D&D 5E adv2d20L+5 (roller)Higher of 2d20 + modifier
D&D 5E dis2d20H+5 (roller)Lower of 2d20 + modifier
D&D 5E scores4d6L (roller)Ability score (drop lowest)
Bladesroll(N)Highest die: 6 success, 4-5 partial, 1-3 fail
Daggerheartroll({ modifier: N })Hope/Fear interpretation
PbtA / Rootroll({ stat: N })10+ strong, 7-9 weak, 6- miss
Salvage Unionroll('Core Mechanic')Roll under; lower is better