System
A System entity describes a game system (e.g. D&D 5e, Pathfinder 2e, etc.) and its associated traits. Systems contain various traits that are inherited and extended by Campaigns. When a Campaign is created, it can optionally be associated with a System, in which case it will inherit all of the System's traits. Campaigns can also have their own traits in addition to the inherited System traits, and they can override any of the inherited traits if they choose.
Systems not only have an entity definition, but also a fixture containing a version of that system's actual values. The app ships with a number of systems pre-loaded, and others can be loaded using fixtures of the same type.
The entity definition is a traditional data object, with members (properties and methods) of various types, meant to be persisted in some kind of database, exposed via API, etc. However, part of a System is logic, which is best implemented not as data, but as code (for instance: dnd5e-2014.ts). This is part of the totality of a logical System, but is somewhere between a data entity and a fixture, in terms of implementation.
Entity Definition
A System is composed of three conceptual layers:
- Core metadata — identity, versioning, and display info (stored in the
systemsDB table) - Campaign trait definitions — collections of data items that Campaigns inherit (stored in trait-specific DB tables with
system_idownership) - Character logic module — validation, suggestions, and class/ability-score rules (implemented as TypeScript code in
packages/shared/src/systems/)
These layers are unified at runtime but have different storage and delivery mechanisms, reflecting the reality that some parts of a System are pure data while others are inherently code.
Layer 1: Core Metadata
The foundation of a System as a database entity. Stored in the systems table.
| Field | Type | Description |
|---|---|---|
id |
string (PK) |
Unique identifier, e.g. 'dnd5e-2014', 'system8' |
name |
string |
Display name, e.g. 'D&D 5th Edition (2014)' |
foundation |
string |
Dice foundation, e.g. 'd20' (default) |
description |
string? |
Brief description for UI display |
apiUrl |
string? |
External API endpoint (e.g. 'https://www.dnd5eapi.co') |
createdAt |
string |
ISO timestamp |
updatedAt |
string? |
ISO timestamp |
Current systems shipped: dnd5e-2014, dnd5e-2024, system8, custom
Layer 2: Campaign Trait Definitions
These are data collections that define the defaults a Campaign inherits when it selects a System. Each trait type is stored in its own database table with a dual-ownership pattern:
- System-level rows:
system_idis set,campaign_idisNULL→ read-only defaults - Campaign-level rows:
campaign_idis set,system_idisNULL→ user-editable overrides
When a Campaign's traits are fetched via API, both sets are merged and annotated with runtime flags (isSystemDefault, isEditable).
Trait Types
| Trait Type | DB Table | Description | Example |
|---|---|---|---|
| Property Types | prop_types |
Custom data types for token properties | Ability Score (renders with modifier in parens) |
| Property Definitions | property_definitions |
Named properties tokens can have, each referencing a prop type | Strength (type: ability-score, category: abilities) |
| Conditions | conditions |
Status effects with SVG badge icons | Blinded, Prone, Stunned |
| Bars | bars |
Visual bar indicators on tokens | HP bar (green gradient, value = hp/maxHp) |
| Light Source Types | light_source_types |
Predefined light sources for vision/lighting | Torch (bright 4, dim 8) |
| Vision Types | vision_types |
Vision modes with darkness/concealment flags | Darkvision (60ft, ignores darkness) |
| Campaign Functions | campaign_functions |
JavaScript functions available in Action code | lightDamage(whichAbilityScore) |
Shared Schema Pattern
Every trait table follows the same ownership columns:
id TEXT PRIMARY KEY
...trait-specific fields...
system_id TEXT -- nullable; set for system-level defaults
campaign_id TEXT -- nullable; set for campaign-level overrides
created_at TEXT NOT NULL
updated_at TEXT
Campaign Inheritance Behavior
| Scenario | What Happens |
|---|---|
Campaign has systemId |
GET endpoint returns system_id rows merged with campaign_id rows. System rows are marked isSystemDefault: true, isEditable: false. |
Campaign has no systemId |
GET endpoint returns only campaign_id rows, all marked isEditable: true. |
| User changes system | A confirmation dialog offers: (1) copy new system's defaults as editable campaign properties, (2) delete campaign properties matching old system's defaults, (3) allow incoming to overwrite existing. Server handles via POST /api/campaigns/:id/change-system. |
Prop Types
| Field | Type | Description |
|---|---|---|
id |
string (PK) |
Unique identifier |
name |
string |
Display name (e.g. 'Ability Score', 'Number', 'Text') |
renderFunction |
string? |
JavaScript expression for custom display. Receives value, returns formatted string. |
Property Definitions
| Field | Type | Description |
|---|---|---|
id |
string (PK) |
Unique identifier |
name |
string |
Property name (e.g. 'Strength', 'HP') |
propType |
string |
References a prop type ID or built-in type ('number', 'string', 'boolean') |
category |
string? |
Grouping (e.g. 'abilities', 'combat') |
order |
integer |
Sort order within category |
Conditions
| Field | Type | Description |
|---|---|---|
id |
string (PK) |
Unique identifier |
name |
string |
Condition name (e.g. 'Blinded') |
description |
string? |
Rules text |
badgeSvg |
string |
SVG icon displayed on tokens |
sortOrder |
integer |
Display order |
Bars
| Field | Type | Description |
|---|---|---|
id |
string (PK) |
Unique identifier |
name |
string |
Display name (e.g. 'HP') |
color |
string |
Any valid CSS background, including gradients |
valueFunction |
string |
JavaScript expression receiving metadata, returning 0–1 |
order |
integer |
Display order (lower = top) |
Light Source Types
| Field | Type | Description |
|---|---|---|
id |
string (PK) |
Unique identifier |
name |
string |
Display name (e.g. 'Torch') |
brightRadius |
integer |
Bright light radius in grid squares |
dimRadius |
integer |
Dim light radius in grid squares |
shape |
string |
'circle' or 'cone' |
coneAngle |
float |
Angle in degrees (for cone shape) |
colorType |
string |
Color mode (e.g. 'incandescent', 'cool') |
colorTemperature |
float? |
Kelvin temperature |
color |
string? |
CSS color override |
Vision Types
| Field | Type | Description |
|---|---|---|
id |
string (PK) |
Unique identifier |
name |
string |
Display name (e.g. 'Darkvision') |
ignoresDarkness |
boolean |
Can see in darkness |
ignoresConcealment |
boolean |
Ignores concealment |
ignoresCover |
boolean |
Ignores cover |
range |
integer? |
Range in feet |
Campaign Functions
| Field | Type | Description |
|---|---|---|
id |
string (PK) |
Unique identifier |
name |
string |
Function name used in Action code |
args |
string |
Comma-separated argument names (e.g. 'whichAbilityScore') |
functionType |
string |
'global' (top-level) or 'token' (method on token proxy) |
code |
string |
JavaScript function body |
description |
string? |
Human-readable description |
sortOrder |
integer |
Display order |
Layer 3: Character Logic Module
The code layer of a System, implemented as TypeScript modules in packages/shared/src/systems/. These are not stored in the database—they ship with the application and are lazy-loaded at runtime via the systemRegistry.
SystemModule Interface
interface SystemModule {
id: string;
name: string;
version: string;
description: string;
abilityScores: AbilityScoreConfig;
classes?: SystemClass[];
validateHp?: (character: Character) => HpValidation;
suggestions?: SuggestionEngine;
classAttributePriorities?: ClassAttributePriorities[];
}
AbilityScoreConfig
Defines how ability scores work in this system. Every system must provide this.
interface AbilityScoreConfig {
abilities: string[]; // e.g. ['Strength', 'Dexterity', ...]
standardArray?: number[]; // e.g. [15, 14, 13, 12, 10, 8]
maxBaseScore: number; // Before magic items (5e: 20, S8: 18)
maxScore: number; // Absolute max (5e: 30, S8: 99)
minScore: number; // Floor (typically 1)
getMaxBaseScoreAtLevel(level): number; // Level-dependent cap
validateAbilityScores(character): AbilityScoreValidation;
validateSingleAbility(character, abilityName): ValidationIssue[];
}
SystemClass
Describes a character class available in the system.
interface SystemClass {
name: string;
description?: string;
hitDie?: number; // e.g. 12 for Barbarian, 6 for Wizard
}
SuggestionEngine
Generates character-building recommendations based on class, level, and current stats.
interface SuggestionEngine {
suggestAttributes(context: SuggestionContext): AttributeSuggestion[];
suggestFeats(context: SuggestionContext): FeatSuggestion[];
suggestPowers(context: SuggestionContext): PowerSuggestion[];
}
Each function returns prioritized suggestions with human-readable explanations.
ClassAttributePriorities
Maps each class to its preferred ability scores, used by the suggestion engine.
interface ClassAttributePriorities {
className: string;
primary: string[]; // Most important stats
secondary: string[]; // Useful but not critical
tertiary?: string[]; // Situationally useful
}
How the Three Layers Connect
┌─────────────────────────────────────────────────────────────────────┐
│ System │
│ │
│ ┌─────────────────┐ ┌─────────────────────┐ ┌────────────────┐ │
│ │ Layer 1: Core │ │ Layer 2: Traits │ │ Layer 3: Logic│ │
│ │ (systems table) │ │ (trait tables) │ │ (TS modules) │ │
│ │ │ │ │ │ │ │
│ │ id │ │ prop_types │ │ abilityScores │ │
│ │ name │ │ property_definitions │ │ classes │ │
│ │ description │ │ conditions │ │ validateHp │ │
│ │ foundation │ │ bars │ │ suggestions │ │
│ │ apiUrl │ │ light_source_types │ │ classPriority │ │
│ │ │ │ vision_types │ │ │ │
│ │ │ │ campaign_functions │ │ │ │
│ └─────────────────┘ └─────────────────────┘ └────────────────┘ │
│ │
│ Stored in DB ────────────────────────────────── Shipped as code │
│ Served via REST API ─────────────────────────── Lazy-loaded JS │
│ Seeded by SQL migrations ────────────────────── Imported by UI │
└─────────────────────────────────────────────────────────────────────┘
Campaign relationship: A Campaign references a System via systemId. The Campaign inherits Layer 2 traits (merged at query time) and uses Layer 3 logic for character validation and suggestions. Layer 1 metadata is used for UI display.
Character relationship: A Character references a System via systemId. The Character Sheet loads the Layer 3 module to validate ability scores, HP, and generate optimization suggestions.
Character Trait Definitions
Characters in this system have structured traits that are defined by the System. While the Character entity itself is system-agnostic (it stores raw data), the System provides the rules for interpreting and validating that data.
Character Entity Structure
interface Character {
id: string;
userId: string;
name: string;
level: number;
systemId: string;
// Build
classes: string[]; // Class names (e.g. ['Fighter', 'Wizard'])
classLevels?: CharacterClassLevel[]; // Detailed multiclass levels
origins: string[]; // Race/background/origin
// Core data
attributes: CharacterAttribute[]; // With component breakdown
proficiencyBonus: number; // Computed from level
skills: CharacterSkill[];
feats: CharacterFeat[];
powers: CharacterPower[];
attacks: CharacterAttack[];
// Art
artworks?: ArtworkEntity[];
// System-specific
metadata?: Record<string, unknown>; // e.g. { abilityScoreMode, hpMode }
}
Trait Type Breakdown
Attributes (Ability Scores)
Attributes use a component breakdown model where the total value is the sum of named components. This allows tracking the source of each bonus.
interface CharacterAttribute {
name: string; // e.g. 'Strength'
components: AttributeComponent[]; // [{name:'base',value:15}, {name:'asi',value:2}]
}
Component names are freeform but conventionally include: base (from array/point-buy/roll), ancestry/racial (from race/origin), asi (from ability score increases), magic/item (from equipment). The System's validation logic examines these component names to enforce rules.
System defines: Which abilities exist (abilities[]), their bounds (maxBaseScore, maxScore, minScore), generation modes (standard array, point-buy, rolled), ASI progression, and all validation rules.
Skills
interface CharacterSkill {
name: string; // e.g. 'Athletics'
attribute: string; // Linked ability score, e.g. 'Strength'
proficient: boolean;
expertise: boolean;
brief?: string;
level?: number; // Level at which proficiency was gained
}
System should define: Available skills, their default attributes, and which classes grant proficiency in which skills at which levels.
Feats
interface CharacterFeat {
name: string;
description?: string;
pact?: string; // Source pact (for Warlock-style systems)
source?: string; // Where it came from (class, origin, etc.)
level?: number; // Level gained
slotIndex?: number; // Slot within a level (for multiple feats at same level)
}
System should define: Available feats with prerequisites, which levels offer feat choices (vs ASI), and any class-specific feat lists (e.g. Origin Feats in 5e 2024, Talents in System 8).
Powers (Spells / Class Features)
interface CharacterPower {
name: string;
brief?: string; // Short description
school?: string; // Magic school or discipline
rank?: number; // Spell level / power rank (1–5 for System 8)
type?: string; // Category (e.g. 'spell', 'cantrip', 'class-feature')
className?: string; // Source class (for multiclass)
url?: string; // Canonical URL for lookup
level?: number; // Level at which gained
}
System should define: Available powers per class, rank/level progression, and which powers are available at which character levels.
Attacks
interface CharacterAttack {
name: string;
type: string; // 'Natural Weapon', 'Spellcasting Implement', etc.
proficient: boolean;
attribute: string; // Linked ability score
damage?: DamageWeight; // 'light' | 'medium' | 'heavy' (System 8)
hit: number; // Flat attack bonus
hitPower: number; // Added per die (System 8)
crit: number; // Crit range expansion
critPower: number; // Crit multiplier bonus (System 8)
damageDice?: string; // e.g. '2d6' (5e)
damageType?: string; // e.g. 'slashing' (5e)
damageAttribute?: string; // Ability added to damage
range?: string; // e.g. '30/120 ft'
}
System should define: Weapon types, damage dice/weights, proficiency rules, and how attack/damage bonuses are calculated.
Inventory Items (Future)
Not yet implemented. Expected to include: name, weight, cost, type, properties, equipped/unequipped state. Systems would define equipment lists and encumbrance rules.
System Fixture Definition
A System Fixture is the complete dataset that defines a concrete game system. It combines all three layers into a single deliverable that can be:
- Shipped with the app (built-in systems like D&D 5e, System 8)
- Loaded as a package (community systems, homebrew)
Fixture Structure
A fixture has two halves:
Data Half (Trait Defaults)
Expressed as SQL migrations or JSON import files. Contains rows for each trait table:
fixtures/
dnd5e-2014/
metadata.json # Core metadata (name, description, foundation)
prop-types.json # Default prop types (Number, Text, Ability Score)
property-definitions.json # Default properties (Str, Dex, ..., HP, AC)
conditions.json # Default conditions (Blinded, Charmed, ...)
bars.json # Default bars (HP)
light-source-types.json # Default light sources (Torch, Lantern, ...)
vision-types.json # Default vision types (Normal, Darkvision, ...)
campaign-functions.json # Default functions (if any)
For built-in systems, these are currently seeded via SQL migration files (migrations 0014–0018). For user-installable systems, they would be imported via a fixture loader.
Code Half (Logic Module)
Expressed as a TypeScript module implementing SystemModule:
packages/shared/src/systems/
dnd5e-2014.ts # Implements SystemModule for D&D 5e 2014
dnd5e-2024.ts # Implements SystemModule for D&D 5e 2024
system8.ts # Implements SystemModule for System 8
The code half is inherently tied to the application codebase. For community systems, a plugin architecture would be needed (future consideration).
Packaging Considerations
The challenge: a System fixture contains both data (easily serializable, importable) and code (needs to execute in a JS runtime, has dependencies on the Character type system). Options include:
| Approach | Pros | Cons |
|---|---|---|
| Current: SQL seeds + TS modules | Simple, type-safe, fast | Not portable, requires app rebuild for new systems |
| JSON data + sandboxed JS | Portable, user-installable | Security concerns, limited API surface |
| WASM plugins | Safe, performant | Complex, heavy tooling |
| Hybrid: data = importable, logic = contributor PR | Best of both, maintains type safety | Still requires process for logic contributions |
The recommended near-term path is the hybrid approach: trait data is fully importable/exportable as JSON fixtures, while logic modules are contributed to the codebase and shipped with the app.
System Fixtures: D&D 5e 2014 & 2024
D&D 5e 2014
Metadata
| Field | Value |
|---|---|
| id | dnd5e-2014 |
| name | D&D 5th Edition (2014) |
| foundation | d20 |
| description | Dungeons & Dragons 5th Edition rules from the 2014 release. |
| apiUrl | https://www.dnd5eapi.co |
Campaign Trait Defaults
Prop Types (3):
| Name | Render Function |
|---|---|
| Number | (none) |
| Text | (none) |
| Ability Score | value => `${value} (${Math.floor((value - 10) / 2) >= 0 ? "+" : ""}${Math.floor((value - 10) / 2)})` |
Property Definitions (10):
| Name | Prop Type | Category | Order |
|---|---|---|---|
| Strength | ability-score | abilities | 1 |
| Dexterity | ability-score | abilities | 2 |
| Constitution | ability-score | abilities | 3 |
| Intelligence | ability-score | abilities | 4 |
| Wisdom | ability-score | abilities | 5 |
| Charisma | ability-score | abilities | 6 |
| HP | number | combat | 1 |
| Max HP | number | combat | 2 |
| AC | number | combat | 3 |
| Speed | number | combat | 4 |
Conditions (15): Blinded, Charmed, Deafened, Frightened, Grappled, Incapacitated, Invisible, Paralyzed, Petrified, Poisoned, Prone, Restrained, Stunned, Unconscious, Exhaustion
Bars (1): HP bar — green gradient, (metadata?.hp ?? 0) / (metadata?.maxHp ?? 1)
Light Sources (8): Torch (4/8), Hooded Lantern (6/12), Bullseye Lantern (12/24 cone 90°), Candle (1/2), Light cantrip (4/8), Dancing Lights (0/2), Daylight (12/24), Continual Flame (4/8)
Vision Types (10): Normal, Darkvision 60ft, Superior Darkvision 120ft, Blindsight 10/30/60ft, Truesight 30/60/120ft, Tremorsense 30/60ft
Campaign Functions: (none — D&D 5e uses standard dice expressions rather than system-specific formulas)
Character Logic Module
Ability Score Config:
- Abilities: Strength, Dexterity, Constitution, Intelligence, Wisdom, Charisma
- Standard Array: 15, 14, 13, 12, 10, 8
- Point Buy: 27 points, scores 8–15
- Max Base Score: 20 (before magic); Max Score: 30 (with magic)
- ASI Levels: 4, 8, 12, 16, 19 (+Fighter 6/14, +Rogue 10)
- Each ASI: +2 to one score OR +1/+1 to two scores, OR take a Feat
Classes (12):
| Class | Hit Die | Primary | Secondary |
|---|---|---|---|
| Barbarian | d12 | Str, Con | Dex |
| Bard | d8 | Cha | Dex, Con |
| Cleric | d8 | Wis | Con, Str |
| Druid | d8 | Wis | Con, Dex |
| Fighter | d10 | Str, Con | Dex |
| Monk | d8 | Dex, Wis | Con |
| Paladin | d10 | Str, Cha | Con |
| Ranger | d10 | Dex, Wis | Con |
| Rogue | d8 | Dex | Int, Con |
| Sorcerer | d6 | Cha | Con, Dex |
| Warlock | d8 | Cha | Con, Dex |
| Wizard | d6 | Int | Con, Dex |
HP Validation: Level 1 = max hit die + CON mod. Level 2+ = previous + average (rounded up) + CON mod. Validates against class hit die and warns on deviation.
Suggestion Engine: Suggests ASI targets based on class priorities, feat recommendations (Lucky, GWM, Sharpshooter, War Caster, etc.), and spell/cantrip picks per class.
D&D 5e 2024
Differences from 2014
| Aspect | 2014 | 2024 |
|---|---|---|
| Origin Feats | None | Every character gets a feat at level 1 |
| Ability bonuses | From race (+2/+1) | From background (+2/+1) |
| Weapon Mastery | Not present | Weapon Mastery properties |
| Feat vs ASI | Equal weight | Default to ASI until primary stat ≥ 20 |
Campaign trait defaults (prop types, property definitions, conditions, bars, light sources, vision types) are identical to 2014. The differences are entirely in the character logic module.
Suggestion engine additions: Suggests Origin Feats (Alert, Magic Initiate, Skilled) at level 1; explicitly recommends ASI over feats when primary stat < 20.
System 8
Metadata
| Field | Value |
|---|---|
| id | system8 |
| name | System 8 |
| foundation | d20 |
| description | A streamlined RPG system with per-attribute proficiency bonuses. |
| apiUrl | https://purplecat.wiki |
Campaign Trait Defaults
Prop Types (3): Number, Text, Ability Score (same render function as 5e)
Property Definitions (10): (same as 5e: 6 abilities + HP/MaxHP/AC/Speed)
Conditions (5): Dazed, Stunned, Prone, Slowed, Weakened (simplified set)
Bars (1): HP bar (same formula as 5e)
Light Sources (3): Torch (4/8), Lantern (6/12), Light Spell (6/12)
Vision Types (3): Normal, Darkvision (60ft), Blindsight (30ft)
Campaign Functions (4):
| Function | Type | Args | Description |
|---|---|---|---|
lightDamage |
token | whichAbilityScore |
{rank}d4+{abilityBonus} — dex-based light attack |
mediumDamage |
token | whichAbilityScore |
{rank}d6+{abilityBonus} — balanced attack |
heavyDamage |
token | whichAbilityScore |
{rank}d8+{abilityBonus} — str-based heavy attack |
attackRoll |
token | whichAbilityScore |
1d20+{abilityBonus+profBonus} — attack roll |
(rank = Math.ceil(level / 4), abilityBonus from token.metadata[whichAbilityScore])
Character Logic Module
Ability Score Config:
- Same 6 abilities as 5e
- Standard Array: 16, 14, 12, 12, 10, 10 (sum = 74)
- Max Base Score: 18 (not 20 — proficiency compensates); Max Score: 99
- Trade rule: May reduce one 10→8 to raise a 12 or 14 by +2 (max one 8)
- Proficiency system: Per-attribute proficiency bonuses. Each level (+ 3 starting levels) grants +1 to 2 different abilities. Total points =
(level + 3) × 2. Per-ability cap =level + 3. - No point-buy. Modes: standard-array, rolled, custom.
Classes (9):
| Class | Hit Die | Primary | Secondary |
|---|---|---|---|
| Fighter | d10 | Str, Con | Dex |
| Rogue | d8 | Dex | Int, Con |
| Arcanist | d6 | Int | Con, Dex |
| Elementalist | d8 | Int, Con | Dex |
| Priest | d8 | Wis | Con, Cha |
| Witch | d6 | Wis, Cha | Con |
| Warlock | d8 | Cha | Con, Dex |
| Psion | d6 | Int, Wis | Con |
| Artificer | d8 | Int | Con, Dex |
HP Validation: Not implemented (no validateHp on SystemModule). HP is implied to follow the same average-per-level pattern.
Suggestion Engine: Suggests attribute increases based on class priorities. Feats are a placeholder (System 8 uses "Talents" instead). Power suggestions include class-specific abilities (Arcane Bolt, Sacred Light, Mind Thrust, etc.).
Runtime Architecture
System Registry
Systems are registered in packages/shared/src/systems/index.ts:
const systemRegistry: SystemModuleRegistry = {
'dnd5e-2014': async () => (await import('./dnd5e-2014.js')).dnd5e2014,
'dnd5e-2024': async () => (await import('./dnd5e-2024.js')).dnd5e2024,
'system8': async () => (await import('./system8.js')).system8,
};
Modules are lazy-loaded and cached. The registry also provides systemInfoList for UI display without loading full modules.
API Surface
| Endpoint | Method | Description |
|---|---|---|
/api/systems |
GET | List all systems (metadata only) |
/api/campaigns/:id |
GET | Campaign with systemId reference |
/api/campaigns/:id/change-system |
POST | Change system with merge/delete options |
/api/campaigns/:id/prop-types |
GET | Merged system + campaign prop types |
/api/campaigns/:id/property-definitions |
GET | Merged system + campaign definitions |
/api/campaigns/:id/bars |
GET | Merged system + campaign bars |
/api/campaigns/:id/functions |
GET | Merged system + campaign functions |
/api/campaigns/:id/light-source-types |
GET | Merged system + campaign light sources |
/api/campaigns/:id/vision-types |
GET | Merged system + campaign vision types |
/api/conditions?campaignId=X |
GET | Merged system + campaign conditions |
All trait GET endpoints follow the same pattern: fetch campaign's systemId, query with OR(campaignId, systemId), annotate with isSystemDefault/isEditable.
All trait POST/PUT/DELETE endpoints enforce that system-level rows cannot be modified (403 if systemId is set and campaignId is null).
TODO
- [x] Define the basic System entity members, based on current implementation. Then, plan ahead for the next three TODOs, which will add quite a bit to this object; consider how this object's structure can be digestible with so many members/submembers/etc.
- [x] Considering the existing defintion of Campaign traits which can be inherited from a System (property types, property definitions, conditions, light sources, vision types, and functions), define these same traits at the System level
- [x] Consider also the defined traits of a Character as implemented, such as attributes (HP, Strength, etc), skills, feats, spells/powers, and inventory items, all of which should have their own definitions, with System having members that are collections of these.
- [x] Considering the System-specific logic for character sheet validation and optimization (defined in dnd5e-2014.ts, for instance), define how these members fit into the overall System entity definition. Most of this part of a logical system is best defined as code, not data, so it won't need a data definition per se--rather something like an interface--but also we must consider how to package a data fixture with a bunch of code.
- [x] Considering what was previously defined in the last TODO, define the System Fixtures for D&D 5e 2014 and 2024, with one or two examples of each trait type (both the campaign-inherited ones and the character traits).