Skip to content

Claude Code Skill

RANDSUM provides a dice rolling skill for LLM agents — a structured prompt and reference that enables AI assistants to roll dice using RANDSUM notation for any tabletop RPG system.

An LLM skill is a markdown document that gives an AI assistant specialized knowledge. When an agent loads the RANDSUM dice rolling skill, it learns:

  • RANDSUM dice notation — a compact syntax for any roll (4d6L, 2d20H+5, 3d6!)
  • Game system mechanics — how to interpret rolls for D&D, Blades in the Dark, Daggerheart, PbtA, Root RPG, and Salvage Union
  • Best practices — when to validate, how to explain results, matching the right system

The skill works with any LLM that supports tool use or structured prompts — Claude, ChatGPT, or custom agents.

The skill file lives at skills/dice-rolling/SKILL.md in the RANDSUM repository.

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

Terminal window
# Download the skill
curl -o .claude/skills/dice-rolling/SKILL.md \
https://raw.githubusercontent.com/RANDSUM/randsum/main/skills/dice-rolling/SKILL.md

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

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) → Unique (60) → Multiply (85) → Plus/Minus (90/91) → Count Successes (95) → 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
})

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.

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. Unique
  7. Count Successes
  8. Pre-Arithmetic Multiply (*)
  9. Plus / Minus
  10. 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

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