Dice Notation Reference
Randsum Dice Notation
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", "2d6", "+5")).
Basic Syntax
All notation in randsum is case-insensitive (2d8 = 2D8).
Standard Rolls
// Roll one d20roll(20) // number argumentroll("1d20") // notation stringroll({ sides: 20, quantity: 1})
// Roll four d6roll("4d6")roll({ sides: 6, quantity: 4})Modifiers
Basic Arithmetic
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 }})Cap Modifiers
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 Modifiers
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 } }})
// 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 Modifiers
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 Modifiers
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 } }})Unique Results
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 Modifiers
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.
Exploding Dice
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 100 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 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 100)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 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 100)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) + 3roll({ sides: 6, quantity: 2, modifiers: { multiply: 2, plus: 3 }})
roll("4d6*3") // Multiply dice sum by 3roll({ sides: 6, quantity: 4, modifiers: { multiply: 2 }})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) * 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:
- Pre-Arithmetic (
*):2d6*2+3= (9 × 2) + 3 = 21 - Total (
**):2d6+3\*\*2= (9 + 3) × 2 = 24
Combining Modifiers
Modifiers can be chained together. They are applied in a specific order to ensure consistent results:
Modifier Application Order:
- Cap
- Drop / Keep
- Replace
- Reroll
- Explode / Compound / Penetrate
- Unique
- Success Count
- Pre-Arithmetic Multiply (
*) - Plus / Minus
- Total Multiply (
**)
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 1Important Notes:
- Pre-arithmetic multiply (
*) applies beforeplus/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 stringAdding 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 1d6Common 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 3roll("4d6L") // Equivalent: Roll 4d6, drop lowest 1D&D 5e Advantage/Disadvantage
roll("2d20K") // Advantage: keep highestroll("2d20kl") // Disadvantage: keep lowestHackmaster Penetrating Dice
roll("1d6!p") // Standard penetrateroll("2d6!p+3") // Penetrate with modifierArea Effect Spells
roll("8d6*2") // 8d6 damage, doubled for area effectroll("8d6+5**2") // 8d6+5 damage, doubled for area effectPerformance 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 100 for safety - Default:
!,!!,!p- Limited to 1 explosion per die
Best Practices
- Use depth limits for explosive modifiers in production code
- Keep dice quantities reasonable (< 100 dice for best performance)
- Prefer compound over explode if you don’t need separate dice tracking
- Use multipliers sparingly - they’re cheap but unnecessary if not needed
Attribution
The extended notation syntax was inspired by Sophie’s Dice.