Fields and formulas
The nine field types and their options, the whole formula engine (functions, text comparison) and how effects work.
Fields are what your sheet is made of: strength, health, money, class, conditions… Everything else in the builder (design, combat, levels, perks) works on top of them.
They are edited in the Fields tab.
Label and key
Each field has two names:
- Label: what the player reads, e.g. Strength.
- Key: the internal name, e.g.
fuerza. Formulas, effects, combat and sheet-workers use the key.
Changing the label breaks nothing. Changing the key does: any formula that used fuerza will stop finding it. Choose short keys, in lowercase and without accents, and leave them alone.
The nine types
| Type | What it is for |
|---|---|
| Number | Attributes, skills, values. The most common type |
| Counter | Uses, charges, ammo. The player raises and lowers it live |
| Derived (=) | Calculated by a formula. Updates automatically, not typed by hand |
| Text | Name, background, descriptions |
| Yes / No | A switch that can apply effects while it is on |
| Select | Closed options. Can be the character's class or archetype |
| Resource (current/max) | Health, mana, willpower: a bar with its maximum |
| Boxes (track) | Pips that get marked: humanity, stress, wounds |
| List / tags | Free tags: conditions, traumas, loose statuses |
In each field's row you choose the type, its default value and whether it is rollable. Expand it (the arrow) to see its advanced options.
Rollable
The Roll switch makes the field rollable: it appears as a button on the sheet and uses the system's die. It is not available on Yes/No, Text, Counter or List, since rolling them would make no sense.
Options by type
Number and Counter
- Fixed Minimum / Maximum.
- Min by formula / Max by formula: the limit is recalculated live. For example
3 + constitucionmeans "strength cannot go past 3 plus your constitution".
Things to keep in mind:
- The formula limit clips what is typed on the sheet and in creation.
- It is available in other formulas as
<key>_minand<key>_max. - Effects and bonuses are not clipped: a perk can take you above the maximum.
Shown as → Die (d4…d12): for games where the trait is a die (Savage Worlds, Cortex). The field stores the faces (4, 6, 8, 10, 12; 0 is untrained and 13 is d12+1) and the sheet draws it as a die with its own silhouette. The buttons step up and down one die at a time, and so does the upgrade shop. Formulas still read the number (pelear / 2). Untrained (value 0) is the label shown at 0, for example d4−2.
Derived
You write its formula and the value is calculated automatically. It cannot be edited by hand.
FLOOR((fuerza - 10) / 2)Use derived fields for modifiers, totals and combat values. If you're asking the player to type a number that could be calculated, make it derived.
Select
- Options: the closed list of values.
- Options from a database: instead of typing them, the options are the entries of one of the system's databases (for example the species of a pokédex) and they update when the database grows. With many options, the sheet and the creation wizard show a search box.
- Options = sheet fields: the player picks one of the fields you tick ("which attribute do you roll with?": Strength, Dexterity…). To use the chosen field's value, write
CAMPO(atributo)in a derived field, a roll, a block or a visibility condition. - Effects per option: when that option is chosen its effects apply. This is how classes are built: Warrior adds +2 to strength, Mage adds +2 to mana.
- Help per option: a descriptive text shown when choosing each option (what that class does, what that background means).
- Is class / archetype: marks the field as the character's class. Other parts of the builder (per-class designs, trees, progression) use it as a reference.
- Purchasable upgrade: locks the select in play so it can only be raised by spending experience, with the cost you define per step.
Yes / No
A toggle. It can carry effects that apply while it is on: Heavy armor → −2 to stealth while it is worn.
Resource (current / maximum)
Stores a current value and a maximum. The maximum can be fixed or by formula (fisico + 6). On the sheet it is drawn as a bar, and the player spends and recovers it without entering edit mode.
The maximum is available in formulas as <key>_max.
Boxes (track)
Like a resource, but as clickable pips: you define how many boxes there are (fixed or by formula) and the player marks them.
Damage types and levels (optional). Sometimes a box doesn't always mean the same thing. Health in Vampire, Werewolf or Mage is marked in three ways (a bruise, a cut and a burn don't heal the same way), and each level takes dice away from all your rolls. With Damage types and levels the track handles this automatically, including the parts players tend to forget:
- Sorts by severity. If you have two bruises and get stabbed, the serious wound goes to the top and the bruises move down. This matters because the penalty comes from the deepest box.
- Applies the penalty. You give each box a name and a number ("Wounded −2") and the sheet shows the current level at the top. In formulas it's
<key>_penalty, and it is negative, so a pool is writtendestreza + pelea + salud_penalty. When wounded, you roll fewer dice automatically. - Upgrades the type of what overflows. In Vampire, bashing damage that doesn't fit becomes lethal. The engine applies this and tells you, since it's the rule that turns a beating into a real wound.
A button builds the whole World of Darkness health (seven boxes, three types and the levels from Bruised to Incapacitated). Leave the block empty and the track works as usual.
In play, the player picks the type at the top and marks boxes; clicking a marked box heals it. If an attack from the map targets this field, the attack's damage type decides which one to mark.
List / tags
A list of free texts the player adds and removes. For conditions or statuses that don't need a field of their own.
Formulas
Formulas are written like in a spreadsheet and evaluated by our own safe engine (it doesn't run code; it is an interpreter with an allowlist).
Syntax
- Numbers and quoted text:
'ligera'or"ligera". - Operators:
+ - * / %and parentheses. - Comparators:
< <= > >= == !=. - Logic:
AND/OR/NOT(also&&,||,!). - Ternary:
condition ? ifTrue : ifFalse. - References: the name (the key) of any field.
Note that == and != compare as text if either side is not a number, so you can write conditions on choices:
IF(clase == 'mago', inteligencia, fuerza)
IF(arma == 'espada', 2, 0)Functions
| Function | What it does |
|---|---|
IF(cond, ifTrue, ifFalse) | One value or the other. IF(fuerza > 50, 2, 1) |
IFS(c1, v1, c2, v2, …, default) | Several chained conditions; the final loose value is the default. IFS(vida <= 0, 0, vida < 5, 1, 2) |
MIN(a, b, …) | The smallest. MIN(destreza, 80) |
MAX(a, b, …) | The largest. MAX(1, fuerza - 10) |
FLOOR(x) | Rounds down. FLOOR(destreza / 2) |
CEIL(x) | Rounds up |
ROUND(x) | Rounds to the nearest integer |
ABS(x) | Absolute value |
CLAMP(x, min, max) | Limits x between min and max. CLAMP(pv, 0, pv_max) |
AND(a, b, …) | True if all are met |
OR(a, b, …) | True if any is met |
NOT(a) | Inverts a condition |
XOR(a, b, …) | Holds only if exactly one holds |
BETWEEN(x, min, max) | Holds if x is between min and max (inclusive) |
SUM(a, b, …) | Sum |
AVG(a, b, …) | Average |
LOOKUP(database, name, field) | Value of a field of an entry in a database of the system, looked up by name. LOOKUP('pokedex', especie, 'atk') |
CATALOG_COUNT(database) | How many entries a database has. CATALOG_COUNT('pokedex') |
CATALOG_SUM(database, field) | Sum of a field across all entries of a database. CATALOG_SUM('objetos', 'peso') |
HAS_PERK('key', …) | 1 if the character has any of those perks, 0 otherwise. Lets a creation step appear depending on an earlier choice: HAS_PERK('origen_justiciero') |
CAMPO(name) | The value of the field with that name, by its visible name or its key. With a field selector: CAMPO(atributo) → the value of the chosen attribute |
POW(base, exponent) · SQRT(x) · MROUND(x, multiple) | Power, square root and rounding to a multiple. MROUND(peso, 5) |
COUNT(list) | How many items a list has. COUNT(LOOKUP('pokedex', especie, 'movimientos')) |
INCLUDES(list, value) | 1 if the list contains that value (ignoring case and accents). INCLUDES(LOOKUP('pokedex', especie, 'tipos'), 'Fuego') |
INDEX(list, n) | Item n, starting at 1. INDEX(LOOKUP('pokedex', especie, 'tipos'), 2) |
JOIN(list, separator) | Joins a list into text. JOIN(LOOKUP('pokedex', especie, 'movimientos.[].nombre'), ' / ') |
FILTER(list, field, value) | The items whose field equals that. COUNT(FILTER(LOOKUP('pokedex', especie, 'movimientos'), 'tipo', 'Fuego')) |
CONCAT(a, b, …) · LEN(x) · CONTAINS(text, piece) | Joins texts, measures a text or a list, and looks for a piece (ignoring case and accents). CONCAT(nombre, ' the ', apodo) |
UPPER(x) · LOWER(x) · TEXT(x, decimals) · SIGNED(x) | Upper case, lower case, number with decimals, and number with its sign to show modifiers ("+3"). |
COUNT_PERKS('category') | How many perks of that category the character has (or of those starting like it: 'Arte' counts 'Arte · básicas'). COUNT_PERKS('Bendición') < 5 |
Calculations with databases: LOOKUP, CATALOG_COUNT and CATALOG_SUM read the system's databases (the roster widget's datasets; see the Design → Roster and databases guide). The second argument of LOOKUP is usually a sheet field that stores the entry's name (for example an especie select). If the system doesn't have that database, they return 0.
Text from the database: LOOKUP also returns text. A Derived field with LOOKUP('pokedex', especie, 'tipo') shows "Fire", and a text block can mix it in: Type: {{LOOKUP('pokedex', especie, 'tipo')}} · ATK {{LOOKUP('pokedex', especie, 'atk')}}. So any widget or part of the sheet can show any column you want from the chosen entry.
Lists and objects inside a database (common when importing a JSON: a species' evolutions are a list, its height an object with feet and metres) show as readable text, and in the database editor you click them to edit them as JSON. To pick out a piece, the column accepts a path: LOOKUP('pokedex', especie, 'altura.metros'). A text block can also loop through a list and read each item: {{#each LOOKUP('pokedex', especie, 'movimientos')}}- {{this.nombre}} ({{this.rango}}){{/each}}. A number goes into ONE element ('movimientos.0.nombre', the first) and [] into all of them: LOOKUP('pokedex', especie, 'movimientos.[].nombre') is the list of every name ("Tackle, Growl…").
They work in all formulas: derived fields, formula limits, effects and also those of a roll (the threshold, the bonus, how many dice are rolled, how many each dice group contributes and the condition of a post-roll decision). Mind the shape of the data: an entry's values live inside its columns, so the third argument is the name of the column of the database.
A real-world example from Mutant Year Zero, where the weapon's dice come from its database and are drawn in its color:
LOOKUP('armas', arma, 'dados') → how many dice the weapon group contributes
LOOKUP('armas', arma, 'bono') → what it adds to the totalThere is also the special variable field_value: in roll thresholds it means "the value of the field itself".
Examples
FLOOR((fuerza - 10) / 2) Classic modifier
constitucion + nivel * 2 Health per level
CLAMP(pv, 0, pv_max) Never below 0 nor above the maximum
IF(clase == 'mago', 3, 1) Depends on the class
MAX(destreza, inteligencia) The better of two
IFS(nivel >= 10, 4, nivel >= 5, 2, 1) Step by levelWith blocks. The formula editor (the ƒ icon next to any formula) has three tabs: Text, Blocks and Nodes. In Blocks the formula is built from pieces: fields, operations, rounding, larger or smaller, «if… then… else», look up in a database, count or sum a column, have a perk or count those of a category, and «the field named…». Every function in the table below has its piece. The ones that take several inputs have no limit: «if… else if…», «and», «or», «only one of», «sum of», «average of», «the larger of»… always leave a free slot at the end and, once you fill it, another one appears. All three tabs edit the same formula; anything that can't be shown as pieces stays as a text piece.
With nodes. The same formula as a Blueprint: each piece is a node and values travel along wires, left to right, to Result. Numbers are typed inside the input that receives them as long as no wire reaches it, and a field used several times is a single node with several wires. Pieces are added from the left column (click or drag); a new wire into an input replaces the old one; Delete removes the selection. Anything that doesn't reach Result is dimmed and doesn't count. The written formula is always shown under the canvas. Ctrl+Z undoes and Ctrl+Shift+Z (or Ctrl+Y) redoes; Ctrl+C and Ctrl+V copy and paste the selection, and Ctrl+D duplicates it. The same three buttons sit in the canvas controls column.
What each piece is worth. Every node shows in its header what it is worth right now with the Try with values (like Unreal's debugger), so you can see at which step a formula goes wrong. In Blocks, select a piece and its part of the formula and its value appear below.
Custom functions. In the Functions section you give a name and parameters to a formula you repeat, e.g. BONUS(x) = FLOOR((x - 10) / 2), and use it in any formula of the system: BONUS(strength) + 2. It appears in the editor's help, as a System function piece in Blocks and Nodes, and as a node in the Map. Change its body and every formula that uses it changes. Its parameters only exist inside it, even if they share a field's name.
If a formula references a field that doesn't exist, that slot is worth 0 (it doesn't break the sheet). The editor shows a preview of the result as you type.
Effects
An effect changes the value of another field. Operations:
| Operation | What it does | Example |
|---|---|---|
| Add + | Adds (or subtracts with negatives) | +2 to Strength |
| Subtract − | Takes away | −2 to Agility while poisoned |
| Set = | Imposes a value | Speed = 0 while grappled |
| Multiply × | Multiplies | Speed ×0.5 in heavy armor |
| At least ≥ | Keeps the higher of the two | Natural armor: AC at least 13 |
| At most ≤ | Keeps the lower of the two | Exhausted: Speed at most 10 |
They can be applied to fields of type Number, Derived and Counter. Resources and Boxes offer two targets: their (current) value and their (maximum), so you can raise the health maximum without touching current health.
Where they hook in
Effects appear in several places in the builder, always with the same editor:
| Where | When it applies |
|---|---|
| Yes/No field | While the toggle is on |
| Option of a select | While that option is chosen (classes, backgrounds) |
| Perks | While you have the perk |
| Trees | When you unlock that level of the branch |
| Levels | When you reach the level |
| Combat / statuses | While the condition lasts |
Since effects stack on the base value, the order is: field value → effects → derived formulas that use it.
Advanced: when, in what order and what stacks
The sliders button next to each effect's bin opens four more settings. If you leave them alone, the effect works exactly as before:
| Setting | What for |
|---|---|
| Only if | A formula: the effect only counts while it holds (rage > 0, hp < 5) |
| Priority | The order. Lowest goes first: multiply 10, add, subtract and set 20, at most 30, at least 40. A «set» at 50 goes after everything and nothing moves it |
| When it applies | Last applies the effect on top of computed values, derived ones included: «your Defense is at least 15» even when it comes from a formula |
| Does not stack with | A group name. Among effects in the same group on the same field only the highest counts: two shields don't add up, the best one does |
Effects on one sheet
What happens to a character during play (a spell cast on them, a wound, a blessing) does not belong to the system. The GM records it in GM panel → Effects on that sheet: name, effects and, optionally, how many rounds it lasts. It can be switched off without deleting it, rounds pass with the − button and, at zero, it stops counting.
Automations: when, if, then
In Automations (group The character) you program the sheet without code, as a List, as Blocks (snap-together pieces, like Scratch) or as Nodes (like Unreal's Blueprints). All three views edit the same rules: what you change in one shows up in the others.
In Nodes each rule is a When node with an execution wire (arrows) coming out of it: it links the actions in the order they happen, and an If or a Repeat opens its own branches ("then", "else"). Values reach each action through data wires (dots), from the same pieces as a formula; a number is typed straight into the input. Execution only connects to execution and data to data; an action that doesn't hang from any "When" is dimmed and does nothing. Each rule sits inside a frame with its name and its note: dragging the frame by its top moves the whole rule. Undo, copy, paste and duplicate work as in formulas.
A rule's note is for whoever edits the system (why it's there, what not to touch). In List it goes under the name; in Blocks it's the block comment (the "?"); in Nodes, the frame's text.
In List everything can be edited without switching views: an if… then / else and a repeat hold their own list of actions inside, and they nest. Apply an effect changes several things at once: one row per change (field, operation and value or formula), with its duration in rounds. In Blocks and Nodes the effect grows the same way: pick a field in the last row and another appears.
| Part | What's there |
|---|---|
| When | A field changes, goes up or goes down · on a roll (one field or any; formulas see tirada_total) · on rest · on gaining a perk · on level up · on character creation · with a button on the sheet · anything changes |
| If | A condition: compare fields and values, and, or, not, has the perk |
| Then | Add, subtract or set a value · roll dice and add or set the result · notify (with {{formula}} inside) · apply or remove an effect · grant or remove a perk · if / else · repeat · store a named value |
Values are built from pieces: field, number, operation, rounding, larger or smaller, look up in a database. Templates bring ready-made logic (level up, drop to zero, roll button, rest, critical, temporary status…): drag them in and pick the fields. Test runs the automations with sample values and marks which ones fire. Below, Step by step tells what each one did, in order: which condition was met or not, each round of a repeat, what it rolled and each field's value before and after.
They run on the sheet of whoever makes the change, so an «add» never applies twice even if the GM and the player both have the sheet open. One can trigger another; loops stop on their own after a few rounds.
Advantage and disadvantage (altered states)
Sometimes an effect shouldn't change a number but how you roll. The "Add advantage/disadvantage" button handles this, with three scopes:
| Scope | What for |
|---|---|
| Everything governed by \<attribute\> | The usual case for altered states: Exhausted puts every STR skill at disadvantage, and AGI or INT skills are unaffected |
| One skill | Only that roll |
| All rolls | No distinction |
The middle one saves the most work, and it needs some setup: on each skill, in Fields → (the skill) → Roll, set "Governing attribute". That tag changes nothing on its own. It lets a state say "disadvantage on everything governed by STR" in one line instead of listing Crafting, Swords, Axes, Spears… one by one. You set it once and it works for every state you add later.
Two things to know:
- They stack. Exhausted + Sick on the same attribute are two disadvantages (you roll three dice and keep the worst), not one.
- Advantage and disadvantage cancel out, as in D&D: if a state gives you disadvantage on STR and a perk gives you advantage on Swords, on Swords you roll normally.
Advantage means rolling more dice and keeping the best, not a +2. In pool systems (counting successes) it does not apply: there, more dice mean more successes, and keeping only one would throw away what you rolled.
The Sheet: fields as a spreadsheet
At the top right of Fields, List or Sheet. The Sheet shows each field on a row (folders as a title row) with its label, key, type, value or formula, minimum and maximum. It moves like a spreadsheet: arrows and Enter (Shift+Enter goes up).
- Sample and Result: type a test value in any field and the Result column shows what the sheet gives with it, computed by the real engine (a derived field that depends on it changes at once). The sample isn't saved.
- Paste from Excel or Sheets: copy a block of cells and paste it into any cell; it spreads from there in column order. Whatever goes past the last row becomes new fields, with the key taken from the label. The type is understood by its name ("Number", "Resource", "Derived"…) and a resource's maximum takes a number or a formula.
- Copy as table takes the list to a spreadsheet with the same columns.
Changing a key in the Sheet carries its uses along when saving, as in the List. Whatever doesn't fit in a cell (options, dice, upgrades, effects) stays in the List.
From Foundry. "Import JSON" also understands a Foundry system's template.json: the character's actor type becomes fields (one folder per block; { value, max } is a resource) and each item type becomes an empty database with its columns. Formulas and rules aren't in that file: add them afterwards.
Groups (folders)
You can group fields in folders to keep things manageable as the system grows, and nest them one level deep. This only organizes the editor: the sheet layout comes from the Design, not from the order of this list.
Common mistakes
"I changed the key and the formulas broke" That is expected. If you need to rename, first find where that key was used (formulas, effects, combat, workers).
"My derived field does not change" Check that the formula references keys and not labels: fuerza, not Fuerza (if the key is in lowercase).
"The max by formula will not let me go up with a perk" Limits clip what is typed, but effects can go past them. If your perk raises nothing, check that the effect targets the right destination ((maximum) and not (current)).
"An Edge lets a trait go above d12" Give the trait a max by formula (12 + tope_disparar) and have the Edge add to that counter. Advances and creation respect the formula cap, not just the fixed one.
"One language per row, each with its own die" Mark the skill as specializable: the player adds named rows and, if the field is a die, each row has its own die with steps. For each row to roll its own die, write the roll formulas with field_value instead of the field key.
"I want the player not to be able to touch this number" Make it Derived: it is calculated automatically and is not editable.
Next step
With the fields defined, the next step is placing them: continue with the Sheet design guide.