Per the updated project policy, AI/Claude attribution lives in exactly one NOTICE file per repo and nowhere else -- removed it from the About description, README, wiki, and roadmap.
12 KiB
Propensity — nature × nurture
Propensity is the suite's disposition engine. It answers one question and only one: "would
this pawn do it?" — never "would a prisoner," never "would a colonist." Every behaviour in
the suite that hinges on a pawn's character routes through here, so the crime system, contraband
brewing, escape arming, and gang recruitment all read the same answer instead of each guessing.
The model is deliberately old-fashioned: nature × nurture.
- Nature is a fixed property of the person — their traits. Almost nobody is strongly disposed. This does not change over a pawn's life.
- Nurture is what the colony has done to them — mood, mistreatment, unmet needs, the shadow a sentence leaves. This is the half you control, and the half no other mod models.
Good conditions pull nurture down; neglect and cruelty push it up. A saint left to rot can cross the line; a monster kept content and deterred may never act. The engine is built so both of those stories are possible.
Everything on this page is verified against Source/Core/Propensity.cs.
Nature — who they are
Nature(pawn) : float in [0, 1]
n = 0.05 // baseline — everyone has a little
for each listed trait the pawn has:
n += weight
return Clamp01(n) // never below 0, never above 1
The baseline is 0.05. That is the floor of the human condition in this model: everyone is capable
of something, most people barely. From there, traits add or subtract.
Trait weights
Traits are read reflectively by defName via DefDatabase<TraitDef>.GetNamedSilentFail. If a
trait's mod is not installed, that entry is silently skipped rather than throwing a hard reference —
which is how Core consumes Vanilla Traits Expanded's dark traits without depending on VTE.
| Trait | Weight | Source | Note |
|---|---|---|---|
| Psychopath | +0.45 | Vanilla | no empathy; the heaviest single input |
| Kleptomaniac | +0.40 | Vanilla Traits Expanded | consumed, not rebuilt |
| Bloodlust | +0.35 | Vanilla | enjoys violence |
| Pyromaniac | +0.30 | Vanilla Traits Expanded | consumed, not rebuilt |
| Greedy | +0.25 | Vanilla | wants more than their share |
| Abrasive | +0.15 | Vanilla | friction with everyone |
| Ascetic | −0.15 | Vanilla | wants little, takes little |
| Kind | −0.30 | Vanilla | the strongest pull down |
The design note in the source is explicit: "We CONSUME Vanilla Traits Expanded's kleptomaniac/pyromaniac as inputs here rather than rebuild them; vanilla's own dark traits count too." Core does not add traits of its own — it reads the ones the ecosystem already has.
Worked Nature values
Because weights simply add and then clamp, Nature is easy to read by hand:
| Pawn | Arithmetic | Nature |
|---|---|---|
| Ordinary pawn (none of the above) | 0.05 |
0.05 |
| Kind pawn | 0.05 − 0.30 = −0.25 → clamp |
0.00 |
| A single Greedy trait | 0.05 + 0.25 |
0.30 |
| Psychopath | 0.05 + 0.45 |
0.50 |
| Psychopath and Kind | 0.05 + 0.45 − 0.30 |
0.20 |
| Greedy + Abrasive | 0.05 + 0.25 + 0.15 |
0.45 |
| Psychopath + Bloodlust + Kleptomaniac | 0.05 + 0.45 + 0.35 + 0.40 = 1.25 → clamp |
1.00 |
Two things fall out of this. First, the clamp is not decorative: a Kind pawn floors at exactly 0
(nature can never make them disposed, only never disposed), and a stacked monster ceilings at 1.
Second, traits genuinely net against each other — a Kind Psychopath is a real, middling 0.20, not a
contradiction the engine has to resolve by fiat.
Most rolled pawns sit at or near the 0.05 floor. That is intended: "Most pawns sit near the floor;
a rare few are strongly inclined." A colony full of ordinary people is supposed to be mostly safe on
nature alone. What makes them dangerous is nurture.
Nurture — what you have done to them
Nurture(pawn) : float (a multiplier, >= ~0.5 in normal play)
m = 1.0 // ordinary circumstance
mood = pawn.needs.mood.CurLevelPercentage (or 1.0 if none)
if mood < 0.20: m *= 2.5 // at the floor of despair
else if mood < 0.35: m *= 1.6 // badly kept
if IsHeld(pawn) and mood < 0.40: m *= 1.4 // held AND unhappy compounds
rec = criminal record (peek — does not create one)
if rec != null and rec.reform != 0:
m *= Clamp(1 - rec.reform * 0.5, 0.4, 2.0) // prisonization
m *= DeterrenceFactor(pawn) // colony climate of order (1.0 in Core alone)
return m
1.0 is an ordinary pawn under ordinary conditions. The number rises as things go wrong and falls as
they go right. Each clause below is one lever.
The multiplier ladder
| Clause | Factor | Applies when | Stacks? |
|---|---|---|---|
| Despair | ×2.5 | mood < 0.20 |
mutually exclusive with "badly kept" |
| Badly kept | ×1.6 | 0.20 ≤ mood < 0.35 |
mutually exclusive with "despair" |
| Held & unhappy | ×1.4 | IsHeld and mood < 0.40 |
on top of the mood clause |
| Prisonization | ×Clamp(1 − reform×0.5, 0.4, 2.0) |
reform ≠ 0 |
on top |
| Deterrence | ×DeterrenceFactor(pawn) |
always (neutral 1.0 in Core alone) |
on top |
The two mood clauses are an if / else-if: a pawn is either in despair or badly kept, never both.
The "held & unhappy" clause is separate and multiplies again — so a mistreated prisoner in despair
compounds 2.5 × 1.4 = 3.5 before anything else. The source calls that exactly what it is: "a badly
run cell."
Prisonization — the reform lever
The reform clause deserves its own look, because it is where a sentence leaves a permanent mark.
reform factor = Clamp(1 - reform * 0.5, 0.4, 2.0)
reform |
Meaning | Factor |
|---|---|---|
| +1.0 | fully rehabilitated | 0.50 |
| +0.5 | improving | 0.75 |
| 0 | untouched (clause skipped) | 1.00 |
| −0.5 | hardening | 1.25 |
| −1.0 | prisonized | 1.50 |
| ≥ +1.2 | (over-reformed) | clamp floor 0.40 |
| ≤ −2.0 | (utterly broken) | clamp ceiling 2.00 |
reform is nominally documented as 0..1, but the punishment machinery can and does drive it
negative — the source discusses reform < 0 ("hardened, prisonized") in as many words. Within the
realistic range [−1, +1] the factor spans 0.5 … 1.5; the 0.4 / 2.0 clamps only bite at extremes
outside that, catching a pawn who has been endlessly punished or endlessly rehabilitated.
The important design property: Core only ever reads reform here. Nothing in Core moves it. The
Justice layer's Discipline and Parole are what nudge it up or down — which means
institutionalization and recidivism become the suite's without a parallel mechanic. A pawn who was
broken by a brutal prison stays broken (nurture ×1.5) after release; a pawn genuinely reformed stays
calmer (×0.5) for good. The scar is carried by one float.
Deterrence — the climate of order
The final × DeterrenceFactor(pawn) is the seam that lets the whole colony's climate feed back into
each pawn's disposition. In Core alone it is neutral — a flat 1.0 — because Core does not know
what deterrence is. When Institution: Justice is loaded, it fills this in: a well-policed colony pulls
the factor below 1 and deters everyone a little; a lawless one pushes it above 1 and emboldens
them. That is what makes catching and punishing one pawn matter to the disposition of the rest.
The full mechanics of the seam — and how to fill it from your own mod — are on the Modder API page. For now: with Core installed by itself, this clause does nothing, and that is correct.
Would — the full roll
Nature and Nurture are ingredients. Would is the meal: the actual yes/no for a specific
behaviour at a specific base rate.
Would(pawn, baseChance, salt, cap = 0.85) : bool
chance = min(cap, baseChance * (0.1 + Nature(pawn) * 2) * Nurture(pawn))
return Rand.ChanceSeeded( Clamp01(chance), pawn.thingIDNumber ^ salt )
Three things are happening in that one line of arithmetic:
baseChanceis the caller's dial — the base rate of this behaviour for an average pawn (a rare act passes a small number, a common one a larger). Core does not decide it; the module asking the question does.(0.1 + Nature × 2)is the nature amplifier. It spans ×0.1 (a saint, Nature 0) to ×2.1 (Nature 1). At the common0.05floor it is×0.2. So a floor pawn is one-fifth as likely as the base rate; a maxed monster over twice as likely — before nurture.× Nurtureapplies circumstance on top, andmin(cap, …)caps the whole thing at0.85by default. Nobody is ever a dead certainty; there is always slack. A caller who wants a harder or softer ceiling passes their owncap.
Worked example 1 — an ordinary colonist, content
Base rate 0.10, an ordinary pawn (Nature 0.05), good mood so Nurture 1.0:
nature term = 0.1 + 0.05 * 2 = 0.2
chance = min(0.85, 0.10 * 0.2 * 1.0) = 0.02 → 2%
Two percent, and — critically — seeded. For this pawn and this question it is a fixed 2% coin that either comes up or does not; it is not re-flipped every tick.
Worked example 2 — a mistreated psychopath prisoner
A Psychopath + Bloodlust prisoner (Nature 0.85), mood 0.15 (despair ×2.5), held and unhappy
(×1.4), reform = −0.5 (hardening → ×1.25), Core-only so deterrence 1.0. Base rate 0.10:
nature term = 0.1 + 0.85 * 2 = 1.8
nurture = 2.5 * 1.4 * 1.25 = 4.375
chance = min(0.85, 0.10 * 1.8 * 4.375) = min(0.85, 0.7875) = 0.7875 → ~79%
Same base rate as example 1, same engine — but nature and a badly-run cell have turned a 2% pawn into a near-certainty. The player did that, clause by clause.
Worked example 3 — hitting the cap
A fully stacked monster (Nature clamps to 1.0), in despair (×2.5) and held & unhappy (×1.4), in a
lawless colony where Justice has set deterrence to 1.4. Base rate 0.10:
nature term = 0.1 + 1.0 * 2 = 2.1
nurture = 2.5 * 1.4 * 1.4 = 4.9
raw chance = 0.10 * 2.1 * 4.9 = 1.029
chance = min(0.85, 1.029) = 0.85 → capped at 85%
The raw product blew past 1.0; the cap reins it to 0.85. Even here, a 0.15 sliver of "not
today" survives. That sliver is deliberate — it keeps the worst pawn a character with a bad day
coming, not a scripted event.
The seeded roll — characters, not a dice cup
The last argument of Would is a salt, and the seed is:
seed = pawn.thingIDNumber ^ salt
Rand.ChanceSeeded turns that seed into a deterministic pass/fail. This is the single most
important design decision on this page, and it is worth being precise about why.
Save/reload stability. Because the seed is derived from the pawn's stable thingIDNumber and a
fixed salt — not from the live RNG stream — asking "would they?" gives the same answer before and
after a save/reload, as long as the inputs (traits, mood, reform) are the same. You cannot scum a
reload to re-roll a pawn into a different person. "WHO they are does not shift under them on a
save/reload."
A character, not a per-tick lottery. A naive implementation would flip a fresh coin every tick, so any pawn eventually does anything if you wait long enough. Seeding instead makes "would this pawn pocket a shiv?" a fixed fact about that pawn under those conditions — "what makes them a character, not a dice cup."
Live circumstance still bites. Seeding freezes the identity, not the situation. Nurture is
recomputed live, so as mood collapses or reform hardens, the same pawn's chance climbs and the same
seeded coin can flip from "no" to "yes." The pawn who would not have acted last month acts now — not
because the dice changed, but because you let their world get worse.
Different questions, different salts. Each behaviour passes its own salt, so the rolls are independent. The source puts it plainly: "a man who would pocket a shiv is not therefore a man who would inform on his cellmate." One pawn can be reliably one kind of trouble and reliably not another, and that pattern is stable across the whole game.
Part of the Institution suite.