Aggregate the whole suite wiki onto the Institution page
Wiki/Home.md is the suite's front door; every layer's wiki now mirrors here under Wiki/<layer>/ (core, contraband, justice, gangs, ward), so the whole suite reads in one place with the index linking to the local pages. The layer repos stay canonical -- Tools/sync-wiki.sh refreshes the aggregated copy on demand. The README's main page points to it.
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
# 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](Modder-API.md) 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:
|
||||
|
||||
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`.
|
||||
|
||||
### 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. AI disclosure: developed with substantial assistance from Claude (Anthropic).*
|
||||
Reference in New Issue
Block a user