Skip to content

Job data reference

This page is the full schema reference for job data directories. If you are new to job authoring, start with examples/jobs-cookbook.md for step-by-step recipes, then come back here when you need the exact attribute list.


Each job lives in its own directory under resources/Jobs/:

resources/Jobs/<id>/
job.xml required -- identity, title, description, eligibility
effects.xml optional -- stat/skill/brothel changes applied each shift
performance.xml optional -- how the performance score is calculated
wage.xml optional -- gold earned as a function of performance
gains.xml optional -- skill XP and trait grants each shift
messages/
work.xml optional -- shift-end message variants
text/
en.xml optional -- localizable strings for the messages above

Only job.xml is required. A job with no other files is valid: the girl shows up in the job list, can be assigned, but nothing happens each shift.

The directory name (<id>) must be lowercase, no spaces, and must match the id attribute in job.xml. It also becomes the prefix for all text IDs in messages/work.xml and text/en.xml.


Declares the job’s identity and optional eligibility gate.

<?xml version="1.0" encoding="UTF-8"?>
<Job id="barmaid" schema="1">
<Title>Barmaid</Title>
<Description>She will staff the bar and serve drinks.</Description>
<DefaultImage>profile</DefaultImage>
</Job>
Element / attributeRequiredNotes
id (attribute on <Job>)YesMust match the directory name. Lowercase, no spaces.
schema="1" (attribute on <Job>)YesAlways 1 for now.
<Title>YesDisplayed in the job picker UI.
<Description>YesShort tooltip shown in job details.
<DefaultImage>NoImage catalog type used for the shift-event portrait. Defaults to profile if omitted. Common values: profile, sex, strip, oral, wait, cook, massage, escort, dom. Individual outcomes can override this per-<Text> via the Image= attribute (see messages/work.xml below).
<Eligibility>NoA block of <When> leaves. Leaves are direct children of <Eligibility> (do not wrap them in <When> here; the loader iterates the children directly, unlike the message-bank <When> surfaces where the wrapper is required). If present, a girl can only be assigned this job while every child leaf is satisfied; the same gate is re-checked at the top of every shift, so a girl whose state flipped after assignment (stock drained, status changed) produces a “could not work” event instead of running the shift. See reference/when-conditions.md for the full leaf catalogue.
<Filter>NoRestricts the job to buildings that have the named feature. Text content; current values: Bar. Omit (or leave empty) for brothel-core jobs that any building hosts.
<Phase>NoSelects the turn-loop pass this job runs in. Text content; one of Prepare, Produce, Main (default), Late. Use Prepare for jobs that must run before customer traffic (Advertising), Produce for jobs that publish a value other jobs read this turn (BarCook publishing food quality before BarMaid serves it), Main for everything else, Late for clean-up passes that run after the rest of the turn.

Example with eligibility:

<Job id="basictraining" schema="1">
<Title>Basic Training</Title>
<Description>She trains her combat skills.</Description>
<DefaultImage>profile</DefaultImage>
<Eligibility>
<Stat name="Level" le="10"/>
</Eligibility>
</Job>

Example with <Filter> and <Phase>:

<Job id="barcook" schema="1">
<Title>Bar Cook</Title>
<Description>She will cook food for the bar.</Description>
<DefaultImage>profile</DefaultImage>
<Filter>Bar</Filter>
<Phase>Produce</Phase>
</Job>

This job is offered only in buildings that have a Bar, and runs in the Produce pass so that any Main-pass consumer (BarMaid, BarWaitress) sees the value it publishes for the rest of the turn.


Applied once per shift for every girl working this job. Can change the girl’s stats and skills, fan out changes to all girls in the building, or change building properties.

<?xml version="1.0" encoding="UTF-8"?>
<Effects>
<SetStat target="self" stat="Tiredness" delta="-25"/>
<SetStat target="self" stat="Happiness" delta="10"/>
</Effects>

Changes a girl’s stat by a fixed or random amount.

AttributeRequiredNotes
targetYesWho is affected. See targets below.
statYesStat name. Same set as <When><Stat>.
deltaYes (or delta_min+delta_max)Fixed integer change. Negative values decrease the stat.
delta_min / delta_maxYes (or delta)Random range, inclusive. The engine picks uniformly.
clamp_maxNoCap the resulting value at this ceiling. Useful for Fame, Health, etc.

Changes a girl’s skill by a fixed or random amount.

<SetSkill target="self" skill="NormalSex" delta_min="2" delta_max="4"/>

Same attributes as <SetStat> with skill instead of stat. Skill names: Anal, Magic, BDSM, NormalSex, Beastiality, Group, Lesbian, Service, Strip, Combat, Performance.

Changes a property on the building.

<SetBrothel target="brothel" key="Filthiness" delta="-5"/>
<SetBrothel target="player.brothels" key="Fame" delta="$delta" clamp_max="100"/>
AttributeNotes
targetbrothel for the current building; player.brothels to fan out to every building the player owns.
keyBuilding property to change. Current set: Filthiness, Fame.
deltaFixed integer, or $bindname to reference a <Bind> value (see below).
clamp_maxOptional ceiling.

Groups a set of effects behind a <When> condition and optional <Bind> declarations. If the <When> is not satisfied, the entire group is skipped.

<EffectGroup>
<Bind name="delta" expr="(Charisma + Intelligence + Service) / 60"/>
<When>
<Bind name="delta" ge="1"/>
</When>
<SetBrothel target="player.brothels" key="Fame" delta="$delta" clamp_max="100"/>
<SetStat target="self" stat="Happiness" delta="1"/>
</EffectGroup>

Order inside a group: <Bind> elements are evaluated first, then <When> is checked using those binds, then the remaining entries are applied if the check passed.

The short alias <Group> is accepted by the engine in addition to <EffectGroup>.

Declares a named integer computed from a formula. Only valid inside an <EffectGroup>. The result can be referenced in that group’s <When> as <Bind name="..." op="..." value="N"/> and in delta="$name" on effect entries.

<Bind name="bonus" expr="clamp((Intelligence + Service) / 2 / 33, 0, 3)"/>

Supported identifiers in expr: stat names, skill names, and the clamp(value, min, max) function. You cannot reference a bind from another group; repeat the formula if you need it in two places.

Picks exactly one child <Option> per shift, weighted.

<RandomChoice>
<Option weight="6"><SetStat target="self" stat="Exp" delta="0"/></Option>
<Option weight="2"><SetSkill target="self" skill="Service" delta="1"/></Option>
<Option weight="2"><SetSkill target="self" skill="Strip" delta="1"/></Option>
</RandomChoice>

Weights are relative. In the example above: 60% no gain, 20% Service +1, 20% Strip +1.

Target stringMeaning
selfThe girl working the job this shift.
brothel.girlsEvery girl currently assigned to the same building (including self).
player.brothelsAll buildings the player currently owns (for <SetBrothel> only).
brothelThe current building (for <SetBrothel> only).

Defines how the girl’s performance score is calculated for this shift. The score is a weighted sum of her stats and skills, modified by traits.

<?xml version="1.0" encoding="UTF-8"?>
<Performance>
<Factor skill="Service" weight="3"/>
<Factor stat="Intelligence" weight="3"/>
<Factor stat="Charisma" weight="2"/>
<Factor skill="Performance" weight="2"/>
<TraitMod trait="Psychic" delta="10"/>
<TraitMod trait="Fleet of Foot" delta="10"/>
<TraitMod trait="Cum Addict" delta="-5"/>
</Performance>

Contributes one stat or skill to the performance total.

AttributeRequiredNotes
stat or skillYes (one)The stat or skill to pull from.
weightYesMultiplier. Higher = more influence. No maximum.
<When> childNoIf present, this factor only applies when the condition is satisfied.

The engine multiplies the girl’s value by the weight for each factor, sums all factors, and divides by the total weight. The result is a number in roughly 0-1000+ range.

Flat bonus or penalty added to the score when the girl has the named trait.

AttributeNotes
traitTrait name, case-sensitive.
deltaInteger. Positive = bonus, negative = penalty.

If the girl does not have the trait, the entry has no effect.

Jobs without a performance.xml are pure-effect jobs (no gold, no performance tier). Use this shape for rest, training, and household chores.


Converts the performance score into gold earned this shift. Uses a piecewise linear curve: the engine finds which two breakpoints the score falls between and interpolates linearly. For the full picture of how wages compose with girl Ask Prices, tips, and trait modifiers, see pricing.md.

<?xml version="1.0" encoding="UTF-8"?>
<Wage currency="gold">
<Curve type="piecewise">
<Point perf="245" wage="155"/>
<Point perf="185" wage="95"/>
<Point perf="145" wage="55"/>
<Point perf="100" wage="15"/>
<Point perf="70" wage="-5"/>
<Point perf="0" wage="-15"/>
</Curve>
</Wage>
AttributeNotes
currency="gold"Only gold is supported in v1. Required.
type="piecewise"Only piecewise is supported in v1. Required.
perf on <Point>Performance score at this breakpoint. Points must be listed in descending order.
wage on <Point>Gold earned at this performance. Negative values deduct from the brothel (costs the player money).

A job without wage.xml earns no gold. This is correct for training and household-service jobs.


Controls skill and stat XP distributed at the end of each shift. XP is shared across the declared entries proportional to their weights.

<?xml version="1.0" encoding="UTF-8"?>
<Gains xp="15" baseSkill="3">
<Skill name="Service" weight="3"/>
<Skill name="Performance" weight="2"/>
<Stat name="Charisma" weight="1"/>
</Gains>
Attribute / elementNotes
xp on <Gains>Total XP distributed this shift, before weights.
baseSkill on <Gains>Flat skill-point floor. Added to each entry regardless of XP.
<Skill name="..." weight="..." Max="...">A skill that receives XP. Max (1.13.1+) caps the resulting skill value; further XP into this entry is discarded once the cap is reached.
<Stat name="..." weight="...">A stat that receives XP.
<GainTrait> / <LoseTrait>Optional. Grant or remove a trait via a progress accumulator. Key attributes: trait= (required), threshold= (default 1000), amount= (default 100). The trait fires after enough qualifying shifts. Can include a <When> gate so only matching shifts count. Progress is visible to players in Girl Details. See gains.md for the full reference and examples.

A job without gains.xml grants no skill XP. Jobs that are purely for rest or housework (cook, rest) typically omit this file.


Shift-end message variants. The engine picks one eligible entry at random (weighted) and shows it in the turn summary.

<?xml version="1.0" encoding="UTF-8"?>
<Bank id="work">
<Text id="barmaid.work.perfect.1" weight="1">
<When><Performance ge="245"/></When>
</Text>
<Text id="barmaid.work.great.1" weight="2">
<When><Performance ge="185" le="244"/></When>
</Text>
<Text id="barmaid.work.ok.1" weight="2">
<When><Performance ge="100" le="144"/></When>
</Text>
</Bank>
AttributeNotes
id on <Bank>Always work for shift messages.
id on <Text>Must match an entry in text/en.xml. Convention: <jobid>.work.<tier>.<n>.
weightRelative probability. Higher = more likely to be selected.
Updates="..."Optional shorthand for in-line stat / earnings adjustments fired together with the message. Semicolon-separated list of <Name><op><Value> entries, e.g. Updates="Tiredness+=5;Tips+=10". Recognised aliases on the earnings side: Tips= and Wages= (1.13.1+).
Image="..."Optional. Overrides the job’s <DefaultImage> for this single outcome (1.15.5+). Same vocabulary as <DefaultImage>: any image-catalog tag name (sex, oral, anal, massage, wait, ecchi, etc.). Empty or omitted: the job’s <DefaultImage> is used. Unknown tag names degrade to profile the same way <DefaultImage> does, so a typo can’t brick a shift.
<When> childIf present, this variant only fires when the condition is satisfied. Multiple eligible variants are drawn from using their weights.

Every job should have at least one entry with no <When> (or a <When> that always matches), so the turn summary always has something to show.

A single message bag can paint different portraits for different outcomes. The Masseuse bag is the canonical example: a clean shift shows the massage portrait, a happy-ending variant shows sex or oral, a refusal arm shows refuse.

<Bank id="work">
<!-- Default outcome: uses the job's <DefaultImage> (massage). -->
<Text id="masseuse.work.great.1" weight="3">
<When><Performance ge="185"/></When>
</Text>
<!-- Horny variant: paints "oral" instead of "massage". -->
<Text id="masseuse.work.horny.oral.1" weight="1" Image="oral">
<When>
<Performance ge="185"/>
<Stat name="Libido" ge="80"/>
</When>
</Text>
</Bank>

Image= is per-<Text>, never per-<Bank>. Leave it off and you fall back to <DefaultImage>. Set it to any image-catalog tag and that outcome paints the matching portrait instead. Unknown tag names degrade to profile, just like an unknown <DefaultImage>.


Localizable text strings. Each <Text id="..."> entry provides the display string for the matching id in messages/work.xml.

<?xml version="1.0" encoding="UTF-8"?>
<Locale lang="en">
<Text id="barmaid.work.perfect.1">${name} was sliding drinks all over the bar without spilling a drop.</Text>
<Text id="barmaid.work.ok.1">${name} made a few mistakes but none of them were lethal.</Text>
</Locale>

The <Text> body recognizes a small placeholder grammar evaluated at message-emission time:

TokenSubstitution
${name}The girl’s display name.
${shift}The current shift name (e.g. day, night).
${<bind_name>}The integer value of any <Bind> declared earlier in the same <Group> as the message; useful for reporting a derived performance score, a tip total, etc.
$$A literal $ character.

Unknown tokens are left in place verbatim, so a typo like ${nmae} shows up in the turn summary rather than corrupting the message; this also keeps packs authored against a newer engine readable on an older one.

<Group>
<Bind name="tip" expr="(Charisma + Service) / 20"/>
<Text id="barmaid.work.great.1">${name} pulled in ${tip} extra coins on the ${shift} shift.</Text>
</Group>

This file only holds strings. The logic (weights, conditions) lives entirely in messages/work.xml. Translators only need to touch text/en.xml (or add a text/fr.xml sibling).


  • examples/jobs-cookbook.md: three worked recipes (pure-effect, performance+wage, parameterized multi-slot)
  • snippets/effects.xml: copy-paste starter for common effect patterns
  • reference/when-conditions.md: full <When> condition language reference