Display hints
Display hints (display.schema.json) are optional presentation data under display in a rules document. They never affect calculations or saved state. Apps may ignore them, and hint-only changes need no upgrade script. Homebrew packs can override hints like anything else.
Groups
Section titled “Groups”A group is a set of keys with an ID, a label, a priority and members.
display: groups: - id: abilities priority: 0 - id: skills priority: 1 members: { type: field, where: "'skill' in item.tags" } - id: spellcasting priority: 1 showWhen: 'spellcasting.level > 0'labelis a translation key; the default isgroups.<id>.label.- A field joins a group by listing the group in its own hints (
fields.<id>.groups), or by matching the group’smembersquery. A query lets homebrew join automatically. - A field may belong to several groups, such as AC in both
combatandsummary. showWhenis a condition: the Spellcasting group hides itself for a fighter.
Group kinds
Section titled “Group kinds”A group’s kind says what it holds:
kind |
Members | members query |
|---|---|---|
fields (default) |
Fields | type: field, optional (fields may join by their own hints) |
actions |
The actions computeSheet lists, one member per listing (a weapon attack per weapon) |
type: action, required; where sees item as { id, tags, entry, source } |
content |
The character’s copies and picked content of one type, one member per copy or pick | the content type, required; where sees item and self as a change to copies does |
display: groups: - id: attacks kind: actions priority: 0 members: { type: action, where: "'attack' in item.tags" } - id: rests kind: actions priority: 2 members: { type: action, where: "'rest' in item.tags" } - id: spells kind: content priority: 1 showWhen: spellcasting.casterLevel > 0 || pactSlots.max > 0 members: { type: spell } sectionBy: item.level-
Actions: in
where,item.idis the action’s qualified ID as listed (5e-srd-5.1:death-save,<item id>#<action id>for an action inline on an item),item.tagsits tags, anditem.entryanditem.sourcethe listing’s instance ID and source content, absent when it has none (has(item.entry)). The action’s definition (parameters, roles) isn’t visible: an app reads it from the action. Prefer tags to IDs, so homebrew actions join too. -
Content: a member is a copy (an entry in
featuresorinventory) or content active through a pick or a grant (a wizard’s spells), once per activation, in activation order: computeSheet’sactivationswhosescopeisn’tapplied(engine-core.md).itemis its item (base plus templates) andselfits copy; for picked or granted content, the owning copy, as everywhere else, and absent when there is none (a rules-level pick). Templates show with their copy: a template on a copy, saved or kept (an activation withvia: template), is part of that copy’s member, as itsitemalready is, and the copy’s name names it ({base} +1). It is never a member of its own, and never in “Other”; its actions and choices are listed like any other. Applied entries (applied) aren’t members unless the query saysfrom: applied(below). As in a change aimed at copies, the character’s fields are visible too. Only the character’s own content is listed: content the character doesn’t have (a shop, a class’s spell list) is the app’s catalog. -
Applied entries: a content group’s
membersmay sayfrom: applied(the default isfrom: own, the members above). Its members are then the character’sappliedentries whose source is of the type, in file order, inactive and blocked ones included; each member is the entry’s instance ID. Inwhere,sectionByandsortBy,itemis the source’s item andselfthe entry (idand its state, with defaults), as in anapplied: { where }change. The app shows an entry’sactiveflag and state from the file, and a blocked one by itsimmuneissue. A group lists one or the other, so a wizard’s spells group doesn’t pick up an ally’s Bless:- id: conditionskind: contentpriority: 0members: { type: condition, from: applied }sortBy: "has(self.by) ? self.by : ''"- id: effects-on-you # Bless, Mage Armor cast by someone elsekind: contentpriority: 1members: { type: spell, from: applied } -
A field hint’s
groupsnames onlyfieldsgroups. -
showWhenandprioritywork as for fields groups. An action or content item in no group still exists: an “Other” section keeps homebrew from disappearing. A template on a copy isn’t such an item: it shows with its copy (above). -
Empty groups:
showWhensays whether a group applies to the character; members don’t. A pack givesshowWhento a group that applies to some characters only (a dragonborn’s ancestry), true from when the character can gain its members (before the pick is made). A group that applies but has no members on this character (no feats yet, nothing applied) may be shown empty or collapsed, or hidden, as the app likes; an app shouldn’t need emptiness to tell relevance. A group of content the character gains by a choice askshasChoice('<type>')(formulas.md): true while an applicable choice draws from the type, made or pending, so homebrew that brings the same choice shows the group without naming its items.
Sections and order
Section titled “Sections and order”A content group may also have:
sectionBy: a formula giving each member’s section key, an integer or a string (spell level). Sections are ordered by key: numbers ascending, then strings in code-point order. There is one level of sections.- Section labels:
groups.<id>.sections.<key>.name(groups.spells.sections.0.name: Cantrips). Optional; without one, the app shows the key. sortBy: a number or string formula. Members are sorted by it (within their section), then by instance order. Without it, instance order. Sorting by translated name is the app’s call.
Both see item and self as where does.
Priority
Section titled “Priority”- Priority is a whole number where 0 is highest, then 1, 2 and so on. There are no negatives, so nothing can jump above 0.
- Group priority is compared among groups. Field priority is compared among fields in the same group. A homebrew field can reorder its own group but can’t take over the whole sheet.
- Ties keep their written order, with pack load order as the final tiebreaker, so every app shows the same order.
- Priority orders; it doesn’t say what to leave out. Which values a view shows is
visibility(below); how much a compact view shows within that is the app’s call.
Visibility
Section titled “Visibility”visibility says how prominently a group, a field or a copy’s state key is
shown, with a fixed meaning every app follows:
| Value | Meaning |
|---|---|
primary |
Always on the sheet, compact views (a token tooltip, a summary card) included |
secondary (default) |
Shown on the play sheet; an app may collapse it |
background |
Kept out of play views; shown when editing the sheet. Base scores, maximums, per-level bookkeeping, overrides: values that exist and are edited but aren’t read in play |
display: groups: - id: summary priority: 0 visibility: primary fields: abilities.str.base: { groups: [abilities], priority: 2, visibility: background } abilities.str.max: { groups: [abilities], priority: 3, visibility: background }- Default. A field’s visibility is its own
visibility; without one, the visibility of the group it is shown in; without that,secondary. A field in several groups can so differ per group: AC isprimaryinsummaryandsecondaryincombat. A state key’s is its own, elsesecondary. Priority never decides visibility. - Groups. A
backgroundgroup is kept out of play views with all its members, whatever their own; aprimarygroup is in compact views, showing itsprimarymembers. An app collapsing asecondarygroup may keep itsprimarymembers in view. hiddenis separate.hidden: truemeans never shown, not even when editing (an in-between helper); a hint can’t have both (visibilitywithhidden: trueis a schema error). A value the player edits but doesn’t read in play isbackground, nothidden.showWhenis independent: an irrelevant field or group isn’t shown at any visibility.
Field hints
Section titled “Field hints”display: fields: abilities.str.score: { groups: [abilities], priority: 0, shortLabel: fields.abilities.str.score.short } abilities.str.mod: { format: signed } helpers.strModRaw: { hidden: true }| Hint | Meaning |
|---|---|
groups |
Groups the field joins |
priority |
Order within each group |
label, shortLabel |
Translation keys overriding fields.<id>.name / fields.<id>.short |
format |
One of the display formats: plain, signed (+3), percent, dice, fraction (1/4); or { bands } (below) |
unit |
A unit ID: its text is units.<id>.name (“feet”) and units.<id>.short (“ft”) |
input |
Editor hints: min, max, step, widget (number, checkbox, dots, track, text, select). The field’s own min/max still apply. |
visibility |
primary, secondary or background (Visibility); not with hidden |
hidden |
An in-between helper value that players should never see |
showWhen |
Relevance condition |
rolls |
The rolls the field starts (Rolls), the first its own |
with |
Companion fields shown in the field’s tile (Companions); not with hidden |
Hidden fields need no translations.
Naming a value by a band set. A field’s format may be { bands: <set> }, a
result band set the field’s pack sees (an unknown one is the load error unknown-reference). The
app names the value by the first band of the set whose when holds with roll.total the value
(roll.faces and roll.kept empty, every tally 0, and roll.value the set’s value evaluated
so first, when it has one), and shows bands.<set>.<band>.name beside or
instead of the number. Like every hint, the engine never evaluates it. Fate’s skills are rungs of
its ladder, shown “Great (+4)”:
# fate-core, rules/skills.yamldisplay: fields: skills.fight: { format: { bands: ladder } }Bands that read anything else (flags, steps, params) don’t fit a field’s value. State keys
and item fields keep the plain formats.
rolls says which rolls a field’s tile starts, so an app makes it rollable
without reading formulas. Each is a rules action, local or qualified, with literal values for
some of its parameters:
display: fields: abilities.int.score: rolls: - { action: ability-check, params: { ability: int } } - { action: saving-throw, params: { ability: int } } skills.insight: { rolls: [{ action: skill-check, params: { skill: insight } }] } stats.strength: { rolls: [{ action: stat-check, params: { stat: stats.strength } }] } combat.initiative: { rolls: [{ action: initiative }] }- Order: the first roll is the field’s own: a tap on the tile starts it. The others are offered beside it (a menu), in order: an ability’s save beside its check, Fate’s four actions on a skill.
- Starting it: the app calls
startActionwith the roll’sparams. Parameters it doesn’t give are filled as when the player starts the action from the action list: an optional one (a DC) asked or left out, a required one asked (Fate’sactiononskill-roll). Situational modifiers, participants and checkpoints come through the run as for any action. - Values: a field parameter’s value is a field ID, a content parameter’s an item ID;
loadRulesetqualifies them, and the action, in the hint’s pack (an unknown one isunknown-reference). - Inference: a field with
rollsstarts those and no others. Without it, an app may still infer a roll from an action whose only required parameter is a field the field matches. - Copies have no
rolls: a copy’s actions (a weapon’s attack) are listed per copy bycomputeSheetwith theirentry. pnpm validatechecks that the action is a rules action the pack sees, that each parameter is declared, and that each value fits its declaration: an enum one of its values, a number of the right kind within a literalminandmax, a field one the pack sees that the parameter’swherematches (when the field’s tags are static), an item one of the parameter’s type.
Companions
Section titled “Companions”with names companion fields shown in a field’s own tile, so a long sheet
condenses: six tiles reading “13 (+1)” instead of twelve, one reading “0 / 195 lb”.
display: fields: abilities.str.score: { with: [{ field: abilities.str.mod, as: modifier }] } carrying.weight: { unit: lb, with: [{ field: carrying.capacity, as: max }] } types: class: state: level: { with: [{ key: hitDiceSpent, as: detail }] }| Role | Shown | Types |
|---|---|---|
modifier |
After the value, signed: “13 (+1)” | The field and companion numeric |
max |
After the value, as “x / y”: “0 / 195” | The field and companion numeric |
detail |
A small second line under the value | Any |
- Several: a list, each role at most once; the tile reads “value / max (modifier)” with
the detail beneath, whatever the list’s order. A track or dots input with a
maxcompanion takes its length from it. - The tile is the field’s: its label, groups, priority and visibility. Each companion is
shown with its own
formatandunit, a modifier always signed. - Groups: in a group where the field has a tile, its companions have no tile of their own; elsewhere they show as usual. A companion needn’t be in the field’s groups.
visibility: a companion shows in the tile whatever its own, which still applies to its own tiles elsewhere. A compact view may drop adetail.showWhen: a field not shown leaves its companions to show as usual; a companion whose condition is false is left out of the tile.hidden: a hidden field has no companions, and a companion isn’t hidden.- Editing: an app may split the tile into an input per field.
- No chains: a companion has no companions of its own and isn’t the field itself. A field may be several fields’ companion.
- State keys: a state hint’s
withnames other state keys of the same copy bykey. loadRulesetqualifies a field companion;pnpm validatechecks the rules above and that the companion exists.
unit names a unit for a field, a state key or an item field: unit: ft shows
5E’s speed as “30 ft”. Unit IDs are pack-local like other keys, and each needs
units.<id>.name and units.<id>.short in the default language. A unit only changes display:
converting between units (feet to meters) is the app’s call.
Content-type hints
Section titled “Content-type hints”display.types.<type> holds hints for the copies of a content type:
display: types: class: state: level: { priority: 0 } hitDiceSpent: { priority: 1 } startingClass: { hidden: true } quantity: { hidden: true } weapon: fields: damage: { format: dice } weight: { unit: lb }| Key | Hints |
|---|---|
state.<key> |
A field hint without groups: priority (among the copy’s state), label, shortLabel, format, unit, input, visibility, hidden, showWhen, with (naming state keys) |
fields.<field> |
format, unit and hidden (off the card; a form still asks it) for an item field |
quantity |
hidden: true when quantity never matters (class features) |
- A state key’s own
minandmaxstill apply. State keys are named bytypes.<type>.state.<key>.nameor a state hint’slabel, item fields bytypes.<type>.fields.<field>.name(i18n.md). Their enum values have names, and an item’s card header is its type’s block, a text template, not a hint (i18n.md). - A character’s headline (“Level 3 Hill dwarf Wizard”) is text too, the pack’s
headlinetemplate (i18n.md), not a hint: the template names the fields and content types it reads. - A state hint’s
showWhensees the copy asselfand its item asitem, with the character’s fields. - The type must be a content type visible from the pack; each state key must be declared by the
type or by an item of it, and each item field by the type (
pnpm validate). - A pack can hint another pack’s type; per type, one rules document per pack holds its hints.
Add or Replace
Section titled “Add or Replace”A content type’s copy limit (copies; content.md) is
not a hint but tells an app how to offer copies. computeSheet’s copyLimits gives, per limited
type, max, perItem, the count held and the copies:
- At
count >= max, the type’s Add becomes Replace whenmaxis 1 (aremoveItemthen anaddItemreusing the entry’s ID, in one batch), and is disabled otherwise. max: 0: the type is never added as a copy (it is picked in a choice); offer no Add.- An item with
perItemcopies amongentriesis shown as held, not offered again.