Traits reference
Traits live in Traits.traitsx files. Same schema as the core trait files in resources/data/traits/.
Trait structure
Section titled “Trait structure”<Trait Name="Night Owl" Type="Action" InheritChance="0" RandomChance="15"> <description>This girl comes alive after dark.</description> <effects> <effect name="Agility" type="stat" value="5" /> </effects></Trait>Attributes
Section titled “Attributes”| Attribute | Required | Purpose |
|---|---|---|
Name |
Yes | Unique display name. Case-sensitive when referenced. |
Type |
Yes | Category. See below. |
InheritChance |
No | 0-100. Intended chance a child inherits this trait. Default 0. Parsed and validated, but not yet driving inheritance — activation is a later pass; today children inherit by built-in defaults. |
RandomChance |
No | 0-100. Fallback spawn chance, used when a random-girl template lists this trait without an explicit percent=. Not a global roll — the trait only appears where a template lists it. Default 0. |
Types and categories
Section titled “Types and categories”Type is the single-category shorthand. Traits can also belong to multiple categories declared in a <Categories> block:
<Trait Name="Bouncer"> <description>Trained as a doorman; tough and intimidating.</description> <Categories> <Category>Physical</Category> <Category>Job</Category> </Categories></Trait>Type= still works for single-category traits and remains the shorthand for the “primary” category (used by the legacy valence inference). When both Type= and <Categories> are present, both contribute to the multi-category list; the primary stays as whatever Type= named.
Categories are also inferred from filenames for traits loaded from the legacy .traits text format: a trait in a file named Physical.traits joins the Physical category with no XML. Since 1.15 the core catalogue is .traitsx with explicit Type= (split by category under resources/data/traits/), so filename inference now only matters for a legacy .traits file a pack might still ship.
The new <TraitCategory id="X"/> <When> leaf (see when-conditions.md) tests whether a girl has any trait in category X, which replaces long <Any><Trait>...</Trait></Any> patterns.
Mutual exclusion and implies
Section titled “Mutual exclusion and implies”A trait can declare other traits it mutually excludes (<Excludes>) or implies (<Implies>). When the engine adds the trait to a girl, it removes every named exclude and adds every named imply automatically.
<Trait Name="Big Boobs"> <Excludes>Small Boobs</Excludes> <Excludes>Abnormally Large Boobs</Excludes></Trait>
<Trait Name="Incorporial"> <Implies>Sterile</Implies></Trait>Each element holds one trait name as its text content. Multiple <Excludes> or <Implies> siblings accumulate.
The engine’s built-in mutex pairs ship in resources/data/traits/_Mutexes.traitsx and use this same schema. Pack authors can extend or add their own without engine changes.
<Excludes> is auto-symmetrized on load: declaring <Excludes>Small Boobs</Excludes> on Big Boobs is enough – the loader adds the reverse so Small Boobs excludes Big Boobs too. You never write both sides. (<Implies> is one-directional by design: A implies B does not mean B implies A.)
MutexGroup – whole-axis exclusion (1.15+) New
Section titled “MutexGroup – whole-axis exclusion (1.15+) New”For an axis of three or more traits where every one excludes every other, declare a <MutexGroup> instead of writing an <Excludes> pair for every combination:
<MutexGroup Name="BreastSize"> <Member>Big Boobs</Member> <Member>Small Boobs</Member> <Member>Flat Chest</Member></MutexGroup>Every member now excludes every other member. <MutexGroup> is a top-level element – a sibling of <Trait>, not nested inside one – and can appear in any .traitsx file, including one shipped in a pack. A trait can belong to several groups (hair length and hair colour are independent axes), and a <Member> may name a trait defined in a different file. A member naming an unknown trait is skipped with a log warning. <MutexGroup> and per-trait <Excludes> mix freely.
Extending an existing axis. Two blocks with the same Name= in different files do not logically merge — each block expands independently and overlapping exclusion pairs are de-duplicated. To connect a new trait to an existing axis, include the new trait plus at least one existing member. The loader’s “fewer than 2 members” check rejects single-member groups, so a pack-side extension block must always pair the new member with an anchor from the core axis.
Use <MutexGroup> instead of prose. If your new trait describes a mutually exclusive body or appearance tier (size, length, colour, silhouette), add it to an axis with <MutexGroup> rather than describing the contradiction in the trait’s flavour text. Prose does not gate the trait registry; <MutexGroup> does.
Do not use <Implies> for a tier ladder. A natural-reading ladder like Hourglass Figure → Great Figure would double-apply Beauty and Charisma under the current effect-stacking rules. <Implies> is for traits that genuinely demand a second trait’s presence (e.g. Incorporial → Sterile), not for tier hierarchies. Tier ladders need an effect-stacking rework before they’re safe to author.
<Excludes> and <Implies> are merged into existing traits: a thin .traitsx file that only adds excludes/implies onto a trait already loaded from .traits does not need to redefine the trait body.
Values actually used across the core trait files in resources/data/traits/. Type is a free-form tag used for grouping in the Gallery and the tagger; the engine doesn’t reject unknown values, but sticking to the shipped taxonomy keeps new traits discoverable.
| Type | For |
|---|---|
Action |
What she does well or badly (Agile, Brawler, Clumsy, Strong) |
Addiction |
Addictions (Alcoholic, Cum Addict, Smoker, Fairy Dust Addict) |
Appearance |
Visible body marks (Tattoos, Piercings, Scars, Beauty Mark) |
Breasts |
Bust size and shape |
Disease |
Illness / infection traits |
Job |
Role / occupation traits (Actress, Chef, Doctor) |
Magical |
Magic-related traits |
Mental |
Mental-state traits (Aggressive, Bimbo, Broken Will) |
Perception |
Senses / first-impression (Cool Person, Cute, Nerd) |
Physical |
Body traits (Great Figure, Flexible, Fat) |
Sexual |
Sexual preference traits (Nymphomaniac, Frigid) |
Social |
Social traits (Shy, Charismatic, Noble) |
Species |
Non-human species (Elf, Demon, Beastgirl) |
Temporary |
Transient flags (Kidnapped, Emprisoned Customer) |
Undead |
Undead species (Vampire, Zombie, Skeleton) |
<effects> block
Section titled “<effects> block”Effects are live: the engine sums the contribution from every active trait every time the game reads a stat / skill / modifier. Removing a trait cleanly removes its contribution. Stacking the same trait twice does not double-apply (the cache rebuilds against the active trait set, not by additive mutation).
<effects> <effect name="Beauty" type="stat" value="10" /> <effect name="Charisma" type="stat" value="5" /> <effect name="NormalSex" type="skill" value="15" /></effects>Effect types
Section titled “Effect types”type= |
name= |
value= |
Effect |
|---|---|---|---|
stat |
Stat name | Signed int | Additive modifier on every stat read. |
skill |
Skill name | Signed int | Additive modifier on every skill read. |
skill_cap |
Skill name | Signed int | Shifts the training ceiling for that skill (clamped to [0, 100]). |
enjoyment |
Action name | Signed int | Per-action enjoyment contribution. Stacks with other sources. |
modifier |
Named modifier slot | Signed int | Contribution to a per-girl named-modifier registry. Read from C++ via g_Girls.GetTraitModifier(girl, "NAME") and from Lua via wm.get_modifier("NAME"). The forward-facing way to data-drive arbitrary game numbers; see registry list below. |
delta_cap |
Stat or skill name | Positive int | Caps how much the stat / skill can change in one UpdateStat call. Tighter cap wins when multiple traits cap the same target. |
preg_duration |
(omit) | Signed weeks | Pregnancy-duration delta. Negative shortens, positive lengthens. |
name= is case-insensitive for stats and skills. preg_duration takes no name=.
Worked examples
Section titled “Worked examples”A trait that hides part of a girl’s training potential while pinning a custom flag for Lua scripts:
<Trait Name="Stage Fright" Type="Mental" InheritChance="5" RandomChance="10"> <description>Performs poorly under pressure. Hard to push past her ceiling.</description> <effects> <effect type="skill_cap" name="performance" value="-25"/> <effect type="modifier" name="stage_fright" value="1"/> <effect type="stat" name="confidence" value="-10"/> </effects></Trait>A pregnancy-modifier trait:
<Trait Name="Quick Breeder" Type="Physical"> <description>Her body hurries pregnancy along.</description> <effects> <effect type="preg_duration" value="-4"/> </effects></Trait>A trait that limits how fast health can swing per shift (useful for “tough” or “fragile” archetypes):
<Trait Name="Iron Hide" Type="Physical"> <description>Damage and exhaustion only nibble at her.</description> <effects> <effect type="delta_cap" name="health" value="3"/> <effect type="delta_cap" name="tiredness" value="3"/> <effect type="enjoyment" name="bdsm" value="5"/> </effects></Trait>Registered modifier slots
Section titled “Registered modifier slots”Modifier slots are an open registry: any string key works on the data side, but unless gameplay code reads from a particular slot it does nothing. The slots wired into gameplay so far:
| Slot | Domain | Read by | Notes |
|---|---|---|---|
COMBAT_INSTINCT |
Combat threshold for Freetime gear-purchase decisions | WorkFreetime.cpp |
Added to girl.combat() before comparing against the danger threshold. |
Adding a new gameplay-relevant slot is a code + XML change; see docs/modding-and-packages/trait-modifiers.md in the main repo. Pack authors can add their own slots and read them from Lua scripts they ship in the same pack; no engine change needed for that path.
Reading trait state from Lua
Section titled “Reading trait state from Lua”Inside a script (see lua-scripting.md), four helpers expose the live cache:
wm.get_base_stat(name): base stat with no trait contribution.wm.stat_effect(name): only the trait-cache contribution (effective = base + effect).wm.get_modifier(key): sum of<effect type="modifier">contributions forkey.wm.add_temp_trait(name, weeks): grants a temporary trait whose effects appear in the cache immediately.
Lifecycle inflict rules: <OnGenerate> and <Periodic> (1.15+) New
Section titled “Lifecycle inflict rules: <OnGenerate> and <Periodic> (1.15+) New”A trait can carry lifecycle inflict rules that attach a timed StateFlag to a girl when the trait is active. Two rule types are supported:
<OnGenerate>– fires once at trait acquisition (girl creation, slave-market purchase, arena capture, direct grant). If the chance roll passes, the named StateFlag is inflicted for the specified number of turns.<Periodic>– fires on a hashed-phase week driven by the girl’s UID and theEveryWeekscadence. If the roll passes, the named StateFlag is inflicted for the specified number of turns.
Both elements are optional children of <Trait>. A single trait can carry multiple rules of either type; they are evaluated independently.
<Trait Name="Channeler" Type="Magical"> <description>A conduit for spirit possession; her channel opens at birth and flickers thereafter.</description> <OnGenerate Flag="SpiritPossessed" Chance="25" Turns="2"/> <Periodic Flag="SpiritPossessed" Chance="40" Turns="2" EveryWeeks="6"/></Trait>Attribute table
Section titled “Attribute table”| Attribute | Element | Required | Default | Range |
|---|---|---|---|---|
Flag |
both | yes | – | string – must match a registered StateFlag |
Chance |
both | no | 100 | integer 0-100 |
Turns |
OnGenerate |
no | permanent |
positive integer or "permanent" |
Turns |
Periodic |
yes | – | positive integer (no permanent) |
EveryWeeks |
Periodic |
yes | – | positive integer >= 1 |
Validation rules
Section titled “Validation rules”- Unknown
Flagvalue: the rule is skipped with a log warning at pack load. Chancenon-numeric or out of range (0-100): the rule is skipped with a log warning.Chanceof 0: the rule is skipped with a log warning (would never fire).PeriodicmissingTurns: parse error, rule rejected.PeriodicwithTurns="permanent": parse error – periodic inflicts must decay.Periodicwith non-positiveTurns: parse error.EveryWeeksof 0 or negative: parse error.OnGeneratewith non-positiveTurns(when numeric): parse error.
Worked examples
Section titled “Worked examples”Channeler (Magical trait – initial chance + recurrent flicker):
<Trait Name="Channeler" Type="Magical" InheritChance="10" RandomChance="5"> <description>A conduit for spirit possession; her channel opens at birth and flickers every few weeks.</description> <OnGenerate Flag="SpiritPossessed" Chance="25" Turns="2"/> <Periodic Flag="SpiritPossessed" Chance="40" Turns="2" EveryWeeks="6"/></Trait>At acquisition there is a 25% chance she starts with SpiritPossessed for 2 weeks. Every 6 weeks thereafter (on a phase offset derived from her UID so not all Channelers fire the same week), she has a 40% chance of being possessed for another 2 weeks. Re-inflicting while still active refreshes the turn counter and does not re-emit the onset line in Turn Summary.
Beastgirl (Species trait – deterministic heat cycle):
<Trait Name="Beastgirl" Type="Species" InheritChance="80" RandomChance="3"> <description>A girl of beast-kin heritage; her nature surfaces on a reliable cycle.</description> <Periodic Flag="InHeat" Chance="100" Turns="1" EveryWeeks="8"/></Trait>Every 8 weeks she enters InHeat for 1 week. Chance="100" makes the cycle deterministic – no roll needed. The 1-week duration means she is always out of heat between cycles.
Cross-references
Section titled “Cross-references”- StateFlag registry and semantics – what each flag means, decay rules, permanent vs timed.
<HasStateFlag>condition – how to gate text variants, gains, and effects on a girl’s current StateFlag.
Conflicts
Section titled “Conflicts”Traits with the same name across packs: the first-loaded pack wins. Core traits always beat pack traits.
Before adding a new trait, grep the files in resources/data/traits/ for the name to make sure it doesn’t already exist. If it does, use the existing one; don’t duplicate.
Aliases
Section titled “Aliases”resources/data/TraitAliases.xml maps old names to new ones. If you want your new trait to be referenceable by several names, add an alias there (or in a pack-level file; the game merges them).
See also
Section titled “See also”resources/data/traits/: every trait the engine ships with, split into one.traitsxfile per categoryresources/data/TraitAliases.xml: alias mappingSample_TraitsOnly/: a pack that is nothing but traits- state-flags.md: StateFlag registry
- when-conditions.md:
<HasStateFlag>and other<When>leaves