Wiki: player-facing rewrite of every layer + add a scenario FAQ, linked from Home

This commit is contained in:
flan
2026-07-16 02:18:25 +00:00
parent ff703271ba
commit 8250dcb369
32 changed files with 1654 additions and 2510 deletions
+112 -165
View File
@@ -1,58 +1,47 @@
# 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.
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 comes back to this, so crime, 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.
- **Nature** is a fixed property of the person — their traits. Almost nobody is strongly disposed, and
this never changes 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`.
line; a monster kept content and deterred may never act. Both of those stories are possible by design.
---
## 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.
Every pawn starts from a **baseline of 0.05** — the floor of the human condition in this model:
everyone is capable of *something*, most people barely. From there, each trait the pawn has adds or
subtracts, and the total is held to the 0–1 range (never below 0, never above 1).
### 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.
Traits are matched **by name**, so if a trait's mod isn't installed that entry is simply skipped.
That's how Core uses Vanilla Traits Expanded's dark traits when VTE is present without *requiring* VTE
to be installed.
| Trait | Weight | Source | Note |
|---|---:|---|---|
| Psychopath | **+0.45** | Vanilla | no empathy; the heaviest single input |
| Kleptomaniac | **+0.40** | Vanilla Traits Expanded | consumed, not rebuilt |
| Kleptomaniac | **+0.40** | Vanilla Traits Expanded | counted if VTE is installed |
| Bloodlust | **+0.35** | Vanilla | enjoys violence |
| Pyromaniac | **+0.30** | Vanilla Traits Expanded | consumed, not rebuilt |
| Pyromaniac | **+0.30** | Vanilla Traits Expanded | counted if VTE is installed |
| 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.
Core adds no traits of its own — it reads the ones the game and your other mods already provide.
Vanilla's own dark traits count right alongside VTE's.
### Worked Nature values
@@ -60,205 +49,163 @@ 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** |
| 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`
Two things fall out of this. First, the clamp isn't 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.
contradiction.
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.
Most rolled pawns sit at or near the `0.05` floor. That's intended: 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.
Nurture is a **multiplier**. An ordinary pawn under ordinary conditions sits at **1.0**. The number
rises as things go wrong and falls as they go right. Each lever below stacks on top of the last.
### The multiplier ladder
| Clause | Factor | Applies when | Stacks? |
| Lever | 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 |
| **Despair** | ×2.5 | mood below 20% | mutually exclusive with "badly kept" |
| **Badly kept** | ×1.6 | mood 20–35% | mutually exclusive with "despair" |
| **Held & unhappy** | ×1.4 | held **and** mood below 40% | on top of the mood lever |
| **Prisonization** | × (depends on reform, see below) | the pawn has a non-zero reform score | on top |
| **Deterrence** | × (the colony's climate of order) | always (neutral 1.0 with only Core) | 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."*
The two mood levers are either/or: a pawn is either in despair *or* badly kept, never both. "Held &
unhappy" is separate and multiplies again — so a mistreated prisoner in despair compounds
`2.5 × 1.4 = 3.5` before anything else. That 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.
The reform lever is where a sentence leaves a permanent mark. A pawn's **reform score** runs from
about −1 to +1 and turns into a multiplier:
```
reform factor = Clamp(1 - reform * 0.5, 0.4, 2.0)
```
| `reform` | Meaning | Factor |
| Reform score | Meaning | Factor |
|---:|---|---:|
| **+1.0** | fully rehabilitated | 0.50 |
| +0.5 | improving | 0.75 |
| 0 | untouched (clause skipped) | *1.00* |
| 0 | untouched (no effect) | *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** |
| beyond +1.2 | (over-reformed) | floors at **0.40** |
| beyond −2.0 | (utterly broken) | ceilings at **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.
Reform is nominally a 0-to-1 rehabilitation score, but harsh punishment can drive it **negative** —
that's the "hardened, prisonized" end. Within the realistic −1…+1 range the factor spans 0.5…1.5; the
0.4 / 2.0 limits only bite at the extremes, catching a pawn who's 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.
Propensity only *reads* this score — nothing in Core moves it. The Justice layer's discipline and
parole are what nudge it up or down, which is how institutionalization and recidivism enter the game
without a separate mechanic. A pawn broken by a brutal prison stays broken (nurture ×1.5) after
release; a pawn genuinely reformed stays calmer (×0.5) for good. The scar rides on one number.
### 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 final lever multiplies by the colony's overall **climate of order**. **With only Core installed
it's neutral — a flat 1.0** — because Core by itself doesn't know what deterrence is. Install
**Institution: Justice** and 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's 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](Modder-API.md) page. For now: with Core installed by itself, this clause does nothing,
and that is correct.
With only Core installed, this lever does nothing, and that is correct. The technical details of how a
mod fills in deterrence are on the [Modder API](Modder-API.md) page.
---
## 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.
Nature and Nurture are ingredients. The full roll is the meal: the actual yes/no for a specific
behaviour at a specific base rate. For a given behaviour, the chance works out to:
```
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 )
```
> **chance = base rate × (0.1 + Nature × 2) × Nurture**, capped at 85%.
Three things are happening in that one line of arithmetic:
Three things are happening there:
1. **`baseChance`** is 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.
2. **`(0.1 + Nature × 2)`** is the nature amplifier. It spans **×0.1** (a saint, Nature 0) to
**×2.1** (Nature 1). At the common `0.05` floor 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.
3. **`× Nurture`** applies circumstance on top, and `min(cap, …)` caps the whole thing at `0.85` by
default. Nobody is ever a dead certainty; there is always slack. A caller who wants a harder or
softer ceiling passes their own `cap`.
1. **Base rate** is the behaviour's own dial — how likely an *average* pawn is to do this particular
thing (a rare act uses a small number, a common one a larger). Core doesn't decide it; whichever
system is asking does.
2. **(0.1 + Nature × 2)** is the nature amplifier. It spans **×0.1** (a saint, Nature 0) to **×2.1**
(Nature 1). At the common `0.05` floor it's **×0.2** — so an ordinary pawn is one-fifth as likely
as the base rate, while a maxed monster is over twice as likely — *before* nurture.
3. **× Nurture** applies circumstance on top, and the whole thing is capped at **85%** by default.
Nobody is ever a dead certainty; there's always slack. A behaviour that wants a harder or softer
ceiling can set its own cap.
### Worked example 1 — an ordinary colonist, content
Base rate `0.10`, an ordinary pawn (Nature `0.05`), good mood so Nurture `1.0`:
Base rate 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%
```
- nature term = 0.1 + 0.05 × 2 = 0.2
- chance = 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.
Two percent — and, critically, **fixed**. For this pawn and this question it's a settled 2% coin that
either comes up or doesn't; it isn't re-flipped every moment.
### 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`:
A Psychopath + Bloodlust prisoner (Nature 0.85), mood 15% (despair ×2.5), held and unhappy (×1.4),
reform −0.5 (hardening → ×1.25), Core-only so deterrence 1.0. Base rate 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%
```
- nature term = 0.1 + 0.85 × 2 = 1.8
- nurture = 2.5 × 1.4 × 1.25 = 4.375
- chance = 0.10 × 1.8 × 4.375 = 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.
a near-certainty. The player did that, lever by lever.
### 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`:
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 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%
```
- 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
- 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 raw product blew past 100%; the cap reins it to 85%. Even here, a 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 fixed roll — characters, not a dice cup
The last argument of `Would` is a **salt**, and the seed is:
Each pawn's answer to "would they?" is **fixed** by two things: who the pawn is (their permanent
identity) and which behaviour is being asked about. It is not re-rolled from scratch each time. This is
the single most important design decision on this page, and it's worth being precise about why.
```
seed = pawn.thingIDNumber ^ salt
```
**Save/reload stability.** Because the answer is tied to the pawn's permanent identity and the specific
question — not to the live random 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 can't scum a reload to
re-roll a pawn into a different person. *Who* they are doesn't shift under them on a reload.
`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.
**A character, not a moment-to-moment lottery.** If the game flipped a fresh coin every tick, any pawn
would eventually do anything if you waited long enough. Fixing the roll instead makes "would this pawn
pocket a shiv?" a settled fact about that pawn under those conditions — what makes them a character,
not a dice cup.
**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
**Live circumstance still bites.** The roll 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
fixed coin can flip from "no" to "yes." The pawn who wouldn't 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.
**Different questions, independent answers.** Each behaviour is its own separate question, so the
answers don't leak into each other: a pawn who would pocket a shiv is not therefore a pawn who would
inform on a cellmate. One pawn can be reliably one kind of trouble and reliably not another, and that
pattern is stable across the whole game.
---