Blades in the Dark
The @randsum/games/blades subpath provides mechanics for Blades in the Dark, a tabletop RPG about a crew of daring scoundrels seeking their fortunes on the haunted streets of an industrial-fantasy city.
Installation
Section titled “Installation”bun add @randsum/gamesnpm install @randsum/gamesimport { roll } from '@randsum/games/blades'
// Roll with a dice pool of 3const { result } = roll(3)console.log(result) // 'critical' | 'success' | 'partial' | 'failure'import { roll } from '@randsum/games/blades'
roll(0) // Zero dice: rolls 2d6, keeps lowestroll(1) // One dieroll(2) // Two diceroll(3) // Three diceroll(4) // Four dice (e.g., with assistance)roll(5) // Five dice (e.g., with assistance + push)roll(6) // Six dice (maximum pool)import { roll } from '@randsum/games/blades'
const { result } = roll(2)
switch (result) {case 'critical': // Multiple 6s - exceptional outcome breakcase 'success': // Highest die is 6 - full success breakcase 'partial': // Highest die is 4-5 - success with complication breakcase 'failure': // Highest die is 1-3 - things go badly break}roll(rating?: number)
Section titled “roll(rating?: number)”Input:
| Parameter | Type | Description |
|---|---|---|
rating | number (optional) | Action rating / dice pool size (0-6) |
Returns: GameRollResult with:
| Property | Type | Description |
|---|---|---|
result | BladesRollResult | 'critical' | 'success' | 'partial' | 'failure' |
total | number | Sum of kept dice after modifiers |
rolls | RollRecord[] | Raw dice data from the core roller |
Outcomes
Section titled “Outcomes”| Outcome | Condition | Description |
|---|---|---|
critical | Multiple 6s | Critical success — exceptional outcome |
success | Highest die is 6 | Full success — you do it |
partial | Highest die is 4-5 | Partial success — you do it, but with a complication |
failure | Highest die is 1-3 | Bad outcome — things go wrong |
Zero Dice Pool
Section titled “Zero Dice Pool”When the dice pool is 0, the system rolls 2d6 and keeps only the lowest die. This makes failure much more likely and prevents critical results.
About this implementation
Section titled “About this implementation”This package handles the dice roll outcome only. Position (controlled, risky, desperate) and Effect (limited, standard, great) are narrative mechanics in Blades in the Dark that a caller manages separately — they are not part of the roll output. Use result to determine the mechanical outcome, then apply position and effect in your own game logic.
Error handling
Section titled “Error handling”Game roll() can throw two types of errors:
ValidationError(from@randsum/roller) — numeric input out of range or not finite (e.g., rating of 7 when max is 6)SchemaError(from@randsum/games/blades) — game-specific issues like unmatched outcome tables
import { roll, SchemaError } from '@randsum/games/blades'import { ValidationError } from '@randsum/roller'
try {roll(99)} catch (error) {if (error instanceof ValidationError) { // Out-of-range rating (must be 0–6) console.log(error.code) // 'VALIDATION_ERROR'} else if (error instanceof SchemaError) { // Game-specific error console.log(error.code) // 'NO_TABLE_MATCH'}}import type { BladesRollResult, GameRollResult, RollRecord } from '@randsum/games/blades'import { SchemaError } from '@randsum/games/blades'Schema
Section titled “Schema”This game is powered by a .randsum.json spec that defines the dice pool, outcome tiers, and validation rules. The TypeScript code is generated from this spec at build time. See Schema Overview for how game specs work.