Skip to content

Schema Reference

Every .randsum.json spec conforms to the JSON Schema at https://randsum.dev/schemas/v1/randsum.json (referenced via $schema). This page documents every field.

{
"$schema": "https://randsum.dev/schemas/v1/randsum.json",
"name": "Blades in the Dark",
"shortcode": "blades",
"version": "1.0.0",
"game_url": "https://bladesinthedark.com",
"srd_url": "https://bladesinthedark.com/basics"
}
FieldTypeRequiredDescription
$schemastringYesJSON Schema URL for validation
namestringYesHuman-readable game name
shortcodestringYesPackage identifier — used for subpath exports (@randsum/games/{shortcode}) and generated filenames
versionstringYesSpec version (semver)
game_urlstringNoOfficial game website
srd_urlstringNoSystem reference document URL

Named dice pool definitions. Each pool describes a type of die used by the game.

"pools": {
"actionDice": { "sides": 6 },
"hope": { "sides": 12 },
"fear": { "sides": 12 }
}
FieldTypeRequiredDescription
sidesnumberYesNumber of sides on the die

Pools are referenced elsewhere in the spec via { "$ref": "#/pools/{poolName}" }.

Lookup tables that map numeric results to string outcomes. Each table has an array of ranges.

"tables": {
"coreMechanic": {
"ranges": [
{
"poolCondition": { "countWhere": { "operator": "=", "value": 6 }, "atLeast": 2 },
"result": "critical"
},
{ "exact": 6, "result": "success" },
{ "min": 4, "max": 5, "result": "partial" },
{ "min": 1, "max": 3, "result": "failure" }
]
}
}

Each entry in the ranges array matches a roll total (or pool condition) to a result string. Ranges are evaluated in order — the first match wins.

FieldTypeDescription
resultstringThe outcome string returned when this range matches
exactnumberMatches when the total equals this value
minnumberMatches when the total is at least this value (used with max)
maxnumberMatches when the total is at most this value (used with min)
poolConditionobjectMatches based on individual die values instead of the total

Inspects the raw die values before modifiers, not the total. Used for mechanics like Blades criticals (two or more 6s).

"poolCondition": {
"countWhere": { "operator": "=", "value": 6 },
"atLeast": 2
}
FieldTypeDescription
countWhereobjectCondition to test each die: { "operator": "=" | ">" | "<" | ">=" | "<=", "value": number }
atLeastnumberMinimum number of dice that must satisfy countWhere

Named outcome definitions that can be referenced by $ref. An outcome either delegates to a table or defines its own inline ranges.

"outcomes": {
"coreMechanicOutcome": { "tableLookup": { "$ref": "#/tables/coreMechanic" } },
"desperateActionOutcome": {
"ranges": [
{ "exact": 6, "result": "success" },
{ "min": 4, "max": 5, "result": "partial" },
{ "min": 1, "max": 3, "result": "failure" }
]
}
}
FieldTypeDescription
tableLookup{ "$ref": string }Reference to a named table
rangesarrayInline range entries (same format as tables ranges)

The core of the spec. Defines inputs, dice configuration, modifiers, resolution, outcome determination, and conditional overrides.

Defines what the caller passes to the game’s roll() function. Each key becomes a property on the input object (or a positional argument for single-input games).

"inputs": {
"rating": {
"type": "integer",
"minimum": 0,
"maximum": 4,
"default": 1,
"description": "Action rating (0-4)"
},
"rollingWith": {
"type": "string",
"enum": ["Advantage", "Disadvantage"],
"optional": true
},
"crit": {
"type": "boolean",
"optional": true,
"description": "When true, check for natural 1 and natural 20"
}
}
FieldTypeDescription
type"integer" | "string" | "boolean"Input value type
minimumnumberMinimum value (integer inputs only)
maximumnumberMaximum value (integer inputs only)
defaultanyDefault value when the input is omitted
optionalbooleanWhether the input can be omitted (no default needed)
enumstring[]Allowed values (string inputs only)
descriptionstringHuman-readable description

Codegen produces validateRange() and validateFinite() calls for integer inputs with minimum/maximum, and enum validation for string inputs with enum.

Configures the dice to roll. For single-pool games.

"dice": {
"pool": { "$ref": "#/pools/actionDice" },
"quantity": { "$input": "rating" }
}
FieldTypeDescription
poolobjectEither { "$ref": "#/pools/{name}" } or inline { "sides": number }
quantitynumber | { "$input": string }Number of dice, or dynamic from an input value

For multi-pool games (e.g., Daggerheart’s hope/fear dice). Each key names a pool that can be referenced in resolve and details.

"dicePools": {
"hope": { "pool": { "sides": { "$input": "amplifyHope", "ifTrue": 20, "ifFalse": 12 } } },
"fear": { "pool": { "sides": { "$input": "amplifyFear", "ifTrue": 20, "ifFalse": 12 } } }
}

Dynamic sides use { "$input": string, "ifTrue": number, "ifFalse": number } for boolean-conditional values.

Pools that are only rolled when a condition is met. Used for Daggerheart’s advantage/disadvantage extra die.

"conditionalPools": {
"advantage": {
"condition": { "input": "rollingWith", "operator": "=", "value": "Advantage" },
"pool": { "sides": 6 },
"arithmetic": "add"
}
}
FieldTypeDescription
conditionobjectWhen to include this pool (same format as when conditions)
poolobjectDie definition ({ "sides": number })
arithmetic"add" | "subtract"How to combine this pool’s result with the total

Array of modifier operations applied to the dice. Modifiers run in the order specified.

"modify": [
{ "keepHighest": 1 },
{ "add": { "$input": "modifier" } }
]

Available modifiers:

ModifierValueDescription
keepHighestnumberKeep the N highest dice
keepLowestnumberKeep the N lowest dice
addnumber | { "$input": string }Add to the total

The { "$input": string } form reads the value from a named input at runtime.

How the roll total is determined.

ValueDescription
"sum"Sum all dice values after modifiers (most games)
{ "comparePoolHighest": ... }Compare highest values across named pools (Daggerheart)
{ "remoteTableLookup": ... }Look up results from an external table (Salvage Union)
"resolve": {
"comparePoolHighest": {
"pools": ["hope", "fear"],
"ties": "critical hope",
"outcomes": { "hope": "hope", "fear": "fear" }
}
}
FieldTypeDescription
poolsstring[]Named pools to compare
tiesstringResult string when pools tie
outcomesobjectMaps winning pool name to result string

Tables are fetched at build time and baked into the generated code. No network calls at runtime.

"resolve": {
"remoteTableLookup": {
"url": "https://salvageunion.io/schema/roll-tables.json",
"find": {
"field": "name",
"input": "tableName",
"errorMessage": "Invalid table name: \"${value}\""
},
"tableField": "table",
"resultMapping": {
"key": { "$lookupResult": "key" },
"label": { "$lookupResult": "result.label", "fallback": { "$lookupResult": "key" } },
"description": { "$lookupResult": "result.value" },
"tableName": { "$input": "tableName" },
"table": { "$foundTable": "table" },
"roll": { "expr": "total" }
}
}
}

Modifiers applied after resolution. Used when the resolve step produces a non-sum result (e.g., Daggerheart applies the stat modifier after comparing hope/fear pools).

"postResolveModifiers": [{ "add": { "$input": "modifier" } }]

Determines the string outcome from the numeric total. Either a $ref to a named outcome or inline ranges.

// Reference a named outcome
"outcome": { "$ref": "#/outcomes/coreMechanicOutcome" }
// Inline ranges
"outcome": {
"ranges": [
{ "min": 10, "max": 27, "result": "strong_hit" },
{ "min": 7, "max": 9, "result": "weak_hit" },
{ "min": -11, "max": 6, "result": "miss" }
]
}

Games without an outcome field return a numeric result (like D&D 5e).

Additional structured data attached to the GameRollResult.details field. Each key becomes a property on the details object.

"details": {
"criticals": {
"when": { "input": "crit" },
"value": {
"isNatural1": {
"$dieCheck": { "pool": 0, "field": "final", "die": 0, "operator": "=", "value": 1 }
},
"isNatural20": {
"$dieCheck": { "pool": 0, "field": "final", "die": 0, "operator": "=", "value": 20 }
}
}
}
}

Detail values can be:

FormDescription
{ "$input": string }Value from a named input
{ "$input": string, "default": any }Input value with a default
{ "$pool": string, "field": "total" }Total from a named pool
{ "$conditionalPool": string, "field": "total" }Total from a conditional pool
{ "$dieCheck": object }Check a specific die value
{ "expr": string }Expression reference (e.g., "diceTotal", "total")
{ "when": object, "value": object }Conditional detail — only present when input is truthy

Inspects a specific die in the roll results.

FieldTypeDescription
poolnumberPool index (0-based)
field"final" | "initial"Check the post-modifier or pre-modifier value
dienumberDie index within the pool (0-based)
operator"=" | ">" | "<" | ">=" | "<="Comparison operator
valuenumberValue to compare against

Conditional overrides that change the roll behavior based on input values. Each entry has a condition and an override that replaces specific fields.

"when": [
{
"condition": { "input": "rating", "operator": "=", "value": 0 },
"override": {
"dice": { "pool": { "$ref": "#/pools/actionDice" }, "quantity": 2 },
"modify": [{ "keepLowest": 1 }],
"outcome": { "$ref": "#/outcomes/desperateActionOutcome" }
}
}
]
FieldTypeDescription
inputstringInput field to check
operator"=" | "!=" | ">" | "<" | ">=" | "<="Comparison operator
valueanyValue to compare against

An override can replace any of dice, dicePools, modify, resolve, or outcome. Only the fields specified are overridden — unmentioned fields keep their default values.

When multiple when entries match, they are evaluated in order and the first match is used.

Specs use JSON Pointer-style references ($ref) to avoid duplication. References always point within the same spec file using the #/ prefix.

{ "$ref": "#/pools/actionDice" } // resolves to pools.actionDice
{ "$ref": "#/tables/coreMechanic" } // resolves to tables.coreMechanic
{ "$ref": "#/outcomes/coreMechanicOutcome" } // resolves to outcomes.coreMechanicOutcome

For a complete, working spec, see the Salvage Union source — the richest spec in the ecosystem with multiple roll tables, range-based outcomes, and dynamic table selection — or any other game spec in the packages/games/ directory.