Dice Rolling
Roll dice, interpret results, and answer questions about RANDSUM notation and supported game systems. Covers all 19+ modifiers and 6 game systems.
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 scoreWhat are the odds of rolling 15+ on 3d6?Create a .randsum.json spec for IronswornOr 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:
# Download all three skillsfor skill in dice-rolling dice-probability game-spec-creator; domkdir -p .claude/skills/$skill/referencescurl -o .claude/skills/$skill/SKILL.md \ https://raw.githubusercontent.com/RANDSUM/randsum/main/skills/$skill/SKILL.mddone
# Download reference filescurl -o .claude/skills/dice-rolling/references/NOTATION.md \https://raw.githubusercontent.com/RANDSUM/randsum/main/skills/dice-rolling/references/NOTATION.mdcurl -o .claude/skills/dice-rolling/references/GAME_SYSTEMS.md \https://raw.githubusercontent.com/RANDSUM/randsum/main/skills/dice-rolling/references/GAME_SYSTEMS.mdcurl -o .claude/skills/dice-probability/references/PROBABILITY_TABLES.md \https://raw.githubusercontent.com/RANDSUM/randsum/main/skills/dice-probability/references/PROBABILITY_TABLES.mdcurl -o .claude/skills/game-spec-creator/references/SPEC_EXAMPLES.md \https://raw.githubusercontent.com/RANDSUM/randsum/main/skills/game-spec-creator/references/SPEC_EXAMPLES.mdLoad skill content as part of your agent’s system prompt:
import { readFileSync } from 'fs'
// Load whichever skills your agent needsconst diceSkill = readFileSync('skills/dice-rolling/SKILL.md', 'utf-8')const probSkill = readFileSync('skills/dice-probability/SKILL.md', 'utf-8')const specSkill = readFileSync('skills/game-spec-creator/SKILL.md', 'utf-8')
// Include in your agent's system promptconst systemPrompt = `You are a TTRPG assistant.\n\n${diceSkill}`Paste skill content directly into any LLM conversation. Each skill is self-contained with all references included.
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:
4d6L, 2d20H+5, 3d6!, d%, 4dF, etc.)Files:
skills/dice-rolling/SKILL.md — main skill definitionskills/dice-rolling/references/NOTATION.md — full notation referenceskills/dice-rolling/references/GAME_SYSTEMS.md — game system mechanicsTeaches 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:
Files:
skills/dice-probability/SKILL.md — main skill definitionskills/dice-probability/references/PROBABILITY_TABLES.md — precomputed reference tablesTeaches 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:
.randsum.json schema (pools, modifiers, conditions, outcome tables)$ref for reusable components)Files:
skills/game-spec-creator/SKILL.md — main skill definitionskills/game-spec-creator/references/SPEC_EXAMPLES.md — example specs for existing game systemsIf you’re building a custom agent, you can also use @randsum/roller directly:
bun add @randsum/rollerimport { roll } from '@randsum/roller'
const result = roll('4d6L')console.log(result.total) // Sum after dropping lowestconsole.log(result.rolls) // Individual die resultsSee the Roller documentation for the full API.
The full skill file that agents receive:
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:
bunx @randsum/cli <notation> # in bun projects (preferred)npx @randsum/cli <notation> # anywhere elseExamples:
bunx @randsum/cli 4d6L # D&D ability scorebunx @randsum/cli 2d20L+7 # advantage attackbunx @randsum/cli 3d6 # Blades in the Dark poolbunx @randsum/cli 5d10S{7} # World of Darkness successesIf 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.
After rolling, always show:
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.”).
All notation is case-insensitive. Basic syntax: NdS (N dice, S sides).
| Notation | Description |
|---|---|
4d6L | Drop lowest die |
4d6H | Drop highest die |
4d6L2 | Drop 2 lowest |
4d6K3 | Keep highest 3 |
4d6kl2 | Keep lowest 2 |
1d20+5 | Add 5 to total |
4d6-1 | Subtract 1 |
2d6*2 | Multiply dice sum (before +/-) |
2d6+3**2 | Multiply final total (after +/-) |
Conditions use comparison operators: >N (greater than), <N (less than), >=N (at or over), <=N (at or under), =N or bare N (exact match).
| Notation | Description |
|---|---|
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}3 | Reroll 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 |
4d20U | All 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| Notation | Description |
|---|---|
3d6! | Explode: new die on max result |
3d6!! | Compound: add to same die on max |
3d6!p | Penetrate: explode minus 1 each time |
3d6!5 | Explode up to 5 times per die |
| Notation | Description |
|---|---|
5d10S{7} | Count dice >= 7 (successes) |
5d10S{7,1} | Successes >= 7, subtract botches <= 1 |
5d10F{3} | Count dice <= 3 (failures) |
| Notation | Description |
|---|---|
5d6W | D6 System wild die (last die is wild) |
4d6//2 | Integer divide total by 2 |
4d6%3 | Total modulo 3 |
| Notation | Description |
|---|---|
g6 | Geometric die: roll d6 until 1, return count |
3g6 | Three independent geometric rolls |
DD6 | Draw die: sample without replacement from d6 |
3DD6 | Draw 3 unique values from d6 pool |
| Notation | Description |
|---|---|
2d6+3[fire] | Label a roll group (no mechanical effect) |
4d6Lx6 | Repeat notation 6 times (ability score gen) |
1d20+2d6+5 # attack (d20) + damage (2d6) + mod2d12-1d6 # roll 2d12, subtract 1d6Modifier 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
See references/GAME_SYSTEMS.md for full mechanics.
| Game | Roll | Interpretation |
|---|---|---|
| D&D 5E advantage | 2d20L+mod | Keep highest + modifier |
| D&D 5E disadvantage | 2d20H+mod | Keep lowest + modifier |
| D&D 5E ability score | 4d6L | Drop lowest of 4d6 |
| D&D 5E crit | 2×damage dice + mod | Double the dice |
| Blades in the Dark | Nd6, keep highest | 6 = success, 4–5 = partial, 1–3 = failure; two 6s = critical |
| Daggerheart | 2d12 (Hope/Fear) | Higher die = narrative control; sum = mechanical success |
| PbtA / Root RPG | 2d6+stat | 10+ = strong hit, 7–9 = weak hit, 6– = miss |
| Salvage Union | d20 vs target | Roll under; 1 = critical success, 20 = critical failure |
import { roll } from "@randsum/roller"
roll(20) // 1d20roll("4d6L") // notation stringroll({ sides: 6, quantity: 4, modifiers: { drop: { lowest: 1 } } })roll("1d20+5", "2d6") // multiple args, combined total
const result = roll("2d6+3")result.total // final sumresult.rolls // RollRecord[] — full roll data per dice groupresult.values // string[] — rolled values as stringsDice 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")).
All notation in randsum is case-insensitive (2d8 = 2D8).
// Roll one d20roll(20) // number argumentroll("1d20") // notation stringroll({ sides: 20, quantity: 1})
// Roll four d6roll("4d6")roll({ sides: 6, quantity: 4})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 rollsDDN)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 valuesroll("6DD6") // Full permutation of [1,2,3,4,5,6]roll("8DD6") // Full permutation + 2 more (reshuffles after exhaustion)roll("4d6+2") // Add 2 to totalroll({ sides: 6, quantity: 4, modifiers: { plus: 2 }})
roll("4d6-1") // Subtract 1 from totalroll({ sides: 6, quantity: 4, modifiers: { minus: 1 }})Limit roll values to specific ranges:
roll("4d20C{>18}") // Cap rolls over 18 to 18roll({ sides: 20, quantity: 4, modifiers: { cap: { greaterThan: 18 } }})
roll("4d20C{<3}") // Cap rolls under 3 to 3roll({ sides: 20, quantity: 4, modifiers: { cap: { lessThan: 3 } }})
roll("4d20C{<2,>19}") // Cap rolls under 2 to 2 and over 19 to 19roll({ sides: 20, quantity: 4, modifiers: { cap: { greaterThan: 19, lessThan: 2 } }})Drop specific dice from the results:
roll("4d6L") // Drop lowestroll({ sides: 6, quantity: 4, modifiers: { drop: { lowest: 1 } }})
roll("4d6L2") // Drop 2 lowestroll({ sides: 6, quantity: 4, modifiers: { drop: { lowest: 2 } }})
roll("4d6H") // Drop highestroll({ sides: 6, quantity: 4, modifiers: { drop: { highest: 1 } }})
roll("4d6H2") // Drop 2 highestroll({ sides: 6, quantity: 4, modifiers: { drop: { highest: 2 } }})
roll("4d6LH") // Drop both lowest and highestroll({ sides: 6, quantity: 4, modifiers: { drop: { lowest: 1, highest: 1 } }})
// Drop by valueroll("4d20D{>17}") // Drop rolls over 17roll({ sides: 20, quantity: 4, modifiers: { drop: { greaterThan: 17 } }})
roll("4d20D{<5}") // Drop rolls under 5roll({ sides: 20, quantity: 4, modifiers: { drop: { lessThan: 5 } }})
roll("4d20D{8,12}") // Drop 8s and 12sroll({ sides: 20, quantity: 4, modifiers: { drop: { exact: [8, 12] } }})Reroll dice matching certain conditions:
roll("4d20R{>17}") // Reroll results over 17roll({ sides: 20, quantity: 4, modifiers: { reroll: { greaterThan: 17 } }})
roll("4d20R{<5}") // Reroll results under 5roll({ sides: 20, quantity: 4, modifiers: { reroll: { lessThan: 5 } }})
roll("4d20R{8,12}") // Reroll 8s and 12sroll({ sides: 20, quantity: 4, modifiers: { reroll: { exact: [8, 12] } }})
roll("4d20R{<5}3") // Reroll under 5, max 3 attemptsroll({ sides: 20, quantity: 4, modifiers: { reroll: { lessThan: 5, max: 3 } }})Replace specific results with new values:
roll("4d20V{8=12}") // Replace 8s with 12sroll({ sides: 20, quantity: 4, modifiers: { replace: { from: 8, to: 12 } }})
roll("4d20V{>17=20}") // Replace results over 17 with 20roll({ sides: 20, quantity: 4, modifiers: { replace: { from: { greaterThan: 17 }, to: 20 } }})
roll("4d20V{<5=1}") // Replace results under 5 with 1roll({ sides: 20, quantity: 4, modifiers: { replace: { from: { lessThan: 5 }, to: 1 } }})Force unique rolls within a pool:
roll("4d20U") // All results must be uniqueroll({ sides: 20, quantity: 4, modifiers: { unique: true }})
roll("4d20U{5,10}") // Unique except 5s and 10s can repeatroll({ sides: 20, quantity: 4, modifiers: { unique: { notUnique: [5, 10] } }})Keep specific dice from the result (complement to drop):
roll("4d6K3") // Keep highest 3roll({ sides: 6, quantity: 4, modifiers: { keep: { highest: 3 } }})
roll("4d6K") // Keep highest 1roll({ sides: 6, quantity: 4, modifiers: { keep: { highest: 1 } }})
roll("4d6kl2") // Keep lowest 2roll({ sides: 6, quantity: 4, modifiers: { keep: { lowest: 2 } }})
roll("4d6kl") // Keep lowest 1roll({ 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.
Roll additional dice on maximum results:
roll("4d20!") // Roll an extra d20 for each 20 rolledroll({ sides: 20, quantity: 4, modifiers: { explode: true }})
roll("3d6!5") // Explode with max depth of 5roll({ 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.
Exploding dice that add to the triggering die instead of creating new dice:
roll("3d6!!") // Compound explode: add to die instead of new diceroll({ sides: 6, quantity: 3, modifiers: { compound: true }})
roll("3d6!!5") // Compound explode with max depth of 5roll({ 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:
!): Creates new dice → [6, 4, 6] becomes [6, 4, 6, 5, 3] (5 dice)!!): Modifies existing die → [6, 4, 6] becomes [15, 4, 12] (still 3 dice)Exploding dice where each subsequent explosion subtracts 1 (Hackmaster-style):
roll("3d6!p") // Penetrate explode: subtract 1 from subsequent rollsroll({ sides: 6, quantity: 3, modifiers: { penetrate: true }})
roll("3d6!p5") // Penetrate with max depth of 5roll({ 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:
!): [6] → [6, 6, 4] = 16 (new dice added)!!): [6] → [16] = 16 (die value modified)!p): [6] → [12] = 12 (6 + (6-1) + (3-1) if it keeps penetrating, die value modified with -1 on each subsequent roll)Multiply the dice sum before adding/subtracting arithmetic modifiers:
roll("2d6*2+3") // (dice sum * 2) + 3roll({ sides: 6, quantity: 2, modifiers: { multiply: 2, plus: 3 }})
roll("4d6*3") // Multiply dice sum by 3roll({ 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.
Multiply the entire final total after all other modifiers:
roll("2d6+3**2") // (dice + 3) * 2roll({ sides: 6, quantity: 2, modifiers: { plus: 3, multiplyTotal: 2 }})
roll("4d6L+2**3") // ((drop lowest) + 2) * 3roll({ 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:
*): 2d6*2+3 = (9 × 2) + 3 = 21**): 2d6+3\*\*2 = (9 + 3) × 2 = 24Count 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 >= 7roll({ sides: 10, quantity: 5, modifiers: { countSuccesses: { threshold: 7 } }})
// With botch threshold (successes - botches)roll("5d10S{7,1}") // Count successes >= 7, subtract botches <= 1roll({ 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 divide the total (truncates toward zero via Math.trunc):
roll("4d6//2") // Integer divide total by 2roll({ sides: 6, quantity: 4, modifiers: { integerDivide: 2 }})Apply modulo to the total:
roll("4d6%3") // Total modulo 3roll({ sides: 6, quantity: 4, modifiers: { modulo: 3 }})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 <= 3roll({ sides: 10, quantity: 5, modifiers: { countFailures: { threshold: 3 } }})D6 System wild die (West End Games). Last die is “wild”:
roll("5d6W") // Last die is wildroll({ sides: 6, quantity: 5, modifiers: { wildDie: true }})Attach metadata labels to dice terms — flavor text with no mechanical effect:
roll("2d6+3[fire]+1d4[cold]") // Labels on roll groupsroll("4d6L[strength]") // Label the roll purposeRepeat a roll expression N times (notation sugar):
roll("4d6Lx6") // Equivalent to six separate 4d6L rollsroll("2d6+3x4") // Roll 2d6+3 four timesModifiers can be chained together. They are applied in a specific order to ensure consistent results:
Modifier Application Order:
*)**)roll("4d6L+2") // Drop lowest, add 2roll("2d20H!+1") // Drop highest, explode, add 1roll("4d6R{<3}L") // Reroll under 3, then drop lowestroll("3d6!!*2+3") // Compound explode, multiply by 2, add 3roll("2d6*2+3**2") // Multiply dice by 2, add 3, multiply total by 2roll("4d6K3!+2") // Keep highest 3, explode, add 2roll("3d6!pL+1") // Penetrate explode, drop lowest, add 1roll("5d6W+2") // Wild die with modifierroll("4d6Lx6") // Six ability score rollsImportant Notes:
*) applies before plus/minus: 2d6*2+3 = (sum × 2) + 3**) applies after everything: 2d6+3**2 = (sum + 3) × 22d6*2+3**2 = ((sum × 2) + 3) × 2You 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 stringYou 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 1d62d6+3*2 → (9 × 2) + 3 = 212d6+3**2 → (9 + 3) × 2 = 24roll("4d6K3") // Roll 4d6, keep highest 3roll("4d6L") // Equivalent: Roll 4d6, drop lowest 1roll("2d20K") // Advantage: keep highestroll("2d20kl") // Disadvantage: keep lowestroll("1d6!p") // Standard penetrateroll("2d6!p+3") // Penetrate with modifierroll("8d6*2") // 8d6 damage, doubled for area effectroll("8d6+5**2") // 8d6+5 damage, doubled for area effectAll explosive modifiers (explode, compound, penetrate) have built-in depth limits:
!N, !!N, !pN - Limited to N depth!0, !!0, !p0 - Capped at 1000 for safety!, !!, !p - Limited to 1 explosion per dieThe extended notation syntax was inspired by Sophie’s Dice.
'critical' | 'success' | 'partial' | 'failure'roll(rating?: number) — 0 = desperate (2d6, keep lowest), 1-4 = roll that many d6, keep highestconst result = roll(3)result.result // 'critical' | 'success' | 'partial' | 'failure'result.total // highest die value'hope' | 'fear' | 'critical hope'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 }2d20L — drop lowest)2d20H — drop highest)roll("4d6L") from @randsum/roller directlynumber (d20 + modifier)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' })'Strong Hit' | 'Weak Hit' | 'Miss'roll(bonus: number) — range -20 to +20const result = roll(2)result.result // 'Strong Hit' | 'Weak Hit' | 'Miss'result.total // 2d6 + bonus'strong_hit' | 'weak_hit' | 'miss'roll(arg: PbtARollArgument) — stat is requiredinterface 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.
SalvageUnionRollRecord (label, description, table metadata)roll(tableName?: SalvageUnionTableName) — defaults to 'Core Mechanic'const result = roll() // defaults to Core Mechanic tableconst result = roll('Morale') // specific table
result.result.label // human-readable result nameresult.result.description // result descriptionresult.result.roll // d20 value (1-20)result.result.tableName // table usedAll game packages:
@randsum/roller core packageresult, total, and rolls fields| Game | Notation / Pattern | Outcome |
|---|---|---|
| D&D 5E | roll({ modifier: 5 }) | d20 + modifier |
| D&D 5E adv | 2d20L+5 (roller) | Higher of 2d20 + modifier |
| D&D 5E dis | 2d20H+5 (roller) | Lower of 2d20 + modifier |
| D&D 5E scores | 4d6L (roller) | Ability score (drop lowest) |
| Blades | roll(N) | Highest die: 6 success, 4-5 partial, 1-3 fail |
| Daggerheart | roll({ modifier: N }) | Hope/Fear interpretation |
| PbtA / Root | roll({ stat: N }) | 10+ strong, 7-9 weak, 6- miss |
| Salvage Union | roll('Core Mechanic') | Roll under; lower is better |