Traits
Edit tabs/traits.json.
Trait Categories
Traits must be organized into categories in tabs/traits.json under traitCategories.
| Field | Description |
|---|---|
name |
Display name for the category |
maxSelections |
How many traits player can pick. 0 means none can be picked — don't use it. Applies to the whole category, subcategories included |
traits |
Array of trait keys in this category |
description |
Optional player-facing text explaining what the category represents |
subcategories |
Optional groups within the category: { name, traits, description? }. In character creation, subcategory traits are listed first, then the ungrouped root traits |
Category patterns:
- Single selection (class/background):
maxSelections: 1 - Single selection (species):
maxSelections: 1 - Multiple selection (miscellaneous):
maxSelections: 3
Category selection at character creation ignores requirements (level-up picks only). unlockedBy and excludedBy do constrain creation choices: see Trait Dependencies below.
Required Fields
| Field | Requirement |
|---|---|
name |
Must match object key exactly |
description |
What the trait is — shown to players at character creation (full paragraph) |
traitNarrativeEffects |
The narrator's reference for portraying the trait. Usually the same as description; elaborate further or add hidden aspects the player shouldn't see |
attributes |
Array of attribute modifiers (can be empty []) |
skills |
Array of skill modifiers (can be empty []) |
resources |
Array of resource modifiers (can be empty []) |
startingItems |
Array of items granted (can be empty []) |
abilities |
Array of ability names granted (can be empty []) |
requirements |
Array of requirement objects (can be empty []). Only gates level-up trait picks — ignored at character creation |
unlockedBy |
Prerequisites (OR logic): trait keys or story start ids. Affects level-up picks and character creation (see Trait Dependencies) |
excludedBy |
Conflicts: trait keys or story start ids. Affects level-up picks and character creation (see Trait Dependencies) |
description and traitNarrativeEffects
For non-species traits:
- By default,
descriptionandtraitNarrativeEffectscarry the same content — write both as full paragraphs. descriptionis shown to players at character creation.traitNarrativeEffectsis the narrator's reference during play.- Let them diverge only when: (a) the narrative effects hold hidden aspects the player shouldn't know, or (b) the description is a shorter summary and
traitNarrativeEffectsis the elaborated, fuller version.
Older configs used a field named quirk for this; it is auto-migrated to traitNarrativeEffects verbatim. Always write traitNarrativeEffects in new configs.
For species traits, see the Species Traits section below.
Conditional Fields
| Field | When to Include |
|---|---|
attributes (non-empty) |
Class/background traits that affect core stats |
skills (non-empty) |
Traits that grant expertise or training |
resources (non-empty) |
Traits that affect resource pools |
startingItems (non-empty) |
Class/background traits with signature equipment |
abilities (non-empty) |
Traits that unlock special perks or powers |
requirements (non-empty) |
Traits in the level-up pool that should be gated behind stats, skills, other traits, or character level |
vulnerabilities / resistances / immunities |
Traits that should change how much typed damage the player takes in combat (e.g. a fire-elemental species immune to fire). Values must match combatSettings.damageTypes |
Level-Up Trait Picks
Traits can be offered as level-up rewards via progressionSettings.levelUpTraitPool in tabs/settings.json (default cadence: 1 pick every 10 levels). When a pick is pending, the player chooses from pool traits they don't already have whose unlockedBy/excludedBy conditions pass and whose requirements are met; the chosen trait applies immediately, exactly like a starting trait. An empty pool means no picks are ever granted.
Gotcha: requirements does nothing at character creation, it only filters the level-up pick list. To keep a trait out of starting selection everywhere, leave it out of every trait category.
Trait Dependencies (unlockedBy and excludedBy)
Both fields list trait keys or story start ids: a trait can require, or be blocked by, other traits or specific story starts. At character creation, categories are evaluated in traitCategories order after the story start is chosen. A story start dependency always takes effect; a trait dependency only constrains choices in categories after the referenced trait's own category. A dependency that is neither an earlier-category trait nor a story start leaves the trait unconditionally unlocked. Changing an earlier selection removes later selections that are no longer eligible. Both fields also filter level-up trait picks.
Trigger-granted traits: a trait added by a player-traits trigger effect applies its modifiers and abilities but does NOT grant its startingItems. Only permanent acquisition paths (character creation, level-up picks) hand out starting items. Trait bonuses are reconciled as net deltas, so swapping traits never double-applies a modifier.
TraitModifier Format
All modifiers use the same structure:
{ attribute: string, modifier: number } // For attributes
{ skill: string, modifier: number } // For skills
{ resource: string, modifier: number } // For resources
The modifier is an additive value (positive or negative). Multiple traits stack.
Point Cost Guidelines
Traits should be balanced around a point budget. Use these guidelines:
| Modifier Type | Point Cost |
|---|---|
| Attribute | 1 point |
| Skill at modifier 0 | 1 point |
| Resource per 10 | 1 point |
| Starting item | 1 point |
| Ability | 1 points |
Every trait within a single traitCategory should be roughly equal.
Target values:
- The
isHealthresource should be set at roughly 100. - Attributes should be at an average of 12, ranging from 0-20.
startingItems Format
{ item: string, quantity: number }
The item must reference a valid item key from tabs/items.json.
requirements Format
Array of prerequisite checks. All must be met for the trait to appear as a level-up pick. Same format as ability requirements.
{ type: 'skill', variable: 'skill name', amount: 3 } // Skill level >= 3
{ type: 'attribute', variable: 'strength', amount: 14 } // Attribute value >= 14
{ type: 'characterLevel', amount: 5 } // Character level >= 5 (no variable)
{ type: 'resource', variable: 'mana', amount: 50 } // Resource max >= 50
{ type: 'trait', variable: 'fire affinity', amount: 1 } // Has trait (amount ignored)
Schema
interface TraitCategory {
name: string
maxSelections: number
traits: string[]
description?: string
subcategories?: TraitSubcategory[]
}
interface TraitSubcategory {
name: string
traits: string[]
description?: string
}
interface Trait {
name: string
description: string
traitNarrativeEffects: string
attributes: Array<{attribute: string, modifier: number}>
skills: Array<{skill: string, modifier: number}>
resources: Array<{resource: string, modifier: number}>
startingItems: Array<{item: string, quantity: number}>
abilities: string[]
requirements: TraitRequirement[]
unlockedBy: string[]
excludedBy: string[]
vulnerabilities?: string[]
resistances?: string[]
immunities?: string[]
}
type TraitRequirement =
| { type: 'resource' | 'attribute' | 'skill' | 'trait'; variable: string; amount: number }
| { type: 'characterLevel'; amount: number }
Species Traits
When creating a trait that represents a species (playable race):
- Must have corresponding NPC Type and World Lore entries
- The
descriptioncontains lore paragraphs + skill blocks separated by\n\n - The
traitNarrativeEffectscontains lore paragraphs only (no skills) - Lore paragraphs are identical across NPC Type
description, Traitdescription, and TraittraitNarrativeEffects - Must include exactly 3 skills that work as both player skills AND NPC abilities
- Skills should reflect innate species abilities or cultural training
See Species Consistency Rules for the full requirements.
Reference
For detailed documentation, see traits-reference.md.