Blueprint Format
Every key a blueprint, a calculation, an input, a step and a cases file can have.
Formats are v1 and will change.
A Blueprint
imports: [...]
tables: {...}
calculations: {...}
| Key | Holds | Required |
|---|---|---|
imports |
a list of paths (ending .yml or .yaml, or containing /, relative to this file) or names (a shipped blueprint, or whatever the host resolves) |
no |
tables |
name to a list of rows. Rows are mappings | no |
calculations |
name to a calculation. The first is the default | at least one |
Imported calculations and tables join this blueprint’s own. A name defined both here and in an import is a problem.
YAML is read with these differences from plain YAML:
- Decimals load as exact
BigDecimal, andYYYY-MM-DDloads as a date. - Keys such as
on,yes,no,y,n,off,trueandfalsestay text, never booleans. - Aliases (
&name,*name) are refused.
A Calculation
| Key | Holds | Required |
|---|---|---|
inputs |
name to an input | no |
steps |
a list of steps, run in order | yes |
outputs |
a list of the names it hands back: inputs or steps | yes |
A calculation that another runs declares every value it reads as an input, including ones its parent provides, and hands back only its outputs.
An Input
weapon: {type: string, one_of: {table: weapons, column: weapon}}
shots: {type: list, default: []}
| Key | Holds |
|---|---|
type |
number, string, date, boolean or list. Required |
one_of |
a list of words, [regular, hard, extreme], or a table column, {table: weapons, column: weapon} |
default |
the value when facts, a row or the values before a run leave it out. Makes the input optional. Must have the input’s type |
A Step
- name: dealt
formula: MAX(0, MIN(rolled, DICE_MAX(dice)) - armour)
Every step has a snake_case name and exactly one step type key. A step can use the calculation’s
inputs, the blueprint’s tables and any earlier step by name.
formula
formula: EXPRESSION
rules
rules:
pick: first # first, highest or all
rules:
- {name: extreme, when: difficulty = "extreme", value: 5}
- {name: regular, when: true, value: 1}
| Key | Holds |
|---|---|
pick |
first: the first match in order. highest: the largest value. all: every match as {name: value} |
rules |
a list of {name, when, value}. when is a condition; value is a formula, and a rule without one yields its name |
lookup
lookup: {table: prices, where: {zone: zone}, value: price, on: travel_date}
| Key | Holds | Required |
|---|---|---|
table |
a table, a list input, or an earlier step that is a list of rows | yes |
where |
column to the name of the value it must equal. {} matches every row |
yes |
value |
the column to take. Without it, the whole row | no |
on |
the name of a date. Rows need a from date; those with the latest from on or before it win |
no |
band |
{by: name, below: column}: the row whose column is the smallest above the value. A row without one covers everything above |
no |
run
run: {calculation: sanity_roll, over: losses, position: roll, carry: {sanity: sanity_after}, earlier: before}
| Key | Holds |
|---|---|
calculation |
the calculation to run. Required |
over |
a list input, table or earlier list to run over, once per row. Without it, the run happens once |
with |
input name to a formula: the calculation sees each input as that formula. On a run over a list it is worked out per row |
position |
a name: each row sees its place, 1 for the first |
carry |
{value: output}: each row after the first sees the previous row’s output as value |
start |
{value: formula}: what the first row sees for a carried value |
earlier |
a name: each row sees the rows already worked out |
update |
true: each row keeps its own columns and the outputs replace or add to them |
carry, start, earlier and update apply only to a run over a list.
repeat
repeat: {calculation: combat_round, position: round, carry: {standing: standing_after}, until: fight_over, limit: 50}
| Key | Holds |
|---|---|
calculation |
the calculation to run. Required |
times |
how many rows: a number or a formula |
until |
an output: stop after the first row where it is true. Needs limit |
limit |
the most rows until may run |
position, carry, start, earlier, with |
as for run |
Give times, or until and limit.
sum
sum: {output: damage, of: wounds}
| Key | Holds |
|---|---|
output |
the output to add up |
of |
an earlier run over a list, or repeat |
group
group: {of: sightings, by: [creature], sum: [loss]}
| Key | Holds |
|---|---|
of |
an earlier run over a list, or repeat |
by |
the outputs to group by |
sum |
the outputs to add up in each group |
compare
compare: {steps: [fighting, dodge], pick: highest}
| Key | Holds |
|---|---|
steps |
earlier steps |
pick |
highest |
Custom steps
A custom step type, which the licensed engine can add, takes the keys it declares. See Custom Steps.
Reading Values in Formulas
| Write | Reads |
|---|---|
name |
an input, a table (as a list of rows), or an earlier step |
row.column |
a column of a row, such as one a lookup took whole |
run.output |
an output of a run once |
list[0] |
an item of a list |
A run over a list, or a repeat, is a list of rows: read it with sum, group, or FILTER, PLUCK,
ANY and REDUCE in a formula, not as name.output.
A Cases File
- name: a club blow through a leather jacket
# 6 rolled on 1D8, less 1 armour = 5; 12 - 5 = 7
calculation: blow
facts: {weapon: club, rolled: 6, armour: 1, hp: 12}
expect: {dealt: 5, hp_after: 7}
| Key | Holds | Required |
|---|---|---|
name |
what the case shows | yes |
facts |
the facts to run | no, {} when left out |
expect |
outputs to compare: any subset | yes |
calculation |
the calculation, when not the first | no |
Numbers compare by value, rows on the columns the case lists, and lists item by item.
Facts
Facts are a JSON object (from the CLI) or a hash (from Ruby) of the calculation’s inputs by name.
Decimals are exact, and text written YYYY-MM-DD is a date. A fact that is not an input, a value of the
wrong type or outside one_of, and a missing input without a default are each a problem, and nothing
runs until there are none.
Problems check Reports
Each is prefixed with the calculation name, and the step where there is one.
| Problem | Example |
|---|---|
| YAML that cannot be read | The blueprint is not valid YAML: ... |
| an alias | The blueprint uses a YAML alias (&name and *name); write the values out instead |
| no calculations | The blueprint needs at least one calculation under calculations |
| an unknown input type | input weapon has type text; input types are number, string, date, boolean, list |
| an unknown input key | input day does not take required; inputs take type, one_of, default |
a bad one_of |
input zone takes one_of from column name of zones, which its rows do not have |
| a default of the wrong type | input off_peak has default "no_thanks", which is not true or false |
| a name that is not snake_case | step Base Price should be a snake_case name, such as base_price |
| no step type, or two | step price has more than one step type (formula, rules); give it one |
| an unknown or missing step key | step total sum_field does not take extra; it takes of, field, step r lookup needs where |
| a formula that cannot be read | step x has a formula that cannot be read: ... If it sits inside {...} in the YAML, quote it |
| an unknown name | step dealt uses armor, which is not an input or an earlier step |
| an unknown table or calculation | step quoted runs quote, which is not a calculation in this blueprint |
| a run missing an input | step posted runs postage, which needs kg; add it as an input or an earlier step, or give it with with |
a wrong name.output |
step shown uses posted.cost, but posted only outputs price |
| an output read from a list | step shown uses each.price, but each is a list of rows; use sum, or PLUCK, FILTER or ANY over it |
| a repeat without an end | step t repeats b without times or until; give it times, or until and a limit |
| missing outputs | needs outputs, the steps it hands back, such as outputs: [price] |
| an unknown output | output total is not an input or a step |
| an import problem | imports missing.yml, which does not exist, imports rates, which is not known |
| a name defined twice | table postage is defined here and in postage.yml |