Builder's Manual · Chapter 2

How the Character Sheet Thinks

The character schema has one design rule. Store decisions and derive everything else. A sheet stores the six base ability scores, level and experience, references to race, class, subclass, background, feats, and gear, and a few play-state fields. Everything else is computed.

The derivation chain

  • Each final ability score is min(20, base + racial increase + feat increases) plus item increases and overrides. The race file carries strengthIncrease to charismaIncrease, so choosing a race applies its scores with nothing to remember. A level 20 Barbarian's Primal Champion is applied automatically.
  • Modifiers, saves, and all 18 skills derive from there. Skills read two lists, proficientSkills and expertSkills. Jack of All Trades and Remarkable Athlete fill in untrained skills on their own.
  • armorClass reads the equipped armor file's acBase, addDexterity, and dexterityCap, then adds shield, magic, and miscArmorBonus. Without armor it uses the best unarmored option you have, including class Unarmored Defense and the Draconic subclass.
  • Attack lines such as mainHandAttackBonus and mainHandDamageDice key off the weapon file's finesse and ranged flags and its magic bonus.
  • Spell slots come from casterLevel, which counts full-caster levels plus half of each Paladin or Ranger level, rounded down, once two classes can cast. A single spellcasting class uses its own table. Warlock Pact Magic runs on its own track in pactMagic.
  • Features come from one computed list, features. It reads featuresLevelN off the class and subclass files for every level you have. A character levels up by changing one number.

Multiclassing

level is always the TOTAL character level. A multiclassed hero also sets secondClass, secondSubclass, and secondClassLevel, and the main class gets the rest. Proficiency Bonus and XP use the total. Hit points use the first class's full die at level 1 and averages after that. Saving throws come from the first class only. The spell pickers offer spells from both classes.

Resources and play state

Every limited pool lives in one stored map, resourceState, keyed by a stable slug. Spell slots use spell-slot-1 to spell-slot-9, then pact-slots, hit-dice, and each class or subclass pool (rage, wild-shape, focus-points, and so on). A missing key means the pool is full. The pool definitions live on the class and subclass files in their resources field, so a new pool is data, not a formula. The sheet shows them as tappable circles at the top. On/off states such as concentration, rage, and a held Bardic Inspiration die live in activeStates.

Class choices

Choices that used to live in notes now have their own fields on the Features tab. They are eldritchInvocations, metamagicOptions, pactBoon, draconicAncestry, and landType. Each offers only the SRD options, and each shows only for the classes, subclasses, or race that use it. The Resources card shows how many invocations, Metamagic options you may know at your level. The Mage Armor toggle sets unarmored AC to 13 plus your Dexterity modifier.

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, the three attunedItem slots, and every worn slot (head to feet) holding an item that needs no attunement. These are strengthIncrease to charismaIncrease, acBonus, initiativeBonus, speedBonus, hpBonus, hpBonusPerLevel, and saveBonusAll, plus score-override fields for items like the Gauntlets of Ogre Power. Give any feat or magic item those fields and it wires itself into every derived number. @Missing file is the worked example.

Platform limits that shaped this type

A type holds at most 200 top-level fields, and a single formula can be at most 5,000 characters. Long per-item families belong in one computed array, which is why skills, saves, spell slots, resources, and features are each one list. Reference-picker filters only work on top-level fields, which is why preparedSpellsLevel1 to preparedSpellsLevel9 and secondClass live at the top. Schema edits push with craft push --include-types after craft check. The server validates the new schema against every existing file and names each record a change would break.

Manual updated Oct 2, 2026.