Skip to content

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.

Terminal window
bun add @randsum/games
import { roll } from '@randsum/games/blades'
// Roll with a dice pool of 3
const { result } = roll(3)
console.log(result) // 'critical' | 'success' | 'partial' | 'failure'
import { roll } from '@randsum/games/blades'
roll(0) // Zero dice: rolls 2d6, keeps lowest
roll(1) // One die
roll(2) // Two dice
roll(3) // Three dice
roll(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
break
case 'success':
// Highest die is 6 - full success
break
case 'partial':
// Highest die is 4-5 - success with complication
break
case 'failure':
// Highest die is 1-3 - things go badly
break
}

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
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

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.

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.

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'

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.