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>
AttributeRequiredPurpose
NameYesUnique display name. Case-sensitive when referenced.
TypeYesCategory. See below.
InheritChanceNo0-100: chance a child inherits this trait. Default 0.
RandomChanceNo0-100: weight when random girls roll traits. Default 0.

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.

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.

TypeFor
ActionWhat she does well or badly (Agile, Brawler, Clumsy, Strong)
AddictionAddictions (Alcoholic, Cum Addict, Smoker, Fairy Dust Addict)
AppearanceVisible body marks (Tattoos, Piercings, Scars, Beauty Mark)
BreastsBust size and shape
DiseaseIllness / infection traits
JobRole / occupation traits (Virgin, Noble, Slave Born)
MagicalMagic-related traits
MentalMental-state traits (Dependant, Shy)
PerceptionSenses (Blind, Deaf, Mute, Sharp-Eyed)
PhysicalBody traits (Pretty, Delicate, Fragile, Tough)
SexualSexual preference traits (Nymphomaniac, Frigid)
SocialSocial traits (Cheerful, Exotic)
SpeciesNon-human species (Elf, Demon, Beastkin)
TemporaryTransient flags (Kidnapped, Emprisoned Customer)
UndeadUndead species (Vampire, Zombie, Skeleton)

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, and preg_duration work 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, set RequiresEngine="1.12.0" in package.xml so 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>
type=name=value=Effect
statStat nameSigned intAdditive modifier on every stat read.
skillSkill nameSigned intAdditive modifier on every skill read.
skill_capSkill nameSigned intShifts the training ceiling for that skill (clamped to [0, 100]).
enjoymentAction nameSigned intPer-action enjoyment contribution. Stacks with other sources.
modifierNamed modifier slotSigned intContribution 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_capStat or skill namePositive intCaps 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 weeksPregnancy-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:

SlotDomainRead byNotes
COMBAT_INSTINCTCombat threshold for Freetime gear-purchase decisionsWorkFreetime.cppAdded 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 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 for key.
  • wm.add_temp_trait(name, weeks): grants a temporary trait whose effects appear in the cache immediately.

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