Builder's Manual · Chapter 2

How the Character Sheet Thinks

The character schema has one design rule: store decisions, derive everything else. It stores 132 fields — six raw attribute modifiers, level and XP, references to ancestry, heritage, background, class, subclass, gear and feats, proficiency ranks, and current-state counters — and computes the other 67.

The derivation chain

  • Every attribute has a raw field and a computed total: strength and strengthTotal. The raw field is what you typed; the total adds every active bonus. Nothing reads the raw field but the total. This pattern repeats for every derived number in the project.
  • armorClass reads the equipped armor's acBonus, dexCap, and potencyRune, caps your Dexterity at the armor's limit, adds a raised shield, and subtracts statusPenalty and clumsyValue.
  • Skill totals — all sixteen plus two Lore slots — read skillProficiencies.<skill>, add level + 2/4/6/8 for trained through legendary and zero for untrained, then apply the armor check penalty if you don't meet the Strength requirement.
  • Attack lines pick Dexterity automatically for finesse and ranged weapons, choose the right proficiency for the weapon's category, and fold in the potency rune.
  • actionsPerTurn is 3 + quickened − max(slowed, stunned). The action economy is a computed field, which is why conditions bite immediately.

The bonuses contract

This is the integration point for everything you add. Ancestry, heritage, background, class, subclass, domain, feat, weapon, armor, shield, and item files each carry a bonuses array of {stat, value, perLevel, note} rows, where stat is a fixed enum of attributes, defenses, Perception, and every skill.

bonusSourceRefs gathers every file the character currently has linked — equipped gear, invested items, and only feats at or below the character's level — and every matching row is summed into the matching total automatically. Unlink the file and the bonus leaves. bonusesSummary prints each active bonus with its source, so the GM can audit it.

Give any new feat or magic item those rows and it wires itself into every derived number. That is the whole contract. Never hand-adjust a total for a bonus a linked file already grants — @Missing file instructs against exactly that, and you would be double-counting.

Inventory is a state machine

inventoryItems and inventoryWeapons are carried. investedItems is active, capped at 10 by the invested rule. Equipped slots — mainHand, offHand, armor, shield — are active too. A magic item in the pack contributes nothing until it moves.

Platform limits that shaped this type

A file type holds at most 200 top-level fields, and this one sits at 199 — the tightest budget of any system in the library. There is no room to add a field without nesting or removing one; coins, personality, image, and skillProficiencies are already nested objects for this reason. Formula nesting caps at depth 64, so split long ternary tables. Reference-picker filters only work on top-level fields, which is why the ten preparedSpellsRankN fields live at the top rather than in an object.

There is also no creation flow on this type — characters are built by hand following @Missing file. If you add one, its option nodes hold references into this project's files, and those referenceIds must be remapped if the type is ever copied elsewhere.

Schema edits push with craft push --include-types after craft check; the server revalidates every existing file and names each record a change would break.

Manual updated Sep 2, 2026.