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 born in game inherits this trait from the parent, rolled once per birth. Leave it out and the game uses 30. Values outside 0-100 are pulled back into range. A small set of core traits is hard-wired to always or never pass down (species-defining traits, scars, diseases and similar); for those the game ignores this value and the pack validator will point it out. |
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. |
Inheritance
Section titled “Inheritance”When a worker gives birth in game, each trait the parent carries rolls its
own InheritChance once: on a success the child is born with the trait.
A trait without the attribute inherits at 30, and out-of-range values are
pulled back into 0-100. (The current runtime inherits from the birth mother,
and only daughters enter the roster; the rules are documented in child/parent
terms so they remain applicable as support for other parent and child genders
lands.)
Two things limit what a pack can change here:
- Hard-wired exceptions. A small set of core traits ignores
InheritChanceentirely: species-defining traits and the fantasy addictions always pass down, while diseases, scars,Sterileand similar never do. The pack validator warns when you author a chance on one of these. - First declaration wins. Re-declaring a core trait in your pack merges effects and categories onto it, but does not override its chance attributes. You set inheritance only for traits your pack introduces.
The core data follows a simple ladder, and a new trait reads best when it sits in the band that matches what it represents:
InheritChance |
What it means | Typical traits |
|---|---|---|
0 |
Acquired in life, never inherited | job training, piercings, brands, injuries |
1-10 |
Personality and tastes, a rare echo | temperament, preferences |
20-50 |
Predispositions and talents | toughness, learning speed, magic aptitude |
50-70 |
Body and looks | figure, eyes, build |
80-100 |
Blood and species | heritage and species traits |
You can author the value directly in your .traitsx, or through the trait
dialog in the Content Editor; both write the same attribute.
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) 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+)
Section titled “MutexGroup – whole-axis exclusion (1.15+)”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 Free Time gear-purchase decisions | the Free Time job | Added to her Combat before the game decides whether she feels dangerous enough to buy a weapon. |
Adding a slot that core gameplay reads is an engine change. 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), 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+)
Section titled “Lifecycle inflict rules: <OnGenerate> and <Periodic> (1.15+)”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.
These rules inflict state flags, not traits, and they carry no conditions:
<Periodic> fires on its cadence and <OnGenerate> fires at acquisition, with
the chance roll as the only gate. There is no way to say “only once she is over
30” here.
If that is what you are after, the route today is a script. A Stat trigger
plus wm.add_trait grants a trait when a stat crosses a threshold, age
included. See the recipe in
lua-scripting. The trade is
scope: a lifecycle rule follows the trait to every character carrying it, while
a trigger you ship applies to the one character it is attached to.
<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