Research trees
This page shows how to give a game system of your own research trees: a value your characters earn, trees of nodes they spend it on, and what each node unlocks once it's paid for.
Research is a way for characters to grow between scenes. Your system names one number on the sheet as the currency, such as Lore, and lays out trees of nodes, each with a price. A player invests the currency in a node, a little at a time or all at once; when it's paid in full, the node is unlocked: its row goes on the sheet like any item, so its effects count and anything it grants arrives with it. Research is an engine piece any system can declare, not something one system owns. Ninefold, RollQuest's near-future system, uses it for five trees bought with Insight, and the Ninefold packs show a full example. This page builds a smaller one, the Charts of Driftglass, a made-up system about a salvage crew diving a drowned city. It's for the Game Master (GM) writing the pack; what players see is in Research.
Plan: homebrew is on Pro, with up to 20 homebrew packs.
Read A game system of your own first: research goes in a system's base pack, beside its value fields and rules. Every example on this page is part of the Driftglass pack, which passes Check this pack as it stands.
How research fits together
| Part | Where it goes | What it does |
|---|---|---|
| The currency | A value field in the base pack, such as LORE |
The number research spends. |
systemRules.research |
The base pack's rules | Names the currency, the block's label and the trees. Its presence switches research on. |
| Nodes | Inventory items with meta.research, in any pack of the system |
Each node sits in one tree, at a tier, with a cost and the nodes it needs first. |
| The research block | The sheet's Form View | One tab per tree, the nodes in columns by tier, and the Invest button. |
A system that declares no research shows none of it, and the checker refuses a node in such a system.
Declare research
Driftglass spends Lore, calls its research Charts, and has two trees:
"research": {
"currencyField": "LORE",
"label": "Charts",
"trees": [
{ "id": "currents", "label": "Currents", "order": 1, "description": "The water itself: tides, routes and how long you can stay down." },
{ "id": "machines", "label": "Machines", "order": 2, "description": "The pumps, gates and lamps the city left behind." }
]
}
This goes inside systemRules. The currency is an ordinary value field:
{ "kind": "valueField", "key": "LORE", "spec": { "id": "LORE", "label": "Lore", "value": 0, "min": 0, "max": 99, "group": "progress", "order": 0 } }
| Field | Needed | What it does |
|---|---|---|
currencyField |
Yes | The ID of the value field research spends, spelt exactly as declared: LORE, not lore. Its label is the word the block uses, as in Invest 2 Lore. |
label |
No | What the research is called: the heading of the block, and the name of its page in the Form View. Up to 40 characters. Without it, Research. |
trees |
Yes | 1 to 12 trees. With more than one, each is a tab. |
trees[].id |
Yes | 1 to 40 lower-case letters, digits, _ or -, different for each tree. Nodes name their tree by it. |
trees[].label |
Yes | The tab's name, up to 40 characters. |
trees[].description |
No | A line about the tree, shown with it. |
trees[].order |
No | A number that sorts the tabs, lowest first. Without it, the trees keep the order you list them in. |
Make the currency a plain number, as LORE is, not a formula. Research takes the cost off the number the sheet stores, and a formula has no stored number to take it from, so the checker refuses a formula currency: pointed at Max Noise, it says currencyField "NOISE_MAX" is a formula field (its value is "=3+WITS+NOISE_BONUS"), so it holds no number research could spend and every Invest would be refused -- make it a plain number field (value 0).
Characters earn the currency however your game says: the GM or the player types it on the sheet, an item's effect adds to it, or a recipe gives it as an output (see Recipes and crafting). Driftglass gives 1 Lore each time a diver strips a drowned pump at the bench.
Write a node
A node is an inventory item whose meta.research says where it sits and what it costs. The rest of the item is the reward: its effects, and the items it grants with autoInclude. Here are Driftglass's four:
{
"kind": "inventoryItem",
"key": "CHART_SLACK_WATER",
"spec": {
"subType": "chart",
"name": "Slack Water",
"meta": {
"tree": "Currents",
"effect": "+2 Max Hull",
"description": "You know when the tide turns and the water goes still. Your Max Hull is 2 higher.",
"research": { "tree": "currents", "tier": 1, "cost": 2 }
},
"actions": [],
"effects": [
{ "id": "CHART_SLACK_WATER_HULL", "target": "HULL_BONUS", "op": "add", "value": 2, "condition": "", "trigger": "" }
]
}
},
{
"kind": "inventoryItem",
"key": "CHART_DEEP_ROUTES",
"spec": {
"subType": "chart",
"name": "Deep Routes",
"meta": {
"tree": "Currents",
"effect": "+1 Salvage",
"description": "The flooded streets under the old market, mapped. Your Salvage is 1 higher.",
"research": { "tree": "currents", "tier": 2, "cost": 4, "requires": ["CHART_SLACK_WATER"] }
},
"actions": [],
"effects": [
{ "id": "CHART_DEEP_ROUTES_SALVAGE", "target": "SALVAGE", "op": "add", "value": 1, "condition": "", "trigger": "" }
]
}
},
{
"kind": "inventoryItem",
"key": "CHART_PUMP_LORE",
"spec": {
"subType": "chart",
"name": "Pump Lore",
"meta": {
"tree": "Machines",
"effect": "Plan: Barbed Tip",
"description": "How the drowned pumps were put together. You learn the Barbed Tip plan.",
"research": { "tree": "machines", "tier": 1, "cost": 3 }
},
"actions": [],
"effects": [],
"autoInclude": [ { "packSlug": "driftglass-core", "itemKey": "PLAN_BARBED_TIP" } ]
}
},
{
"kind": "inventoryItem",
"key": "CHART_QUIET_HANDS",
"spec": {
"subType": "chart",
"name": "Quiet Hands",
"meta": {
"tree": "Machines",
"effect": "+1 Max Noise; the Short Out trick",
"description": "You work the old wiring without waking it. Your Max Noise is 1 higher, and you learn Short Out.",
"research": { "tree": "machines", "tier": 2, "cost": 5, "requiresAny": ["CHART_PUMP_LORE", "CHART_DEEP_ROUTES"] }
},
"actions": [],
"effects": [
{ "id": "CHART_QUIET_HANDS_NOISE", "target": "NOISE_BONUS", "op": "add", "value": 1, "condition": "", "trigger": "" }
],
"autoInclude": [ { "packSlug": "driftglass-core", "itemKey": "TRICK_SHORT_OUT" } ]
}
}
meta.research, field by field:
| Field | Needed | What it does |
|---|---|---|
tree |
Yes | The ID of the tree it sits in, one systemRules.research declares, spelt exactly. |
tier |
No | Its column in the tree, a whole number from 1 to 9. Tier 1 is the first column. Without it, 1. |
cost |
Yes | How much of the currency unlocks it, a whole number from 1 to 999. A player can pay it in several goes. |
requires |
No | Item keys of nodes that must all be unlocked first. Deep Routes needs Slack Water. |
requiresAny |
No | Item keys of nodes of which at least one must be unlocked first. Quiet Hands needs Pump Lore or Deep Routes. |
With both lists, a node needs every requires node and one of the requiresAny nodes. Each key must belong to a research node of the same system, in this pack or one it can refer to, and nodes can't wait on each other in a circle.
The rest of the node is an ordinary item, and the research block reads it:
| Part of the item | Where it shows |
|---|---|
name |
The node's title. |
meta.description |
The text on the node's card. |
effects |
Listed under Gives, as the field's label and the change, such as Hull bonus +2. An effect on the node's own row (a target starting with SELF, such as its uses coming back at a rest) isn't listed, because it's how the row keeps itself, not something the node gives: say what such a node does in meta.description. |
autoInclude |
Listed under Unlocks, by the names of the granted items. |
requires, requiresAny |
Listed under Needs, each with a tick once it's unlocked. A requiresAny list is one line, such as One of Pump Lore or Deep Routes. |
Give the effects a target the sheet already works out, as Driftglass does with HULL_BONUS, a hidden field that HULL_MAX adds in: =8+GRIT*2+HULL_BONUS. Write the field's label the way players should read it, because Gives shows it.
The nodes' kind of item
Nodes are items of a kind your system declares in systemRules.categories. Driftglass's is chart:
{ "id": "chart", "label": "Chart", "displayMode": "row", "columns": [ { "key": "tree", "label": "Tree" }, { "key": "effect", "label": "Effect" } ], "flags": { "activeGate": "trait", "tableOffer": "gm" } }
"activeGate": "trait"keeps a node's effects on for as long as the character holds it."tableOffer": "gm"keeps nodes out of the players' + From pack: players unlock nodes through research, and only you can add one by hand. See Category roles for the flag.
What an unlock does
When the last of a node's cost is paid, RollQuest puts the node's row on the sheet, the same row + From pack would add:
- Its effects count by its kind's
activeGate: fortrait, at once. - Its grants arrive with it. Every
autoIncludeitem is added too, unless the sheet already holds it or its pack has retired it. Pump Lore hands the diver the Barbed Tip plan, ready in the workshop. - It counts as unlocked. Any node that names it in
requiresorrequiresAnycan now be bought. - Anything that asks for the item has it. A pool builder extra that's offered only to a sheet holding certain items, by
requires.itemKeys, is offered from now on: Driftglass's Quiet hands extra, one more die on a Wire roll, asks for Quiet Hands. See The pool builder.
Paying works like this:
- A player invests any whole amount from 1 up to whatever is smaller: what the node still needs, or the currency they hold. The amount is taken off the currency at once. An Invest that asks for more than the character holds, such as one pressed twice at once, is refused in the currency's own name: 4 Lore is needed and you have 3.
- What's paid so far stays on the sheet as the node's progress. A diver with 3 Lore can put all 3 into Deep Routes, which costs 4, and pay the last 1 after the next dive.
- A node is unlocked when the sheet holds its row, however the row got there.
- Prerequisites are read by item key. A key that names a node in the node's own pack is met only by that pack's row, so a node with the same key in another pack doesn't count. A key the node's own pack doesn't have, such as a base-pack node named from an add-on, is met by the row with that key.
- Anyone who can edit the sheet can invest, including the players of a sheet you've shared with the party.
As the GM you also see two buttons on each node, with a line under them that says what each will do before you press it:
- Grant unlocks it at no cost. On a node whose prerequisites aren't met, it unlocks it anyway, and the line names what it skips: Grant unlocks it without Slack Water. Otherwise it reads Grant unlocks it at no cost. On a node that's partly paid, it adds what is lost, such as The 2 Lore invested is not given back.
- Revoke on an unlocked node takes its row off the sheet: Revoke locks it again, and nothing spent on it is given back. Plan: Barbed Tip stays on the sheet. Items the node granted stay, as the line says: remove those by hand if you mean to. On a partly paid node, Revoke clears its progress: Revoke clears the 2 Lore invested, and it is not given back.
Neither asks you to confirm: the press does it.
Keep a node's grants simple. A granted item that asks the player a choice, delivers a bundle or is a class level can't arrive this way, so the checker refuses the node. Give Short Out a choice, and Quiet Hands is refused with this node grants "TRICK_SHORT_OUT", which needs a choice or a builder (its own choices, a bundle or a class kit), so unlocking the node would be refused at the table -- what a node grants arrives with no picker; grant an item that needs none, or leave this one for the catalogue. The checker reads the granted item only when it's in this pack or a pack this one builds on through derivedFrom; a grant from any other pack, such as another add-on of your system, is taken on trust. If that item does need a choice, the unlock is refused at the table with "...grants ..., which needs a choice or a builder and must be added from the catalogue", and nothing is spent.
A node you retire (see Remove or rename a row) leaves the block and can't be unlocked any more; characters who have it keep it, and still see it.
Where research shows on the sheet
- A system of your own, with no sheet layout: the Form View gets a page of its own for research, named by
label. Driftglass's is Charts. - A sheet layout: in design mode, click + Add a block and choose Research. See Customise a sheet layout.
The block heads itself with label. At the top, a line such as 5 Lore to spend (or No Lore to spend) says what the character holds. With more than one tree, the trees are tabs in order, each with a count such as 1 of 2 unlocked; the open tree's description sits under them. Each tree draws its nodes in columns by tier, each one Unlocked, Available or Locked. Selecting a node shows its card: its description, Gives, Unlocks, Needs, the cost and progress, and Invest with the amount, which starts at the most the character can put in. A tree with no nodes says Nothing to research here yet.

Nodes in an add-on pack
Nodes don't have to live in the base pack. An add-on pack built on your system can add nodes to the trees the base declares, and require the base's nodes. Here is a third Currents chart in a pack of its own:
{
"name": "Driftglass Charts",
"slug": "driftglass-charts",
"system": "Driftglass (d10)",
"category": "addon",
"derivedFrom": "driftglass-core",
"version": "0.1.0",
"license": "USER_HOMEBREW",
"description": "A third Currents chart for Driftglass.",
"items": [
{
"kind": "inventoryItem",
"key": "CHART_TIDE_TABLES",
"spec": {
"subType": "chart",
"name": "Tide Tables",
"meta": {
"tree": "Currents",
"effect": "+1 Grit",
"description": "You can read the tide tables carved on the harbour wall. Your Grit is 1 higher.",
"research": { "tree": "currents", "tier": 3, "cost": 6, "requires": ["CHART_DEEP_ROUTES"] }
},
"actions": [],
"effects": [
{ "id": "CHART_TIDE_TABLES_GRIT", "target": "GRIT", "op": "add", "value": 1, "condition": "", "trigger": "" }
]
}
}
]
}
- Create the base pack first. Checked before
driftglass-coreexists, this pack is refused: derivedFrom names pack "driftglass-core", which is neither a curated game system nor one of your own packs. - The tree,
currents, is the base pack's. An add-on can't declare trees of its own. - Bind the add-on to your game (see Put your pack on a table). Its nodes then appear in the Currents tab beside the base pack's. Unbound, they don't show.
When the checker refuses it
Every problem with research is refused with the code RESEARCH_INVALID, and the message names the exact place, such as items[26].spec.meta.research.cost. These are the checker's own words, with the values from a broken copy of Driftglass:
| What's wrong | What the checker says |
|---|---|
research isn't an object |
research must be an object { currencyField, label?, trees } |
A key it doesn't know, in research, a tree or meta.research |
unknown key "currency" -- research reads only currencyField, label, trees, so it would be ignored |
| The currency names no value field, or the wrong case | currencyField "lore" must name a value field in scope, compared exactly -- research spends from that field by its exact id |
| The currency is a formula field | currencyField "NOISE_MAX" is a formula field (its value is "=3+WITS+NOISE_BONUS"), so it holds no number research could spend and every Invest would be refused -- make it a plain number field (value 0) |
label empty, or too long |
label must be a non-empty string / label must be 40 characters or fewer |
| No trees, or more than 12 | trees must be a list of 1 to 12 { id, label, description?, order? } (this declares 0) |
| A tree that isn't an object | a tree must be { id, label, description?, order? } |
| A tree ID with capitals or spaces | tree id "Currents" must be 1-40 lower-case letters, digits, _ or - |
| Two trees with one ID | duplicate tree id "currents" |
| A tree with no label, or a long one | a tree label must be a non-empty string / a tree label must be 40 characters or fewer |
A tree's description isn't text |
description must be a string |
A tree's order isn't a number |
order must be a number |
meta.research isn't an object |
meta.research must be an object { tree, tier?, cost, requires?, requiresAny? } |
| A node in a system with no research | this item is a research node, and the system declares no research (systemRules.research), so it would never show and could never be unlocked |
| A node's tree isn't declared | tree "tides" names no tree systemRules.research declares (compared exactly) |
| A node's tree isn't text, with no research in reach | tree must name one of the trees systemRules.research declares |
| A cost of 0, a fraction or over 999 | cost 0 must be a whole number from 1 to 999 -- what unlocking the node spends |
| A tier outside 1 to 9 | tier 10 must be a whole number from 1 to 9 -- the column the node is drawn in |
requires or requiresAny isn't a list |
requires must be a list of research node item keys |
| An empty entry in the list | requires entries must be item keys |
| A node that needs itself | "CHART_DEEP_ROUTES" requires itself, so it could never be unlocked |
| A key that names nothing | "CHART_NOWHERE" names no item in this system |
| A key that names an item that isn't a node | "PART_BRASS" is not a research node (it carries no meta.research), so it can never be unlocked here |
| Nodes that need each other | CHART_SLACK_WATER -> CHART_DEEP_ROUTES -> CHART_SLACK_WATER form a cycle: each waits on the next, so none of them can ever be unlocked |
| A node grants an item that asks a choice, delivers a bundle or is a class level | this node grants "TRICK_SHORT_OUT", which needs a choice or a builder (its own choices, a bundle or a class kit), so unlocking the node would be refused at the table -- what a node grants arrives with no picker; grant an item that needs none, or leave this one for the catalogue |
The messages for requiresAny read the same, with requiresAny in place of requires. For the codes on other parts of a pack, see When a pack is refused.
What the checker doesn't check
| Rule | What happens if you break it |
|---|---|
| Keep item keys different across your packs. | A prerequisite naming a key that the node's own pack doesn't have is met by a row with that key from any pack, so a same-key row from another pack would count. |
| Grant from another add-on only items that need no choice. | The checker can't read such a grant, so it passes, and the unlock is refused at the table. |
Related
A game system of your own · Recipes and crafting · Choices and grants · Research · When a pack is refused
