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: chance a child inherits this trait. Default 0. |
RandomChance | No | 0-100: weight when random girls roll traits. Default 0. |
Types and categories (1.14+) New
Section titled “Types and categories (1.14+) New”Type is the single-category shorthand. As of 1.14 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 (1.14+) New
Section titled “Mutual exclusion and implies (1.14+) New”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.
<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 (Virgin, Noble, Slave Born) |
Magical | Magic-related traits |
Mental | Mental-state traits (Dependant, Shy) |
Perception | Senses (Blind, Deaf, Mute, Sharp-Eyed) |
Physical | Body traits (Pretty, Delicate, Fragile, Tough) |
Sexual | Sexual preference traits (Nymphomaniac, Frigid) |
Social | Social traits (Cheerful, Exotic) |
Species | Non-human species (Elf, Demon, Beastkin) |
Temporary | Transient flags (Kidnapped, Emprisoned Customer) |
Undead | Undead species (Vampire, Zombie, Skeleton) |
<effects> block
Section titled “<effects> block”Effects are live as of 1.11: 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).
Engine version note.
stat,skill, andpreg_durationwork on 1.11 and up. The remaining effect types below (skill_cap,enjoyment,modifier,delta_cap) require 1.12. If your pack uses any of them, setRequiresEngine="1.12.0"inpackage.xmlso older installs warn instead of silently ignoring the effect.
<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 (1.13.3+)
Section titled “Registered modifier slots (1.13.3+)”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 1.12 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.
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