Builder's Manual · Chapter 2

How the Character Sheet Thinks

The character schema has one design rule: store decisions; derive everything else. A sheet stores the six base ability scores, level and experience, references to race, class, subclass, background, feats, and gear, current-state counters (hit points, slots used, conditions, exhaustionLevel) — and computes the other 115 fields.

The derivation chain

  • Each final ability score is min(20, base + racial increase + feat increases + attuned-item overrides) — the race file carries strengthIncreasecharismaIncrease, so choosing a race applies its scores with nothing to remember. Modifiers, saves, and all 18 skills derive from there, reading proficiency choices and Expertise from skillProficiencies.
  • armorClass reads the equipped armor file's acBase, addDexterity, and dexterityCap, adds shield and magic bonuses and miscArmorBonus, and falls back to unarmored math — including class unarmored defenses.
  • Attack linesmainHandAttackBonus, mainHandDamageDice, and the off-hand set — key off the weapon file's finesse and ranged flags, magic bonus, and your modifiers.
  • Spell slots derive from casterLevelceil(level × casterProgression − 0.01) so half-casters round correctly — with Warlock pact magic computed on its own track.
  • Level gating is a formula idiom: classFeaturesLevel120 and the subclass equivalents read featuresLevelN off the class and subclass files, and the layout shows each block only when level qualifies. A character levels up by changing one number.

Aggregation by shared field names

The sheet never knows which feat or relic you took — it sums every instance of shared field names across the feats array and the three guarded attunedItem slots: strengthIncreasecharismaIncrease, acBonus, initiativeBonus, speedBonus, hpBonus, hpBonusPerLevel, saveBonusAll. Give any feat or magic item those fields and it wires itself into every derived number, attributed and stackable. That is the whole integration contract — @Missing file and @Missing file are the two worked examples.

Platform limits that shaped this type

Learned the hard way, worth knowing before you edit: a type holds at most 200 top-level fields (this one sits at 196 — nest display-only fields into objects, as coins and personality already are); formula nesting caps at depth 64 (split long ternary tables into tier formulas); reference-picker filters only work on top-level fields, which is why the nine preparedSpellsLevelN arrays live at the top. Schema edits push with craft push --include-types (unscoped — pathspec pushes refuse types) after craft check; the server validates the new schema against every existing file and names each record a change would break.

Manual updated Jul 29, 2026.