Write a whole game system of your own, with its own numbers, rolls, damage, tokens and character builder, as a homebrew base pack modelled on Ashfall.

For: Game Masters · Plan: Pro · 34 minutes · What it's for: Game systems

A game system is the set of rules a game plays by: what a character sheet holds, how a roll works, where damage goes, what a rest does. In RollQuest a system is data, not code: a pack of numbers, rolls and rules that RollQuest reads to build every sheet, token and character builder in a game. RollQuest's own Ashfall, Brightline and Tidewrack are built this way, and so can yours be. This page takes a system apart block by block, using real pieces of Ashfall, then builds Cinder, a tiny d100 system in one pack, shows how to test it and how to add packs to it, and ends with a reference to every engine piece a system can switch on.

Experimental: a system of your own is accepted, and it appears in the Game System list when you create a game, but it hasn't been tested with players at the table. Try it with a second account, playing as a player, before your group relies on it.

Plan: homebrew is on Pro, with up to 20 homebrew packs. A system of your own is one pack, and each add-on pack you build on it is one more.

Extend a system, or build your own?

Most homebrew extends a system RollQuest already has. Build your own only when you need different rules.

You want Do this
New weapons, talents, archetypes or corruptions for Ashfall Build on Ashfall. Supported.
New items, spells, subclasses or classes for 5e or 5.5e Start at Homebrew: what you can build. Supported.
Different characteristics, meters, rolls, damage, rests or builder steps A system of your own, on this page. Experimental.
New items for a system of your own An add-on pack built on it: see Add-on packs for your system.

A pack that extends a system can't change its rules: it adds items, and the rules stay in the system's own pack. That's why new rules need a system of your own.

How a system is put together

A system is one base pack: a pack that builds on nothing and carries the rules. Here's what each part of it becomes in a game:

flowchart LR
  pack["Your base pack"] --> numbers["Value fields and calculations"]
  pack --> items["Items"]
  pack --> money["Money and pools"]
  pack --> rules["systemRules"]
  pack --> steps["requiredDecisions"]
  numbers --> sheet["Every character sheet"]
  items --> sheet
  money --> sheet
  rules --> sheet
  rules --> board["Tokens and damage on the board"]
  steps --> builder["The character builder"]
Part What it is Ashfall has
The envelope The pack's name, ID, system name, version and licence. ashfall-core, "Ashfall (d100)"
Value fields The numbers on a sheet. 150: eight characteristics, the vitals, 42 skills and the points and ticks behind them
Calculations The rolls. 52 tests
Items The things a character can hold. Kept in four add-on packs: archetypes, gear, talents and corruptions, and a bestiary
Money and pools Coins, and points a character spends, such as Fate. Pennies, Shillings, Crowns; Fate
systemRules How the game behaves: kinds of item, results, damage, tokens, rests, conditions. 26 rules
requiredDecisions The character builder's steps. 6 steps

Ashfall keeps its items in four add-on packs (see Build on Ashfall). A system of yours can do the same: an add-on pack can build on your own base pack, so your system can grow pack by pack. See Add-on packs for your system.

The rest of this section goes through each part with Ashfall's real JSON.

The envelope

The top of the pack says what it is.

Field For a system of your own
name What the Game System list shows, such as Cinder.
slug A short ID, unique among your packs, such as cinder-core. The builder's steps and your add-on packs name the pack by it.
system The system's family name, such as Cinder (d100). Any text, kept the same in every pack of the system. The System list in New homebrew shows it.
category base.
derivedFrom "", empty: a system builds on nothing.
version, license, description Your version text, USER_HOMEBREW, and a line describing it.
canvasEnabled false unless you have sheet artwork. With it off, sheets use the Form View, built from your value fields.

Every other top-level field is described in The pack file, field by field.

Value fields: the numbers

A value field is one number on the sheet: a characteristic, a skill, a meter. Ashfall's Brawn:

{
  "kind": "valueField",
  "key": "BRAWN",
  "spec": {
    "id": "BRAWN",
    "label": "Brawn",
    "value": 30,
    "min": 0,
    "max": 250,
    "width": 60,
    "group": "characteristics",
    "order": 0,
    "notes": "Strength and the force behind a blow.",
    "calculationId": "BRAWN_CHECK"
  }
}

A value can also be a formula. Ashfall's Max Wounds is a tenth of Vigour plus Frame, rounded down:

{
  "kind": "valueField",
  "key": "WOUNDS_MAX",
  "spec": {
    "width": 60,
    "group": "vitals",
    "order": 1,
    "id": "WOUNDS_MAX",
    "label": "Max Wounds",
    "value": "=FLOOR((VIGOUR+FRAME)/10)",
    "min": 1,
    "max": 99,
    "notes": "A tenth of Vigour plus Frame, rounded down."
  }
}
Field What it does
id The name formulas and effects use, in capitals by habit, such as BRAWN. Set it the same as key.
label What the sheet shows.
value The starting number, or a formula starting with =, worked out live.
min, max The lowest and highest it can be set to.
group Which block of the Form View it sits in. Give every field one: a field with no group is left out of the sheet's blocks. Ashfall's blocks are Characteristics, Vitals and Skills, named from its groups.
order Its place inside the group.
calculationId The calculation it rolls when clicked. Leave it out for a number that isn't rolled, such as Wounds.
notes A line saying what the field is for.
width Its width if you place it on Canvas artwork. Not needed without artwork.
control "dots" or "boxes" to draw it as a track. See Track controls.
engineOnly true for a number the rules keep for themselves, with no group, such as the points behind a skill. It tells the checker the field is meant to stay off the sheet.

Each Ashfall skill is a base plus the points bought for it at creation: Melee is =25+MELEE_PTS, and MELEE_PTS is an engineOnly field the builder's Spend Skill Points step fills in (see Point-budget steps).

Calculations: the rolls

A calculation is a roll. Ashfall's Melee test:

{
  "kind": "calculation",
  "key": "MELEE_CHECK",
  "spec": {
    "id": "MELEE_CHECK",
    "name": "Melee",
    "formula": "=1d100<=MELEE"
  }
}

name titles the roll's result. The formula is a spreadsheet formula with dice: it starts with =, can use dice such as 1d100 or 2d10+30, value field names, and functions such as FLOOR, MIN, MAX and IF. A comparison with the dice on the left, such as =1d100<=MELEE, makes it a test: the result shows the roll, the target and Success or Failure, or the named result your system declares (see Roll outcome bands). The number of dice can be a field too, for a dice pool: =(CLIMB)d6kh1 (see Dice pools).

The checker refuses a formula that names something your pack doesn't declare, because RollQuest would read it as 0. It also refuses RAND, RANDBETWEEN, NOW and TODAY. A calculation no value field points at can't be rolled from anywhere, and the checker says so.

See Calculations and values for how values and calculations behave on a sheet.

Items: what characters hold

Archetypes, talents, weapons and corruptions are all items. Each has a subType, the kind of item it is, which must be one of the kinds the system declares in systemRules.categories (below). Ashfall's Hardened talent:

{
  "kind": "inventoryItem",
  "key": "TAL_HARDENED",
  "spec": {
    "subType": "talent",
    "name": "Hardened",
    "meta": {
      "tier": "1",
      "effect": "+2 Armour against the first hit each scene."
    },
    "actions": [],
    "effects": [
      {
        "id": "TAL_HARDENED_ARMOUR",
        "target": "ARMOUR",
        "op": "add",
        "value": 2,
        "condition": "",
        "trigger": ""
      }
    ]
  }
}

meta holds what the item's row says, actions its buttons, and effects what it changes on the sheet. Its effect adds 2 to Armour, all the time: RollQuest has no "first hit each scene", so that part is words for the Game Master (GM). Every item field is in the pages for each kind, starting with Weapons and armour, and every effect in Effects: what an item can change.

Money and pools

Ashfall's money, from the smallest coin up:

"currencyGroups": [
  {
    "id": "coinage",
    "label": "Coinage",
    "primaryDenomId": "c",
    "denominations": [
      { "id": "p", "label": "Penny", "abbr": "p", "minorUnits": 1 },
      { "id": "s", "label": "Shilling", "abbr": "s", "minorUnits": 12 },
      { "id": "c", "label": "Crown", "abbr": "Cr", "minorUnits": 240 }
    ]
  }
]

minorUnits says how many of the smallest coin each is worth, and one coin must be worth 1. Prices are written in the smallest coin: 36 pennies is 3 shillings. primaryDenomId is the largest coin used when amounts are shown.

A pool is a set of points a character spends and gets back, such as Fate:

"resourcePools": [
  { "id": "fate", "label": "Fate", "max": 3, "used": 0, "group": "fate", "level": null }
]

The sheet shows the pool's label with − to spend a point and + to give one back. max is a fixed number. A new sheet gets the pool once; applying the system again never resets points already spent.

systemRules: the rules

systemRules is one block on the base pack that says how the game behaves. Each rule you set replaces RollQuest's own default for that rule completely, and a rule RollQuest doesn't know is dropped without a word. Ashfall sets 26:

Rule What it controls Ashfall
vocabulary Renames RollQuest's own words: the tab for each kind of item, the rest buttons, the word on a test someone is asked to make. Archetypes, Skills, Weapons, Talents, Corruptions, Armour, Kit, Rites, Cinderbooks, Horrors; Recuperate, Catch Breath, Tempering; Test, Defend
categories The kinds of item: their tab, their row columns, when their effects count and their button verb. archetype, skill, weapon, talent, corruption, armour, kit, rite, cinderbook, horror
damageTypes The damage types, each with the dice effect it plays. Cutting, Impact, Rending, Toxic, Warp
rollModes Ways to roll beside a plain roll, such as advantage. Fortune, Misfortune, Fortune ×2, Misfortune ×2
rollOutcomes The named results a roll can land on, and the buttons under them. Triumph, Disaster, Sharp, Solid, Success, Failure; Press the test, Spend Ember, Spend Fate to reroll
crit What makes a critical. The result: Triumph is a critical, Disaster a fumble
damagePipeline Where each type of damage goes, and what one hit means for a meter. 2 rules (below)
roundChecks A test a creature makes at the end of its turn while it holds a condition. Vigour each turn while Dying
tables Rollable tables the rules roll on, added to a game when it's created. Shaken Bout, Haunted
contests How a defender answers an attack. Dodge or Fight back against a melee weapon
scalingFields The numbers a token publishes for the rules' tests to read. Vigour, Wits, Dodge, Melee and the Sanity test bonus
tokenBindings What a token shows on the board. Wounds, Sanity, Corruption, Armour
conditions The game's conditions, copied into a new game's condition library. Broken, Dead, Grievous, Dying, Unconscious, Shaken, Lost, Haunted
incapacitatedConditions Conditions put on a token automatically when its life meter runs out. Broken
healingDenialConditions Conditions that stop healing. Dead
triggerButtons The rest buttons, and other buttons that fire effects. Recuperate, Catch Breath, Tempering
abilityScores How the builder sets the characteristics. Rolled: 3d6 × 5, or (2d6+6) × 5 for Frame, Wits and Learning; Sanity starts at Resolve
environmentEffects What weather does to rolls. Misfortune on Spot tests in mist, heavy rain, heavy snow and thick ashfall
macroTemplates The ready-made macros a game on the system is offered. Weapon strike, Melee strike, Dodge, Steady your nerves
maxLevel The highest level. 1 means no levels and no Starting Level in the builder. 1
maxPowerTier The highest spell or power tier. 0
combatRuleDefs 5e's rules switches on the sheet. An empty list hides them. none
initiative The die a battle rolls for turn order. d10
roundSeconds How much game time a battle round takes. 30
distance How far one grid square is. 1 yard
calendar The in-world calendar for the journal. The Ash Reckoning: 12 months of 28 days and one extra day, The Long Dark; 6-day weeks; year 998

The rules Ashfall leaves out get RollQuest's defaults, which are mostly "none": no action economy, no resistances, no carrying limits, no extra roll options. The defaults that do carry a choice: each grid square is 5 ft unless distance says otherwise, and lighting is Bright, Dim and Darkness. Weather changes a roll only with a roll mode your own system declares, so a d100 system can use it: see Weather.

Here are the ones you're most likely to write, as Ashfall has them.

The words. Each subType.<kind> names a kind of item, .plural its tab, trigger.<button> a rest or other trigger button, resourcePool.group.<group> a pool in the rest preview, and prompt.check and prompt.contest the word on a test or a defence the rules ask someone to make:

"vocabulary": {
  "subType.archetype": "Archetype",
  "subType.archetype.plural": "Archetypes",
  "subType.skill": "Skill",
  "subType.weapon": "Weapon",
  "subType.talent": "Talent",
  "subType.corruption": "Corruption",
  "subType.skill.plural": "Skills",
  "subType.weapon.plural": "Weapons",
  "subType.talent.plural": "Talents",
  "subType.corruption.plural": "Corruptions",
  "trigger.longRest": "Recuperate",
  "trigger.shortRest": "Catch Breath",
  "resourcePool.group.fate": "Fate Points",
  "trigger.tempering": "Tempering",
  "subType.armour": "Armour",
  "subType.armour.plural": "Armour",
  "subType.kit": "Kit",
  "subType.kit.plural": "Kit",
  "subType.rite": "Rite",
  "subType.rite.plural": "Rites",
  "subType.cinderbook": "Cinderbook",
  "subType.cinderbook.plural": "Cinderbooks",
  "prompt.check": "Test",
  "prompt.contest": "Defend",
  "subType.horror": "Horror",
  "subType.horror.plural": "Horrors"
}

A kind of item. flags.activeGate decides when its effects count: trait means always, equipped only while equipped, prepared only while prepared. activateVerb is its button's word. Two more flags give a kind a role: see Category roles.

{
  "id": "talent",
  "label": "Talent",
  "displayMode": "row",
  "columns": [
    { "key": "tier", "label": "Tier" },
    { "key": "effect", "label": "Effect" }
  ],
  "flags": { "activeGate": "trait", "activateVerb": "Invoke" }
}

columns name the meta keys an item of this kind fills in. Don't count on them all showing as columns: the Form View's Items page is where a system's own kinds show best.

A damage type and a roll mode. effect is the dice animation a hit of that type plays. A roll mode's rewrite replaces the first die of a formula, so Fortune turns =1d100<=MELEE into =2d100kl1<=MELEE, two d100s keeping the lower. Ashfall also has Fortune ×2 and Misfortune ×2, which roll three d100s (3d100kl1 and 3d100kh1):

{ "id": "cutting", "label": "Cutting", "effect": "slashing", "fallback": "neutral" }
{ "id": "fortune", "label": "Fortune", "target": "primary", "rewrite": "2d100kl1", "keep": "lowest" }

An item's effect can apply any roll mode its system declares: on Ashfall the effect op is fortune, fortune2, misfortune or misfortune2, and advantage is refused.

Criticals. Ashfall's is { "mode": "band" }: the result's own tone decides it, so a roll that lands Triumph is a critical and one that lands Disaster a fumble (see Roll outcome bands). { "mode": "doublesRollUnder" } makes doubles on a passed d100 a critical and on a failed one a fumble; Cinder, below, uses it to stay small. For a d20 game, { "range": 19 } makes 19 and 20 criticals.

Characteristics in the builder. fields lists the value fields to set. A method's id decides how: dice rolls formula (one dice term and plain numbers, such as 2d10+30 or 3d6*5) for each field, count times, or rolls each field with its own formula from formulas; array and pointBuy exist for 5e-style sets. startingValues seeds another field from a characteristic once it's set: Ashfall's Sanity starts at Resolve.

"abilityScores": {
  "fields": [
    { "id": "BRAWN", "label": "Brawn" },
    { "id": "VIGOUR", "label": "Vigour" },
    { "id": "FRAME", "label": "Frame" },
    { "id": "REFLEX", "label": "Reflex" },
    { "id": "PRESENCE", "label": "Presence" },
    { "id": "WITS", "label": "Wits" },
    { "id": "RESOLVE", "label": "Resolve" },
    { "id": "LEARNING", "label": "Learning" },
    { "id": "EMBER", "label": "Ember" }
  ],
  "range": { "min": 1, "max": 100 },
  "resetTo": 30,
  "methods": [
    {
      "id": "dice",
      "label": "Rolled",
      "formula": "3d6*5",
      "formulas": {
        "BRAWN": "3d6*5",
        "VIGOUR": "3d6*5",
        "FRAME": "(2d6+6)*5",
        "REFLEX": "3d6*5",
        "PRESENCE": "3d6*5",
        "WITS": "(2d6+6)*5",
        "RESOLVE": "3d6*5",
        "LEARNING": "(2d6+6)*5",
        "EMBER": "3d6*5"
      }
    }
  ],
  "startingValues": [ { "field": "SANITY", "from": "RESOLVE" } ]
}

Every fields ID must be a value field of yours, or its rolled number is thrown away. The checker doesn't check any of these IDs yet, in fields, formulas or startingValues, so spell each one exactly. The builder counts the step done once any characteristic differs from resetTo, so set resetTo to the starting value and, when you can, to a number the dice can't roll (Cinder's 2d10+30 gives 32 to 50). Always declare range.

Rests and other buttons. A trigger button does nothing by itself. What it does is the effects on a character's items whose trigger matches the button's (Ashfall puts them on every archetype; see Build on Ashfall):

"triggerButtons": [
  {
    "trigger": "longRest",
    "label": "Recuperate",
    "kind": "primary",
    "hint": "A night somewhere the ash cannot reach. Fate and Kindling return in full and 3 Wounds close. Sanity and Corruption never come back."
  },
  {
    "trigger": "shortRest",
    "label": "Catch Breath",
    "kind": "secondary",
    "hint": "A few stolen minutes. 1 Wound closes and 1 Kindling returns."
  },
  {
    "trigger": "tempering",
    "label": "Tempering",
    "kind": "secondary",
    "rest": false,
    "hint": "Between stories, take stock of what you learned the hard way. Roll d100 for each ticked skill: over the skill, or 96 and up, and it gains 1d10. Roll d100 against Ember: over it, and Ember gains 1d10. Every tick clears."
  }
]

hint is what the button's preview says. "rest": false marks a button that isn't a rest, such as Ashfall's Tempering between stories: its preview is worded for what it is.

Conditions. Each is a badge with a description; only the base pack can declare them. id is lower case with hyphens and never changes; kind is condition or state. Ashfall's Broken:

{
  "id": "broken",
  "name": "Broken",
  "kind": "condition",
  "description": "Your Wounds have reached their maximum. You fall and can take no actions. Any further hit that deals Wounds kills you outright. Spend a Fate point to stagger back to your feet with Wounds one below maximum. Healing that closes even one Wound ends this.",
  "color": "#8B1A1A",
  "iconColor": "#FFFFFF",
  "iconType": "svg-library",
  "iconKey": "system-assets/condition-icons/v2/critical-condition-life-support.svg",
  "iconKey16": null
}
"incapacitatedConditions": ["broken"],
"healingDenialConditions": ["dead"]

A condition does nothing by itself beyond the badge and its words. What RollQuest does with Broken and Dead comes from the two lists and the meter rules: it puts Broken on a token whose Wounds are full, takes it off when one is healed, and refuses to heal a Dead token; a further hit on a Broken token makes it Dead (see Meter rules). See Conditions.

requiredDecisions: the builder's steps

requiredDecisions lists the character builder's steps, in order. Ashfall's last two:

{ "id": "starting_funds", "label": "Starting Funds", "prompt": "What did you scrape together before the ash came down? Roll for the coin in your purse. It pays for your starting gear.", "kind": "startingFunds", "dependsOn": ["pick_archetype"],
  "spec": { "default": [ { "kind": "roll", "groupId": "coinage", "formula": "3d6", "multiplier": 12, "label": "Roll 3d6 shillings" } ] } },
{ "id": "starting_gear", "label": "Starting Gear", "prompt": "Take your starting weapons.", "kind": "pickFromPacks", "skippable": true, "satisfiedBy": { "packSlugs": ["ashfall-gear"] }, "dependsOn": ["starting_funds"] }
Field What it does
id The step's ID. Never change it once characters exist.
label The step's heading in the builder.
prompt The line under the heading.
kind What the step does (below).
dependsOn Steps that must come first. Each must be a real step, never itself.
satisfiedBy.packSlugs For a pick step: the packs whose items it offers.
skippable true lets players skip the step, and lets them take any number of items.
spec Settings for the step's kind, such as the money roll.
filterBy, maxPicks Narrow what a pick step offers, and how many a player takes there. See Creation shaping.
kind The step Ashfall's
characterName A name, and a Starting Level when maxLevel is above 1. Name Your Character
abilityScoreAllocation Sets the characteristics with systemRules.abilityScores. Allocate Characteristics
pickFromPacks Cards for every item in the named packs. Choose an Archetype, Starting Gear
pointBudget Points to spend on value fields, such as skills. See Point-budget steps. Spend Skill Points
startingFunds Money: a roll of plain dice times multiplier in the smallest coin, or a fixed amount. Starting Funds (3d6 × 12 pennies: 3d6 shillings)

Rules the builder holds you to:

  • One pick or many. A pick step without skippable: true lets a character hold one item from its packs: picking another replaces it. With skippable: true they take as many as they like, or skip. maxPicks sets a number in between.
  • One pack, one step. A one-pick step's packs must hold only the things it chooses between, and two pick steps never name the same pack.
  • Spell the kind right. The checker doesn't check kind, and a step of a kind RollQuest doesn't know shows nothing and never lets the player past, unless it's skippable.
  • Money before gear. Put a money step before a priced gear step, and make the lowest roll afford the cheapest item.

Damage: where it goes

damagePipeline sends each type of damage to the right meter. Here is Ashfall's, with its meter rules taken out (they're in Meter rules, below):

"damagePipeline": [
  {
    "types": ["cutting", "impact", "rending", "toxic"],
    "steps": [
      { "stat": "armour", "mode": "soak" },
      { "stat": "wounds", "mode": "accumulate", "floorRole": "vitality" }
    ]
  },
  { "types": ["warp"], "steps": [ { "stat": "sanity", "mode": "deplete" } ] }
]
flowchart LR
  hit["A hit, with its type"] --> which{"Which type?"}
  which -->|"Cutting, Impact, Rending, Toxic"| armour["Armour takes its share off"]
  armour --> wounds["The rest is added to Wounds"]
  which -->|"Warp"| sanity["Taken off Sanity"]
  wounds -->|"Wounds full"| broken["Token marked Broken"]
  • One rule handles each hit: the first whose types lists the damage type. A rule with no types catches every type no other rule lists. Without one, a type that no rule lists does nothing at all, so route every type you declare.
  • Damage or healing with no type goes to a rule with no types if there is one, and otherwise to the rule with the life meter: on Ashfall, through Armour to Wounds.
  • stat names a token binding (below), not a value field: armour, not ARMOUR.
  • Healing runs backwards: it closes Wounds, or fills Sanity when its type is Warp. A heal can also name the meter it restores (see Meter rules).
mode What it does with the damage
soak Takes the stat's value off the damage, without using the stat up. Armour.
deplete Takes the damage off the stat and passes any left over to the next step. Sanity, or hit points.
absorb The same, for a buffer such as temporary hit points.
accumulate Adds the damage to the stat, up to its maximum. Wounds.
overflow Puts whatever is left into the stat.
resist Turns the hit into a check the target makes, and lands conditions when it fails. See Resist damage.

floorRole: "vitality" marks the life meter: emptying a deplete meter, or filling an accumulate one, puts the incapacitatedConditions on the token.

The numbers, from Ashfall's own tests, for a character with Max Wounds 10: a hit of 6 Cutting through Armour 2 adds 4 Wounds. A hit of 5 or more that reaches Wounds is grave: the token is marked Grievous and asked a Vigour test. A hit that fills Wounds marks it Broken, and a further hit that gets past its Armour while it's Broken marks it Dead. Losing 5 Sanity in one go asks a Wits test, and Sanity reaching 0 marks the character Lost.

Token bindings: what a token shows

tokenBindings decides which numbers a character's token carries on the board, and the vitals at the top of the sheet. Ashfall's:

"tokenBindings": [
  {
    "id": "wounds",
    "label": "Wounds",
    "kind": "pips",
    "source": "WOUNDS",
    "max": "WOUNDS_MAX",
    "role": "vitality"
  },
  {
    "id": "sanity",
    "label": "Sanity",
    "kind": "bar",
    "source": "SANITY",
    "max": "SANITY_MAX",
    "color": "#8b5cf6"
  },
  {
    "id": "corruption",
    "label": "Corruption",
    "kind": "pips",
    "source": "CORRUPTION",
    "max": "CORRUPTION_MAX"
  },
  { "id": "armour", "label": "Armour", "kind": "number", "source": "ARMOUR", "role": "soak" }
]
Field What it does
id The stat's ID on the token, and the name the damage rules use.
label Shown on the board and in the token's Edit window.
kind bar (a bar captioned like "25/30 Sanity"), pips (a row of dots), number (a label and a number) or badge (shown only when it isn't 0).
source, max The value fields that hold the number and its maximum.
role vitality for the life meter, defense for the number an attack has to reach, tempBuffer for temporary points. Ashfall marks Armour soak: damage takes it off, and no attack is compared with it.
color A bar's colour, as a hex code.
appliesTo On a defense: melee, ranged or any. See Defences by range.
maxPips On pips: the most dots it draws. See Token pips.

A pips meter draws one dot per point up to 20; above that it draws the same reading as a bar. That's why Ashfall's Corruption, out of 100, shows as a bar.

Every stat starts hidden on the board. The GM turns each one on for themselves, the token's player and the other players, in the token's Edit Character window under On the board (see Damage and healing). The bindings also decide which vitals the Stat Blocks window offers when you write a creature for the system.

Build Cinder, step by step

Cinder is the smallest system that works like Ashfall: two characteristics (Grit and Wits), one skill (Fight), Wounds up to 6, Armour, 2 Fate points, one damage type, two archetypes, three builder steps and one rest. It's one pack, cinder-core, and it passes the checker. Build it in this order.

1. The envelope, money and Fate

  "name": "Cinder",
  "slug": "cinder-core",
  "system": "Cinder (d100)",
  "category": "base",
  "derivedFrom": "",
  "version": "0.1.0",
  "license": "USER_HOMEBREW",
  "description": "A tiny d100 roll-under system, modelled on Ashfall.",
  "canvasEnabled": false,
  "currencyGroups": [
    {
      "id": "coinage",
      "label": "Coinage",
      "primaryDenomId": "s",
      "denominations": [
        { "id": "p", "label": "Penny", "abbr": "p", "minorUnits": 1 },
        { "id": "s", "label": "Shilling", "abbr": "s", "minorUnits": 12 }
      ]
    }
  ],
  "resourcePools": [
    { "id": "fate", "label": "Fate", "max": 2, "used": 0, "group": "fate", "level": null }
  ],

A base pack (category: "base") that builds on nothing (derivedFrom: ""), with no Canvas artwork. Pennies and shillings give every sheet a purse, ready for prices later. Fate has 2 points.

2. The numbers and the rolls

    { "kind": "valueField", "key": "GRIT", "spec": { "id": "GRIT", "label": "Grit", "value": 30, "min": 0, "max": 100, "group": "characteristics", "order": 0, "calculationId": "GRIT_CHECK" } },
    { "kind": "valueField", "key": "WITS", "spec": { "id": "WITS", "label": "Wits", "value": 30, "min": 0, "max": 100, "group": "characteristics", "order": 1, "calculationId": "WITS_CHECK" } },
    { "kind": "valueField", "key": "FIGHT", "spec": { "id": "FIGHT", "label": "Fight", "value": "=GRIT", "min": 0, "max": 100, "group": "skills", "order": 0, "calculationId": "FIGHT_CHECK" } },
    { "kind": "valueField", "key": "WOUNDS", "spec": { "id": "WOUNDS", "label": "Wounds", "value": 0, "min": 0, "max": 99, "group": "vitals", "order": 0 } },
    { "kind": "valueField", "key": "WOUNDS_MAX", "spec": { "id": "WOUNDS_MAX", "label": "Max Wounds", "value": 6, "min": 1, "max": 99, "group": "vitals", "order": 1 } },
    { "kind": "valueField", "key": "ARMOUR", "spec": { "id": "ARMOUR", "label": "Armour", "value": 0, "min": 0, "max": 20, "group": "vitals", "order": 2 } },
    { "kind": "calculation", "key": "GRIT_CHECK", "spec": { "id": "GRIT_CHECK", "name": "Grit", "formula": "=1d100<=GRIT" } },
    { "kind": "calculation", "key": "WITS_CHECK", "spec": { "id": "WITS_CHECK", "name": "Wits", "formula": "=1d100<=WITS" } },
    { "kind": "calculation", "key": "FIGHT_CHECK", "spec": { "id": "FIGHT_CHECK", "name": "Fight", "formula": "=1d100<=FIGHT" } },
  • Grit and Wits start at 30 and each rolls its own test when clicked.
  • Fight is always equal to Grit ("=GRIT") and rolls its own test.
  • Wounds start at 0 and fill up to Max Wounds, 6. Armour starts at 0.
  • Three groups make three blocks on the sheet: Characteristics, Skills and Vitals.

3. The archetypes

    {
      "kind": "inventoryItem",
      "key": "ARCH_WARDEN",
      "spec": {
        "subType": "archetype",
        "name": "Warden",
        "meta": { "role": "Guard", "grants": "+2 Armour", "description": "You stood on the wall when the cinders fell." },
        "actions": [],
        "effects": [
          { "id": "ARCH_WARDEN_ARMOUR", "target": "ARMOUR", "op": "add", "value": 2, "condition": "", "trigger": "" },
          { "id": "REST_FATE", "target": "POOL(\"fate\")", "op": "reset", "value": "", "condition": "", "trigger": "longRest" },
          { "id": "REST_WOUNDS", "target": "VF(\"WOUNDS\")", "op": "increment", "value": -2, "condition": "", "trigger": "longRest" }
        ]
      }
    },
    {
      "kind": "inventoryItem",
      "key": "ARCH_SCHOLAR",
      "spec": {
        "subType": "archetype",
        "name": "Scholar",
        "meta": { "role": "Reader", "grants": "+10 Wits", "description": "You kept the books when nobody else would." },
        "actions": [],
        "effects": [
          { "id": "ARCH_SCHOLAR_WITS", "target": "WITS", "op": "add", "value": 10, "condition": "", "trigger": "" },
          { "id": "REST_FATE", "target": "POOL(\"fate\")", "op": "reset", "value": "", "condition": "", "trigger": "longRest" },
          { "id": "REST_WOUNDS", "target": "VF(\"WOUNDS\")", "op": "increment", "value": -2, "condition": "", "trigger": "longRest" }
        ]
      }
    }

Each archetype carries its own benefit as an effect (+2 Armour, or +10 Wits) and the rest rules: on a rest, Fate refills and 2 Wounds close. Every character has exactly one archetype, so every character rests the same way.

These are Cinder's only items. Anything else you put in cinder-core itself (a weapon, a talent) would be offered in the archetype step and picked instead of an archetype, so keep the rest in a second pack built on it: see Add-on packs for your system.

4. The builder's steps

  "requiredDecisions": [
    { "id": "set_name", "label": "Name Your Character", "kind": "characterName" },
    { "id": "allocate_characteristics", "label": "Roll Characteristics", "kind": "abilityScoreAllocation", "dependsOn": ["set_name"] },
    { "id": "pick_archetype", "label": "Choose an Archetype", "kind": "pickFromPacks", "satisfiedBy": { "packSlugs": ["cinder-core"] }, "dependsOn": ["allocate_characteristics"] }
  ],

Three steps: a name, the characteristics, and one archetype picked from the pack itself (cinder-core). Without skippable, the archetype step is a one-pick step.

5. The rules

  "systemRules": {
    "vocabulary": {
      "subType.archetype": "Archetype",
      "subType.archetype.plural": "Archetypes",
      "trigger.longRest": "Rest"
    },
    "categories": [
      { "id": "archetype", "label": "Archetype", "displayMode": "row", "columns": [ { "key": "role", "label": "Role" }, { "key": "grants", "label": "Grants" } ], "flags": { "activeGate": "trait" } }
    ],
    "damageTypes": [
      { "id": "cutting", "label": "Cutting", "effect": "slashing", "fallback": "neutral" }
    ],
    "rollModes": [
      { "id": "fortune", "label": "Fortune", "target": "primary", "rewrite": "2d100kl1", "keep": "lowest" },
      { "id": "misfortune", "label": "Misfortune", "target": "primary", "rewrite": "2d100kh1", "keep": "highest" }
    ],
    "abilityScores": {
      "fields": [ { "id": "GRIT", "label": "Grit" }, { "id": "WITS", "label": "Wits" } ],
      "range": { "min": 1, "max": 100 },
      "resetTo": 30,
      "methods": [ { "id": "dice", "label": "Rolled (2d10+30)", "formula": "2d10+30", "count": 2 } ]
    },
    "crit": { "mode": "doublesRollUnder" },
    "initiative": { "die": "d10" },
    "maxLevel": 1,
    "maxPowerTier": 0,
    "combatRuleDefs": [],
    "damagePipeline": [
      {
        "types": ["cutting"],
        "steps": [
          { "stat": "armour", "mode": "soak" },
          { "stat": "wounds", "mode": "accumulate", "floorRole": "vitality" }
        ]
      }
    ],
    "tokenBindings": [
      { "id": "wounds", "label": "Wounds", "kind": "pips", "source": "WOUNDS", "max": "WOUNDS_MAX", "role": "vitality" },
      { "id": "armour", "label": "Armour", "kind": "number", "source": "ARMOUR", "role": "defense" }
    ],
    "triggerButtons": [
      { "trigger": "longRest", "label": "Rest", "kind": "primary", "hint": "Fate returns and 2 Wounds close." }
    ],
    "conditions": [
      { "id": "broken", "name": "Broken", "kind": "condition", "description": "Your Wounds are at their maximum. You cannot act. Healing one Wound ends this.", "color": "#8B1A1A", "iconColor": "#FFFFFF" }
    ],
    "incapacitatedConditions": ["broken"]
  }

How each rule fits the rest:

  • One kind of item, archetype, always on (trait). The archetypes' subType must match it.
  • One damage type, Cutting. Its pipeline rule takes Armour off and adds the rest to Wounds, the life meter.
  • Every pipeline stat (armour, wounds) is a token binding id, and every binding's source and max is a value field.
  • abilityScores lists exactly the two characteristics, rolls count: 2 times, and resets to 30, which 2d10+30 can never roll.
  • maxLevel: 1 and maxPowerTier: 0 switch off levels and spell tiers, which RollQuest would otherwise give you up to 20 and 9. combatRuleDefs: [] hides 5e's rules switches.
  • One rest button, longRest, labelled Rest. The archetypes' rest effects use the same trigger.
  • incapacitatedConditions names broken, a condition the pack declares.

6. The whole file

Put the parts together:

{
  "name": "Cinder",
  "slug": "cinder-core",
  "system": "Cinder (d100)",
  "category": "base",
  "derivedFrom": "",
  "version": "0.1.0",
  "license": "USER_HOMEBREW",
  "description": "A tiny d100 roll-under system, modelled on Ashfall.",
  "canvasEnabled": false,
  "currencyGroups": [
    {
      "id": "coinage",
      "label": "Coinage",
      "primaryDenomId": "s",
      "denominations": [
        { "id": "p", "label": "Penny", "abbr": "p", "minorUnits": 1 },
        { "id": "s", "label": "Shilling", "abbr": "s", "minorUnits": 12 }
      ]
    }
  ],
  "resourcePools": [
    { "id": "fate", "label": "Fate", "max": 2, "used": 0, "group": "fate", "level": null }
  ],
  "items": [
    { "kind": "valueField", "key": "GRIT", "spec": { "id": "GRIT", "label": "Grit", "value": 30, "min": 0, "max": 100, "group": "characteristics", "order": 0, "calculationId": "GRIT_CHECK" } },
    { "kind": "valueField", "key": "WITS", "spec": { "id": "WITS", "label": "Wits", "value": 30, "min": 0, "max": 100, "group": "characteristics", "order": 1, "calculationId": "WITS_CHECK" } },
    { "kind": "valueField", "key": "FIGHT", "spec": { "id": "FIGHT", "label": "Fight", "value": "=GRIT", "min": 0, "max": 100, "group": "skills", "order": 0, "calculationId": "FIGHT_CHECK" } },
    { "kind": "valueField", "key": "WOUNDS", "spec": { "id": "WOUNDS", "label": "Wounds", "value": 0, "min": 0, "max": 99, "group": "vitals", "order": 0 } },
    { "kind": "valueField", "key": "WOUNDS_MAX", "spec": { "id": "WOUNDS_MAX", "label": "Max Wounds", "value": 6, "min": 1, "max": 99, "group": "vitals", "order": 1 } },
    { "kind": "valueField", "key": "ARMOUR", "spec": { "id": "ARMOUR", "label": "Armour", "value": 0, "min": 0, "max": 20, "group": "vitals", "order": 2 } },
    { "kind": "calculation", "key": "GRIT_CHECK", "spec": { "id": "GRIT_CHECK", "name": "Grit", "formula": "=1d100<=GRIT" } },
    { "kind": "calculation", "key": "WITS_CHECK", "spec": { "id": "WITS_CHECK", "name": "Wits", "formula": "=1d100<=WITS" } },
    { "kind": "calculation", "key": "FIGHT_CHECK", "spec": { "id": "FIGHT_CHECK", "name": "Fight", "formula": "=1d100<=FIGHT" } },
    {
      "kind": "inventoryItem",
      "key": "ARCH_WARDEN",
      "spec": {
        "subType": "archetype",
        "name": "Warden",
        "meta": { "role": "Guard", "grants": "+2 Armour", "description": "You stood on the wall when the cinders fell." },
        "actions": [],
        "effects": [
          { "id": "ARCH_WARDEN_ARMOUR", "target": "ARMOUR", "op": "add", "value": 2, "condition": "", "trigger": "" },
          { "id": "REST_FATE", "target": "POOL(\"fate\")", "op": "reset", "value": "", "condition": "", "trigger": "longRest" },
          { "id": "REST_WOUNDS", "target": "VF(\"WOUNDS\")", "op": "increment", "value": -2, "condition": "", "trigger": "longRest" }
        ]
      }
    },
    {
      "kind": "inventoryItem",
      "key": "ARCH_SCHOLAR",
      "spec": {
        "subType": "archetype",
        "name": "Scholar",
        "meta": { "role": "Reader", "grants": "+10 Wits", "description": "You kept the books when nobody else would." },
        "actions": [],
        "effects": [
          { "id": "ARCH_SCHOLAR_WITS", "target": "WITS", "op": "add", "value": 10, "condition": "", "trigger": "" },
          { "id": "REST_FATE", "target": "POOL(\"fate\")", "op": "reset", "value": "", "condition": "", "trigger": "longRest" },
          { "id": "REST_WOUNDS", "target": "VF(\"WOUNDS\")", "op": "increment", "value": -2, "condition": "", "trigger": "longRest" }
        ]
      }
    }
  ],
  "requiredDecisions": [
    { "id": "set_name", "label": "Name Your Character", "kind": "characterName" },
    { "id": "allocate_characteristics", "label": "Roll Characteristics", "kind": "abilityScoreAllocation", "dependsOn": ["set_name"] },
    { "id": "pick_archetype", "label": "Choose an Archetype", "kind": "pickFromPacks", "satisfiedBy": { "packSlugs": ["cinder-core"] }, "dependsOn": ["allocate_characteristics"] }
  ],
  "systemRules": {
    "vocabulary": {
      "subType.archetype": "Archetype",
      "subType.archetype.plural": "Archetypes",
      "trigger.longRest": "Rest"
    },
    "categories": [
      { "id": "archetype", "label": "Archetype", "displayMode": "row", "columns": [ { "key": "role", "label": "Role" }, { "key": "grants", "label": "Grants" } ], "flags": { "activeGate": "trait" } }
    ],
    "damageTypes": [
      { "id": "cutting", "label": "Cutting", "effect": "slashing", "fallback": "neutral" }
    ],
    "rollModes": [
      { "id": "fortune", "label": "Fortune", "target": "primary", "rewrite": "2d100kl1", "keep": "lowest" },
      { "id": "misfortune", "label": "Misfortune", "target": "primary", "rewrite": "2d100kh1", "keep": "highest" }
    ],
    "abilityScores": {
      "fields": [ { "id": "GRIT", "label": "Grit" }, { "id": "WITS", "label": "Wits" } ],
      "range": { "min": 1, "max": 100 },
      "resetTo": 30,
      "methods": [ { "id": "dice", "label": "Rolled (2d10+30)", "formula": "2d10+30", "count": 2 } ]
    },
    "crit": { "mode": "doublesRollUnder" },
    "initiative": { "die": "d10" },
    "maxLevel": 1,
    "maxPowerTier": 0,
    "combatRuleDefs": [],
    "damagePipeline": [
      {
        "types": ["cutting"],
        "steps": [
          { "stat": "armour", "mode": "soak" },
          { "stat": "wounds", "mode": "accumulate", "floorRole": "vitality" }
        ]
      }
    ],
    "tokenBindings": [
      { "id": "wounds", "label": "Wounds", "kind": "pips", "source": "WOUNDS", "max": "WOUNDS_MAX", "role": "vitality" },
      { "id": "armour", "label": "Armour", "kind": "number", "source": "ARMOUR", "role": "defense" }
    ],
    "triggerButtons": [
      { "trigger": "longRest", "label": "Rest", "kind": "primary", "hint": "Fate returns and 2 Wounds close." }
    ],
    "conditions": [
      { "id": "broken", "name": "Broken", "kind": "condition", "description": "Your Wounds are at their maximum. You cannot act. Healing one Wound ends this.", "color": "#8B1A1A", "iconColor": "#FFFFFF" }
    ],
    "incapacitatedConditions": ["broken"]
  }
}

7. Check and create it

  1. In a game you created, open the menu (three lines, top left) and click My Homebrew under Game Master tools.
  2. Click + New homebrew. Under Which system are you extending?, any system will do for a new base pack: the choice only decides which contract downloads. Choose I already have the JSON, then Continue.
  3. Paste the whole file into the box in Check and create and click Check this pack.
  4. The report reads Nothing here stops this pack being created. and Nothing to report.: nothing is listed under Blocking or Advice.
  5. Click Create pack. A message says Cinder was created. It is private to you.
Check and create with the Cinder file pasted: Check this pack, then the report: nothing stops this pack being created, and nothing to report.

Once Cinder exists, it has a contract and a seed of its own. Click + New homebrew again: under Which system are you extending?, the System list now has Cinder (d100) after RollQuest's own systems. Choose it, then:

  • Download the contract gives you an authoring contract generated from Cinder itself: the IDs, kinds and rules an add-on pack for Cinder may use. It's the file to give an AI assistant when it writes packs for your system.
  • Download the seed pack gives you cinder-core-seed-pack.json, a small add-on for Cinder with two items that work as soon as the pack is bound: one with charges a rest refills, one that asks the player a choice. Rename its keys before you create it.

8. Start a game on it

  1. On your dashboard, click New Game. The Create New Game window opens.
  2. Type a Game Name and choose Start from Scratch if it's offered.
  3. Under Game System, choose Cinder.
  4. Click Create Game.

The Create New Game window with the Game System list open, showing Cinder with RollQuest's own systems, Brightline and Tidewrack among them.

Choose carefully: once the game has a character sheet, its system can't be changed. See Game systems.

9. Make a character

  1. Click Character Sheets on the tool rail, then New Sheet. Choose Build your own, type a Title and click Create Sheet. The builder opens.
  2. On Roll Characteristics, click Open Ability Allocator. The Set Ability Scores window opens. Roll, give each number to Grit or Wits, and click Confirm, then Next.
  3. On Choose an Archetype, click Warden. The builder adds it and finishes.

If the builder opens on Roll Characteristics, that's because the sheet already has its name.

A new Cinder sheet: the builder's Roll Characteristics step, Open Ability Allocator, roll, assign Grit and Wits, Confirm, Next; Choose an Archetype shows Warden and Scholar only; click Warden; the sheet opens with Armour 2.

Test it

The checker catches mistakes in names and references: every formula, effect, damage type, kind of item, rest, builder step and condition is checked when you click Check this pack. It can't see how the system plays. Run through these by hand, the same checks Ashfall's own tests make:

Check How With Cinder you should see
The builder Make a character, as above. Three steps to the end. Choose an Archetype offers Warden and Scholar, nothing else.
What the character holds Open the sheet. The archetype on the Items page; a Warden's Armour reads 2, a Scholar's Wits is 10 higher.
The sheet Look at the Form View. Wounds and Armour at the top, with no hit points or armour class; blocks for Characteristics, Skills and Vitals.
A test Click Grit. A d100 against Grit, reading Success or Failure.
Damage In the vitals tile, type 5, choose Cutting, click Damage. A Warden takes 3 Wounds: Armour took 2.
The life meter On a Warden with no Wounds, type 9, choose Cutting, click Damage. Wounds stop at 6 and the token is marked Broken.
Healing Type 1, choose No type, click Heal. Wounds drop to 5 and Broken is lifted.
The rest Spend a Fate point with −, then click Rest. The preview reads "Fate returns and 2 Wounds close." Confirm it: Fate is full again and Wounds drop by 2.
The board Put the character's token on the board, right-click it, choose Edit Character. On the board lists Wounds and Armour with a switch for the GM, the player and the other players. Turned on, Wounds show as pips.
Damage on the board Right-click the token, choose Apply Damage. The type list offers No type and Cutting.
Enemies Open a token's Stat block, then New creature. Vitals offers Wounds and Armour.
A player Invite a second account of yours to the game (see Invite players), sign in with it and make a character. The same as above, with nothing refused.
A Warden's Cinder sheet: 5 Cutting gives Wounds 3; healed back to 0, 9 Cutting fills Wounds at 6; with the sheet closed, the token shows Broken and its Conditions window has Broken ticked; Heal 1 with No type gives 5 and Broken goes.

The damage numbers are the real results of Cinder's rules: Armour 2 against 5 Cutting leaves 3 Wounds; 9 Cutting would be 7, capped at 6.

Add-on packs for your system

An add-on pack adds items to a system without touching its rules, the way Ashfall's gear and archetypes sit in packs of their own. For a system of yours, the add-on names your base pack in derivedFrom, and it may use everything the base declares: its kinds of item, value fields, damage types and conditions. Here is a third archetype for Cinder:

{
  "name": "Cinder Archetypes",
  "slug": "cinder-archetypes",
  "system": "Cinder (d100)",
  "category": "addon",
  "derivedFrom": "cinder-core",
  "version": "0.1.0",
  "license": "USER_HOMEBREW",
  "description": "A third archetype for Cinder.",
  "items": [
    {
      "kind": "inventoryItem",
      "key": "ARCH_SMITH",
      "spec": {
        "subType": "archetype",
        "name": "Smith",
        "meta": {
          "role": "Maker",
          "grants": "+10 Grit",
          "description": "You kept the forge lit through the long dark."
        },
        "actions": [],
        "effects": [
          {
            "id": "ARCH_SMITH_GRIT",
            "target": "GRIT",
            "op": "add",
            "value": 10,
            "condition": "",
            "trigger": ""
          },
          {
            "id": "SMITH_REST_FATE",
            "target": "POOL(\"fate\")",
            "op": "reset",
            "value": "",
            "condition": "",
            "trigger": "longRest"
          },
          {
            "id": "SMITH_REST_WOUNDS",
            "target": "VF(\"WOUNDS\")",
            "op": "increment",
            "value": -2,
            "condition": "",
            "trigger": "longRest"
          }
        ]
      }
    }
  ]
}
  • derivedFrom: "cinder-core" names your base pack by its slug. RollQuest looks for it among its own systems first, then among your own packs (the oldest one with that slug, if you have copies). Nobody else's pack can be a parent.
  • system is the same text as the base pack's.
  • category is addon, and an add-on carries no systemRules: the rules stay in the base.

To use it:

  1. Create the base pack first, then the add-on. Checked before its parent exists, the add-on is refused: derivedFrom names pack "cinder-core", which is neither a curated game system nor one of your own packs.
  2. Create the add-on the same way as Cinder, choosing Cinder (d100) under Which system are you extending?.
  3. In your Cinder game, bind it to the table. Under Where should its items appear?, tick Choose an Archetype, so the builder offers the Smith beside the Warden and the Scholar. The window warns that this step lets a player hold only one choice. Left unticked, the pack's items are still offered to players with + From pack on their sheets.

See Put your pack on a table for binding, routing and moving a table to a new revision. Your base pack can also name an add-on in one of its own builder steps once the add-on exists, as the gift step in Creation shaping does.

What a system of your own can't do yet

  • Packs come in order. An add-on can only be created once its base exists, and a base step that names an add-on can only be added once the add-on exists. Create the base, then the add-on, then edit the base.
  • No sheet layout or artwork. Sheets use the Form View, built from your value field groups. Don't declare mobileGroups (named Form View tabs) unless it lists every group: a field whose group isn't listed disappears.
  • Private to you. Nobody else can choose your system for a game of theirs, and it can't be published or sold.
  • Fixed once a game uses it. A game's system can't change after its first character sheet. Changes to the pack reach new sheets; sheets already made keep what they were built with.
  • Only the pieces RollQuest has. Much of what used to need a change to RollQuest is now data any system can declare: the next section lists every piece. Something none of them covers, such as a new kind of token display or a new damage mode, still needs a change to RollQuest itself.

Design rules worth copying from Ashfall

  • Give every characteristic a calculationId, so it's clickable, and make a skill a value field whose value is a formula: of its characteristic, or a base plus the points bought for it.
  • List exactly your characteristics in abilityScores.fields, and a resetTo the dice are unlikely to roll on every field.
  • Route every damage type you declare, name token bindings (not value fields) in the pipeline, and mark the life meter vitality in both.
  • Put rest effects on the item every character has exactly one of, and match their trigger to a rest button.
  • Keep a one-pick step's pack to the things it chooses between, and put the rest in add-on packs.
  • Set maxLevel: 1 and maxPowerTier: 0 unless your system has levels and spells.
  • Never rename an id or key once characters exist: sheets find things by them.

Engine pieces a system can declare

RollQuest's own systems are built from pieces any base pack can switch on: named results, buttons under a result, dice pools, point budgets in the builder, tracks, tiered statuses, clocks and more. Each one is data: declare it in your base pack, and the sheets, the builder and the board do the rest. Leave it out, and nothing of it shows.

The examples come from three made-up systems, and each is part of a complete pack that passes Check this pack:

  • Cinder, from this page, grown with a weapon, three charms, a Nerve meter, a Doubt track and Scars, a Sneak skill and a gift step.
  • Mossback, a d6 pool system: roll as many d6s as a knack's dots and read the highest.
  • Kestrel, a d20 system where a hit is resisted rather than counted off hit points.
Piece What it does at the table Where it goes
Roll outcome bands Names what a roll landed on, such as Hard success systemRules.rollOutcomes
Follow-ups Buttons under a result: reroll, or spend points to pass a band's or an item's followUps
Roll tags and marks A choice made with a roll, and a field marked after it systemRules.rollTags, rollMarks
Track controls A number drawn as dots or boxes a value field's control
Track overflow A full track resets and marks another systemRules.trackOverflow
Token pips How many dots a token meter draws a token binding's maxPips
Meter rules What one big hit, an empty meter or a held condition does the damage pipeline, roundChecks, tables, contests
Category roles Which kinds of item are weapons or spend power slots a category's flags
Weather What rain or mist does to a roll systemRules.environmentEffects
Ready-made macros The macros a game on your system is offered systemRules.macroTemplates
Point-budget steps Points to spend in the builder a pointBudget builder step
Creation shaping A pick step that depends on an earlier pick filterBy, maxPicks, applyAtCreation
Dice pools Rolling as many dice as a field says a formula, and systemRules.diceRules
The pool builder A short build step before a pool roll systemRules.poolBuilder
Tag sets Tags the players write, worth dice systemRules.tagSets
Tiered statuses A condition with a tier from 1 up systemRules.statusTiers
Clocks and party sheets Progress clocks, and a sheet the whole party plays systemRules.clocks, partySheets
Defences by range One defence against melee, another against ranged a defence's appliesTo, systemRules.defenseBase
Resist damage A hit becomes a check that lands conditions a resist damage step
Ranked items Items a player builds by rank and modifiers an item's meta.ranked, systemRules.rankedItems

Every piece is checked when you click Check this pack: a key it doesn't know, a field ID in the wrong case or a test it can't read is refused with a code that names the piece, such as ROLL_OUTCOME_INVALID, so a typo can't make a piece that quietly never works. See When a pack is refused.

Roll outcome bands

A band names what a roll landed on. After a roll, the result that pops up and the chat card show the band's label, such as Hard success, in place of a plain Success or Failure. Cinder's, with crit set to read them:

"crit": { "mode": "band" }
"rollOutcomes": {
  "bands": [
    {
      "id": "blaze",
      "label": "Blaze",
      "tone": "crit",
      "when": { "passed": true, "rolled": { "op": "<=", "value": 3 } }
    },
    {
      "id": "smother",
      "label": "Smothered",
      "tone": "fumble",
      "when": { "passed": false, "rolled": { "op": ">=", "value": 96 } },
      "followUps": [
        {
          "kind": "reroll",
          "label": "Burn Fate to reroll",
          "markLabel": "Fated",
          "cost": { "pool": "fate", "amount": 1 }
        }
      ]
    },
    {
      "id": "hard",
      "label": "Hard success",
      "tone": "success",
      "when": { "passed": true, "rolled": { "op": "<=", "ofTarget": 0.5 } }
    },
    { "id": "success", "label": "Success", "tone": "success", "when": { "passed": true } },
    {
      "id": "failure",
      "label": "Failure",
      "tone": "failure",
      "when": { "passed": false },
      "followUps": [
        {
          "kind": "reroll",
          "label": "Push your luck",
          "markLabel": "Pushed",
          "unless": { "tags": ["noPush"] },
          "onFailure": { "tone": "fumble", "note": "The push failed. The GM decides what it costs you." }
        },
        {
          "kind": "spendToTarget",
          "label": "Spend Nerve",
          "field": "NERVE",
          "unless": { "tags": ["noNerve"] }
        },
        {
          "kind": "reroll",
          "label": "Burn Fate to reroll",
          "markLabel": "Fated",
          "cost": { "pool": "fate", "amount": 1 }
        }
      ]
    }
  ],
  "tags": { "ATK_ROLL": ["noPush"], "GRIT_CHECK": ["noNerve"] }
}

With Grit 42, a Grit test reads: 1 to 3 Blaze, 4 to 21 Hard success, 22 to 42 Success, 43 to 95 Failure, and 96 to 100 Smothered.

Field What it does
bands The bands, in order. After a roll, the first band whose when holds names the result, so put the narrowest first and end with one that catches the rest.
id 1 to 40 letters, digits, _ or -, unique. Roll marks name a band by it.
label What the result says, up to 60 characters.
tone crit, success, partial, failure or fumble. It colours the result, and with "crit": { "mode": "band" } it decides the critical and fumble fanfare.
when The tests that must all hold (below). An empty when, {}, always holds.
steps How many degrees the band is worth, 0 to 20. A resist damage step reads it. See Resist damage.
cost A button that adds an amount to a field on the roller's sheet. See Dice pools.
followUps Buttons under the result. See Follow-ups.
tags Tags on rolls, by calculation ID or action tag, that a follow-up's unless reads.

The tests a when can make:

Test Holds when
passed true: the test passed. false: it failed.
rolled The rolled number compares as asked, such as { "op": "<=", "value": 3 }.
target The target number compares as asked.
margin How far the roll passed or failed by: on a roll-under test the target minus the roll, on a roll-over test the roll minus the target.
highest The highest die kept compares as asked. For dice pools.
faces { "value": 6, "atLeast": 2 }: at least 2 dice show a 6, counting dropped dice too (add "of": "kept" to count only the kept ones).
zeroPool true: the roll was a pool of no dice. false: it wasn't.
any, all A list of whens, any or all of which must hold.

A comparison is { "op", "value" } or { "op", "ofTarget" }, with op one of <, <=, =, >= and >. "ofTarget": 0.5 is half the target, rounded down. A roll with no target, such as =2d6+3, can only meet rolled, highest and faces, so make a band list for tests out of tests such as =1d100<=GRIT.

rollOutcomes can also hold a tick, which marks a skill's tick field each time a test of it succeeds, for improvement later: Ashfall's Tempering button reads these ticks.

Follow-ups: buttons under a result

A follow-up is a button under a result: the player presses it after seeing the roll. In the Form View the buttons sit above the result, with Keep result to put them away, and they stay until the next roll; an item's button carries the item's name. Cinder's Failure band offers three:

{
  "id": "failure",
  "label": "Failure",
  "tone": "failure",
  "when": { "passed": false },
  "followUps": [
    {
      "kind": "reroll",
      "label": "Push your luck",
      "markLabel": "Pushed",
      "unless": { "tags": ["noPush"] },
      "onFailure": { "tone": "fumble", "note": "The push failed. The GM decides what it costs you." }
    },
    { "kind": "spendToTarget", "label": "Spend Nerve", "field": "NERVE", "unless": { "tags": ["noNerve"] } },
    {
      "kind": "reroll",
      "label": "Burn Fate to reroll",
      "markLabel": "Fated",
      "cost": { "pool": "fate", "amount": 1 }
    }
  ]
}

With Fight 30, a roll of 34 is a Failure, and the result offers Push your luck, Spend Nerve and Burn Fate to reroll:

  • Push your luck rolls the same test once more. The new result is named with the markLabel, as Fight (Pushed), and offers no follow-ups of its own. If it fails too, the onFailure note shows on the result and the chat card in the fumble's colour.
  • Spend Nerve moves the roll onto the target, 34 to 30, and takes the difference, 4, off Nerve. It's offered only when Nerve holds enough.
  • Burn Fate to reroll rolls once more and spends 1 Fate. With no Fate left, the button is greyed out and says why.

unless hides a button on rolls that carry a tag: in Cinder's tags, attacks (ATK_ROLL) carry noPush and Grit tests carry noNerve.

Field What it does
kind reroll (roll the same test once more) or spendToTarget (pay points to move a failed roll onto its target).
label The button's words.
markLabel For a reroll: the word added to the new result's name.
cost For a reroll: { "pool": "fate", "amount": 1 } spends from a pool, or { "field": "NERVE", "amount": 1 } from a value field that holds a plain number. One or the other, never both.
faceFloor For a reroll: { "below": 11, "add": 10 } makes a rerolled die under 11 count 10 more.
onFailure For a reroll: { "tone", "note" }, a note shown when the reroll fails too.
field For spendToTarget: the value field to pay from. It must hold a plain number, not a formula.
unless { "tags": [...] }: hide the button on rolls carrying any of these tags.

An item can offer a follow-up of its own, in meta.followUps, on the rolls it names. This charm offers one reroll of a failed Fight test between rests:

{
  "kind": "inventoryItem",
  "key": "CHARM_LUCKY_COAL",
  "spec": {
    "subType": "charm",
    "name": "Lucky Coal",
    "maxUses": 1,
    "uses": 1,
    "meta": {
      "effect": "Once between rests, reroll a failed Fight test.",
      "followUps": [
        {
          "kind": "reroll",
          "label": "Rub the coal",
          "markLabel": "Lucky",
          "on": { "calcs": ["FIGHT_CHECK"], "tones": ["failure", "fumble"] },
          "uses": true
        }
      ]
    },
    "actions": [],
    "effects": [
      {
        "id": "LUCKY_COAL_REST",
        "target": "SELF.uses",
        "op": "reset",
        "value": "",
        "condition": "",
        "trigger": "longRest"
      }
    ]
  }
}

on.calcs names the tests it offers on and on.tones the results. "uses": true pays with one of the item's own uses, so the item needs maxUses; its reset effect gives the use back on a rest.

Roll tags and marks

A roll tag is a choice made with a roll that changes no dice, such as a stance. The roll menus offer one choice per group, a plain click rolls with the group's default, and the result and the chat card print the choice, such as Stance: Reckless. A roll mark adds to a field after a roll, when the roll carried a tag or landed a band.

"rollTags": [
  {
    "id": "stance",
    "label": "Stance",
    "default": "steady",
    "options": [ { "id": "steady", "label": "Steady" }, { "id": "reckless", "label": "Reckless" } ],
    "calculations": ["FIGHT_CHECK"]
  }
]
"rollMarks": [
  {
    "id": "reckless-doubt",
    "when": { "tags": { "stance": "reckless" } },
    "calculations": ["FIGHT_CHECK"],
    "field": "DOUBT",
    "amount": 1
  },
  { "id": "smother-doubt", "when": { "outcomes": ["smother"] }, "field": "DOUBT" }
]

A Fight test rolled Reckless adds 1 to Doubt, and any roll that lands Smothered adds 1 too.

Field What it does
rollTags[].id, label, options, default The group, its choices and the one a plain click uses. At most 8 groups; IDs 1 to 40 letters, digits, _ or -; labels up to 40 characters.
rollTags[].calculations The rolls that offer the group. Left out, every roll does.
rollMarks[].when tags ({ "stance": "reckless" }, or a list of choices) and outcomes (band IDs). Both may be given; then both must hold. It can't be empty.
rollMarks[].calculations The rolls the mark watches. Left out, every roll.
rollMarks[].field, amount The value field to add to, and how much: a whole number from -10 to 10, never 0, 1 if left out.

A mark applies once per roll, after the dice settle, on the roller's sheet. A reroll from a follow-up never marks.

Track controls: dots and boxes

control draws a value field as a track: "dots" for a rating, "boxes" for a track that fills up. Clicking a glyph sets the value to it, and clicking the highest filled one lowers the value by one.

{
  "kind": "valueField",
  "key": "DOUBT",
  "spec": {
    "id": "DOUBT",
    "label": "Doubt",
    "value": 0,
    "min": 0,
    "max": 6,
    "control": "boxes",
    "group": "vitals",
    "order": 4
  }
},
{
  "kind": "valueField",
  "key": "SCARS",
  "spec": {
    "id": "SCARS",
    "label": "Scars",
    "value": 0,
    "min": 0,
    "max": 3,
    "control": "dots",
    "group": "vitals",
    "order": 5
  }
}

Doubt shows six boxes and Scars three dots. A track needs whole-number min and max, max above min, and at most 20 steps between them; leave a longer range as a number. A field whose value is a formula draws its track filled and can't be clicked.

Track overflow

A track pushed past its maximum resets, and another field gains 1. The sheet's log says so, such as Doubt overflowed: Scars +1.

"trackOverflow": [ { "field": "DOUBT", "into": "SCARS", "resetTo": 0, "whenIntoFull": "clamp" } ]

Doubt at 5 that gains 2 resets to 0 and Scars gains 1: what went past the maximum isn't carried over. Landing exactly on the maximum fills the track without overflowing.

Field What it does
field The track. It must have a plain-number max.
into The field that gains 1.
resetTo What the track resets to. 0 if left out.
whenIntoFull "reset" (the default): reset anyway, and into stays full. "clamp": don't overflow; the track fills and stops, and the log says Doubt cannot overflow: Scars is full.

Overflow happens on changes the rules make, such as a band's cost or a roll mark. A click on a track can fill it but never overflow it.

Token pips

A pips token meter draws one dot per point up to 20. maxPips lowers that for one meter: above it, the meter draws as a bar with its "value/max" caption.

{
  "id": "wounds",
  "label": "Wounds",
  "kind": "pips",
  "source": "WOUNDS",
  "max": "WOUNDS_MAX",
  "role": "vitality",
  "maxPips": 8
}

Cinder's Wounds, out of 6, draw as six dots; if a character's Max Wounds ever went above 8, they'd draw as a bar. maxPips is a whole number from 1 to 20, on a pips binding only.

Meter rules: what one hit means

A damage step can say what one hit means for its meter, beyond the number. Cinder's Wounds step, with a threshold and a rule for a hit on a Broken character, then the round check, table and published fields they use:

{
  "stat": "wounds",
  "mode": "accumulate",
  "floorRole": "vitality",
  "thresholds": [
    {
      "singleHitAtLeast": { "ofMax": 0.5 },
      "apply": "bleeding",
      "prompt": "WITS_CHECK",
      "onFail": { "condition": "dazed", "table": "daze", "durationRounds": "1d4" }
    }
  ],
  "onHitWhile": { "condition": "broken", "apply": "dead" }
}
"roundChecks": [
  {
    "while": "bleeding",
    "prompt": "GRIT_CHECK",
    "onFail": { "damage": "1", "damageType": "cutting", "label": "Blood loss" }
  }
]
"tables": [
  {
    "id": "daze",
    "name": "Daze",
    "formula": "1d4",
    "entries": [
      { "text": "You drop whatever is in your off hand." },
      { "text": "You stagger one square back." },
      { "text": "Your ears ring: you hear nothing this round." },
      { "text": "You fall to one knee." }
    ]
  }
]
"scalingFields": ["GRIT", "WITS", "FIGHT"]

At Cinder's Max Wounds of 6, one hit of 3 Wounds or more makes the character Bleeding and asks them a Wits test; failing it makes them Dazed for 1d4 rounds and rolls on the Daze table. While Bleeding, they test Grit at the end of each of their turns, and a failure deals 1 Cutting damage, which Armour soaks like any other. A hit that gets past Armour while they're Broken makes them Dead.

Field What it does
thresholds On a step: singleHitAtLeast (a number, or { "ofMax": 0.5 } for that share of the meter's maximum, rounded down), what lands (apply), and a test to ask (prompt) with what a pass or a failure lands (onPass, onFail). It counts the hit as it reaches the step, after Armour.
onEmpty On a draining step: { "apply": "lost" } when the meter reaches 0.
onFullWhile On a filling step: { "condition", "apply" } when the meter is full while the creature holds the condition.
onHitWhile { "condition", "apply" } for any hit that reaches the step while the creature already held the condition. The hit that breaks a creature never also kills it.
roundChecks { "while", "prompt", "onPass", "onFail" }: a test a creature holding while makes at the end of its turn.
tables { "id", "name", "formula", "entries" }: rollable tables, added to a game when it's created, or the first time a rule rolls one. A result names one with table.
scalingFields The value fields a token publishes for these tests to read. List every field your prompts and checks read.

A result (onPass, onFail) is a condition ID, or an object with any of condition, table, durationRounds (a whole number or a dice term such as "1d4"), damage or heal (dice and numbers only), damageType, meter and label. Every test the rules ask is a plain test: a calculation such as =1d100<=GRIT, never a total such as =1d20+GRIT. vocabulary["prompt.check"] is the word the prompt uses for it, such as Test.

Three more pieces belong with these. Contests let a defender answer an attack: a weapon whose meta.contest is "melee" offers the target Parry or Duck. Both rolls are judged into bands and the better band wins; equal bands go to the higher skill, then ties decides. A contest can only take a hit away.

"contests": {
  "responses": [
    { "id": "parry", "label": "Parry", "check": "FIGHT_CHECK", "ties": "defender" },
    { "id": "duck", "label": "Duck", "check": "WITS_CHECK", "ties": "attacker" }
  ],
  "against": { "melee": ["parry", "duck"] }
}
{
  "kind": "inventoryItem",
  "key": "WPN_POKER",
  "spec": {
    "subType": "weapon",
    "name": "Iron Poker",
    "meta": {
      "damage": "1d6",
      "description": "A hearth poker, bent from use.",
      "applies": [ { "kind": "damage", "formula": "=1d6", "damageType": "cutting" } ],
      "targets": { "max": 1 },
      "contest": "melee"
    },
    "actions": [
      { "id": "ATK", "label": "Swing", "formula": "=1d100<=FIGHT", "damageType": "", "tag": "ATK_ROLL" },
      { "id": "DMG", "label": "Damage", "formula": "=1d6", "damageType": "cutting", "tag": "DMG_ROLL" }
    ]
  }
}

A heal can name its meter, so it restores exactly that one:

{
  "kind": "inventoryItem",
  "key": "CHARM_ASH_SALVE",
  "spec": {
    "subType": "charm",
    "name": "Ash Salve",
    "meta": {
      "effect": "Closes 1d3 Wounds.",
      "applies": [ { "kind": "heal", "formula": "=1d3", "meter": "wounds" } ],
      "targets": { "max": 1 }
    },
    "actions": []
  }
}

A check as an action puts a test to every target, each target rolling it, and lands the result:

{
  "kind": "inventoryItem",
  "key": "CHARM_SOOT_POUCH",
  "spec": {
    "subType": "charm",
    "name": "Soot Pouch",
    "meta": {
      "effect": "Thrown into a face: the target tests Wits or is Dazed for a round.",
      "applies": [
        { "kind": "check", "check": "WITS_CHECK", "onFail": { "condition": "dazed", "durationRounds": 1 } }
      ],
      "targets": { "max": 1 }
    },
    "actions": []
  }
}

Category roles

A kind of item's role is its flag, never its ID. Three flags beside activeGate and activateVerb give a kind a role the engine reads. Cinder's two new kinds, added to its categories:

{
  "id": "weapon",
  "label": "Weapon",
  "displayMode": "row",
  "columns": [ { "key": "damage", "label": "Damage" } ],
  "flags": { "activeGate": "equipped", "activateVerb": "Swing", "baseWeapon": true }
},
{
  "id": "charm",
  "label": "Charm",
  "displayMode": "row",
  "columns": [ { "key": "effect", "label": "Effect" } ],
  "flags": { "activeGate": "equipped", "activateVerb": "Use" }
}
Flag What it does
activeGate When the item's effects count: trait always, equipped while equipped, prepared while prepared. Under carrying limits that count only active items, a kind with no gate is never carried.
activateVerb The word on the item's button, and in the chat line when it's used.
baseWeapon true: items of this kind can be a magic weapon's base, the weapon a "+1 weapon" asks for. 5e and 5.5e set it on weapon.
usesSlots true: using an item of this kind spends a power slot and counts against the spell limits, like a 5e spell. Using one also fires onCast triggers.
tableOffer "gm": items of this kind are the GM's to hand out, as Ashfall's horror is. + From pack at the top of a sheet offers them to the GM, on any plan, from every pack the sheet has, bound or not, and never to a player; a player who tries to add one another way is refused with Only the GM can add "..." to a sheet. Only the exact value "gm" counts, and the checker doesn't look at it, so check the spelling.

A kind called power or weapon gets no role from its name: give it the flag.

Weather

environmentEffects says what weather does to rolls, with a roll mode your system declares. Added to Cinder, mist gives Wits tests Misfortune:

"environmentEffects": [ { "preset": "mist", "op": "misfortune", "target": "WITS_CHECK" } ]

preset is a weather the GM can set on a scene, such as rain, mist, snow or ash. op is one of your own roll modes, and the target roll's first die must be the mode's die: Misfortune rewrites a d100, so it can only aim at a d100 test. minIntensity, a number, applies a row only from that intensity up. Weather rows belong in the base pack, with the rest of your rules.

Ready-made macros

macroTemplates lists the ready-made macros a game on your system is offered in the Macros panel. Each is an ordinary macro, written as steps, plus a templateId and a summary:

"macroTemplates": [
  {
    "templateId": "cinder-grit",
    "summary": "Roll d100 under Grit and post the result",
    "name": "Grit test",
    "shortcut": "grit",
    "showOn": { "shortcutBar": true, "tokenBar": true, "palette": true },
    "steps": [
      {
        "id": "grit-roll",
        "kind": "roll",
        "label": "Grit",
        "formula": "1d100",
        "mode": "normal",
        "versus": "@{GRIT}",
        "beat": "atMost",
        "critOn": null,
        "damageType": "",
        "saveAs": "grit"
      },
      {
        "id": "grit-card",
        "kind": "card",
        "layout": "check",
        "title": "Grit test",
        "lines": [],
        "show": ["grit"],
        "audience": "everyone",
        "buttons": []
      }
    ]
  }
]

In the macro editor's Text view, this one reads:

[[1d100]] label="Grit" vs=@{GRIT} beat=under -> grit
&{card:check} "Grit test" show=grit
end
  • templateId is lower case with hyphens, unique in the list. A macro made from it finds it again by it.
  • Every @{ID} must be a value field your system declares. A template that reads no sheet value may say "anySystem": true.
  • Write each template the way the macro editor saves it: the checker refuses a template it would have to change.
  • With no macroTemplates, a game on your system is offered the two general macros, Roll a d20 and Secret check. With "macroTemplates": [], it's offered none.

See Macros: what they are and The macro language.

Point-budget steps

A pointBudget builder step hands the player points to spend on value fields, such as skills, with limits. Cinder's Fight becomes a base plus bought points, =20+FIGHT_PTS, and Sneak =10+SNEAK_PTS. Then:

{
  "id": "spend_training",
  "label": "Spend Training",
  "prompt": "Spend your Wits as points on Fight and Sneak. No skill may go above 70 at creation.",
  "kind": "pointBudget",
  "dependsOn": ["pick_archetype"],
  "recordField": "TRAINING_SET",
  "budgets": [
    { "id": "training", "label": "Training", "formula": "=WITS", "spendOn": { "ids": ["FIGHT", "SNEAK"] } }
  ],
  "targets": { "field": "<ID>_PTS", "value": "<ID>", "costPerRank": 1 },
  "caps": [ { "max": 70 } ]
}

A character with Wits 35 gets 35 Training points to spend on Fight and Sneak, a point a rank, and neither may go above 70. Each rank is written to the field the target names (<ID>_PTS, so Fight's go to FIGHT_PTS), and the step is done once TRAINING_SET, an engineOnly field, is set to 1.

Field What it does
recordField A value field the step sets to 1 when it's answered. Usually engineOnly.
budgets Each { "id", "label", "formula", "spendOn" }. formula is worked out on the sheet, such as =WITS. formulaFrom: "archetype.points" lets the archetype picked bring its own formula, from its meta.points. spentElsewhere is a formula for points already spent outside the step.
spendOn What a budget may buy: from (a list on the picked item, such as archetype.skills), group (every field in a value-field group), ids, or "any": true; less except, plus also.
targets One rule or a list: field (where a rank is written, <ID> standing for the field), value (the field the caps read), costPerRank (may be a fraction, such as 0.5), and ids, group, min and max to narrow it.
caps Limits on a field's value: { "max": 70 } for every target, or one scoped to a group, to fields, to one field (with min), or a paired { "sum": [...], "max" }. A bound may be a number, a formula, or { "from": "archetype.standing.max" }.
presetFrom A suggested build the picked item carries, offered as Use the suggested build.

Ashfall's Spend Skill Points uses two budgets: the archetype's points for its eight skills and Standing, and Pursuits for anything but Ashlore.

Creation shaping

Three settings shape what a new character is offered:

  • filterBy on a pick step offers only the items whose meta field matches what an earlier step picked.
  • maxPicks on a pick step sets how many items a player takes there.
  • "applyAtCreation": false on an add-on keeps it off new characters: for a pack meant for a sheet the party shares, applied by hand.

Cinder's gift step offers only the gifts of the archetype picked, up to two:

{
  "id": "pick_gift",
  "label": "Choose Gifts",
  "prompt": "Take up to two gifts your archetype knows.",
  "kind": "pickFromPacks",
  "satisfiedBy": { "packSlugs": ["cinder-gifts"] },
  "dependsOn": ["pick_archetype"],
  "filterBy": { "field": "archetype", "fromStep": "pick_archetype" },
  "maxPicks": 2,
  "skippable": true
}

The gifts live in an add-on, cinder-gifts. Each gift names its archetype in meta.archetype; one with no meta.archetype is offered to everyone:

{
  "name": "Cinder Gifts",
  "slug": "cinder-gifts",
  "system": "Cinder (d100)",
  "category": "addon",
  "derivedFrom": "cinder-core",
  "version": "0.1.0",
  "license": "USER_HOMEBREW",
  "description": "Gifts for Cinder, each kept to one archetype.",
  "items": [
    {
      "kind": "inventoryItem",
      "key": "GIFT_WATCHFUL",
      "spec": {
        "subType": "charm",
        "name": "Watchful",
        "meta": { "archetype": "Warden", "effect": "+5 Wits." },
        "actions": [],
        "effects": [
          {
            "id": "GIFT_WATCHFUL_WITS",
            "target": "WITS",
            "op": "add",
            "value": 5,
            "condition": "",
            "trigger": ""
          }
        ]
      }
    },
    {
      "kind": "inventoryItem",
      "key": "GIFT_IRON_GRIP",
      "spec": {
        "subType": "charm",
        "name": "Iron Grip",
        "meta": { "archetype": "Warden", "effect": "+5 Fight." },
        "actions": [],
        "effects": [
          {
            "id": "GIFT_IRON_GRIP_FIGHT",
            "target": "FIGHT",
            "op": "add",
            "value": 5,
            "condition": "",
            "trigger": ""
          }
        ]
      }
    },
    {
      "kind": "inventoryItem",
      "key": "GIFT_MARGIN_NOTES",
      "spec": {
        "subType": "charm",
        "name": "Margin Notes",
        "meta": { "archetype": "Scholar", "effect": "+5 Sneak." },
        "actions": [],
        "effects": [
          {
            "id": "GIFT_MARGIN_NOTES_SNEAK",
            "target": "SNEAK",
            "op": "add",
            "value": 5,
            "condition": "",
            "trigger": ""
          }
        ]
      }
    },
    {
      "kind": "inventoryItem",
      "key": "GIFT_LAMP",
      "spec": {
        "subType": "charm",
        "name": "Shuttered Lamp",
        "meta": { "effect": "Any archetype may take it." },
        "actions": [],
        "effects": []
      }
    }
  ]
}

A Warden is offered Watchful, Iron Grip and Shuttered Lamp; a Scholar Margin Notes and Shuttered Lamp. If the player goes back during creation and picks another archetype, the gifts it no longer offers come off. fromStep must be a pick step this one depends on, directly or through others; fromField compares a meta field of the earlier pick instead of its name. maxPicks is a whole number from 1; at 1, a new pick replaces the one held.

Create cinder-gifts before you add the gift step to cinder-core: a step naming a pack that doesn't exist yet is refused with DECISION_UNKNOWN_PACK.

Dice pools

A dice pool rolls as many dice as a field says. The count goes in brackets, (CLIMB)d6, and may add fields: Mossback rolls a knack's dots plus the extra dice the pool builder adds, and keeps the highest:

{
  "kind": "calculation",
  "key": "ROLL_CLIMB",
  "spec": { "id": "ROLL_CLIMB", "name": "Climb", "formula": "=(CLIMB+POOL_EXTRA)d6kh1" }
},
{
  "kind": "calculation",
  "key": "ROLL_TALK",
  "spec": { "id": "ROLL_TALK", "name": "Talk", "formula": "=(TALK+POOL_EXTRA)d6kh1" }
}
"diceRules": { "zeroPool": { "count": 2, "keep": "kl1" } }

zeroPool says what a pool of no dice rolls: here, two dice keeping the lower. Without it, a term with no dice rolls nothing and adds 0. The brackets hold plain arithmetic over fields, no functions.

Mossback's bands read the highest die, and one carries a cost, a button on the result:

"rollOutcomes": {
  "bands": [
    {
      "id": "clear",
      "label": "Clear",
      "tone": "crit",
      "when": { "zeroPool": false, "faces": { "value": 6, "atLeast": 2 } }
    },
    { "id": "done", "label": "Done", "tone": "success", "when": { "highest": { "op": ">=", "value": 6 } } },
    {
      "id": "messy",
      "label": "Messy",
      "tone": "partial",
      "when": { "highest": { "op": ">=", "value": 4 } },
      "cost": { "field": "STRAIN", "formula": "=1", "label": "Take {n} Strain" }
    },
    { "id": "botched", "label": "Botched", "tone": "failure", "when": {} }
  ]
}

Two sixes are Clear, a six is Done, a four or five is Messy and offers Take 1 Strain, and anything less is Botched. A cost is { "field", "formula", "label" }: the formula is arithmetic over HIGHEST (the highest kept die), ROLLED and value fields, {n} in the label shows the amount, and calculations limits it to some rolls. Only the GM and the roller see the button on the chat card, and it can be pressed once.

The pool builder

With a pool builder, a roll in its calculations opens a short build step before the dice are thrown: the player ticks tags and extras, and the builder adds that many dice for this one roll.

"poolBuilder": {
  "base": "field",
  "extraDiceField": "POOL_EXTRA",
  "calculations": ["ROLL_CLIMB", "ROLL_TALK"],
  "tagCap": 2,
  "spend": { "label": "Burn", "face": 6, "max": 1 },
  "flaw": { "dice": -1, "marksTrack": "wear" },
  "chips": [
    {
      "id": "push",
      "label": "Push",
      "dice": 1,
      "cost": { "field": "STRAIN", "amount": 1 },
      "description": "One more die. Take 1 Strain."
    },
    {
      "id": "help",
      "label": "Help",
      "dice": 1,
      "helper": {
        "cost": { "field": "STRAIN", "amount": 1, "label": "Take {n} Strain for helping" },
        "prompt": "Who helps?"
      }
    },
    { "id": "risk", "label": "Risk", "dice": 1, "note": { "prompt": "What could go wrong?" } }
  ]
}

The builder shows the Base (the knack's own dots), Tags from the character's tag sets, the tag set's flaw, any Statuses worth dice, and Extras (the chips), then Roll or Cancel.

Field What it does
base "field": the rolled knack's own value is the base dice.
extraDiceField The value field the builder sets for one roll; the calculation must read it, as Mossback's =(CLIMB+POOL_EXTRA)d6kh1 does.
calculations The rolls that open the builder. Every other roll rolls at once.
tagCap The most dice tags may add, 0 to 6.
spend { "label", "face", "max" }: a tag can be spent instead, as a die fixed at face, and can't be used again until it's recovered. max is the most one roll may spend.
flaw { "dice", "marksTrack" }: invoking a tag set's flaw takes dice away and marks a track on that set.
chips Up to 8 extras, each { "id", "label", "dice" } plus one of: cost (the roller pays), helper (asks who helps, and they pay), or note (asks for a line of text the roll prints).

Tag sets

Tag sets are tags the players write on their own sheets, such as "knows every eddy", grouped into sets. The pack declares their shape; the sheet shows them in a block headed with the plural noun, with New Thread to start one, and the pool builder offers their tags as dice.

"tagSets": {
  "noun": { "one": "Thread", "many": "Threads" },
  "slots": 2,
  "minTags": 2,
  "maxTags": 5,
  "kinds": [
    { "id": "craft", "label": "Craft", "description": "Something you learned to do with your hands." },
    { "id": "bond", "label": "Bond", "description": "Someone or somewhere you belong to." }
  ],
  "tracks": [ { "id": "wear", "label": "Wear", "max": 3, "onFull": "flipKind" } ],
  "flaw": { "label": "Fault" },
  "books": [
    {
      "id": "boatwright",
      "kind": "craft",
      "name": "Boatwright",
      "tagQuestions": ["What do you build best?", "Whose tools did you inherit?"],
      "flawQuestions": ["What do you always leave unfinished?"]
    }
  ]
}
Field What it does
noun What one set and several are called.
slots How many sets a character carries, 1 to 8.
minTags, maxTags How many tags a set holds.
kinds 2 to 4 kinds; every set is one of them.
tracks Up to 4, each { "id", "label", "max", "onFull" }. onFull is "gainTag" (ask for a new tag), "flipKind" (rewrite the set as another kind) or left out.
flaw { "label" }: every set gets one flaw, written by the player.
books Prompts a player writes from: questions for the tags and the flaw. A book adds no tags of its own.

Tiered statuses

A tiered status is a condition with a tier, from 1 up: one row on the character, raised and lowered by the GM and the character's player, shown as its name and tier. A penalty status takes its tier's dice away from a roll; a bonus adds them.

"statusTiers": {
  "max": 3,
  "conditions": { "winded": { "polarity": "penalty" }, "inspired": { "polarity": "bonus" } },
  "tiers": [
    { "tier": 1, "dice": 0, "note": "Narrative only: the GM says what it changes." },
    { "tier": 2, "dice": 1 },
    { "tier": 3, "dice": 1, "incapacitates": true, "note": "Out of the scene." }
  ]
}

Winded 2 is worth a die less, and Winded 3 takes the character Out; lowering it lifts that again. Lowering a status below 1 removes it. The sheet shows them in a Statuses block, and the pool builder lists the character's statuses under Statuses, each with its dice, to tick into a roll.

Field What it does
max The highest tier, 1 to 6.
conditions The pack's own conditions that are tiered, each with a polarity, "penalty" or "bonus".
tiers One row for every tier from 1 to max: dice (0 to 6), a note, and incapacitates on at most one, which applies the first of incapacitatedConditions.

Gear can step a status down. The Tin Flask spends one use to take Winded down a tier, and only a penalty status:

{
  "kind": "inventoryItem",
  "key": "GEAR_FLASK",
  "spec": {
    "subType": "gear",
    "name": "Tin Flask",
    "maxUses": 2,
    "uses": 2,
    "meta": {
      "effect": "A swallow steps Winded down one tier.",
      "statusStep": -1,
      "statusStepPolarity": "penalty"
    },
    "actions": [],
    "effects": [
      {
        "id": "FLASK_REFILL",
        "target": "SELF.uses",
        "op": "reset",
        "value": "",
        "condition": "",
        "trigger": "rest"
      }
    ]
  }
}

meta.statusStep is a negative whole number; meta.statusStepPolarity limits it to penalty or bonus statuses. The uses come back through an ordinary reset effect on a trigger button.

Clocks and party sheets

Two features belong to a game rather than a sheet, and a game offers them only when its system switches them on:

"clocks": { "enabled": true },
"partySheets": { "enabled": true }
  • Clocks gives the game a Clocks panel, on the tool rail and in the GM's menu: named progress clocks of 4, 6, 8 or 12 segments that the GM and the players tick. A clock can be kept GM only, and Post to chat shares one.
  • Party sheets lets the GM tick Shared with the party when editing a sheet: every player in the game can then open and play it, and only its owner and the GM can rebuild it.

Leave either out, or set "enabled": false, and the game shows nothing of it. A change reaches a game the next time it loads.

A pack of rows for a shared sheet should stay off new characters. Mossback's crew rows sit in an add-on with applyAtCreation: false:

{
  "name": "Mossback Crews",
  "slug": "mossback-crews",
  "system": "Mossback (d6 pool)",
  "category": "addon",
  "derivedFrom": "mossback-core",
  "applyAtCreation": false,
  "version": "0.1.0",
  "license": "USER_HOMEBREW",
  "description": "Rows for the crew sheet the whole party shares.",
  "items": [
    {
      "kind": "inventoryItem",
      "key": "CREW_FERRY",
      "spec": {
        "subType": "crew",
        "name": "The Night Ferry",
        "meta": { "effect": "The crew owns a flat-bottomed ferry." },
        "actions": [],
        "effects": []
      }
    }
  ]
}

Bind it to the game, then put it on the shared sheet by hand: open that sheet and click Add Mossback Crews, in the Form View's Edit toolbar or, on a Canvas sheet, in Sheet options while you edit. Only the GM sees it. See Shaping character creation for the details.

Defences by range

A token binding with role: "defense" is the number an attack has to reach. appliesTo says which attacks it stops, so a system can check ranged attacks against one defence and melee attacks against another. defenseBase is added to every defence first: 10 when a defence is a rank, as in Kestrel, where an attack has to reach 10 + Dodge at range and 10 + Block up close.

"defenseBase": 10
"tokenBindings": [
  {
    "id": "dodge",
    "label": "Dodge",
    "kind": "number",
    "source": "DODGE",
    "role": "defense",
    "appliesTo": "ranged"
  },
  {
    "id": "block",
    "label": "Block",
    "kind": "number",
    "source": "BLOCK",
    "role": "defense",
    "appliesTo": "melee"
  },
  { "id": "tough", "label": "Tough", "kind": "number", "source": "TOUGH" },
  { "id": "hurt", "label": "Hurt", "kind": "badge", "source": "HURT" },
  { "id": "minion", "label": "Minion", "kind": "badge", "source": "MINION" }
]

An attack says its range on the item, in meta.attackRange, "melee" or "ranged":

{
  "kind": "inventoryItem",
  "key": "BASIC_PUNCH",
  "spec": {
    "subType": "power",
    "name": "Punch",
    "meta": {
      "attackRange": "melee",
      "targets": { "max": 1 },
      "applies": [ { "kind": "damage", "formula": "=MIGHT", "damageType": "force" } ]
    },
    "actions": [
      { "id": "ATK", "label": "Attack", "formula": "=1d20+MIGHT", "damageType": "", "tag": "ATK_ROLL" },
      { "id": "DMG", "label": "Rank", "formula": "=MIGHT", "damageType": "force", "tag": "DMG_ROLL" }
    ]
  }
}

An attack with no range, against a system whose defences are typed, goes to the GM to decide. Two typed defences can't answer the same range.

Resist damage

A resist damage step turns a hit into a check: the target rolls the step's dice plus its stat against dcBase plus the hit's rank, and how many Steps it fails by decides what lands, using the steps on your bands. Kestrel's bands:

"rollOutcomes": {
  "bands": [
    {
      "id": "win-2",
      "label": "Success (2 Steps)",
      "tone": "success",
      "steps": 2,
      "when": { "passed": true, "margin": { "op": ">=", "value": 5 } }
    },
    { "id": "win-1", "label": "Success (1 Step)", "tone": "success", "steps": 1, "when": { "passed": true } },
    {
      "id": "lose-1",
      "label": "Failure (1 Step)",
      "tone": "failure",
      "steps": 1,
      "when": { "passed": false, "margin": { "op": ">", "value": -5 } }
    },
    {
      "id": "lose-2",
      "label": "Failure (2 Steps)",
      "tone": "failure",
      "steps": 2,
      "when": { "passed": false, "margin": { "op": ">", "value": -10 } }
    },
    {
      "id": "lose-3",
      "label": "Failure (3 Steps)",
      "tone": "failure",
      "steps": 3,
      "when": { "passed": false }
    }
  ]
}

And its pipeline:

"damagePipeline": [
  {
    "types": ["force"],
    "steps": [
      {
        "mode": "resist",
        "stat": "tough",
        "dice": "1d20",
        "dcBase": 10,
        "critBonus": 5,
        "penalty": { "stat": "hurt", "condition": "hurt" },
        "ladder": [
          { "steps": 1, "apply": ["hurt"] },
          { "steps": 2, "apply": ["hurt", "staggered"] },
          { "steps": 3, "apply": ["down"] }
        ],
        "minion": { "stat": "minion", "anyFailure": ["down"] }
      }
    ]
  }
]

Punch a hero with Might 3: the hit's rank is 3, so they roll 1d20 + Tough, less 1 for each Hurt, against 13 (18 on a critical). Failing by 1 Step makes them Hurt; by 2, Hurt and Staggered; by 3 or more, Down. A target whose Minion field is above 0 goes Down on any failure. The target's own player rolls the check (the GM, for a creature nobody owns), and vocabulary["prompt.resist.dc"] names the number to beat.

Field What it does
stat The token binding added to the roll.
dice The dice the target rolls, such as "1d20".
dcBase Added to the hit's rank to make the number to beat.
critBonus Added on a critical hit.
penalty { "stat", "condition" }: a counter taken off the roll, raised by 1 each time a rung lands that condition.
ladder Rungs from 1 Step up, each landing its apply conditions. Past the last rung, the last one lands.
minion { "stat", "anyFailure" }: a target whose stat is above 0 takes anyFailure on any failure.

An item's own damage entry can carry a resist of its own, for a power that roots rather than hurts. It has no penalty counter, and without dice it rolls the dice the system's step declares.

Ranked items

A ranked item is one a player builds: they set its rank and pick modifiers with Build on its row, and the item's cost is worked out from them. The item says what it costs and where its rank is written; the system declares the modifiers.

{
  "kind": "inventoryItem",
  "key": "POWER_BOLT",
  "spec": {
    "subType": "power",
    "name": "Bolt",
    "meta": {
      "attackRange": "ranged",
      "targets": { "max": 1 },
      "applies": [ { "kind": "damage", "formula": "=1", "damageType": "force" } ],
      "ranked": {
        "costPerRank": 2,
        "minRank": 1,
        "maxRank": 10,
        "rank": 1,
        "offers": ["WIDE", "CLOSE", "QUIET"],
        "scales": [ { "action": "DMG" }, { "applies": 0 } ]
      }
    },
    "actions": [
      { "id": "ATK", "label": "Attack", "formula": "=1d20+AIM", "damageType": "", "tag": "ATK_ROLL" },
      { "id": "DMG", "label": "Rank", "formula": "=1", "damageType": "force", "tag": "DMG_ROLL" }
    ]
  }
}
"rankedItems": {
  "minPerRank": 1,
  "fractional": true,
  "minTotal": 1,
  "groupExtra": 1,
  "costField": "POWER_COST",
  "labels": {
    "rank": "Rank",
    "cost": "Cost",
    "costUnit": "points",
    "costUnitOne": "point",
    "raises": "Extras",
    "lowers": "Flaws",
    "group": "Power set"
  },
  "modifiers": [
    {
      "id": "WIDE",
      "label": "Wide",
      "cost": 1,
      "per": "rank",
      "description": "Hits up to four targets. No attack roll: each target resists.",
      "meta": { "targets": { "max": 4 } },
      "actions": { "ATK_ROLL": null }
    },
    {
      "id": "CLOSE",
      "label": "Close",
      "cost": -1,
      "per": "rank",
      "description": "Only works up close: the attack uses Might against 10 + Block.",
      "meta": { "attackRange": "melee" },
      "actions": { "ATK_ROLL": "=1d20+MIGHT" }
    },
    {
      "id": "QUIET",
      "label": "Quiet",
      "cost": 1,
      "per": "flat",
      "description": "Nobody notices you using it."
    }
  ]
}

Bolt costs 2 points a rank. At rank 4, its damage action and its damage entry both read 4. Adding Wide costs 1 more a rank and drops the attack roll: each of up to four targets resists instead. Close saves 1 a rank and makes it a melee attack on Might. Quiet costs 1 point however high the rank. What the sheet's ranked items cost together is written to POWER_COST, for a build budget to read.

Field What it does
meta.ranked.costPerRank Points per rank.
minRank, maxRank, rank The range, and the rank the item ships at. The row in the pack must be the build at that rank.
offers The modifiers this item may take.
scales Where the rank is written: an action ({ "action": "DMG" }), a damage entry by its position ({ "applies": 0 }) or an effect ({ "effect": id }), with times and plus to scale it.
rankedItems.modifiers Each { "id", "label", "cost", "per" }: per is "rank" or "flat". A modifier may change meta.attackRange and meta.targets, and replace an action's formula by its tag, or remove it with null. excludes lists modifiers it can't be taken with.
minPerRank, fractional, minTotal The cost rule. An item costs its points a rank, per-rank modifiers included, times its rank. When that comes to less than minPerRank a rank and fractional is true, one point buys several ranks instead: at minPerRank 1, 0 a rank buys 2 ranks a point and -1 buys 3. Flat modifiers are added after, and no item costs less than minTotal.
groupExtra Items sharing a group cost the dearest plus this for each other one, and only one is active at a time.
costField A value field the engine writes the total into.
labels The rank builder's words for rank, cost, raises, lowers and group.

Build on Ashfall · The pack file, field by field · Put your pack on a table · Effects: what an item can change · Choices and grants · When a pack is refused · Calculations and values · Game systems