How the Character Sheet Thinks
The character schema has one design rule: store what the player decided; derive everything else.
A character file stores its aspects (as references), its skillRatings, its linked stunts and extras, its current fatePoints, and its stress state — which boxes are checked. Everything else is a computed field:
skillsis the whole visible skill block, built by expression: for each of the eighteen skills, the rawrating, thetotal(rating plus every bonus instance granted by linked stunts and extras), the total's name on the Ladder, and asourcesline listing each bonus with its circumstance. The raw value is what the player edits; the total is what everyone reads.refreshisbaseRefreshminus 1 per stunt past three, floored at 1. Link a fourth stunt and it drops by itself.physicalStressBoxesandmentalStressBoxesread Physique and Will (2 boxes, 3 at rating 1+, 4 at 3+), then add any boxes granted by stunts and extras, clamped 2–6. The extra mild consequence slots unlock the same way — skill at 5+, or granted.skillBonusListis the aggregation underneath: it flat-mapsskillBonusesout of every linked stunt and extra. Write a stunt with askillBonusesentry (skill, bonus, label, circumstance) and every holder's sheet updates with the bonus attributed by name.
Storing a derivable number is the classic way these systems rot: two fields disagree and nobody knows which one lied. Here the stored values are the single source of truth, and a rules change is a one-expression edit that updates every sheet in the project at once.
References, not copies
An aspect on a sheet is the Aspect file — highConcept and trouble are references, and free invokes live on the aspect's own freeInvokes field, where every invoker can see them. The mirror rule: per-character state lives on the character, never on the shared record. Stunts and skills are shared; which stress boxes are checked (physicalStressState, mentalStressState) and which consequence fills which slot (mildConsequence, moderateConsequence, severeConsequence) belong to the character alone. Consequences are files because they are aspects — created when the hit lands, linked on the slot, recovered on their own recoveryStatus clock.
Changing the schema
Schema edits push with craft push --include-types, which validates the new schema against every existing file of that type — run craft check first. When you add a mechanic, the order of preference is: a computed field, if it is arithmetic on existing data; a stored field, only for a genuinely new player decision; and never a second copy of something derivable. One hard-won rule: give every numeric stepper field a maximum — the app's stepper control misrenders without one.