Skip to content

Traits reference

Traits live in Traits.traitsx files. Same schema as the core trait files in resources/data/traits/.

<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>
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.

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.

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 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>
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=.

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>

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.

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 for key.
  • 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 the EveryWeeks cadence. 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 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
  • Unknown Flag value: the rule is skipped with a log warning at pack load.
  • Chance non-numeric or out of range (0-100): the rule is skipped with a log warning.
  • Chance of 0: the rule is skipped with a log warning (would never fire).
  • Periodic missing Turns: parse error, rule rejected.
  • Periodic with Turns="permanent": parse error – periodic inflicts must decay.
  • Periodic with non-positive Turns: parse error.
  • EveryWeeks of 0 or negative: parse error.
  • OnGenerate with non-positive Turns (when numeric): parse error.

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.


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.

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).

  • resources/data/traits/: every trait the engine ships with, split into one .traitsx file per category
  • resources/data/TraitAliases.xml: alias mapping
  • Sample_TraitsOnly/: a pack that is nothing but traits
  • state-flags.md: StateFlag registry
  • when-conditions.md: <HasStateFlag> and other <When> leaves