Skip to content

The @randsum/games/pbta subpath provides generic Powered by the Apocalypse (PbtA) mechanics that work with any PbtA game, including Dungeon World, Monster of the Week, Apocalypse World, Masks, and many others.

Terminal window
bun add @randsum/games
import { roll } from '@randsum/games/pbta'
// Roll 2d6 + stat modifier
const result = roll({ stat: 2 })
// result.result: 'strong_hit' | 'weak_hit' | 'miss'
import { roll } from '@randsum/games/pbta'
// With forward and ongoing bonuses
const result = roll({
stat: 1,
forward: 1, // One-time bonus
ongoing: 0 // Persistent bonus
})
import { roll } from '@randsum/games/pbta'
// Roll with advantage (3d6, keep 2 highest)
const result = roll({
stat: 2,
rollingWith: 'Advantage'
})
// Roll with disadvantage (3d6, keep 2 lowest)
const result2 = roll({
stat: 2,
rollingWith: 'Disadvantage'
})
import { roll } from '@randsum/games/pbta'
const { result, total } = roll({ stat: 2 })
switch (result) {
case 'strong_hit':
// 10+ total: complete success
break
case 'weak_hit':
// 7-9 total: success with cost
break
case 'miss':
// 6- total: failure
break
}

roll(input: { stat: number, forward?: number, ongoing?: number, rollingWith?: 'Advantage' | 'Disadvantage' })

Section titled “roll(input: { stat: number, forward?: number, ongoing?: number, rollingWith?: 'Advantage' | 'Disadvantage' })”

Input:

PropertyTypeDescription
statnumberStat modifier (-3 to 5)
forwardnumber (optional)One-time bonus (-5 to 5)
ongoingnumber (optional)Persistent bonus (-5 to 5)
rollingWith'Advantage' | 'Disadvantage' | undefinedRoll 3d6, keep 2 highest or lowest

Returns: GameRollResult with:

PropertyTypeDescription
resultPbtaRollResult'strong_hit' | 'weak_hit' | 'miss'
totalnumberSum of 2d6 + all modifiers
rollsRollRecord[]Raw dice data from the core roller
detailsPbtaRollDetails{ stat, forward, ongoing, diceTotal }
OutcomeTotalDescription
strong_hit10+Complete success
weak_hit7-9Partial success, success with cost
miss6-Failure

This package works with any PbtA game, including:

  • Apocalypse World — The original PbtA game
  • Dungeon World — Fantasy dungeon crawling
  • Monster of the Week — Urban fantasy monster hunting
  • Masks — Teen superheroes
  • The Sprawl — Cyberpunk missions
  • Urban Shadows — Urban supernatural politics
  • And many more

Game roll() can throw two types of errors:

  • ValidationError (from @randsum/roller) — numeric input out of range or not finite (e.g., stat: 99 when max is 5)
  • SchemaError (from @randsum/games/pbta) — game-specific issues like invalid rollingWith values
import { roll, SchemaError } from '@randsum/games/pbta'
import { ValidationError } from '@randsum/roller'
try {
roll({ stat: 99 })
} catch (error) {
if (error instanceof ValidationError) {
// Out-of-range stat (must be -3 to 5)
console.log(error.code) // 'VALIDATION_ERROR'
} else if (error instanceof SchemaError) {
// e.g., invalid rollingWith enum value
console.log(error.code) // 'INVALID_INPUT_TYPE'
}
}
import type { PbtaRollResult, PbtaRollDetails, GameRollResult, RollRecord } from '@randsum/games/pbta'
import { SchemaError } from '@randsum/games/pbta'

This game is powered by a .randsum.json spec that defines the 2d6 resolution, stat/bonus ranges, and outcome tiers. The TypeScript code is generated from this spec at build time. See Schema Overview for how game specs work.