Player-facing wiki rewrite
+145
@@ -0,0 +1,145 @@
|
|||||||
|
# FAQ — handling what Institution throws at you
|
||||||
|
|
||||||
|
New situations the suite introduces, and what to actually do about them. Grouped by where the trouble
|
||||||
|
comes from. Every layer is a checkbox in **Options → Mod Settings** — if a whole category of problem
|
||||||
|
isn't for you, turn that layer off and it stops immediately.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Escapes & contraband
|
||||||
|
|
||||||
|
**A prisoner is digging a tunnel under my wall!**
|
||||||
|
Order a warden to **search** them — a search can turn up the digging tool and end the tunnel, and a
|
||||||
|
half-dug shaft is easier to spot than the pick that started it. Tunnels take days, so you have time.
|
||||||
|
Prevention: house dangerous prisoners in **interior** cells with no short line to the wilderness, and
|
||||||
|
keep them searched. See [Tunnels](contraband/Tunnels.md) and [Warden Search](contraband/Warden-Search.md).
|
||||||
|
|
||||||
|
**My prisoner keeps having a shiv / weapon on them.**
|
||||||
|
Contraband is hidden — you won't see it in the gear tab; that's the point. Order **searches**, and
|
||||||
|
search the *likely* prisoner first: a prisoner with a bad record and a gnawed bed is the one to turn
|
||||||
|
over. If searches keep coming up empty but weapons keep appearing, suspect a **bent warden** or a
|
||||||
|
**gang** resupplying them (below). See [Concealment & Intake](contraband/Concealment-and-Intake.md).
|
||||||
|
|
||||||
|
**Where is all this contraband coming from? I keep searching!**
|
||||||
|
Three doors: prisoners **make** it (from their cell's own furniture), a **bent warden** smuggles it in,
|
||||||
|
or a **gang** moves it from a stashed member to a searched one. Deny the materials (barer cells), watch
|
||||||
|
your wardens, and **segregate** gang rivals so the network can't reach across the wing.
|
||||||
|
|
||||||
|
**One of my wardens is corrupt / letting things through.**
|
||||||
|
Warden corruption is a hidden disposition — a greedy or low-conduct colonist makes a leakier warden and
|
||||||
|
may even smuggle. You can't get a clean readout, so if a wing stays dirty no matter how you search,
|
||||||
|
rotate who wardens it. See [Corruption](contraband/Corruption.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prisoners who won't reform
|
||||||
|
|
||||||
|
**I'm warding a prisoner but their reform isn't moving.**
|
||||||
|
Reform only moves when a warden actually runs **rehabilitation** sessions — assign a warden to the
|
||||||
|
prisoner and let them work. A **better cell** (more impressive) and a **higher-Social** warden reform
|
||||||
|
faster; a bare cell and a rookie crawl. Don't leave a flagged prisoner untended: **neglect hardens**
|
||||||
|
them. See [Rehabilitation](corrections/Rehabilitation.md).
|
||||||
|
|
||||||
|
**A prisoner is "prisonized" / getting worse the longer I hold them.**
|
||||||
|
Harsh **discipline** (solitary) raises the colony's order but **hardens** the prisoner — held long and
|
||||||
|
handled roughly, they turn more dangerous, not less. If you want them out reformed, counsel more and
|
||||||
|
discipline less. Discipline is a tool for *order*, not for *rehabilitation*. See
|
||||||
|
[Discipline & Reform](corrections/Discipline-and-Reform.md).
|
||||||
|
|
||||||
|
**How do I actually release a reformed prisoner?**
|
||||||
|
Set their interaction mode to **Parole**. They go free when their **reform** is high enough **and** their
|
||||||
|
**security grade** is low enough (a Supermax pawn won't parole). With Ideology, an execution-happy
|
||||||
|
ideoligion sets a *high* bar (paroles rarely); an execution-abhorring one lets them out readily. A
|
||||||
|
"Ready for parole" alert tells you when one qualifies. See [Parole](corrections/Parole.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Classification & housing
|
||||||
|
|
||||||
|
**I got a "dangerous prisoner unsecured" alert.**
|
||||||
|
A high-grade prisoner (Maximum/Supermax) is being held somewhere too weak for their grade — in the open,
|
||||||
|
or sharing a cell. Move them to a stronger wing. Place a wall-mounted **security marker** to grade a
|
||||||
|
whole cell block at once, then the game knows which prisoners are misplaced. See
|
||||||
|
[Classification](corrections/Classification.md).
|
||||||
|
|
||||||
|
**A prisoner is miserable and I don't know why.**
|
||||||
|
Two common Institution causes: they're **caged with a dangerous cellmate** (cell-safety mood hit — give
|
||||||
|
them a solo cell) or they're on a **Lockup** schedule with no recreation. Give dangerous prisoners their
|
||||||
|
own cells and hand prisoners some **Yard** time. See [Regime](corrections/Regime.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Colony crime (Policing layer)
|
||||||
|
|
||||||
|
**My own colonists are committing crimes now!**
|
||||||
|
That's the **Policing** layer — colonists offend on the same propensity spectrum as everyone else, more
|
||||||
|
often when they're mistreated, dangerous by nature, or living in a big anonymous colony. Keep colonists
|
||||||
|
happy and the colony tight and it's rare. If you don't want colony crime at all, turn off the
|
||||||
|
**Policing** toggle and the rest of the suite keeps working. See [Policing](policing/Policing.md).
|
||||||
|
|
||||||
|
**A crime happened but no one was caught.**
|
||||||
|
Witnesses and a **constable's** investigation accrue evidence over time before a culprit is named — give
|
||||||
|
it a few days and assign someone capable to police work. Not every crime is solved. See
|
||||||
|
[Policing](policing/Policing.md).
|
||||||
|
|
||||||
|
**I don't want to arrest my star colonist over a petty theft.**
|
||||||
|
You often won't have to — arrests are **weighed**: a valuable colonist and a minor crime tips toward
|
||||||
|
letting it slide, and a small colony (or no free prison bed) won't arrest at all. And a disposed culprit
|
||||||
|
may **resist** the arrest and try to break out.
|
||||||
|
|
||||||
|
**A riot broke out!**
|
||||||
|
The colony's **climate of order** collapsed. Order falls when crime goes unanswered and rises with
|
||||||
|
**visible justice** — arrests and discipline. Riots only catch when order is already low, so the fix is
|
||||||
|
to stop letting crime pay: catch and punish, and order recovers. See [Deterrence](policing/Deterrence.md).
|
||||||
|
|
||||||
|
**Why does punishing one prisoner seem to calm the whole colony?**
|
||||||
|
By design — the order climate feeds back into **everyone's** disposition. Catching and punishing one
|
||||||
|
pawn deters the rest a little; letting crime slide emboldens them. That feedback is the whole point of
|
||||||
|
the suite. See [Deterrence](policing/Deterrence.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gangs
|
||||||
|
|
||||||
|
**A gang formed in my prison.**
|
||||||
|
Gangs are a **symptom of a badly-run colony** — disposed prisoners band together along ties they already
|
||||||
|
share. The counter-play is the rest of the suite: **segregate** rivals into different wings (a network
|
||||||
|
whose members can't reach each other is starved), keep prisoners reformed, and don't let contraband
|
||||||
|
flow. A well-run colony grows few gangs, if any. See [Joining](gangs/Joining.md) and
|
||||||
|
[Affiliations & Segregation](gangs/Affiliations-and-Segregation.md).
|
||||||
|
|
||||||
|
**Two prisoners keep fighting.**
|
||||||
|
Rival-gang brawls are booked as **crimes** — they land on the attacker's record and erode the colony's
|
||||||
|
order, so they matter beyond the moment. Segregating the rivals is how you stop it. See
|
||||||
|
[Rivalry & Fights](gangs/Rivalry-and-Fights.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Psychiatric care (Ward layer)
|
||||||
|
|
||||||
|
**A prisoner broke mentally — I'd rather treat them than punish them.**
|
||||||
|
Commit them to the **Ward**: sustained counselling reduces the disorder that broke them and builds a
|
||||||
|
recovery track toward **discharge**. It's the other door out of the prison — recovery instead of
|
||||||
|
punishment. Don't neglect a committed patient, or they deteriorate. A **sedative** can calm a patient
|
||||||
|
who's about to break. See [Commitment](ward/Commitment.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Settings & compatibility
|
||||||
|
|
||||||
|
**This is too much / I only want part of it.**
|
||||||
|
Every layer is independent. Open **Options → Mod Settings → Institution** and untick any of Contraband,
|
||||||
|
Policing, Corrections, Gangs, or Ward. The one you turn off goes inert immediately; the rest still work.
|
||||||
|
|
||||||
|
**Does it play nice with other prison / psychiatry mods?**
|
||||||
|
Yes — Institution rides vanilla's own prisoner rail and is tested against the popular psych/prison mods.
|
||||||
|
It extends rather than replaces, so it coexists with the mods that own the parts it doesn't. Ward in
|
||||||
|
particular is built to sit alongside Hospitality, Psychology, Rim Disorders and friends.
|
||||||
|
|
||||||
|
**Do I need anything else?**
|
||||||
|
Just **[Harmony](https://steamcommunity.com/sharedfiles/filedetails/?id=2009463077)**. The shared Core
|
||||||
|
engine ships inside the mod. **Foul Play** (a separate mod) bridges in if you also run it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Back to the [Wiki home](Home.md).*
|
||||||
+6
-4
@@ -9,10 +9,12 @@ That one sentence is the whole suite. Colonists, prisoners, and slaves drift tow
|
|||||||
continuous spectrum; a policing-and-corrections machine discovers, catches, weighs, punishes, reforms,
|
continuous spectrum; a policing-and-corrections machine discovers, catches, weighs, punishes, reforms,
|
||||||
treats, and releases them — and catching one pawn changes the disposition of the rest.
|
treats, and releases them — and catching one pawn changes the disposition of the rest.
|
||||||
|
|
||||||
**Institution** is the single mod that ships all of it. It bundles the suite's layers into one
|
**Institution** is the single mod that ships all of it — every layer bundled into one install with a
|
||||||
install with a **checkbox per layer** (all on by default), owning no gameplay code itself — it is a
|
**checkbox per layer** (all on by default), so you run as much or as little of the suite as you like.
|
||||||
thin settings shim over the same separate, still-splittable feature DLLs. Pull it apart into its
|
|
||||||
layers again and nothing is lost.
|
> **New to it?** The **[FAQ](FAQ.md)** covers what to do when the mod throws something new at you — an
|
||||||
|
> escape tunnel, a gang in the wing, a riot, a prisoner who won't reform, a "dangerous prisoner
|
||||||
|
> unsecured" alert.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+68
-80
@@ -1,128 +1,116 @@
|
|||||||
# Compatibility
|
# Compatibility
|
||||||
|
|
||||||
*Contraband ships zero Harmony patches. It adds defs, subclasses a work-giver, mutates two duty
|
*Contraband adds its own content and reads from the base game rather than rewriting it. That is why it
|
||||||
trees at startup, and polls. That is why it composes instead of colliding.*
|
composes with other mods instead of colliding with them.*
|
||||||
|
|
||||||
## Requires: Institution: Core
|
## Requires: Institution: Core
|
||||||
|
|
||||||
Contraband declares a hard `modDependency` on **`flan.institution.core`** and loads after it. This
|
Contraband has a hard dependency on **Institution: Core** and loads after it. This is not optional —
|
||||||
is not optional — after the split, the entire substrate Contraband reads lives in Core:
|
after the split, everything Contraband reads lives in Core:
|
||||||
|
|
||||||
| Core provides | Contraband uses it for |
|
| Core provides | Contraband uses it for |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `SecuredContext` / `SecuredContexts` / `CanConceal` / `IsHeld` | who counts as a concealer; who can be intaken, whittle, or dig |
|
| Which pawns count as "in custody," and who can hide things | who counts as a concealer; who can be intaken, whittle, or dig |
|
||||||
| `Propensity.Would(pawn, base, salt)` | every disposition roll — intake (0.6), improvise (0.10), dig (0.08), all seeded and scaled by nature × nurture |
|
| A pawn's criminal propensity (nature × nurture) | every disposition roll — intake (0.6), improvise (0.10), dig (0.08), all scaled and settled per pawn |
|
||||||
| `CriminalRecord` + `GameComponent_CriminalRecords` | `timesSearched`, `timesCaught`, `escapeAttempts`, `contrabandMade` — written by search and acquisition, read by priority |
|
| The criminal record | times searched, times caught, escape attempts, contraband made — written by search and acquisition, read by search priority |
|
||||||
|
|
||||||
Install Contraband without Core and it will not run. Everything else on this page is optional
|
Install Contraband without Core and it will not run. Everything else on this page is optional interplay
|
||||||
interplay that degrades gracefully when the other mod is absent.
|
that degrades gracefully when the other mod is absent.
|
||||||
|
|
||||||
## Zero Harmony patches
|
## It plays nicely with other prison mods
|
||||||
|
|
||||||
Post-split, the mod's one Harmony patch (the regime that forces Joy for prisoners) moved to
|
Contraband adds its own content rather than rewriting the base game's. It:
|
||||||
**Institution: Justice**, so Contraband is Harmony-free again. Instead of patching, it:
|
|
||||||
|
|
||||||
- **Adds a new `WorkGiver`** (`WorkGiver_Warden_Search`) rather than patching a vanilla one.
|
- **Adds a new warden job** (search) rather than changing an existing one.
|
||||||
- **Subclasses `WorkGiver_Warden`**, inheriting its behaviour rather than overriding it.
|
- **Reuses vanilla warden behaviour** rather than overriding it, so anything that adjusts warden
|
||||||
- **Mutates two `DutyDef` think trees at startup** via `StaticConstructorOnStartup` (not Harmony) —
|
behaviour on the base class flows through to searches for free.
|
||||||
an XML patch on a duty think tree silently no-ops in 1.6, so it is done in C#, deterministically.
|
- **Extends two escape-related behaviours at startup** so prisoners arm themselves and use what they
|
||||||
- **Polls** every held pawn from a `MapComponent` for intake, improvisation, and tunnels — no hook
|
are hiding when they break — inserted at a specific point, and it logs a warning if that anchor is
|
||||||
on the capture event is needed because the tick already visits every held pawn.
|
missing rather than failing silently.
|
||||||
|
- **Runs on a heartbeat** over held pawns for intake, improvisation, and tunnels — no hook on the
|
||||||
|
capture event is needed because the heartbeat already visits every held pawn.
|
||||||
|
|
||||||
The net effect: the surfaces other prison mods touch are mostly untouched here, so conflicts are the
|
The net effect: the surfaces other prison mods touch are mostly untouched here, so conflicts are the
|
||||||
exception, not the rule.
|
exception, not the rule.
|
||||||
|
|
||||||
## Foul Play — the carboy bridge
|
## Foul Play — the carboy bridge
|
||||||
|
|
||||||
**Foul Play** (the "Piss Nuke" mod) stays its own mod with its own front door, and yet its **carboy
|
**Foul Play** (the "Piss Nuke" mod) stays its own mod with its own front door, and yet its **carboy is
|
||||||
is a first-class contraband item.** This is the flagship demonstration of Contraband's *"items are
|
a first-class contraband item.** This is the flagship demonstration of Contraband's *"items are
|
||||||
defs, subsystems are classes, mods are audiences"* principle, and it works through two seams:
|
content, subsystems are shared, mods are audiences"* principle, and it works two ways:
|
||||||
|
|
||||||
1. **Cross-assembly worker resolution.** `CompProperties_Contraband`'s `useWorker` / `escapeWorker`
|
1. **The carboy brings its own behaviour.** The carboy simply declares itself as contraband and points
|
||||||
fields are plain `Type`s, and RimWorld resolves def `Type` fields with
|
at the throw-the-carboy behaviour that lives in *Foul Play*. Contraband never has to know that
|
||||||
`GenTypes.GetTypeInAnyAssembly` — searching **every loaded assembly**. So the carboy declares
|
behaviour exists — it just runs whatever the item brought with it.
|
||||||
`CompProperties_Contraband` and points `useWorker` at a class that lives in *Foul Play's* assembly
|
2. **A concealed carboy keeps its contents.** A carboy is not just "a jar" — it is a jar *of* a
|
||||||
(e.g. a throw-the-carboy worker). Contraband never has to know that class exists.
|
fermented blend, and that blend has to survive being concealed. When a carboy is intaken or
|
||||||
2. **The opaque content tag.** A carboy is not just "a jar" — it is a jar *of* a fermented blend, and
|
smuggled, Contraband captures a label for its contents blindly; on redemption it hands the label
|
||||||
that blend has to survive being concealed. Foul Play sets Contraband's two delegates:
|
back to Foul Play to restore the blend. **Contraband knows nothing about substances or
|
||||||
|
fermentation** — that framework lives entirely in Foul Play. If Foul Play is absent, no content is
|
||||||
```csharp
|
captured and nothing breaks: a jar is just a jar. See
|
||||||
ContrabandUtility.ContentTagOf = thing => /* serialise the carboy's blend */;
|
|
||||||
ContrabandUtility.ApplyContent = (thing, tag) => /* pour that blend back in on redeem */;
|
|
||||||
```
|
|
||||||
|
|
||||||
When a carboy is intaken or smuggled, Contraband captures the tag blind; on redemption it hands
|
|
||||||
the tag back to Foul Play to restore the contents. **Contraband knows nothing about substances or
|
|
||||||
fermentation** — that framework lives entirely in Foul Play. If Foul Play is absent, both delegates
|
|
||||||
are null, no content is captured, and nothing breaks: a jar is just a jar. See
|
|
||||||
[Concealment and Intake](Concealment-and-Intake.md).
|
[Concealment and Intake](Concealment-and-Intake.md).
|
||||||
|
|
||||||
The dependency runs one way only: Foul Play bridges *to* Contraband (and to Core) if they are
|
The dependency runs one way only: Foul Play bridges *to* Contraband (and to Core) if they are present;
|
||||||
present; Contraband takes **no** dependency on Foul Play.
|
Contraband takes **no** dependency on Foul Play.
|
||||||
|
|
||||||
## Institution: Justice — the record is the shared bus
|
## Institution: Justice — the record is the shared bus
|
||||||
|
|
||||||
Contraband and **Justice** never call each other. They meet on Core's `CriminalRecord`:
|
Contraband and **Justice** never call each other. They meet on Core's criminal record — Contraband
|
||||||
|
writes to it, Justice reads from it:
|
||||||
|
|
||||||
```
|
| Contraband writes | Justice reads it into |
|
||||||
Contraband writes ──▶ CriminalRecord ──▶ Justice reads
|
|---|---|
|
||||||
timesSearched (in Core) Classification (security grade)
|
| times searched | Classification (security grade) |
|
||||||
timesCaught Deterrence (colony-wide signal)
|
| times caught | Deterrence (colony-wide signal) |
|
||||||
escapeAttempts Discipline / Parole (reform)
|
| escape attempts | Discipline / Parole (reform) |
|
||||||
contrabandMade
|
| contraband made | |
|
||||||
```
|
|
||||||
|
|
||||||
- **Catching contraband feeds classification.** A prisoner repeatedly caught with shivs or foiled
|
- **Catching contraband feeds classification.** A prisoner repeatedly caught with shivs or foiled
|
||||||
mid-tunnel accrues `timesCaught` and `escapeAttempts`, which Justice's classification weighs into a
|
mid-tunnel racks up catches and escape attempts, which Justice's classification weighs into a higher
|
||||||
higher security grade.
|
security grade.
|
||||||
- **And it feeds back.** Justice's deterrence signal flows *back* through Core's propensity seam
|
- **And it feeds back.** Justice's deterrence signal flows *back* through Core's propensity, nudging
|
||||||
(`Nurture`), nudging every future intake / improvise / dig roll a Contraband pawn makes. A prison
|
every future intake / improvise / dig roll a Contraband pawn makes. A prison that catches and
|
||||||
that catches and disciplines becomes a prison where fewer prisoners bother trying — without either
|
disciplines becomes a prison where fewer prisoners bother trying — without either mod referencing the
|
||||||
mod referencing the other. Run Contraband alone and the deterrence factor is a neutral 1.0; the
|
other. Run Contraband alone and deterrence is a neutral 1.0; the record still routes the warden.
|
||||||
record fields still route the warden.
|
|
||||||
|
|
||||||
## Institution: Gangs — the contraband economy
|
## Institution: Gangs — the contraband economy
|
||||||
|
|
||||||
**Gangs** depends on Core, **Contraband**, and Justice, because a gang *is* a contraband economy:
|
**Gangs** depends on Core, **Contraband**, and Justice, because a gang *is* a contraband economy:
|
||||||
|
|
||||||
- Gang smuggling moves contraband through the same public `Conceal` door intake and corruption use.
|
- Gang smuggling moves contraband through the same door intake and corruption use.
|
||||||
- A gang's outside members are the natural caller for the **reach-in corruption** primitive
|
- A gang's outside members are the natural caller for the **reach-in corruption** — paying a bent
|
||||||
(`Corruption.Smuggle`) — paying a bent warden to make a delivery. That primitive is exposed in
|
warden to make a delivery. That ability exists in Contraband but has no in-game trigger of its own;
|
||||||
Contraband but has no in-repo trigger; Gangs is what drives it. See [Corruption](Corruption.md).
|
Gangs is what drives it. See [Corruption](Corruption.md).
|
||||||
- Gang fights route to Justice's crime record, which then raises search priority here. The loop
|
- Gang fights route to Justice's crime record, which then raises search priority here. The loop closes
|
||||||
closes through Core again.
|
through Core again.
|
||||||
|
|
||||||
## Prison mods the suite is tested against
|
## Prison mods the suite is tested against
|
||||||
|
|
||||||
| Mod | Interaction | Verdict |
|
| Mod | Interaction | Verdict |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Prison Commons** | postfixes `WorkGiver_Warden.ShouldSkip` on the **base** class; Contraband's search subclasses it and does not override, so searches respect prison-commons/allowed areas **for free** | works by inheritance |
|
| **Prison Commons** | it adjusts warden behaviour on the base class, and Contraband's search inherits that, so searches respect prison-commons/allowed areas **for free** | works by inheritance |
|
||||||
| **Custom Prisoner Interactions** | patches only the *named* vanilla warden givers (`_Chat`, `_Convert`, `_Enslave`, `_ReleasePrisoner`); it cannot see a brand-new giver | no conflict |
|
| **Custom Prisoner Interactions** | it only touches the *named* vanilla warden jobs (chat, convert, enslave, release); it cannot see a brand-new job | no conflict |
|
||||||
| **Prisoner Realism** | its escapes are for "mood prisoners" mid-break; a tunnel breakout hands off to vanilla `PrisonBreakUtility.StartPrisonBreak`, which Prisoner Realism layers on. Tunnels **complement** it — they let ward patients and slaves who would never mood-break still, patiently, dig out | complementary |
|
| **Prisoner Realism** | its escapes are for "mood prisoners" mid-break; a tunnel breakout hands off to the vanilla prison-break flow, which Prisoner Realism layers on. Tunnels **complement** it — they let ward patients and slaves who would never mood-break still, patiently, dig out | complementary |
|
||||||
| **Prison Labor** | prisoners working outside the cell are still held pawns and still tracked; Contraband adds no patches to labor jobs. A work assignment is simply another place a disposed prisoner might come by materials | no conflict |
|
| **Prison Labor** | prisoners working outside the cell are still held pawns and still tracked; Contraband adds nothing to labor jobs. A work assignment is simply another place a disposed prisoner might come by materials | no conflict |
|
||||||
| **Prisoner Recreation** | overlaps the *regime* feature — which now lives in **Institution: Justice**, not here, and is written to be idempotent with Prisoner Recreation | not Contraband's concern post-split |
|
| **Prisoner Recreation** | overlaps the *regime* feature — which now lives in **Institution: Justice**, not here, and is written to coexist with Prisoner Recreation | not Contraband's concern post-split |
|
||||||
|
|
||||||
## Load order
|
## Load order
|
||||||
|
|
||||||
```
|
1. Base game
|
||||||
Ludeon.RimWorld
|
2. **Institution: Core** — required, loads before Contraband
|
||||||
flan.institution.core ← required, loads before Contraband
|
3. **Institution: Contraband** — this mod
|
||||||
flan.institution.contraband ← this mod
|
4. **Foul Play / Justice / Gangs** — any order after Core; each bridges if present
|
||||||
(Foul Play / Justice / Gangs — any order after Core; each bridges if present)
|
|
||||||
```
|
|
||||||
|
|
||||||
## The general rule
|
## The general rule
|
||||||
|
|
||||||
If a mod adds prisoner behaviour by **patching vanilla warden givers or the escape duty tree by
|
If a mod adds prisoner behaviour by **changing vanilla warden jobs or the escape flow by name**, it
|
||||||
name**, it will not see Contraband's additions, and Contraband will not see its — they pass each
|
will not see Contraband's additions, and Contraband will not see its — they pass each other harmlessly.
|
||||||
other. If a mod adds a *new* `WorkGiver` or duty node of its own, it stacks. The one place to watch is
|
If a mod adds a *new* warden job or escape behaviour of its own, it stacks. The one place to watch is
|
||||||
another mod that *also* rewrites the same two duty think trees (`PrisonerEscape`,
|
another mod that *also* rewrites the same two escape-related behaviours wholesale; Contraband inserts
|
||||||
`PrisonerAssaultColony`) wholesale; Contraband inserts its nodes at a specific anchor and logs a
|
its part at a specific point and logs a warning if that point is missing, so a load-order or conflict
|
||||||
warning if that anchor is missing, so a load-order or conflict problem is visible in the log rather
|
problem is visible in the log rather than silent.
|
||||||
than silent.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
||||||
Ward. Each stands alone; together they interlock.*
|
Ward. Each stands alone; together they interlock.*
|
||||||
**
|
|
||||||
|
|||||||
+124
-153
@@ -4,98 +4,91 @@
|
|||||||
it becomes real again.*
|
it becomes real again.*
|
||||||
|
|
||||||
This is the spine of the whole mod. Everything else — shivs, tunnels, searches, corruption — is a
|
This is the spine of the whole mod. Everything else — shivs, tunnels, searches, corruption — is a
|
||||||
different way to add to, or remove from, the private list this page describes.
|
different way to add to, or remove from, the private stash this page describes.
|
||||||
|
|
||||||
## While it is concealed, there is no Thing
|
## While it is concealed, there is no item
|
||||||
|
|
||||||
The single most important rule, and the source states it plainly:
|
The single most important rule:
|
||||||
|
|
||||||
> `MapComponent_Contraband` tracks what each pawn is hiding. **Concealed contraband is not a Thing.
|
> The prison quietly tracks what each pawn is hiding. **Concealed contraband is not an item on the
|
||||||
> It is a secret.** It is not in the gear tab, there is no icon, and the player is told nothing —
|
> map. It is a secret.** It is not in the gear tab, there is no icon, and the player is told nothing —
|
||||||
> because an item you can see in a UI is not contraband, it is inventory, and there would be nothing
|
> because an item you can see in the UI is not contraband, it is inventory, and there would be nothing
|
||||||
> for a warden to discover and no reason to ever order a search.
|
> for a warden to discover and no reason to ever order a search.
|
||||||
|
|
||||||
Concretely, a concealed item lives as a `ConcealedItem` row in a `List` on the map component. It
|
A concealed item is only a hidden fact about a pawn: what it is, how far along it is, what material it
|
||||||
carries the def, a `progress` value, the material it ended up made of, an optional source it is
|
ended up made of, an optional source it is being whittled from, its tunnel state if it is a pick, and
|
||||||
being harvested from, tunnel state if it is a pick, and an opaque `contentTag`. It holds **no
|
whatever it holds inside. It has **no physical object** anywhere in the world until one of two things
|
||||||
spawned object** anywhere in the world until one of two things happens:
|
happens:
|
||||||
|
|
||||||
| Moment | What happens | Method |
|
| Moment | What happens |
|
||||||
|---|---|---|
|
|---|---|
|
||||||
| The prisoner **uses** it | It materialises into their inventory as a real `Thing` | `TryRedeem` |
|
| The prisoner **uses** it | It materialises into their inventory as a real item |
|
||||||
| A warden **finds** it | It is deleted from the list; it never becomes a `Thing` at all | `Confiscate` |
|
| A warden **finds** it | It is deleted; it never becomes an item at all |
|
||||||
|
|
||||||
A corollary the search page leans on hard: **an empty search must still cost time.** If the
|
A corollary the search page leans on hard: **an empty search must still cost time.** If the search
|
||||||
`Search` job only ever appeared when there was genuinely something to find, the mere *offer* of the
|
job only ever appeared when there was genuinely something to find, the mere *offer* of the job would
|
||||||
job would be the discovery. You have to be able to toss a clean cell and come up with nothing.
|
be the discovery. You have to be able to toss a clean cell and come up with nothing.
|
||||||
|
|
||||||
## Where a thing can hide: `ConcealSite`
|
## Where a thing can hide
|
||||||
|
|
||||||
Every contraband def declares where it can be hidden. This is not decoration — it decides **who can
|
Every piece of contraband can hide in one of two places, and this decides **who can ever find it**:
|
||||||
ever find it**.
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
[Flags] enum ConcealSite { None = 0, OnBody = 1, InCell = 2 }
|
|
||||||
```
|
|
||||||
|
|
||||||
| Site | Holds | Found by |
|
| Site | Holds | Found by |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `OnBody` | small: a shiv, pills, a phone | searching the **pawn** |
|
| **On the body** | small: a shiv, pills, a phone | searching the **pawn** |
|
||||||
| `InCell` | bulky: a rifle, a carboy, a crude pick | tossing the **room** |
|
| **In the cell** | bulky: a rifle, a carboy, a crude pick | tossing the **room** |
|
||||||
|
|
||||||
A warden turning over a prisoner and their cell reaches both (`OnBody | InCell`). The reason the
|
A warden turning over a prisoner and their cell reaches both. The reason the two are split at all: a
|
||||||
enum is split at all: a **constable** in Justice's now-shipping policing layer, frisking a colonist
|
**constable** in Justice's policing layer, frisking a colonist named in a street crime, only reaches
|
||||||
named in a street crime, reaches only `OnBody`, so whatever is under their floorboards stays there
|
what is on the body, so whatever is under their floorboards stays there until somebody has grounds to
|
||||||
until somebody has grounds to search the *room*. The split between "who may search" and "what a
|
search the *room*. The split between "who may search" and "what a search reaches" is deliberate — see
|
||||||
search reaches" is deliberate — see [Warden Search](Warden-Search.md).
|
[Warden Search](Warden-Search.md).
|
||||||
|
|
||||||
## Three sources of contraband, and no list of items
|
## Three kinds of contraband, and no list of items
|
||||||
|
|
||||||
At startup, `ContrabandUtility` scans every `ThingDef` and decides whether it is contraband. There
|
There is no hardcoded list of contraband item names. At the start of a game, the mod looks at every
|
||||||
is no hardcoded list of item names — there are three recognisers:
|
item in the game and decides, once, whether it counts as contraband, in one of three ways:
|
||||||
|
|
||||||
1. **Explicit.** Any def carrying `CompProperties_Contraband`. This is the opt-in, and it is how a
|
1. **Explicit.** Anything a mod has deliberately marked as contraband. This is the opt-in, and it is
|
||||||
mod plugs in its own `acquireWorker` / `useWorker` / `escapeWorker` behaviour.
|
how a mod plugs in its own behaviour for how the item is acquired, used, or used to escape.
|
||||||
2. **Every drug** (`ThingDef.IsDrug`) — inferred automatically.
|
2. **Every drug** — recognised automatically.
|
||||||
3. **Every weapon** (`ThingDef.IsWeapon`) that is not `destroyOnDrop` — inferred from **mass**.
|
3. **Every weapon** that isn't destroyed when dropped — recognised from its **mass**.
|
||||||
|
|
||||||
The inference (`ContrabandUtility.Infer`) fills in concealability and hiding place so a modder never
|
For anything not marked explicitly, the mod fills in how well it hides and where, so a modder never
|
||||||
has to:
|
has to:
|
||||||
|
|
||||||
| Kind | Test | `concealability` | `hideIn` |
|
| Kind | Test | Concealability | Hides |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Hard drug | `drugCategory == Hard` | **0.75** | `OnBody \| InCell` |
|
| Hard drug | drug rated Hard | **0.75** | on body or in cell |
|
||||||
| Other drug | any other `IsDrug` | **0.5** | `OnBody \| InCell` |
|
| Other drug | any other drug | **0.5** | on body or in cell |
|
||||||
| Light weapon | `Mass ≤ 2.0` | **0.7** | `OnBody \| InCell` |
|
| Light weapon | mass ≤ 2.0 kg | **0.7** | on body or in cell |
|
||||||
| Heavy weapon | `2.0 < Mass ≤ 10.0` | **0.35** | `InCell` only |
|
| Heavy weapon | 2.0 kg < mass ≤ 10.0 kg | **0.35** | in cell only |
|
||||||
| Very heavy weapon | `Mass > 10.0` | — | **not contraband** (a minigun hides nowhere) |
|
| Very heavy weapon | mass > 10.0 kg | — | **not contraband** (a minigun hides nowhere) |
|
||||||
|
|
||||||
`concealability` runs 0..1 where **0 = a warden finds it every time and 1 = never**; higher is
|
**Concealability** runs 0 to 1, where **0 = a warden finds it every time and 1 = never**; higher is
|
||||||
harder to find. Drugs and inferred weapons get **no `acquireWorker`** (you cannot synthesise yayo in
|
harder to find. Drugs and inferred weapons get **no crafting route** (you cannot synthesise yayo in a
|
||||||
a bare cell, and you cannot conjure a rifle) and **no `useWorker`** — because vanilla already knows
|
bare cell, and you cannot conjure a rifle) and **no special use behaviour** — because vanilla already
|
||||||
what to do with them. `JobGiver_SatisfyChemicalNeed` takes a drug; an armed prisoner *is* the
|
knows what to do with them. A prisoner takes a drug on their own; an armed prisoner *is* the payload.
|
||||||
payload. Getting the item into the cell is the entire problem, and that is all these routes solve.
|
Getting the item into the cell is the entire problem, and that is all these routes solve.
|
||||||
|
|
||||||
Nothing here mutates another mod's defs. It is a lookup keyed on the def, computed once, held in a
|
Nothing here edits another mod's items. It is a one-time reading of each item's own stats. Modded
|
||||||
dictionary. Modded drugs and weapons are covered without ever being named.
|
drugs and weapons are covered without ever being named.
|
||||||
|
|
||||||
## The poll: no Harmony
|
## The heartbeat
|
||||||
|
|
||||||
`MapComponent_Contraband` runs on a heartbeat. Every **250 ticks** (`CheckIntervalTicks`) it visits
|
The prison does its ongoing work on a heartbeat. Every **250 ticks** it visits every pawn who could
|
||||||
every pawn who could be concealing something and does all the ongoing work: intake, tunnel progress,
|
be concealing something and does all the ongoing work: intake, tunnel progress, and improvisation. If
|
||||||
and improvisation. If nothing on the map is self-acquirable and there are no escape tools registered,
|
nothing on the map can be self-made and no escape tools are in play, it does nothing and costs
|
||||||
it returns immediately and costs nothing.
|
nothing.
|
||||||
|
|
||||||
Polling — rather than patching — is a deliberate architecture choice. **The tick already visits
|
Because the heartbeat already visits every held pawn, intake needs no special trigger on the capture
|
||||||
every held pawn**, so intake needs no Harmony hook on the capture event: a pawn who is in custody but
|
event: a pawn who is in custody but has not yet been processed is, by definition, one who was newly
|
||||||
not yet in the `intakeProcessed` set is, by definition, one who was newly taken. The whole mod ships
|
taken.
|
||||||
zero Harmony patches, and this is a large part of why.
|
|
||||||
|
|
||||||
### Who counts as a concealer
|
### Who counts as a concealer
|
||||||
|
|
||||||
`ContrabandUtility.Concealers(map)` is **not** just prisoners. It delegates to Core's
|
The set of pawns being tracked is **not** just prisoners. It is every pawn whose belongings are
|
||||||
secured-context predicate and yields every pawn whose belongings are anyone's business — prisoners,
|
anyone's business — prisoners, slaves, ward patients, **and free colonists**:
|
||||||
slaves, **and free colonists**:
|
|
||||||
|
|
||||||
> A free colonist forbidden their drug by a drug policy or an ideoligion is precisely the pawn who
|
> A free colonist forbidden their drug by a drug policy or an ideoligion is precisely the pawn who
|
||||||
> keeps a private stash — and vanilla already denies them, with no valve.
|
> keeps a private stash — and vanilla already denies them, with no valve.
|
||||||
@@ -107,124 +100,102 @@ authority is not.
|
|||||||
## Intake: the moment of capture
|
## Intake: the moment of capture
|
||||||
|
|
||||||
Vanilla never strips a captive *on capture* — a downed pawn keeps whatever they walked in with, and
|
Vanilla never strips a captive *on capture* — a downed pawn keeps whatever they walked in with, and
|
||||||
the warden path just hauls them to a cell. Intake is Contraband's answer to that gap.
|
the warden just hauls them to a cell. Intake is Contraband's answer to that gap, and it runs on the
|
||||||
|
first heartbeat that sees a newly-held pawn:
|
||||||
|
|
||||||
`RunIntake(pawn)` runs from the poll, on the first pass that sees a newly-held pawn:
|
1. **Held only.** The pawn must be a person and in custody — prisoner, slave, or ward patient. A free
|
||||||
|
colonist is tracked, but nobody frisks and processes *them* on the way into a cell, so their
|
||||||
1. **Held only.** The pawn must be humanlike and `IsHeld` (prisoner, slave, ward patient). A free
|
|
||||||
colonist is a concealer, but nobody frisks and processes *them* on the way into a cell, so their
|
|
||||||
carried gear is never intaken.
|
carried gear is never intaken.
|
||||||
2. **Once.** The pawn's `thingIDNumber` is added to a `HashSet<int>`. The set stores an `int`, not a
|
2. **Once.** Each captive is processed a single time; a later pass finds them already done and moves
|
||||||
pawn reference, so it holds no ghost of a captive who has since died. A second pass finds them
|
on. (It remembers by an ID, so it holds no ghost of a captive who has since died.)
|
||||||
already processed and does nothing.
|
3. **Even while downed.** Intake runs *before* the "must be awake" gate. The stash a captive walked in
|
||||||
3. **Even while downed.** Intake runs *before* the "must be awake" gate. The stash a captive walked
|
with is hidden the moment they are in custody, not when they wake — everything *after* intake
|
||||||
in with is hidden the moment they are in custody, not when they wake — everything *after* intake
|
|
||||||
(whittling, digging) needs them awake, but hiding what is already on you does not.
|
(whittling, digging) needs them awake, but hiding what is already on you does not.
|
||||||
4. **Only what can ride on a body.** `IsIntakeConcealable` accepts a thing only if its contraband
|
4. **Only what can ride on a body.** A thing can be intaken only if it is small enough to hide on the
|
||||||
props include the `OnBody` flag. A shiv up a sleeve is intaken; a rifle or a suit of armour is
|
body. A shiv up a sleeve is intaken; a rifle or a suit of armour is too bulky to palm and stays as
|
||||||
too bulky to palm and stays as ordinary strippable gear the player confiscates for free.
|
ordinary strippable gear the player confiscates for free.
|
||||||
5. **Per-item disposition.** For each concealable thing the captive carries, the mod rolls whether
|
5. **Per-item disposition.** For each concealable thing the captive carries, the mod rolls whether
|
||||||
*this* pawn would hide *this* item:
|
*this* pawn would hide *this* item — a base chance of **0.6**, then scaled by the pawn's nature ×
|
||||||
|
nurture (Core's propensity). The roll is **fixed per (pawn, item)** — a settled fact, not a fresh
|
||||||
|
coin each tick. The frightened cook surrenders the knife; the disposed lifer palms it, and always
|
||||||
|
would have.
|
||||||
|
|
||||||
```
|
When a pawn does hide an item, the real object is **removed from the game** and a hidden secret takes
|
||||||
Propensity.Would(pawn, 0.6, SaltIntake ^ item.thingIDNumber)
|
its place, preserving what the item was made of and — critically — anything it holds inside.
|
||||||
```
|
|
||||||
|
|
||||||
Base chance 0.6, then scaled by the pawn's nature × nurture (Core's `Propensity`). The roll is
|
## Preserving contents without understanding them
|
||||||
**seeded per (pawn, item)** — a fixed fact, not a fresh coin each tick. The frightened cook
|
|
||||||
surrenders the knife; the disposed lifer palms it, and always would have.
|
|
||||||
|
|
||||||
When a pawn does hide an item, the real `Thing` is **destroyed** (equipment via
|
A concealed vessel is not just "a jar" — it is a jar *of* something (hooch, piss, a fermented blend),
|
||||||
`DestroyEquipment`, inventory via `Destroy(Vanish)`) and a concealed secret is created in its place,
|
and that something has to survive being hidden and come back when the item is used. Contraband stores
|
||||||
preserving what the item was made of (`stuff`) and — critically — its `contentTag`.
|
what a vessel contains as a single opaque label and **has no idea what it means**.
|
||||||
|
|
||||||
## The content tag: preserving contents without understanding them
|
When the item is concealed, that label is captured; when the item comes back out, the label is used
|
||||||
|
to pour the contents back into the freshly-made thing. This is the exact seam **Foul Play** uses to
|
||||||
|
keep a smuggled carboy's contents intact. Without a bridge mod, no content is captured and nothing is
|
||||||
|
lost — a shiv is just a shiv.
|
||||||
|
|
||||||
A concealed vessel is not just "a jar" — it is a jar *of* something (hooch, piss, a fermented
|
Fermentation itself lives in Foul Play, not here. A concealed vessel comes back out **fresh** and
|
||||||
blend), and that something has to survive being hidden and come back on redemption. Contraband
|
ferments afterward like any other, which suits the theme: fresh-brewed piss is weak, and time is what
|
||||||
stores a single opaque string, `contentTag`, and **has no idea what it means**:
|
makes it a weapon. (The prison remembers how long the vessel was hidden, for any bridge mod that
|
||||||
|
wants to restore its aging, but it never acts on that number itself.) See
|
||||||
```csharp
|
|
||||||
// A bridge mod (Foul Play) fills these in. Null (no bridge) = items have no contents.
|
|
||||||
ContrabandUtility.ContentTagOf; // Func<Thing, string> — read on conceal
|
|
||||||
ContrabandUtility.ApplyContent; // Action<Thing, string> — applied on redeem
|
|
||||||
```
|
|
||||||
|
|
||||||
On concealment, `ContentTagOf(thing)` captures the tag (e.g. "a jar of a piss+hooch cocktail"). On
|
|
||||||
redemption, `ApplyContent(newThing, tag)` pours that content back into the freshly-made thing. This
|
|
||||||
is the exact seam **Foul Play** uses to keep a smuggled carboy's contents intact. Without a bridge,
|
|
||||||
both delegates are null, no content is captured, and nothing is lost — a shiv is just a shiv.
|
|
||||||
|
|
||||||
Fermentation itself lives in Foul Play, not here. A concealed vessel materialises **fresh** and
|
|
||||||
ferments afterward like any other, which suits the theme: fresh-brewed piss is weak, and time is
|
|
||||||
what makes it a weapon. (`concealedTick` records how long it was hidden, for any bridge that wants to
|
|
||||||
restore pre-aging — Contraband keeps the number but never acts on it.) See
|
|
||||||
[Compatibility](Compatibility.md).
|
[Compatibility](Compatibility.md).
|
||||||
|
|
||||||
## Redemption: the secret becomes a Thing
|
## Redemption: the secret becomes real
|
||||||
|
|
||||||
`TryRedeem(pawn, def)` is where a secret turns real:
|
When a hidden item is finally used, it turns real:
|
||||||
|
|
||||||
1. Choose the material — the harvested `stuff`, or the def's default if it was made from stuff.
|
1. It is made from the material it was whittled from (or the item's default material, if it was never
|
||||||
2. `ThingMaker.MakeThing(def, stuff)`.
|
made from a material).
|
||||||
3. If there was a `contentTag`, call `ApplyContent` to restore the contents.
|
2. Anything it held inside is poured back in.
|
||||||
4. `TryAdd` it to the pawn's inventory. If that fails, the thing is vanished and nothing is lost.
|
3. It is placed into the pawn's inventory. If that somehow fails, nothing is dropped and nothing is
|
||||||
5. Remove the secret from the list.
|
lost.
|
||||||
|
4. The secret is cleared from the stash.
|
||||||
|
|
||||||
The trigger for redemption is the breakout duty tree. `ThinkTreeInjection` runs once at startup and
|
The trigger for this is a breakout. When a prisoner enters an escape or assault-the-colony state, they
|
||||||
mutates two duty defs directly (an XML patch on a `DutyDef` think tree silently no-ops in 1.6, so it
|
will arm themselves and then use whatever they are hiding — the same moment a base-game prisoner would
|
||||||
is done in C#):
|
reach for a combat drug. The one contraband hook Ludeon wrote and then left reading an always-empty
|
||||||
|
inventory is exactly where this slots in. For each thing a pawn is hiding:
|
||||||
|
|
||||||
| Duty | Injected | Where |
|
- **Inert contraband** (a drug, a stashed rifle — nothing special to "use"): just materialise it and
|
||||||
|---|---|---|
|
get out of the way. Vanilla takes the drug on its own; a weapon in inventory is a weapon. But a
|
||||||
| `PrisonerEscape` | `JobGiver_ArmSelf`, then `JobGiver_UseContraband` | **after** `JobGiver_TakeCombatEnhancingDrug` |
|
stashed **weapon** only comes out for someone who would actually use it — the same person who would
|
||||||
| `PrisonerAssaultColony` | `JobGiver_ArmSelf`, then `JobGiver_UseContraband` | **before** `JobGiver_AIFightEnemies` |
|
not stop to pick a rifle off the floor does not become a fighter because the knife was in their
|
||||||
|
sock, and a pawn incapable of violence never does at all. If they qualify, the weapon moves into
|
||||||
|
their hands; otherwise it stays hidden and they run. See [Improvised Weapons](Improvised-Weapons.md).
|
||||||
|
- **Active contraband** (something with a special use — e.g. Foul Play's throw-the-carboy):
|
||||||
|
materialise it, then hand off to that item's own use behaviour.
|
||||||
|
|
||||||
`JobGiver_UseContraband` sits right beside `JobGiver_TakeCombatEnhancingDrug` — the one contraband
|
## Confiscation, and the door every route comes through
|
||||||
hook Ludeon wrote and then left reading an always-empty inventory. For each thing a pawn is hiding:
|
|
||||||
|
|
||||||
- **Inert contraband** (a drug, a stashed rifle — no `useWorker`): just materialise it and get out
|
When a warden finds contraband, it is simply removed. It **never becomes a real item** — the warden
|
||||||
of the way. Vanilla takes the drug via `JobGiver_SatisfyChemicalNeed`; a weapon in inventory is a
|
found it, and there is nothing to drop on the floor. (This is why searching is not a way for the
|
||||||
weapon. But a stashed **weapon** only comes out for someone who would actually use it
|
colony to *acquire* a prisoner's drugs; it is a way to *deny* them.)
|
||||||
(`Disposition.WouldArmSelf`) — the same person who would not stop to pick a rifle off the floor
|
|
||||||
does not become a fighter because the knife was in their sock, and a pawn incapable of violence
|
|
||||||
never does at all. If they qualify, the weapon is moved from inventory into their equipment slot;
|
|
||||||
otherwise it stays hidden and they run. See [Improvised Weapons](Improvised-Weapons.md).
|
|
||||||
- **Active contraband** (a `useWorker` — e.g. Foul Play's throw-the-carboy): materialise it, then
|
|
||||||
hand off to the worker's `TryGiveJob`, which produces the actual use job.
|
|
||||||
|
|
||||||
## Confiscation and the door every route comes through
|
At the other end, there is one single door that **every** supply route comes through — intake uses it,
|
||||||
|
a bent warden uses it ([Corruption](Corruption.md)), and a smuggling gangmate
|
||||||
|
([Institution: Gangs](Compatibility.md)) uses it. It hands a prisoner something already finished and
|
||||||
|
hidden, optionally with contents. One door, many keys.
|
||||||
|
|
||||||
`Confiscate(pawn, def)` simply removes the secret. It **never becomes a Thing** — the warden found
|
## Saving
|
||||||
it, and there is nothing to drop on the floor. (This is why searching is not a way for the colony to
|
|
||||||
*acquire* a prisoner's drugs; it is a way to *deny* them.)
|
|
||||||
|
|
||||||
At the other end, `Conceal(prisoner, def, ...)` is the public door **every** supply route comes
|
Concealed contraband is saved with your game — the whole stash, the per-pawn search cooldowns, and who
|
||||||
through — intake calls it, a bent warden calls it ([Corruption](Corruption.md)), and a smuggling
|
has already been intaken. On load, any secret whose pawn or item no longer exists is quietly dropped:
|
||||||
gangmate ([Institution: Gangs](Compatibility.md)) calls it. It hands a prisoner something already
|
prisoners who died or left take their secrets with them.
|
||||||
finished and hidden, optionally with a `contentTag`. One door, many keys.
|
|
||||||
|
|
||||||
## Persistence
|
|
||||||
|
|
||||||
`MapComponent_Contraband.ExposeData` deep-saves the secret list, the per-pawn search cooldowns, and
|
|
||||||
the `intakeProcessed` set. On load it prunes any row whose pawn or def failed to resolve — prisoners
|
|
||||||
who died or left take their secrets with them, and a null key would throw on the next tick.
|
|
||||||
|
|
||||||
## Quick reference
|
## Quick reference
|
||||||
|
|
||||||
| Constant | Value | Meaning |
|
| Constant | Value | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `CheckIntervalTicks` | 250 | poll cadence for intake / tunnels / improvisation |
|
| Heartbeat | every 250 ticks | poll cadence for intake / tunnels / improvisation |
|
||||||
| Intake base chance | 0.60 | per-item, before nature × nurture |
|
| Intake base chance | 0.60 | per-item, before nature × nurture |
|
||||||
| Intake gate | `IsHeld` + `OnBody` | only held pawns, only body-hideable items |
|
| Intake gate | held + body-hideable | only pawns in custody, only items small enough to palm |
|
||||||
| Hard-drug concealability | 0.75 | inferred |
|
| Hard-drug concealability | 0.75 | inferred from being a hard drug |
|
||||||
| Other-drug concealability | 0.50 | inferred |
|
| Other-drug concealability | 0.50 | inferred |
|
||||||
| Light-weapon concealability | 0.70 | `Mass ≤ 2.0`, hides on body |
|
| Light-weapon concealability | 0.70 | mass ≤ 2.0 kg, hides on body |
|
||||||
| Heavy-weapon concealability | 0.35 | `2.0 < Mass ≤ 10.0`, `InCell` only |
|
| Heavy-weapon concealability | 0.35 | 2.0–10.0 kg, in cell only |
|
||||||
| Unhideable mass | > 10.0 | not contraband |
|
| Unhideable mass | > 10.0 kg | not contraband |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
||||||
Ward. Each stands alone; together they interlock.*
|
Ward. Each stands alone; together they interlock.*
|
||||||
**
|
|
||||||
|
|||||||
+32
-62
@@ -3,32 +3,22 @@
|
|||||||
*A search is only as thorough as the warden running it, and a warden is a person with their own
|
*A search is only as thorough as the warden running it, and a warden is a person with their own
|
||||||
diligence and their own price.*
|
diligence and their own price.*
|
||||||
|
|
||||||
The counter to contraband is the [warden search](Warden-Search.md) — but a search is not a machine.
|
The counter to contraband is the [warden search](Warden-Search.md) — but a search is not a machine. It
|
||||||
It is a human being with traits and a mood, and Contraband models that human being on **two axes,
|
is a human being with traits and a mood, and Contraband models that human being on **two axes, both
|
||||||
both read off the same vanilla traits, mood, and relationships — no new stat to maintain**:
|
read off the same vanilla traits, mood, and relationships — no new stat to maintain**:
|
||||||
|
|
||||||
| Axis | Question | Effect on a search |
|
| Axis | Question | Effect on a search |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Diligence** | How hard do they look? | multiplies the find chance |
|
| **Diligence** | How hard do they look? | multiplies the find chance |
|
||||||
| **Corruption** | How bent are they? | a flat chance to look away — and a route *into* the prison |
|
| **Corruption** | How bent are they? | a flat chance to look away — and a route *into* the prison |
|
||||||
|
|
||||||
This is *"what turns 'the game vs the player' into 'the player's own guards, each with an angle.'"*
|
This is *"what turns 'the game vs the player' into 'the player's own guards, each with an angle.'"* The
|
||||||
The prisoner you can control. The guard you assigned to watch them, you cannot.
|
prisoner you can control. The guard you assigned to watch them, you cannot.
|
||||||
|
|
||||||
## Diligence — how hard they look
|
## Diligence — how hard they look
|
||||||
|
|
||||||
`WardenDisposition.Diligence(warden)` returns 0..1 and multiplies the whole find chance in
|
Diligence runs 0 to 1 and multiplies the whole find chance. A lax or miserable warden misses things a
|
||||||
`JobDriver_SearchPawn`. A lax or miserable warden misses things a dutiful one would turn up.
|
dutiful one would turn up. It starts from a baseline of **0.55** and moves with traits and mood:
|
||||||
|
|
||||||
```
|
|
||||||
d = 0.55 // baseline
|
|
||||||
d += 0.1 × degreeOf(Industriousness) // Lazy lowers, Industrious raises (per degree)
|
|
||||||
d += 0.15 if Abrasive // no qualms turning a cell over
|
|
||||||
d -= 0.20 if Kind // a gentle hand
|
|
||||||
d += 0.10 if Psychopath // does not care how it feels
|
|
||||||
d ×= Lerp(0.6, 1.1, mood) // a miserable warden phones it in
|
|
||||||
d = clamp01(d)
|
|
||||||
```
|
|
||||||
|
|
||||||
| Trait / factor | Δ Diligence | Reasoning |
|
| Trait / factor | Δ Diligence | Reasoning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -41,22 +31,10 @@ d = clamp01(d)
|
|||||||
|
|
||||||
## Corruption — their price
|
## Corruption — their price
|
||||||
|
|
||||||
`WardenDisposition.Corruption(warden)` returns 0..1. In a search, a bent warden gets a flat chance
|
Corruption also runs 0 to 1. In a search, a bent warden gets a flat chance to **look away** on each
|
||||||
to **look away** on each item they would otherwise have a shot at finding:
|
item they would otherwise have a shot at finding — that chance is their Corruption × 0.6, rolled per
|
||||||
|
hidden item before the find roll. Corruption starts from a baseline of **0.04** (most guards are
|
||||||
```
|
mostly honest) and moves with traits and mood:
|
||||||
lookAwayChance = Corruption(warden) × 0.6 // rolled per hidden item, before the find roll
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
c = 0.04 // baseline — most guards are mostly honest
|
|
||||||
c += 0.40 if Greedy // has a price
|
|
||||||
c += 0.15 if Kind // bends the rules out of sympathy
|
|
||||||
c -= 0.10 if Ascetic // wants nothing, cannot be bought
|
|
||||||
c -= 0.05 if Abrasive
|
|
||||||
c ×= Lerp(1.6, 0.7, mood) // a desperate, miserable warden is more temptable
|
|
||||||
c = clamp01(c)
|
|
||||||
```
|
|
||||||
|
|
||||||
| Trait / factor | Δ Corruption | Reasoning |
|
| Trait / factor | Δ Corruption | Reasoning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -68,18 +46,17 @@ c = clamp01(c)
|
|||||||
|
|
||||||
## The Kind warden is a double liability
|
## The Kind warden is a double liability
|
||||||
|
|
||||||
Read the two formulas together and one trait jumps out. **Kind** *lowers* Diligence (−0.20) **and**
|
Read the two tables together and one trait jumps out. **Kind** *lowers* Diligence (−0.20) **and**
|
||||||
*raises* Corruption (+0.15). The gentle guard both frisks softly and bends the rules out of pity —
|
*raises* Corruption (+0.15). The gentle guard both frisks softly and bends the rules out of pity — the
|
||||||
the worst warden you can put on a contraband detail, and the least obvious one, because "kind" reads
|
worst warden you can put on a contraband detail, and the least obvious one, because "kind" reads as a
|
||||||
as a virtue everywhere else in the game. Meanwhile **mood** cuts the same way on both axes: a
|
virtue everywhere else in the game. Meanwhile **mood** cuts the same way on both axes: a miserable
|
||||||
miserable warden looks *less* hard and is *more* temptable. A depressed guard is a sieve at both
|
warden looks *less* hard and is *more* temptable. A depressed guard is a sieve at both ends.
|
||||||
ends.
|
|
||||||
|
|
||||||
## Worked examples
|
## Worked examples
|
||||||
|
|
||||||
Find chance for a hidden item is `baseFind × Diligence`, with a separate `lookAway = Corruption ×
|
Find chance for a hidden item is roughly the base find chance × Diligence, with a separate look-away
|
||||||
0.6` chance to ignore it entirely. Assume a mid-skill warden whose raw find chance on a shiv is
|
chance (Corruption × 0.6) to ignore it entirely. Assume a mid-skill warden whose raw find chance on a
|
||||||
around 0.30 before disposition:
|
shiv is around 0.30 before disposition:
|
||||||
|
|
||||||
| Warden | Diligence | Corruption | Look-away/item | Net on a 0.30 shiv |
|
| Warden | Diligence | Corruption | Look-away/item | Net on a 0.30 shiv |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
@@ -95,22 +72,16 @@ guard — he is a supply line you are paying to pretend otherwise.
|
|||||||
|
|
||||||
## The bent warden as a supply route *in*
|
## The bent warden as a supply route *in*
|
||||||
|
|
||||||
Corruption is not only "looks away." The `Corruption.Smuggle` primitive is the reaching-*in* half — a
|
Corruption is not only "looks away." The other half is reaching *in* — a bent guard who slips a
|
||||||
guard who slips a prisoner contraband through the very same `Conceal` door a smuggler or a gangmate
|
prisoner contraband through the very same door a smuggler or a gangmate uses. Only what can ride on a
|
||||||
uses:
|
body gets slipped across a handshake. Whether and when a guard does this is gated on that guard's
|
||||||
|
Corruption.
|
||||||
|
|
||||||
```csharp
|
In Contraband on its own, this "smuggle in" ability exists but is not yet wired to an in-game trigger.
|
||||||
ThingDef Corruption.Smuggle(warden, prisoner, tracker)
|
It is the seam **Institution: Gangs** drives when a gang's outside members pay a bent warden to make a
|
||||||
// picks a random OnBody-hideable contraband and Conceal()s it onto the prisoner; returns the def
|
delivery, and the thing a planned secure-zone checkpoint that frisks *guards* is meant to catch.
|
||||||
```
|
Search-side corruption (looking away) is fully live today; reach-in corruption is the hook the rest of
|
||||||
|
the suite plugs into. See [Compatibility](Compatibility.md).
|
||||||
Only what can ride on a body gets slipped across a handshake (`OnBody`). The method is the **act
|
|
||||||
itself**; the caller decides *who* and *when*, gated on `WardenDisposition.Corruption`. In Contraband
|
|
||||||
on its own, this primitive is exposed but not yet wired to an in-game trigger — it is the seam
|
|
||||||
**Institution: Gangs** drives when a gang's outside members pay a bent warden to make a delivery, and
|
|
||||||
the thing a planned secure-zone checkpoint that frisks *guards* is meant to catch. Search-side
|
|
||||||
corruption (looking away) is fully live today; reach-in corruption is the hook the rest of the suite
|
|
||||||
plugs into. See [Compatibility](Compatibility.md).
|
|
||||||
|
|
||||||
## How to play with it
|
## How to play with it
|
||||||
|
|
||||||
@@ -119,9 +90,9 @@ plugs into. See [Compatibility](Compatibility.md).
|
|||||||
brutally effective if you can stomach them.
|
brutally effective if you can stomach them.
|
||||||
- **Watch mood.** A prison-duty warden in a slump is failing at both jobs — searching *and* not
|
- **Watch mood.** A prison-duty warden in a slump is failing at both jobs — searching *and* not
|
||||||
smuggling. Keep your guards content or rotate them out.
|
smuggling. Keep your guards content or rotate them out.
|
||||||
- **Redundancy beats brilliance.** Because each search is one shot per prisoner per day and even a
|
- **Redundancy beats brilliance.** Because each search is one shot per prisoner per day and even a good
|
||||||
good warden misses a well-hidden shiv, a *second* diligent warden over time matters more than a
|
warden misses a well-hidden shiv, a *second* diligent warden over time matters more than a single
|
||||||
single perfect search.
|
perfect search.
|
||||||
|
|
||||||
## Quick reference
|
## Quick reference
|
||||||
|
|
||||||
@@ -132,10 +103,9 @@ plugs into. See [Compatibility](Compatibility.md).
|
|||||||
| Diligence mood scale | ×0.6 .. ×1.1 | miserable → content |
|
| Diligence mood scale | ×0.6 .. ×1.1 | miserable → content |
|
||||||
| Corruption mood scale | ×1.6 .. ×0.7 | miserable → content |
|
| Corruption mood scale | ×1.6 .. ×0.7 | miserable → content |
|
||||||
| Look-away multiplier | ×0.6 | applied to Corruption, per item |
|
| Look-away multiplier | ×0.6 | applied to Corruption, per item |
|
||||||
| Smuggle payload | `OnBody` contraband only | what fits in a handshake |
|
| Smuggle payload | body-hideable contraband only | what fits in a handshake |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
||||||
Ward. Each stands alone; together they interlock.*
|
Ward. Each stands alone; together they interlock.*
|
||||||
**
|
|
||||||
|
|||||||
+45
-49
@@ -3,15 +3,15 @@
|
|||||||
*Prisoners get hold of things they should not have. Wardens turn the cell over looking for them.*
|
*Prisoners get hold of things they should not have. Wardens turn the cell over looking for them.*
|
||||||
|
|
||||||
This is the player-facing wiki for **Institution: Contraband**, a RimWorld 1.6 mod. It documents
|
This is the player-facing wiki for **Institution: Contraband**, a RimWorld 1.6 mod. It documents
|
||||||
every mechanic with the real numbers pulled from the source, explains why each one works the way it
|
every mechanic with the real numbers, explains why each one works the way it does, and tells you how
|
||||||
does, and tells you how to play with them — and against them.
|
to play with them — and against them.
|
||||||
|
|
||||||
> **Note on scope.** Contraband was recently *split*. It is now one thing: the **physical smuggling
|
> **Note on scope.** Contraband was recently *split*. It is now one thing: the **physical smuggling
|
||||||
> loop** of a prison. The engine it used to carry — propensity, the criminal record, the
|
> loop** of a prison. The engine it used to carry — a pawn's criminal propensity, the criminal
|
||||||
> secured-context predicate — now lives in **Institution: Core**, which this mod requires. The
|
> record, and which pawns count as "in custody" — now lives in **Institution: Core**, which this mod
|
||||||
> response systems (classification, deterrence, discipline, parole, regime) went to **Institution:
|
> requires. The response systems (classification, deterrence, discipline, parole, regime) went to
|
||||||
> Justice**, and the gang network to **Institution: Gangs**. If this page mentions "the record" or
|
> **Institution: Justice**, and the gang network to **Institution: Gangs**. When this page mentions
|
||||||
> "a pawn's propensity," those are Core's; Contraband reads them.
|
> "the record" or "a pawn's propensity," those belong to Core; Contraband reads them.
|
||||||
|
|
||||||
## The one idea
|
## The one idea
|
||||||
|
|
||||||
@@ -20,54 +20,49 @@ The whole Institution suite rests on a single sentence:
|
|||||||
> **Every pawn has a criminal propensity on a spectrum — nature (traits) × nurture (circumstance,
|
> **Every pawn has a criminal propensity on a spectrum — nature (traits) × nurture (circumstance,
|
||||||
> mood, treatment) — and the colony can police it.**
|
> mood, treatment) — and the colony can police it.**
|
||||||
|
|
||||||
Contraband is the half of that sentence you can *hold in your hand*. A propensity is abstract until
|
Contraband is the half of that sentence you can *hold in your hand*. A propensity is abstract until a
|
||||||
a disposed prisoner files a blade off his bunk, palms a twist of yayo on the way into a cell, or
|
disposed prisoner files a blade off his bunk, palms a twist of yayo on the way into a cell, or starts
|
||||||
starts digging. Contraband is where the spectrum becomes an object with edges.
|
digging. Contraband is where the spectrum becomes an object with edges.
|
||||||
|
|
||||||
## Vanilla built the demand and forgot the supply chain
|
## Vanilla built the demand and forgot the supply chain
|
||||||
|
|
||||||
RimWorld already ships a fully-built junkie. Raiders spawn addicted (`chemicalAddictionChance`,
|
RimWorld already ships a fully-built junkie. Raiders spawn addicted and carrying combat drugs. You
|
||||||
`forcedAddictions`) and carrying combat drugs (`combatEnhancingDrugsChance`). You down one, capture
|
down one, capture strips everything, and now withdrawal grips them in the cell. A prisoner already
|
||||||
strips everything (`DropAndForbidEverything`), and now withdrawal grips them in the cell
|
*seeks* a drug on their own, and already *ignores* the forbidden flag inside their own cell.
|
||||||
(`Need_Chemical`, `Hediff_Addiction`). A prisoner already *seeks* a drug on their own —
|
|
||||||
`JobGiver_SatisfyChemicalNeed` is in the prisoner think tree — and already *ignores* the forbidden
|
|
||||||
flag inside their own cell (`ForbidUtility.CaresAboutForbidden`).
|
|
||||||
|
|
||||||
Put a drug within reach of an addicted prisoner and the base game makes them take it. Today. With no
|
Put a drug within reach of an addicted prisoner and the base game makes them take it. Today. The
|
||||||
new code. The consumer, the craving, the reach, the ignore-forbidden — all of it ships.
|
consumer, the craving, the reach, the ignore-forbidden — all of it already works.
|
||||||
|
|
||||||
What does **not** ship is any answer to two questions:
|
What does **not** exist is any answer to two questions:
|
||||||
|
|
||||||
| Vanilla has | Vanilla lacks |
|
| Vanilla has | Vanilla lacks |
|
||||||
|---|---|
|
|---|---|
|
||||||
| A prisoner who wants contraband | Any route for contraband to reach the cell |
|
| A prisoner who wants contraband | Any route for contraband to reach the cell |
|
||||||
| A warden with fifteen jobs | A sixteenth called *search* |
|
| A warden with fifteen jobs | A sixteenth called *search* |
|
||||||
|
|
||||||
Search `Assembly-CSharp.dll` for `Contraband`, `Search`, `Confiscate`, `Smuggle`, `Frisk`,
|
The base game's warden can chat, convert, feed, execute, enslave, release, suppress — and cannot look
|
||||||
`Shakedown` and you get **zero types**. The warden can chat, convert, feed, execute, enslave,
|
in a pocket. Contraband builds exactly the missing two halves: **supply** and **search**. It does not
|
||||||
release, suppress — and cannot look in a pocket. Contraband builds exactly the missing two halves:
|
need to build the junkie, because Ludeon already did.
|
||||||
**supply** and **search**. It does not need to build the junkie, because Ludeon already did.
|
|
||||||
|
|
||||||
## Concealment is a secret, not an item
|
## Concealment is a secret, not an item
|
||||||
|
|
||||||
This is the load-bearing design decision, inherited from Foul Play (the Piss Nuke) and kept:
|
This is the load-bearing design decision:
|
||||||
|
|
||||||
> **While it is concealed, there is no `Thing`.**
|
> **While it is concealed, there is no item.**
|
||||||
|
|
||||||
A hidden shiv is not in the gear tab. There is no icon. There is no stack on a shelf. The player is
|
A hidden shiv is not in the gear tab. There is no icon. There is no stack on a shelf. The player is
|
||||||
told **nothing**. The item exists only as a row in `MapComponent_Contraband`'s private list, and it
|
told **nothing**. The item does not become a real object in the world until exactly two moments:
|
||||||
becomes a real `Thing` at exactly two moments:
|
|
||||||
|
|
||||||
- when the prisoner **uses** it (the secret is "redeemed" into their inventory), and
|
- when the prisoner **uses** it (it appears in their inventory), and
|
||||||
- when a warden **finds** it in a search (it is confiscated and never becomes a thing at all).
|
- when a warden **finds** it in a search (it is confiscated and never becomes an item at all).
|
||||||
|
|
||||||
Why go to the trouble? Because *an item you can see in a UI is not contraband* — it's inventory, and
|
Why go to the trouble? Because *an item you can see in the UI is not contraband* — it's inventory, and
|
||||||
there would be nothing to discover and no reason to ever order a search. The whole tension of the
|
there would be nothing to discover and no reason to ever order a search. The whole tension of the
|
||||||
system is that **you never know**. A warden you send to toss a cell might find a blade, might foil a
|
system is that **you never know**. A warden you send to toss a cell might find a blade, might foil a
|
||||||
tunnel, or might waste twenty minutes on a prisoner who was hiding nothing — and that empty search
|
tunnel, or might waste twenty minutes on a prisoner who was hiding nothing — and that empty search
|
||||||
still has to cost, or the mere *offer* of the job would be the discovery.
|
still has to cost, or the mere *offer* of the job would be the discovery.
|
||||||
|
|
||||||
Read the full lifecycle in **Concealment and Intake**.
|
Read the full lifecycle in **[Concealment and Intake](Concealment-and-Intake.md)**.
|
||||||
|
|
||||||
## The four supply routes and the one counter
|
## The four supply routes and the one counter
|
||||||
|
|
||||||
@@ -80,31 +75,33 @@ Read the full lifecycle in **Concealment and Intake**.
|
|||||||
| **The counter** | A prioritized cell toss that reads the tells | [Warden Search](Warden-Search.md) |
|
| **The counter** | A prioritized cell toss that reads the tells | [Warden Search](Warden-Search.md) |
|
||||||
|
|
||||||
And because every guard is a person with their own diligence and their own price, the search is only
|
And because every guard is a person with their own diligence and their own price, the search is only
|
||||||
as good as who you send — see **Corruption**.
|
as good as who you send — see **[Corruption](Corruption.md)**.
|
||||||
|
|
||||||
## How everything is contraband without a list
|
## How everything is contraband without a list
|
||||||
|
|
||||||
Contraband names no items. It recognises three sources at load and logs the count:
|
Contraband names no items. It recognises three kinds of thing:
|
||||||
|
|
||||||
1. **Anything carrying `CompProperties_Contraband`** — the explicit opt-in, with pluggable behaviour.
|
1. **Anything explicitly marked as contraband** — the opt-in, with pluggable behaviour a mod can
|
||||||
2. **Every drug** (`ThingDef.IsDrug`) — vanilla's, Vanilla Expanded's, mods that don't exist yet.
|
supply.
|
||||||
Hard drugs conceal better (0.75 vs 0.5).
|
2. **Every drug** — vanilla's, Vanilla Expanded's, mods that don't exist yet. Hard drugs conceal
|
||||||
3. **Every weapon** (`ThingDef.IsWeapon`) — concealability and hiding place derived from **mass**. A
|
better (0.75 vs 0.5).
|
||||||
knife rides on the body; a heavy gun goes under the floor; a minigun (over 10 kg) hides nowhere.
|
3. **Every weapon** — how well it hides, and where, is decided by its **mass**. A knife rides on the
|
||||||
|
body; a heavy gun goes under the floor; a minigun (over 10 kg) hides nowhere.
|
||||||
|
|
||||||
It is a lookup, not a patch. Nothing mutates another mod's defs. Details in
|
It reads the item's own stats rather than editing anything. Nothing changes another mod's items.
|
||||||
[Concealment and Intake](Concealment-and-Intake.md) and [Compatibility](Compatibility.md).
|
Details in [Concealment and Intake](Concealment-and-Intake.md) and [Compatibility](Compatibility.md).
|
||||||
|
|
||||||
## Index
|
## Index
|
||||||
|
|
||||||
- **[Concealment and Intake](Concealment-and-Intake.md)** — the secret model, `ConcealSite`, the
|
- **[Concealment and Intake](Concealment-and-Intake.md)** — the secret model, where a thing can hide,
|
||||||
three sources, intake on capture, redemption, and the opaque content-tag bridge.
|
the three kinds of contraband, intake on capture, redemption, and how a smuggled vessel keeps its
|
||||||
- **[Improvised Weapons](Improvised-Weapons.md)** — the shiv whittled from furniture, material
|
contents.
|
||||||
carry-through, the damage tell, and who reaches for it in a breakout.
|
- **[Improvised Weapons](Improvised-Weapons.md)** — the shiv whittled from furniture, how its
|
||||||
- **[Tunnels](Tunnels.md)** — the Prison-Architect dig under the perimeter, which pawns dig, and how
|
material carries through, the damage tell, and who reaches for it in a breakout.
|
||||||
a search uncovers the shaft.
|
- **[Tunnels](Tunnels.md)** — the Prison-Architect dig under the perimeter, which pawns dig, and how a
|
||||||
|
search uncovers the shaft.
|
||||||
- **[Warden Search](Warden-Search.md)** — the prioritized search, how tells and the record drive
|
- **[Warden Search](Warden-Search.md)** — the prioritized search, how tells and the record drive
|
||||||
priority, what a search does, and how it writes back.
|
priority, what a search does, and how it feeds back.
|
||||||
- **[Corruption](Corruption.md)** — Diligence and Corruption from traits and mood, how they scale a
|
- **[Corruption](Corruption.md)** — Diligence and Corruption from traits and mood, how they scale a
|
||||||
find, and the bent warden who looks away or smuggles in.
|
find, and the bent warden who looks away or smuggles in.
|
||||||
- **[Compatibility](Compatibility.md)** — the Core dependency, the Foul Play bridge, Justice/Gangs
|
- **[Compatibility](Compatibility.md)** — the Core dependency, the Foul Play bridge, Justice/Gangs
|
||||||
@@ -121,10 +118,9 @@ Each mod stands alone as an install; together they form one system.
|
|||||||
| **Institution: Justice** | classification, deterrence, discipline, parole, regime | Core |
|
| **Institution: Justice** | classification, deterrence, discipline, parole, regime | Core |
|
||||||
| **Institution: Gangs** | gangs as contraband economies | Core, Contraband, Justice |
|
| **Institution: Gangs** | gangs as contraband economies | Core, Contraband, Justice |
|
||||||
| **Foul Play** | vessel + substance + throw framework; the Piss Nuke; bridges its carboy in as contraband | standalone |
|
| **Foul Play** | vessel + substance + throw framework; the Piss Nuke; bridges its carboy in as contraband | standalone |
|
||||||
| **Ward** | a ward prison mode + the suite's test harness | — |
|
| **Ward** | a ward prison mode | — |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
||||||
Ward. Each stands alone; together they interlock.*
|
Ward. Each stands alone; together they interlock.*
|
||||||
**
|
|
||||||
|
|||||||
+80
-107
@@ -1,70 +1,63 @@
|
|||||||
# Improvised Weapons
|
# Improvised Weapons
|
||||||
|
|
||||||
*The acquisition route you cannot deny — because you are obliged to furnish the cell, and the
|
*The supply route you cannot deny — because you are obliged to furnish the cell, and the furniture is
|
||||||
furniture is the raw material.*
|
the raw material.*
|
||||||
|
|
||||||
You have to give a prisoner a bed. RimWorld will nag you until you do. That bed is a frame of wood,
|
You have to give a prisoner a bed. RimWorld will nag you until you do. That bed is a frame of wood, or
|
||||||
or steel, or — if you built a nice prison — plasteel. Give a disposed prisoner a season of
|
steel, or — if you built a nice prison — plasteel. Give a disposed prisoner a season of unsupervised
|
||||||
unsupervised time with it and they will file a blade off it. **The prison's own furnishings are the
|
time with it and they will file a blade off it. **The prison's own furnishings are the arsenal, and
|
||||||
arsenal, and better furniture is worse.**
|
better furniture is worse.**
|
||||||
|
|
||||||
## The shiv
|
## The shiv
|
||||||
|
|
||||||
`CB_Shiv` is a crude stabbing weapon: *"A blade filed off a bed frame and wrapped in cloth for a
|
The shiv is a crude stabbing weapon: *"A blade filed off a bed frame and wrapped in cloth for a grip.
|
||||||
grip. Barely a weapon — but a barely-a-weapon in a cell you thought was empty is a dead guard."*
|
Barely a weapon — but a barely-a-weapon in a cell you thought was empty is a dead guard."*
|
||||||
|
|
||||||
| Stat | Value |
|
| Stat | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Tech level | Neolithic |
|
| Tech level | Neolithic |
|
||||||
| Mass | 0.4 kg |
|
| Mass | 0.4 kg |
|
||||||
| `concealability` | **0.75** (hides very well — it rides on the body) |
|
| Concealability | **0.75** (hides very well — it rides on the body) |
|
||||||
| `hideIn` | `OnBody \| InCell` |
|
| Hides | on body or in cell |
|
||||||
| Point (Stab) | power 7, cooldown 1.5 s |
|
| Point (Stab) | power 7, cooldown 1.5 s |
|
||||||
| Handle (Blunt) | power 5, cooldown 1.6 s |
|
| Handle (Blunt) | power 5, cooldown 1.6 s |
|
||||||
| `acquireWorker` | `AcquireWorker_Improvise` |
|
| Use | none — an armed prisoner *is* the payload |
|
||||||
| `useWorker` | none — an armed prisoner *is* the payload |
|
|
||||||
|
|
||||||
It is **stuffable** (`stuffCategories`: Metallic, Woody, Stony), which is the entire point of the
|
Its stats come from its material: it can be made of metal, wood, or stone. A wood shiv is feeble; a
|
||||||
harvest mechanic: a shiv's stats come from its material for free. A wood shiv is feeble; a plasteel
|
plasteel one is vicious. That is the whole point of the harvest mechanic — you never furnished a cell
|
||||||
one is vicious. You never furnished a cell thinking of it as an armoury, but that is what it is.
|
thinking of it as an armoury, but that is what it is.
|
||||||
|
|
||||||
## Who whittles: the disposition gate
|
## Who whittles: the disposition gate
|
||||||
|
|
||||||
`AcquireWorker_Improvise.CanAcquire` is not "any prisoner." Three things must all be true:
|
Not just "any prisoner." Three things must all be true:
|
||||||
|
|
||||||
1. **Held.** The pawn is a prisoner, slave, or ward patient (`IsHeld`). Free colonists do not whittle
|
1. **Held.** The pawn is a prisoner, slave, or ward patient. Free colonists do not whittle shivs in
|
||||||
shivs in the commons — *yet* (the concealment layer already tracks them; only the authority to act
|
the commons — *yet* (the concealment layer already tracks them; only the authority to act on them
|
||||||
on them is missing).
|
is missing).
|
||||||
2. **Disposed.** A seeded propensity roll:
|
2. **Disposed.** A base chance of **0.10**, scaled by Core's nature × nurture. It is a *fixed fact*
|
||||||
|
about the pawn — the mistreated, high-propensity lifer chews his bed apart; the frightened cook
|
||||||
```
|
|
||||||
Propensity.Would(pawn, 0.10, SaltImprovise)
|
|
||||||
```
|
|
||||||
|
|
||||||
Base chance **0.10**, scaled by Core's nature × nurture. It is seeded per pawn, so it is a *fixed
|
|
||||||
fact* about them — the mistreated, high-propensity lifer chews his bed apart; the frightened cook
|
|
||||||
never does, and never would have, no matter how many times you reload.
|
never does, and never would have, no matter how many times you reload.
|
||||||
3. **Material in reach.** There is a workable source (see below).
|
3. **Material in reach.** There is a workable source (see below).
|
||||||
|
|
||||||
The base is deliberately low. Most prisoners are not making weapons. When one is, that is a fact
|
The base is deliberately low. Most prisoners are not making weapons. When one is, that is a fact about
|
||||||
about *who they are and how you keep them*, and the tell it leaves is meant to point you at exactly
|
*who they are and how you keep them*, and the tell it leaves is meant to point you at exactly that
|
||||||
that pawn.
|
pawn.
|
||||||
|
|
||||||
## The source: what counts as raw material
|
## The source: what counts as raw material
|
||||||
|
|
||||||
`FindSource` looks for the nearest reachable, damageable, usefully-stuffed piece of furniture:
|
The prisoner looks for the nearest reachable, damageable, usefully-made piece of furniture:
|
||||||
|
|
||||||
- **Their own assigned bed first** — it is right there in the cell.
|
- **Their own assigned bed first** — it is right there in the cell.
|
||||||
- Otherwise the **closest artificial building within 12 tiles** that qualifies.
|
- Otherwise the **closest artificial building within 12 tiles** that qualifies.
|
||||||
|
|
||||||
`IsWorkableSource` requires all of:
|
To be a workable source, a piece of furniture must be:
|
||||||
|
|
||||||
| Requirement | Why |
|
| Requirement | Why |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Made of `Stuff` | you cannot whittle a blade out of nothing |
|
| Made of a material | you cannot whittle a blade out of nothing |
|
||||||
| `useHitPoints` | it has to be damageable |
|
| Damageable | it has to take a beating |
|
||||||
| Stuff category is **Metallic, Woody, or Stony** | cloth and leather cannot be filed into a blade |
|
| Made of metal, wood, or stone | cloth and leather cannot be filed into a blade |
|
||||||
| Reachable at `Touch` through `Danger.Deadly` | they have to get to it |
|
| Reachable, even through danger | they have to get to it |
|
||||||
|
|
||||||
This is also the counter-play. A cell furnished only in cloth and hydroponics offers nothing to file.
|
This is also the counter-play. A cell furnished only in cloth and hydroponics offers nothing to file.
|
||||||
A steel bed offers a steel shiv. **What you build the cell out of decides whether it can become a
|
A steel bed offers a steel shiv. **What you build the cell out of decides whether it can become a
|
||||||
@@ -72,71 +65,54 @@ weapon, and how nasty that weapon is.**
|
|||||||
|
|
||||||
## Harvest by damage: the tell *is* the mechanic
|
## Harvest by damage: the tell *is* the mechanic
|
||||||
|
|
||||||
Each poll the pawn makes progress, `OnHarvestTick` chips at the source:
|
Each heartbeat, a whittling prisoner chips at the source — roughly **1.2% of its max hit points per
|
||||||
|
worked poll**, as blunt damage.
|
||||||
|
|
||||||
```
|
The damage is not a side effect — it is the point. A gnawed bed is **probable cause**. The same tick
|
||||||
damage = max(1, round(source.MaxHitPoints × 0.012)) // Blunt, ~1.2% of max HP per worked poll
|
that advances the shiv damages the furniture, and that visible wear is what
|
||||||
```
|
[Warden Search](Warden-Search.md) reads to send a guard to the *right* prisoner first: a badly damaged
|
||||||
|
bed shoots a prisoner to the top of the search priority list. Whittling a shiv is loud, in the only
|
||||||
The damage is not a side effect — it is the point. A gnawed bed is **probable cause**. The same
|
sense that matters — it leaves a mark you can act on.
|
||||||
poll that advances the shiv damages the furniture, and that visible wear is what
|
|
||||||
[Warden Search](Warden-Search.md) reads to send a guard to the *right* prisoner first: a badly
|
|
||||||
damaged bed shoots a prisoner to the top of the search priority list. Whittling a shiv is loud, in
|
|
||||||
the only sense that matters — it leaves a mark you can act on.
|
|
||||||
|
|
||||||
Because the source takes real damage, a determined prisoner can **reduce a piece of furniture to
|
Because the source takes real damage, a determined prisoner can **reduce a piece of furniture to
|
||||||
wreckage and move on to the next**: if the source is destroyed before the shiv is finished,
|
wreckage and move on to the next**: if the source is destroyed before the shiv is finished, they look
|
||||||
`FindSource` looks for another, and if there is none, the route **stalls** (progress is kept, not
|
for another, and if there is none, the route **stalls** (progress is kept, not lost). A materially
|
||||||
lost). A materially poor cell is a real defence — the prisoner cannot make progress with nothing to
|
poor cell is a real defence — the prisoner cannot make progress with nothing to file.
|
||||||
file.
|
|
||||||
|
|
||||||
## How long, and the material it becomes
|
## How long, and the material it becomes
|
||||||
|
|
||||||
Progress uses the default rate — roughly **1.5 unsupervised days** of active work — jittered ±25%
|
A shiv takes roughly **1.5 unsupervised days** of active work, jittered ±25% each poll so no two
|
||||||
each poll so no two shivs finish on the same schedule:
|
shivs finish on the same schedule.
|
||||||
|
|
||||||
```
|
When it is finished, it happens **silently** — the player is told nothing. Two things happen:
|
||||||
progressPerPoll = 250 / (1.5 × 60000) × Rand.Range(0.75, 1.25)
|
|
||||||
```
|
|
||||||
|
|
||||||
When progress reaches 1, the shiv is finished, **silently** — the player is told nothing. Two things
|
- **Material carry-through.** The shiv is made of whatever the source was made of. A wood bed yields a
|
||||||
happen:
|
wood shiv, a plasteel bunk a plasteel one, and its combat stats follow.
|
||||||
|
- **The record.** The pawn's tally of contraband made ticks up (in Core's record). That number feeds
|
||||||
- **Material carry-through.** `item.stuff = source.Stuff`. A wood bed yields a wood shiv, a plasteel
|
|
||||||
bunk a plasteel one. When the shiv is later redeemed into a real `Thing`, it is made of exactly
|
|
||||||
that material, and its combat stats follow.
|
|
||||||
- **The record.** `CriminalRecord.contrabandMade` is incremented (Core's record). That number feeds
|
|
||||||
the warden's future suspicion of this pawn and, if you run **Institution: Justice**, their security
|
the warden's future suspicion of this pawn and, if you run **Institution: Justice**, their security
|
||||||
classification.
|
classification.
|
||||||
|
|
||||||
## The shiv comes out in a break — and who reaches for it
|
## The shiv comes out in a break — and who reaches for it
|
||||||
|
|
||||||
A hidden shiv is inert until a breakout. When a prisoner enters the `PrisonerEscape` or
|
A hidden shiv is inert until a breakout. When a prisoner enters an escape or assault-the-colony state,
|
||||||
`PrisonerAssaultColony` duty, `JobGiver_UseContraband` offers to materialise what they are hiding —
|
they will use what they are hiding — but a **weapon** only comes out for someone who would actually
|
||||||
but a **weapon** only comes out for someone who would actually use it. That test is
|
use it. That is the same test that decides whether an escapee even bothers to pick a weapon off the
|
||||||
`Disposition.WouldArmSelf`, and it is the same gate `JobGiver_ArmSelf` uses to decide whether an
|
floor.
|
||||||
escapee even bothers to pick a weapon off the floor.
|
|
||||||
|
|
||||||
This matters because **vanilla escapees never arm themselves at all.**
|
This matters because **vanilla escapees never arm themselves at all.** The base game has the behaviour
|
||||||
`JobGiver_PickUpOpportunisticWeapon` exists and is simply absent from the escape duty tree, so a
|
for opportunistically grabbing a weapon, and simply leaves it out of the escape flow, so a base-game
|
||||||
base-game prisoner walks out unarmed, always. Arming is genuinely new behaviour, so it is kept rare
|
prisoner walks out unarmed, always. Arming is genuinely new behaviour, so it is kept rare enough that
|
||||||
enough that when it happens you recognise *who* did it.
|
when it happens you recognise *who* did it.
|
||||||
|
|
||||||
### `WouldArmSelf`
|
### Would this pawn arm itself?
|
||||||
|
|
||||||
First a **hard gate**: a pawn who `WorkTagIsDisabled(Violent)` never arms, ever — a pacifist who
|
First a **hard gate**: a pawn who is incapable of violence never arms, ever — a pacifist who picks up
|
||||||
picks up a rifle is not a rare event, it is a bug. Otherwise:
|
a rifle is not a rare event, it is a bug. Otherwise the chance is the pawn's **disposition** (who they
|
||||||
|
are, fixed) times their **circumstance** (what is happening right now), capped at **0.85** and settled
|
||||||
|
once per pawn as a fixed fact.
|
||||||
|
|
||||||
```
|
**Disposition** — who they are, fixed, nothing about today changes it. A base of 0.10, scaled by trait
|
||||||
chance = ArmDisposition(pawn) × ArmCircumstance(pawn)
|
and skill:
|
||||||
armed = WouldSeeded(pawn, min(chance, 0.85), SaltArm) // seeded — a fixed fact per pawn
|
|
||||||
```
|
|
||||||
|
|
||||||
**Disposition** — who they are, fixed, nothing about today changes it:
|
|
||||||
|
|
||||||
```
|
|
||||||
ArmDisposition = 0.10 (base) × trait × skill
|
|
||||||
```
|
|
||||||
|
|
||||||
| Factor | Value |
|
| Factor | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -145,12 +121,12 @@ ArmDisposition = 0.10 (base) × trait × skill
|
|||||||
| Brawler | ×1.8 |
|
| Brawler | ×1.8 |
|
||||||
| Wimp | ×0.3 |
|
| Wimp | ×0.3 |
|
||||||
| Kind | ×0.4 |
|
| Kind | ×0.4 |
|
||||||
| skill | `0.5 + max(Shooting, Melee)/20 × 1.5` → range **0.5 .. 2.0** |
|
| skill | scales with their best of Shooting or Melee → range **0.5 .. 2.0** |
|
||||||
|
|
||||||
*Being captured in a raid does not make someone a fighter* — plenty of prisoners are cooks and
|
*Being captured in a raid does not make someone a fighter* — plenty of prisoners are cooks and
|
||||||
conscripts. A weapon is only worth grabbing if you know what to do with it, hence the skill term.
|
conscripts. A weapon is only worth grabbing if you know what to do with it, hence the skill term.
|
||||||
|
|
||||||
**Circumstance** — what is happening right now, live, not seeded:
|
**Circumstance** — what is happening right now, live:
|
||||||
|
|
||||||
| Condition | Effect | Reasoning |
|
| Condition | Effect | Reasoning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -160,38 +136,36 @@ conscripts. A weapon is only worth grabbing if you know what to do with it, henc
|
|||||||
|
|
||||||
### Which weapon they reach for
|
### Which weapon they reach for
|
||||||
|
|
||||||
`JobGiver_ArmSelf` prefers, in order:
|
An arming escapee prefers, in order:
|
||||||
|
|
||||||
1. **Their own dropped weapon** — the gun they carried when you downed them, still lying where it
|
1. **Their own dropped weapon** — the gun they carried when you downed them, still lying where it fell
|
||||||
fell within 45 tiles. Vanilla remembers it in `Pawn_MindState.droppedWeapon` and then never uses
|
within 45 tiles. The base game remembers where it fell and then never uses that for prisoners. *"A
|
||||||
the field for prisoners. *"A man walking back to the spot where his own rifle fell is a better
|
man walking back to the spot where his own rifle fell is a better story than a man rummaging through
|
||||||
story than a man rummaging through your stockpile."* A prisoner does not care that you marked it
|
your stockpile."* A prisoner does not care that you marked it forbidden.
|
||||||
forbidden.
|
2. **Anything to hand** within 18 tiles — but only for a pawn over a higher disposition floor (0.35).
|
||||||
2. **Anything to hand** within 18 tiles — but only for a pawn over a higher disposition floor
|
Rummaging a stockpile is a further step than grabbing your own gun back.
|
||||||
(`WouldSeeded(pawn, 0.35, …)`). Rummaging a stockpile is a further step than grabbing your own gun
|
|
||||||
back.
|
|
||||||
|
|
||||||
A concealed shiv slots into this the natural way: a prisoner who has been sitting on a blade and who
|
A concealed shiv slots into this the natural way: a prisoner who has been sitting on a blade and who is
|
||||||
is the sort to use it does not leave it in his pocket. `JobGiver_UseContraband` redeems it and moves
|
the sort to use it does not leave it in his pocket — it goes straight into his hand. The pawn who
|
||||||
it straight from inventory into the equipment slot. The pawn who would *not* arm keeps it hidden and
|
would *not* arm keeps it hidden and runs — and the shiv survives to be found in a later search, or
|
||||||
runs — and the shiv survives to be found in a later search, or used in a later break.
|
used in a later break.
|
||||||
|
|
||||||
## Not only shivs: the crude pick
|
## Not only shivs: the crude pick
|
||||||
|
|
||||||
`AcquireWorker_Improvise` is shared. The **crude pick** (`CB_DiggingTool`) is filed off furniture
|
The same whittling behaviour makes the **crude pick** — filed off furniture exactly the same way, with
|
||||||
exactly the same way — same disposition gate, same damage tell — but it carries an `escapeWorker`
|
the same disposition gate and the same damage tell — but its purpose is a tunnel rather than a fight.
|
||||||
instead of weapon stats, and its purpose is a tunnel rather than a fight. See [Tunnels](Tunnels.md).
|
See [Tunnels](Tunnels.md).
|
||||||
|
|
||||||
## Quick reference
|
## Quick reference
|
||||||
|
|
||||||
| Constant | Value | Meaning |
|
| Constant | Value | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Improvise base chance | 0.10 | before nature × nurture; seeded per pawn |
|
| Improvise base chance | 0.10 | before nature × nurture; fixed per pawn |
|
||||||
| Damage per worked poll | ~1.2% of source max HP | Blunt; the visible tell |
|
| Damage per worked poll | ~1.2% of source max HP | blunt; the visible tell |
|
||||||
| Source search radius | 12 tiles | own bed first, then nearest workable furniture |
|
| Source search radius | 12 tiles | own bed first, then nearest workable furniture |
|
||||||
| Time to finish a shiv | ~1.5 active days | jittered ±25% per poll |
|
| Time to finish a shiv | ~1.5 active days | jittered ±25% per poll |
|
||||||
| Workable stuff | Metallic / Woody / Stony | cloth and leather cannot be whittled |
|
| Workable material | metal / wood / stone | cloth and leather cannot be whittled |
|
||||||
| Shiv `concealability` | 0.75 | hard to find on the body |
|
| Shiv concealability | 0.75 | hard to find on the body |
|
||||||
| Arm chance cap | 0.85 | after disposition × circumstance |
|
| Arm chance cap | 0.85 | after disposition × circumstance |
|
||||||
| Own-weapon reach | 45 tiles | their dropped weapon |
|
| Own-weapon reach | 45 tiles | their dropped weapon |
|
||||||
| Any-weapon reach | 18 tiles | scavenge floor 0.35 disposition |
|
| Any-weapon reach | 18 tiles | scavenge floor 0.35 disposition |
|
||||||
@@ -200,4 +174,3 @@ instead of weapon stats, and its purpose is a tunnel rather than a fight. See [T
|
|||||||
|
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
||||||
Ward. Each stands alone; together they interlock.*
|
Ward. Each stands alone; together they interlock.*
|
||||||
**
|
|
||||||
|
|||||||
+60
-80
@@ -9,82 +9,69 @@ it. It is slow, it is quiet, and it works for pawns who would never join a mood-
|
|||||||
|
|
||||||
## The crude pick
|
## The crude pick
|
||||||
|
|
||||||
The dig needs a tool. `CB_DiggingTool` is a crude pick, filed off the cell's own furniture exactly
|
The dig needs a tool. The crude pick is filed off the cell's own furniture exactly the way a shiv is —
|
||||||
the way a shiv is (`AcquireWorker_Improvise` — same disposition gate, same damage-to-furniture
|
same disposition gate, same damage-to-furniture tell; see
|
||||||
tell; see [Improvised Weapons](Improvised-Weapons.md)). Where the shiv is a weapon, this is a *way
|
[Improvised Weapons](Improvised-Weapons.md). Where the shiv is a weapon, this is a *way out*: it
|
||||||
out*: it carries no combat stats and is never equipped.
|
carries no combat stats and is never equipped.
|
||||||
|
|
||||||
| Stat | Value |
|
| Stat | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Description | *"Useless as a weapon and useless above ground — but pointed downward, patiently, it is a way out that no lock and no guard can see."* |
|
| Description | *"Useless as a weapon and useless above ground — but pointed downward, patiently, it is a way out that no lock and no guard can see."* |
|
||||||
| Max HP | 60 |
|
| Max HP | 60 |
|
||||||
| Mass | 1.2 kg |
|
| Mass | 1.2 kg |
|
||||||
| `concealability` | **0.5** |
|
| Concealability | **0.5** |
|
||||||
| `hideIn` | `InCell` only (too bulky to ride on a body) |
|
| Hides | in cell only (too bulky to ride on a body) |
|
||||||
| `acquireWorker` | `AcquireWorker_Improvise` |
|
| Deteriorates | yes |
|
||||||
| `escapeWorker` | `EscapeTunnelWorker` |
|
|
||||||
| Deterioration | 2.0 |
|
|
||||||
|
|
||||||
Because it hides `InCell` only, a warden turning over the *room* can find the pick itself — and, far
|
Because it can only hide in the cell, a warden turning over the *room* can find the pick itself — and,
|
||||||
more easily, the hole it is digging.
|
far more easily, the hole it is digging.
|
||||||
|
|
||||||
## A second secret riding on the first
|
## A second secret riding on the first
|
||||||
|
|
||||||
A tunnel is *"the third thing a contraband item can be, after 'made' and 'used in a break': a thing
|
A tunnel is *"the third thing a contraband item can be, after 'made' and 'used in a break': a thing
|
||||||
you dig your way OUT with, quietly, over days."* It is deliberately **not a Job**. There is no
|
you dig your way OUT with, quietly, over days."* It is deliberately **not a job** you can catch a pawn
|
||||||
supervised task a warden could walk in on and interrupt — only a secret that grows and a wall that
|
performing. There is no supervised task a warden could walk in on and interrupt — only a secret that
|
||||||
eventually gives. Progress is driven from `MapComponent_Contraband`'s poll (`TickTunnels`), the same
|
grows and a wall that eventually gives. The dig advances on the same 250-tick heartbeat that makes
|
||||||
250-tick heartbeat that makes contraband, so *the same search that finds a shiv can find the tunnel*.
|
contraband, so *the same search that finds a shiv can find the tunnel*.
|
||||||
|
|
||||||
The pick must first be **made** (or smuggled). Only once it is finished (`Ready`) does the dig begin,
|
The pick must first be **made** (or smuggled). Only once it is finished does the dig begin, as a
|
||||||
as a second, longer secret carried on the same `ConcealedItem` (`escapeProgress`, `escapeWall`).
|
second, longer secret carried on the same hidden pick.
|
||||||
|
|
||||||
## Who digs
|
## Who digs
|
||||||
|
|
||||||
`EscapeTunnelWorker.WouldDig` gates on three things:
|
Digging gates on three things:
|
||||||
|
|
||||||
1. **Humanlike and held** — prisoner, slave, or ward patient (`IsHeld`, via Core's secured context).
|
1. **A person, and held** — prisoner, slave, or ward patient.
|
||||||
2. **The disposition to dig for weeks:**
|
2. **The disposition to dig for weeks:** a base chance of **0.08** — deliberately below the shiv's
|
||||||
|
0.10. *"Fewer pawns will dig for weeks than will palm a shiv."* It is a fixed fact about the pawn.
|
||||||
```
|
|
||||||
Propensity.Would(holder, 0.08, SaltTunnel)
|
|
||||||
```
|
|
||||||
|
|
||||||
Base chance **0.08** — deliberately below the shiv's 0.10. *"Fewer pawns will dig for weeks than
|
|
||||||
will palm a shiv."* Seeded per pawn, so it is a fixed fact about them.
|
|
||||||
3. **A viable plan exists** — there has to be somewhere to surface (see below).
|
3. **A viable plan exists** — there has to be somewhere to surface (see below).
|
||||||
|
|
||||||
This is the entire reason tunnels live in Contraband and not in a prisoner-only escape mod:
|
This is the entire reason tunnels live in Contraband and not in a prisoner-only escape mod:
|
||||||
|
|
||||||
> It works for prisoners **and ward patients** alike. Prisoner Realism's escapes are for "mood
|
> It works for prisoners **and ward patients** alike. Prisoner Realism's escapes are for "mood
|
||||||
> prisoners" mid-break bashing a door, and a committed ward patient is never that pawn. A patient
|
> prisoners" mid-break bashing a door, and a committed ward patient is never that pawn. A patient with
|
||||||
> with a filed-down pick and the disposition to use it can still, slowly, dig out — and nothing else
|
> a filed-down pick and the disposition to use it can still, slowly, dig out — and nothing else in the
|
||||||
> in the ecosystem lets them.
|
> ecosystem lets them.
|
||||||
|
|
||||||
## Planning the dig: aim for the wilderness
|
## Planning the dig: aim for the wilderness
|
||||||
|
|
||||||
The target is **not** the cell wall. *"You do not go through the wall, you go under it."* `TryPlan`
|
The target is **not** the cell wall. *"You do not go through the wall, you go under it."* The pawn
|
||||||
finds the **nearest wilderness** — the first standable cell, scanning radially outward from the
|
aims for the **nearest wilderness** — the first standable spot, scanning outward up to **60 tiles**,
|
||||||
holder up to **60 tiles** (`MaxSearchRadius`), whose room *touches the map edge* and is not the room
|
that opens onto the map edge and is not the room they are held in.
|
||||||
they are held in.
|
|
||||||
|
|
||||||
Two consequences fall straight out of that:
|
Two consequences fall straight out of that:
|
||||||
|
|
||||||
- **Depth is your defence, and a single wall is not.** A tunnel from a deep bunker is a long dig; a
|
- **Depth is your defence, and a single wall is not.** A tunnel from a deep bunker is a long dig; a
|
||||||
tunnel from a shack against the map edge is a short one. Wrapping one more wall around a cell does
|
tunnel from a shack against the map edge is a short one. Wrapping one more wall around a cell does
|
||||||
almost nothing — the dig already ignores intervening walls entirely.
|
almost nothing — the dig already ignores intervening walls entirely.
|
||||||
- **A fully enclosed, map-locked base with no reachable edge has nowhere to surface.** `TryPlan`
|
- **A fully enclosed, map-locked base with no reachable edge has nowhere to surface.** The plan fails,
|
||||||
fails, `ProgressPerCheck` returns 0, and the tunnel **stalls** — progress is kept, not lost. Seal
|
no progress is made, and the tunnel **stalls** — progress is kept, not lost. Seal the map and the
|
||||||
the map and the shaft simply waits.
|
shaft simply waits.
|
||||||
|
|
||||||
### How long
|
### How long
|
||||||
|
|
||||||
Dig time scales with how deep the cell sits:
|
Dig time scales with how deep the cell sits — about **6 cells of depth per day**, clamped between 3
|
||||||
|
and 15 days, and jittered ±25% each poll:
|
||||||
```
|
|
||||||
requiredDays = clamp(distanceToWilderness / 6, 3, 15) // CellsPerDay = 6
|
|
||||||
progressPerPoll = 250 / (requiredDays × 60000) × Rand.Range(0.75, 1.25)
|
|
||||||
```
|
|
||||||
|
|
||||||
| | Days |
|
| | Days |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -97,53 +84,47 @@ ceiling of 15 keeps the deepest from being effectively forever.
|
|||||||
|
|
||||||
## The tell: spoil
|
## The tell: spoil
|
||||||
|
|
||||||
A dig has to go somewhere, and the dirt is where you notice it. Each dig tick, with a 50% seeded
|
A dig has to go somewhere, and the dirt is where you notice it. Each dig tick, about half the time,
|
||||||
chance (`OnDigTick`), the worker drops one tile of `Filth_Dirt` at the holder's position:
|
the pawn drops one tile of dirt filth where they are digging:
|
||||||
|
|
||||||
> A dirt floor swallows it; a paved cell shows it — exactly where a player might notice.
|
> A dirt floor swallows it; a paved cell shows it — exactly where a player might notice.
|
||||||
|
|
||||||
It is cheap and occasional on purpose: enough that a paved cell slowly accumulates a suspicious mess,
|
It is cheap and occasional on purpose: enough that a paved cell slowly accumulates a suspicious mess,
|
||||||
not so much that it spams filth or litters a clean cell every tick. If you floor your cells, spoil is
|
not so much that it spams filth or litters a clean cell every tick. If you floor your cells, spoil is a
|
||||||
a genuine visual cue that someone is digging.
|
genuine visual cue that someone is digging.
|
||||||
|
|
||||||
## Finding a tunnel in a search
|
## Finding a tunnel in a search
|
||||||
|
|
||||||
A tunnel is *"a second, far less hideable secret."* The pick has `concealability` 0.5, but the
|
A tunnel is *"a second, far less hideable secret."* The pick hides at concealability 0.5, but the
|
||||||
**shaft** gives itself away in proportion to how far along it is. When a warden search reaches the
|
**shaft** gives itself away in proportion to how far along it is. When a warden search reaches the
|
||||||
`InCell` site, `ExtraDiscoverChance` is added on top of the tool's normal find chance:
|
cell, an extra find chance is added on top of the tool's normal find chance — scaling up to **+0.40**
|
||||||
|
at a nearly-finished tunnel.
|
||||||
```
|
|
||||||
extraChance = clamp01(escapeProgress) × 0.4 // up to +0.40 at a nearly-finished tunnel
|
|
||||||
```
|
|
||||||
|
|
||||||
So a routine cell toss that finds a shiv can also catch a dig in progress — *"the same guard patrol
|
So a routine cell toss that finds a shiv can also catch a dig in progress — *"the same guard patrol
|
||||||
that finds a shiv finds the hole"* — and the deeper the shaft, the harder it is to miss. A foiled
|
that finds a shiv finds the hole"* — and the deeper the shaft, the harder it is to miss. A foiled dig
|
||||||
dig is logged the same as one that ran: on discovery, `CriminalRecord.escapeAttempts` is
|
counts against the prisoner the same as one that ran: on discovery, their escape-attempt tally ticks
|
||||||
incremented, which raises this pawn's future search priority sharply (a known tunneller is the
|
up, which raises this pawn's future search priority sharply (a known tunneller is the prisoner a warden
|
||||||
prisoner a warden checks first — see [Warden Search](Warden-Search.md)). The full find math is on the
|
checks first — see [Warden Search](Warden-Search.md)). The full find math is on the
|
||||||
[Warden Search](Warden-Search.md) page.
|
[Warden Search](Warden-Search.md) page.
|
||||||
|
|
||||||
## Breakout
|
## Breakout
|
||||||
|
|
||||||
When `escapeProgress` reaches 1, `OnBreakout` fires:
|
When the dig finishes:
|
||||||
|
|
||||||
1. **Re-plan the exit** so it is correct even if the base changed during the weeks of digging.
|
1. **Re-plan the exit** so it is correct even if the base changed during the weeks of digging.
|
||||||
2. **Breach the outer wall or fence** the tunnel comes up beside (`LastContainmentOnLine` — the
|
2. **Breach the outer wall or fence** the tunnel comes up beside — the *outermost* impassable wall or
|
||||||
*outermost* impassable edifice or fence between the cell and the surfacing point, walking inward
|
fence between the cell and the surfacing point. It is destroyed, leaving a visible hole the colony
|
||||||
from the exit). It is destroyed with `KillFinalize` — a visible hole the colony must repair, and
|
must repair, and proof of how they got out. An open perimeter needs no breach.
|
||||||
proof of how they got out. An open perimeter needs no breach.
|
3. **Record the attempt** — it happened whether or not they get clear of the map.
|
||||||
3. **Record the attempt** (`escapeAttempts++`) — it happened whether or not they get clear of the
|
|
||||||
map.
|
|
||||||
4. **Surface beyond the perimeter.** They dug all the way out, so they emerge *outside*, not in the
|
4. **Surface beyond the perimeter.** They dug all the way out, so they emerge *outside*, not in the
|
||||||
yard: the pawn is teleported to the exit cell, with a little spoil dirt to mark where they came
|
yard, with a little spoil dirt to mark where they came up.
|
||||||
up.
|
5. **Hand off to vanilla.** Now that they are loose and outside, the base game's prison-break flow —
|
||||||
5. **Hand off to vanilla** (`PrisonBreakUtility.StartPrisonBreak`). Now that they are loose and
|
and anything layered on it, e.g. **Prisoner Realism** — takes it from there. A pawn already outside
|
||||||
outside, the base game's prison-break flow — and anything layered on it, e.g. **Prisoner
|
who no longer qualifies as an in-prison escapee simply flees on their own; the breach and the
|
||||||
Realism** — takes it from there. A pawn already outside who no longer qualifies as an in-prison
|
emergence are the real outcome regardless.
|
||||||
escapee simply flees on their own; the breach and the emergence are the real outcome regardless.
|
|
||||||
|
|
||||||
The player gets one message: *"{pawn} has tunnelled out under the perimeter and escaped."* The tunnel
|
The player gets one message: *"{pawn} has tunnelled out under the perimeter and escaped."* The tunnel
|
||||||
and the pick are spent together — the `ConcealedItem` is removed.
|
and the pick are spent together.
|
||||||
|
|
||||||
## How to defend against it
|
## How to defend against it
|
||||||
|
|
||||||
@@ -155,24 +136,23 @@ and the pick are spent together — the `ConcealedItem` is removed.
|
|||||||
| **Depth** | a deeper cell is a longer dig and a wider window to catch it |
|
| **Depth** | a deeper cell is a longer dig and a wider window to catch it |
|
||||||
| **Seal the map** | no reachable edge means the tunnel stalls indefinitely |
|
| **Seal the map** | no reachable edge means the tunnel stalls indefinitely |
|
||||||
|
|
||||||
Note what does **not** help much: adding perimeter walls. The dig goes under them and only breaches
|
Note what does **not** help much: adding perimeter walls. The dig goes under them and only breaches the
|
||||||
the *last* one on the way out.
|
*last* one on the way out.
|
||||||
|
|
||||||
## Quick reference
|
## Quick reference
|
||||||
|
|
||||||
| Constant | Value | Meaning |
|
| Constant | Value | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Dig base chance | 0.08 | before nature × nurture; seeded per pawn |
|
| Dig base chance | 0.08 | before nature × nurture; fixed per pawn |
|
||||||
| Wilderness search radius | 60 tiles | how far out it looks for a surfacing point |
|
| Wilderness search radius | 60 tiles | how far out it looks for a surfacing point |
|
||||||
| Cells per day | 6 | dig speed vs. cell depth |
|
| Cells per day | 6 | dig speed vs. cell depth |
|
||||||
| Min / max dig time | 3 / 15 days | clamps on `distance / 6` |
|
| Min / max dig time | 3 / 15 days | clamps on depth ÷ 6 |
|
||||||
| Spoil chance per dig tick | 50% | one tile of `Filth_Dirt` |
|
| Spoil chance per dig tick | 50% | one tile of dirt filth |
|
||||||
| Extra find chance | up to +0.40 | scales with `escapeProgress` |
|
| Extra find chance | up to +0.40 | scales with how far along the dig is |
|
||||||
| Pick `concealability` | 0.5 | `InCell` only |
|
| Pick concealability | 0.5 | in cell only |
|
||||||
| On discovery / breakout | `escapeAttempts++` | logged either way |
|
| On discovery / breakout | escape attempt logged | counted either way |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
||||||
Ward. Each stands alone; together they interlock.*
|
Ward. Each stands alone; together they interlock.*
|
||||||
**
|
|
||||||
|
|||||||
+97
-114
@@ -4,177 +4,161 @@
|
|||||||
which is exactly why it has to cost.*
|
which is exactly why it has to cost.*
|
||||||
|
|
||||||
The warden can chat, convert, feed, execute, enslave, release, suppress, and eleven other things.
|
The warden can chat, convert, feed, execute, enslave, release, suppress, and eleven other things.
|
||||||
Searching a prisoner is not one of them, and no such verb exists anywhere in the base game.
|
Searching a prisoner is not one of them, and no such job exists anywhere in the base game. Contraband
|
||||||
Contraband adds it as a **new** `WorkGiver`, not a patch on a vanilla one, and builds it around a
|
adds it as a **new** warden job, not a change to an existing one, and builds it around a single hard
|
||||||
single hard rule: **the search must be able to come up empty.**
|
rule: **the search must be able to come up empty.**
|
||||||
|
|
||||||
## Authority vs. act
|
## Authority vs. act
|
||||||
|
|
||||||
The design splits one job into two classes:
|
The design splits one job into two halves:
|
||||||
|
|
||||||
| Class | Question it answers | Notes |
|
| Half | Question it answers | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `WorkGiver_Warden_Search` | **Who** may be searched? | subclasses `WorkGiver_Warden` — a warden already has the run of the prison, so no new authority is invented |
|
| The search **assignment** | **Who** may be searched? | it is ordinary warden work — a warden already has the run of the prison, so no new authority is invented |
|
||||||
| `JobDriver_SearchPawn` | **What** is a search? | takes any pawn, searches any pawn |
|
| The search **act** | **What** is a search? | walk to the pawn, turn them over, resolve |
|
||||||
|
|
||||||
> The WorkGiver is the authority. This driver is just the act.
|
> The assignment is the authority. The act is just the act.
|
||||||
|
|
||||||
Why bother splitting them? Because the enforcement layer is meant to grow. A future **Institution:
|
Why bother splitting them? Because the enforcement layer is meant to grow. A future **Institution:
|
||||||
Police** module adds a *second* authority — a cop who may stop and frisk a **free colonist** — as a
|
Police** module adds a *second* authority — a cop who may stop and frisk a **free colonist** — reusing
|
||||||
new WorkGiver that reuses the same driver with a **narrower `Reach`** (a street frisk gets what is
|
the same act with a **narrower reach** (a street frisk gets what is on the body, not what is under the
|
||||||
`OnBody`, not what is under the floorboards). That module is an *addition*, not a rewrite, precisely
|
floorboards). That module is an *addition*, not a rewrite, precisely because "who may search" and
|
||||||
because "who may search" and "what a search reaches" were never welded together.
|
"what a search reaches" were never welded together.
|
||||||
|
|
||||||
## The WorkGiver: who gets searched
|
## Who gets searched
|
||||||
|
|
||||||
`Contraband_WardenSearch` is a `Warden` work-type giver:
|
The search is warden work, and it sits at a deliberate spot in the warden's priorities:
|
||||||
|
|
||||||
| Field | Value | Why |
|
| Property | Value | Why |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `workType` | Warden | it is warden work |
|
| Work type | Warden | it is warden work |
|
||||||
| `priorityInType` | 40 | **below feeding** — a starving prisoner matters more than a hidden shiv |
|
| Priority within warden work | **below feeding** | a starving prisoner matters more than a hidden shiv |
|
||||||
| `requiredCapacities` | Manipulation, Sight | you search with your hands and eyes |
|
| Capacities needed | Manipulation, Sight | you search with your hands and eyes |
|
||||||
| `Prioritized` | **true** (in C#) | rank candidates by suspicion, not by distance |
|
| Ranked by suspicion | **yes** | pick the right prisoner, not the nearest |
|
||||||
|
|
||||||
`Prioritized` has to be set in code: in 1.6 it is a `WorkGiver_Scanner` property, **not** a
|
That last one — ranking by suspicion rather than distance — is what lets a gnawed bed and a thick
|
||||||
`WorkGiverDef` field, and an XML `<prioritized>` silently errors on load. That flag is what lets a
|
record send the warden to the *right* prisoner first.
|
||||||
gnawed bed and a thick record send the warden to the *right* prisoner first instead of the nearest.
|
|
||||||
|
|
||||||
`JobOnThing` will offer a search only if all of these hold: the warden should take care of this
|
A search is offered only if all of these hold: the warden should be looking after this prisoner, the
|
||||||
prisoner, the target is a `Pawn`, the per-prisoner cooldown is up (`CanSearchNow`), the prisoner is
|
per-prisoner cooldown is up, the prisoner is **awake, not downed, and not in a mental state**, and the
|
||||||
**awake, not downed, and not in a mental state**, and the warden can reserve them.
|
warden can reach them.
|
||||||
|
|
||||||
Note what is **not** checked — and the source is emphatic about it:
|
Note what is **not** checked:
|
||||||
|
|
||||||
> Whether the prisoner is actually hiding anything. **The warden does not know. Nobody knows until
|
> Whether the prisoner is actually hiding anything. **The warden does not know. Nobody knows until the
|
||||||
> the cell is turned over.** If the job were only offered when there was something to find, the mere
|
> cell is turned over.** If the job were only offered when there was something to find, the mere
|
||||||
> appearance of the job would BE the discovery, and searching would stop being a gamble the player
|
> appearance of the job would BE the discovery, and searching would stop being a gamble the player
|
||||||
> pays for in warden-hours. An empty search has to be possible, and has to cost.
|
> pays for in warden-hours. An empty search has to be possible, and has to cost.
|
||||||
|
|
||||||
### The cooldown
|
### The cooldown
|
||||||
|
|
||||||
`MapComponent_Contraband` won't let the same prisoner be turned over more than **once per day**:
|
The same prisoner cannot be turned over more than **once per day** (60,000 ticks). You cannot
|
||||||
|
brute-force a well-hidden shiv by re-searching the same prisoner ten times in an hour. Every search is
|
||||||
```
|
one shot, and then that prisoner is off the list for a day.
|
||||||
SearchCooldownTicks = 60000 // one in-game day
|
|
||||||
```
|
|
||||||
|
|
||||||
You cannot brute-force a well-hidden shiv by re-searching the same prisoner ten times in an hour.
|
|
||||||
Every search is one shot, and then that prisoner is off the list for a day.
|
|
||||||
|
|
||||||
## Priority: the search reads the tells
|
## Priority: the search reads the tells
|
||||||
|
|
||||||
`GetPriority` is where suspicion becomes routing. Everyone is worth a routine toss; the tells push a
|
Everyone is worth a routine toss; the tells push a prisoner up the queue. Priority is built up like
|
||||||
prisoner up the queue:
|
this:
|
||||||
|
|
||||||
```
|
- **Base 4** — a routine toss for every prisoner.
|
||||||
priority = 4 // baseline — everyone gets frisked
|
- **Up to +12 for a gnawed bed** — scaled by how damaged their owned bed is. A badly damaged bed
|
||||||
+ 12 × bedDamageFraction // chewed furniture = probable cause
|
shoots to the top.
|
||||||
+ min(8, timesCaught×2 + contrabandMade×0.5 + escapeAttempts×3) // the record
|
- **Up to +8 for their record** — prior catches (×2 each), contraband made in the past (×0.5 each),
|
||||||
```
|
and foiled escape attempts (×3 each, **the heaviest term**), capped at +8 so the record cannot
|
||||||
|
completely swamp fresh probable cause.
|
||||||
|
|
||||||
| Term | Weight | Meaning |
|
| Term | Weight | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Base | 4 | a routine toss for every prisoner |
|
| Base | 4 | a routine toss for every prisoner |
|
||||||
| Gnawed bed | up to +12 | `1 − HP/maxHP` of their owned bed — a badly damaged bed shoots to the top |
|
| Gnawed bed | up to +12 | how far below full HP their owned bed sits |
|
||||||
| `timesCaught` | ×2 | already caught with contraband before |
|
| Times caught | ×2 | already caught with contraband before |
|
||||||
| `contrabandMade` | ×0.5 | has finished contraband in the past |
|
| Contraband made | ×0.5 | has finished contraband in the past |
|
||||||
| `escapeAttempts` | ×3 | **the heaviest term** — a known tunneller is checked first, every time |
|
| Escape attempts | ×3 | **the heaviest term** — a known tunneller is checked first, every time |
|
||||||
| (record cap) | +8 max | the record cannot completely swamp fresh probable cause |
|
| (record cap) | +8 max | the record cannot completely swamp fresh probable cause |
|
||||||
|
|
||||||
The record is read with `PeekFor` (a read-only lookup that never creates a record). The base of 4 for
|
The record is only read here, never created. The base of 4 for everyone is not an accident —
|
||||||
everyone is not an accident — **innocent prisoners still get frisked**, because the search must be
|
**innocent prisoners still get frisked**, because the search must be able to come up empty or its mere
|
||||||
able to come up empty or its mere offer would be the discovery.
|
offer would be the discovery.
|
||||||
|
|
||||||
**Worked example.** A prisoner whose bed sits at 40% HP (`missing = 0.6`), with 2 prior catches and
|
**Worked example.** A prisoner whose bed sits at 40% HP (so 60% missing), with 2 prior catches and 1
|
||||||
1 foiled tunnel:
|
foiled tunnel:
|
||||||
|
|
||||||
```
|
> 4 + (12 × 0.6) + min(8, 2×2 + 1×3) = 4 + 7.2 + 7 = **18.2**
|
||||||
priority = 4 + 12×0.6 + min(8, 2×2 + 0 + 1×3)
|
|
||||||
= 4 + 7.2 + min(8, 7)
|
|
||||||
= 4 + 7.2 + 7 = 18.2
|
|
||||||
```
|
|
||||||
|
|
||||||
versus **4** for a clean prisoner with an intact bed. The warden walks past the quiet one and goes
|
versus **4** for a clean prisoner with an intact bed. The warden walks past the quiet one and goes
|
||||||
straight for the one who has been busy. This closes the loop [Improvised Weapons](Improvised-Weapons.md)
|
straight for the one who has been busy. This closes the loop
|
||||||
opens: whittling a shiv chews the bed, the damage is probable cause, and the warden's *suspicion* —
|
[Improvised Weapons](Improvised-Weapons.md) opens: whittling a shiv chews the bed, the damage is
|
||||||
not the player's eye — sends them to search.
|
probable cause, and the warden's *suspicion* — not the player's eye — sends them to search.
|
||||||
|
|
||||||
## The act: what a search does
|
## The act: what a search does
|
||||||
|
|
||||||
`JobDriver_SearchPawn` walks the searcher to the subject, runs a **900-tick** wait toil with a
|
The searcher walks to the subject, spends a **900-tick** turn with a progress bar, then resolves.
|
||||||
progress bar, then resolves. Resolution always does two things, hit or miss:
|
Resolution always does two things, hit or miss:
|
||||||
|
|
||||||
1. `MarkSearched(subject)` — starts the daily cooldown.
|
1. Starts the daily cooldown on that prisoner.
|
||||||
2. `timesSearched++` on the record — *"classification and the guard's future suspicion read how often
|
2. Ticks up how many times this pawn has been searched — *"classification and the guard's future
|
||||||
this pawn has been turned over."*
|
suspicion read how often this pawn has been turned over."*
|
||||||
|
|
||||||
Then it gathers everything the subject is hiding that this search can **reach** (`Reach` defaults to
|
Then it goes through everything the subject is hiding that this search can **reach** (a full warden
|
||||||
`OnBody | InCell`; a subclass can narrow it). If nothing is reachable, the job ends here — the time
|
search reaches both the body and the cell; a narrower future street-frisk reaches only the body). If
|
||||||
was still spent, and the player is told nothing.
|
nothing is reachable, the job ends here — the time was still spent, and the player is told nothing.
|
||||||
|
|
||||||
### Find math, per hidden item
|
### Find math, per hidden item
|
||||||
|
|
||||||
For each reachable secret:
|
For each reachable secret, the chance to find it is built like this:
|
||||||
|
|
||||||
```
|
- Start from a **base of 0.35**, plus **0.03 for each level of the warden's Social skill**.
|
||||||
conceal = item.concealability // 0 = always found, 1 = never
|
- Scale by how concealable the item is: the factor pivots at concealability 0.5. A drug at 0.5 is
|
||||||
chance = clamp( (0.35 + 0.03 × socialSkill) × (1 − conceal) × 2 , 0.02 , 0.95 )
|
average (×1.0), a shiv at 0.75 is halved (×0.5), a heavy weapon at 0.35 is boosted (×1.3).
|
||||||
|
- If the item is a **tunnel in progress**, add its extra find chance (up to +0.40 for a nearly
|
||||||
if item is a tunnel in progress:
|
finished shaft), and the ceiling rises.
|
||||||
chance = clamp( chance + tunnelExtra , 0.02 , 0.98 ) // tunnelExtra up to +0.40
|
- Multiply the whole thing by the warden's **Diligence** — a lax, kind, or miserable guard scales it
|
||||||
|
down.
|
||||||
chance ×= WardenDisposition.Diligence(searcher) // the guard's thoroughness
|
- Before the find roll, a **bent** warden gets a flat chance to look the other way (their Corruption ×
|
||||||
|
0.6) even on an item they would otherwise have found.
|
||||||
if Rand.Chance( WardenDisposition.Corruption(searcher) × 0.6 ):
|
- The chance is clamped to between 0.02 and 0.95 (0.98 for a tunnel).
|
||||||
continue // the warden saw it and said nothing — looked away
|
|
||||||
|
|
||||||
found = Rand.Chance(chance)
|
|
||||||
```
|
|
||||||
|
|
||||||
Reading it in plain terms:
|
Reading it in plain terms:
|
||||||
|
|
||||||
- **Skill raises the ceiling; concealability lowers it.** `BaseFindChance` is 0.35; each level of the
|
- **Skill raises the ceiling; concealability lowers it.** Each level of the warden's Social skill adds
|
||||||
warden's **Social** skill adds 0.03. The `(1 − conceal) × 2` factor pivots at concealability 0.5
|
a little; a well-hidden item takes a lot of it back.
|
||||||
(neutral, ×1.0): a drug at 0.5 is average, a shiv at 0.75 is halved (×0.5), a heavy weapon at 0.35
|
- **A good warden still misses a well-hidden shiv more often than not.** A level-8 Social warden
|
||||||
is boosted (×1.3).
|
searching for a 0.75-concealability shiv is already well under a coin-flip *before* Diligence scales
|
||||||
- **A good warden still misses a well-hidden shiv more often than not.** Worked: a level-8 Social
|
it down further.
|
||||||
warden searching for a 0.75-concealability shiv gets `(0.35 + 0.24) × 0.5 = 0.295` *before*
|
|
||||||
Diligence scales it down further. Well under a coin-flip.
|
|
||||||
- **A tunnel is a different story.** The pick hides at 0.5, but a nearly-finished shaft adds up to
|
- **A tunnel is a different story.** The pick hides at 0.5, but a nearly-finished shaft adds up to
|
||||||
+0.40 and the cap rises to 0.98 — the deeper the dig, the harder it is to miss. A routine cell toss
|
+0.40 and the cap rises to 0.98 — the deeper the dig, the harder it is to miss. A routine cell toss
|
||||||
catches a dig in progress.
|
catches a dig in progress.
|
||||||
- **The warden's own thoroughness and price** enter last: `Diligence` multiplies the whole chance
|
- **The warden's own thoroughness and price** enter last: Diligence multiplies the whole chance down
|
||||||
down for a lax, kind, or miserable guard, and `Corruption` gives a bent guard a flat chance to look
|
for a lax, kind, or miserable guard, and Corruption gives a bent guard a flat chance to look the
|
||||||
the other way even on an item they would otherwise have found. Both are on the
|
other way even on an item they would otherwise have found. Both are on the
|
||||||
[Corruption](Corruption.md) page.
|
[Corruption](Corruption.md) page.
|
||||||
|
|
||||||
### On a find
|
### On a find
|
||||||
|
|
||||||
```
|
- The item is confiscated — it **never becomes a real object**, nothing drops on the floor.
|
||||||
Confiscate(subject, item.def) // it never becomes a Thing — nothing drops on the floor
|
- The prisoner's times-caught tally ticks up (raising future suspicion, feeding classification).
|
||||||
timesCaught++ // raises future suspicion, feeds classification
|
- If it was a tunnel, their escape-attempt tally ticks up too — a foiled dig counts the same as one
|
||||||
if it was a tunnel: escapeAttempts++ // a foiled dig is a logged attempt, same as one that ran
|
that ran.
|
||||||
```
|
|
||||||
|
|
||||||
and the player gets a message — *"{warden} searched {prisoner} and found hidden {item}"*, or the
|
The player gets a message — *"{warden} searched {prisoner} and found hidden {item}"*, or the tunnel
|
||||||
tunnel variant. If nothing is found the player is told **nothing**: they do not learn there *was*
|
variant. If nothing is found the player is told **nothing**: they do not learn there *was* something,
|
||||||
something, only that the warden's time was spent.
|
only that the warden's time was spent.
|
||||||
|
|
||||||
## Writing back to the record
|
## Writing back to the record
|
||||||
|
|
||||||
Every outcome updates Core's `CriminalRecord`, which is what makes the search a *loop* rather than a
|
Every outcome updates Core's criminal record, which is what makes the search a *loop* rather than a
|
||||||
one-off dice roll:
|
one-off dice roll:
|
||||||
|
|
||||||
| Field | Written when | Read by |
|
| Tally | Updated when | Read by |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `timesSearched` | every search, hit or miss | priority, suspicion, classification |
|
| Times searched | every search, hit or miss | priority, suspicion, classification |
|
||||||
| `timesCaught` | a find | priority (×2), classification |
|
| Times caught | a find | priority (×2), classification |
|
||||||
| `escapeAttempts` | a foiled tunnel | priority (×3, heaviest), classification |
|
| Escape attempts | a foiled tunnel | priority (×3, heaviest), classification |
|
||||||
|
|
||||||
If you run **Institution: Justice**, those same fields drive a prisoner's security classification and
|
If you run **Institution: Justice**, those same tallies drive a prisoner's security classification and
|
||||||
feed the deterrence signal — a prison that catches contraband becomes a prison that classifies its
|
feed the deterrence signal — a prison that catches contraband becomes a prison that classifies its
|
||||||
troublemakers correctly and deters the next attempt. If you run Contraband alone, the fields still
|
troublemakers correctly and deters the next attempt. If you run Contraband alone, the tallies still
|
||||||
route the warden. See [Compatibility](Compatibility.md).
|
route the warden. See [Compatibility](Compatibility.md).
|
||||||
|
|
||||||
## Quick reference
|
## Quick reference
|
||||||
@@ -184,15 +168,14 @@ route the warden. See [Compatibility](Compatibility.md).
|
|||||||
| Base find chance | 0.35 | before skill and concealability |
|
| Base find chance | 0.35 | before skill and concealability |
|
||||||
| Per Social level | +0.03 | skill raises the ceiling |
|
| Per Social level | +0.03 | skill raises the ceiling |
|
||||||
| Find chance clamp | 0.02 .. 0.95 | (0.98 for a tunnel) |
|
| Find chance clamp | 0.02 .. 0.95 | (0.98 for a tunnel) |
|
||||||
| Search duration | 900 ticks | one wait toil, hit or miss |
|
| Search duration | 900 ticks | one turn, hit or miss |
|
||||||
| Search cooldown | 60000 ticks (1 day) | per prisoner |
|
| Search cooldown | 60,000 ticks (1 day) | per prisoner |
|
||||||
| Priority base | 4 | every prisoner is worth a routine toss |
|
| Priority base | 4 | every prisoner is worth a routine toss |
|
||||||
| Gnawed-bed weight | up to +12 | `1 − HP/maxHP` |
|
| Gnawed-bed weight | up to +12 | how far below full HP their bed sits |
|
||||||
| Record weight | up to +8 | `timesCaught×2 + contrabandMade×0.5 + escapeAttempts×3` |
|
| Record weight | up to +8 | caught ×2 + made ×0.5 + escapes ×3 |
|
||||||
| Default `Reach` | `OnBody \| InCell` | narrowed by a future street-frisk subclass |
|
| Default reach | body + cell | narrowed by a future street-frisk |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
||||||
Ward. Each stands alone; together they interlock.*
|
Ward. Each stands alone; together they interlock.*
|
||||||
**
|
|
||||||
|
|||||||
+65
-128
@@ -2,163 +2,100 @@
|
|||||||
|
|
||||||
If [Propensity](Propensity.md) is *suspicion* — "this pawn seems dangerous" — the criminal record is
|
If [Propensity](Propensity.md) is *suspicion* — "this pawn seems dangerous" — the criminal record is
|
||||||
*fact*: "this pawn **has** escaped twice and shanked a guard." The two are deliberately different
|
*fact*: "this pawn **has** escaped twice and shanked a guard." The two are deliberately different
|
||||||
kinds of thing. Propensity is computed fresh from who a pawn is and how they are treated; a record is
|
kinds of thing. Propensity is computed fresh from who a pawn is and how they're treated; a record is a
|
||||||
a durable log of what they have actually done, written once when it happens and read forever after.
|
durable log of what they've actually done, written once when it happens and read forever after.
|
||||||
|
|
||||||
`CriminalRecord` is the single per-pawn history the **whole suite** writes to and reads from.
|
There is exactly **one criminal record per pawn**, and the whole suite shares it. Classification grades
|
||||||
Classification grades on it, parole gates on it, the deterrence loop reacts to it, and reintegration
|
on it, parole gates on it, the deterrence loop reacts to it, and reintegration remembers it. Because
|
||||||
remembers it. Because there is exactly one record per pawn and everyone shares it, no two modules can
|
everyone reads the same record, no two parts of the suite can disagree about a pawn's past.
|
||||||
disagree about a pawn's past.
|
|
||||||
|
|
||||||
Everything here is verified against `Source/Core/CriminalRecord.cs`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The fields
|
## What a record remembers
|
||||||
|
|
||||||
`CriminalRecord` is a plain `IExposable` bag of counters plus two special values. Core defines the
|
A record is a small set of counters plus two special marks. Core provides the record and keeps it
|
||||||
fields and the storage; it does **not** write most of them — the modules that own each event do.
|
safe; the individual events are logged by whichever mod they happen in.
|
||||||
|
|
||||||
| Field | Type | Default | What it records | Who writes it | Who reads it |
|
| What it tracks | Starts at | Meaning | Tracked by | Used by |
|
||||||
|---|---|---:|---|---|---|
|
|---|---:|---|---|---|
|
||||||
| `crimesCommitted` | `int` | `0` | count of committed crimes | Justice `RecordCrime` (also on gang fights) | classification risk score, deterrence |
|
| Crimes committed | 0 | count of committed crimes | Justice (and gang fights) | classification risk score, deterrence |
|
||||||
| `escapeAttempts` | `int` | `0` | breakout attempts | Contraband (escape/tunnel logic) | classification risk score |
|
| Escape attempts | 0 | breakout attempts | Contraband | classification risk score |
|
||||||
| `contrabandMade` | `int` | `0` | items brewed / whittled | Contraband (shivs, vessels) | classification risk score |
|
| Contraband made | 0 | items brewed / whittled | Contraband | classification risk score |
|
||||||
| `timesSearched` | `int` | `0` | how often searched | Contraband (warden search) | search prioritisation, audit |
|
| Times searched | 0 | how often searched | Contraband (warden search) | search prioritisation |
|
||||||
| `timesCaught` | `int` | `0` | searches that found something | Contraband (warden search) | classification risk score |
|
| Times caught | 0 | searches that found something | Contraband (warden search) | classification risk score |
|
||||||
| `lastCrimeTick` | `int` | `−1` | game tick of the most recent crime | Justice `RecordCrime` | recency / cooldown checks |
|
| Last crime | never | when the most recent crime happened | Justice | recency / cooldown checks |
|
||||||
| `reform` | `float` | `0` | rehabilitation score (see below) | Justice `Discipline`, `Parole` | **Propensity.Nurture**, parole gate, reintegration |
|
| Reform | 0 | rehabilitation score (see below) | Justice (discipline, parole) | **Propensity's Nurture**, parole gate, reintegration |
|
||||||
| `pardoned` | `bool` | `false` | granted a clean slate after genuine reform | Justice `Parole` (on release) | parole gate, reintegration |
|
| Pardoned | no | granted a clean slate after genuine reform | Justice (on release) | parole gate, reintegration |
|
||||||
|
|
||||||
`lastCrimeTick` defaults to `−1` — the sentinel for "never," distinct from tick `0`. Everything else
|
"Last crime" starts at *never* — a distinct value from "at the very first moment of the game."
|
||||||
defaults to zero/false.
|
Everything else starts at zero / no.
|
||||||
|
|
||||||
> **Note on ownership.** Core is the *vault*, not the *clerk*. It hands out records and persists them;
|
> **Who logs what.** Core is the *vault*, not the *clerk*. It hands out records and keeps them; the
|
||||||
> the write logic ("a crime just happened, bump the counter") lives in the module where the event
|
> actual "a crime just happened, add one" logging lives in the mod where the event occurs. The columns
|
||||||
> occurs, above Core. The columns above name where that logic lives in the suite — Core itself only
|
> above name which mod does that — Core itself only holds the fields.
|
||||||
> defines the fields.
|
|
||||||
|
|
||||||
## `reform` — the pivot of the suite
|
## Reform — the pivot of the suite
|
||||||
|
|
||||||
Most fields are inert tallies. `reform` is the one that feeds back into behaviour, and it is worth
|
Most of a record is inert tallies. **Reform** is the one entry that feeds back into behaviour, and it's
|
||||||
its own section.
|
worth its own section. It's raised by good treatment and good conduct, decays without either, and can
|
||||||
|
be driven negative by harsh punishment. A record is where a sentence's *outcome* is stored, and three
|
||||||
|
systems lean on that one number:
|
||||||
|
|
||||||
```
|
- **[Propensity](Propensity.md)'s Nurture** scales a pawn's disposition by their reform score — so a
|
||||||
reform : float, nominally 0..1 but can go negative
|
genuinely rehabilitated pawn (reform above 0) is calmer *for good*, and a prisonized one (reform
|
||||||
raised by good treatment and good conduct
|
below 0) is inflamed *for good*. This is how institutionalization and recidivism enter the game
|
||||||
decays without either
|
without a separate mechanic bolted on.
|
||||||
read by Propensity.Nurture, the parole gate, and reintegration
|
- **Parole** (Justice) gates release on it: a pawn is only releasable once reform clears a threshold.
|
||||||
```
|
It's the number that says "this one is ready."
|
||||||
|
- **Reintegration** reads it to decide when a clean slate is earned — which is what the *pardoned*
|
||||||
|
mark records.
|
||||||
|
|
||||||
The source is blunt about its role: *"it is what makes reform MEAN something."* A record is where a
|
The negative range isn't a bug — it's the "hardened" end of the spectrum, and Nurture is written to
|
||||||
sentence's *outcome* is stored. Three systems lean on that one float:
|
expect it.
|
||||||
|
|
||||||
- **[Propensity](Propensity.md)'s Nurture** multiplies disposition by `Clamp(1 − reform×0.5, 0.4, 2)`
|
## Pardoned and blank records
|
||||||
— so a genuinely rehabilitated pawn (`reform > 0`) is calmer *for good*, and a prisonized one
|
|
||||||
(`reform < 0`) is inflamed *for good*. This is how institutionalization and recidivism enter the
|
|
||||||
model without a separate mechanic bolted on.
|
|
||||||
- **Parole** (Justice) gates release on it: a pawn is only releasable once `reform` clears a
|
|
||||||
threshold. It is the number that says "this one is ready."
|
|
||||||
- **Reintegration** reads it to decide when a clean slate is earned — which is what `pardoned` marks.
|
|
||||||
|
|
||||||
Although the field is documented as `0..1`, Justice's `Discipline` can drive it negative (harsh
|
The **pardoned** mark flips on exactly once, after genuine reform, so reintegration can grant a clean
|
||||||
punishment subtracts from it). That negative range is not a bug — it is the "hardened" end of the
|
slate without erasing the history that earned it — the counters stay, but the pawn is marked forgiven.
|
||||||
spectrum, and `Nurture`'s clamp is written to expect it.
|
|
||||||
|
|
||||||
## `pardoned` and `IsBlank`
|
A record counts as **blank** when nothing meaningful has ever been written to it: no crimes, no escape
|
||||||
|
attempts, no contraband, never caught, reform still 0, not pardoned. Note what blankness *ignores*:
|
||||||
`pardoned` flips to `true` exactly once, after genuine reform, so reintegration can grant a clean
|
times searched and the last-crime time aren't part of the test. A pawn who was searched and found clean
|
||||||
slate without erasing the history that earned it — the counters stay, but the pawn is marked
|
— searched but never caught, never a crime — still counts as blank. That's intentional: being
|
||||||
forgiven.
|
*checked* is not a mark against you, only being *found* is.
|
||||||
|
|
||||||
`IsBlank` is a computed convenience:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public bool IsBlank => crimesCommitted == 0 && escapeAttempts == 0 && contrabandMade == 0
|
|
||||||
&& timesCaught == 0 && reform == 0f && !pardoned;
|
|
||||||
```
|
|
||||||
|
|
||||||
A blank record is one nothing has ever been written to. Note what `IsBlank` **omits**: `timesSearched`
|
|
||||||
and `lastCrimeTick` are not in the test. A pawn who was searched and found clean — searched but never
|
|
||||||
caught, never a crime — is still "blank" and will be pruned. That is intentional: being *checked* is
|
|
||||||
not a mark against you, only being *found* is.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## `For` vs `PeekFor` — the distinction that matters
|
## Records are created only when needed
|
||||||
|
|
||||||
Records are handed out by `GameComponent_CriminalRecords`, and there are two ways to ask for one. The
|
Records are made lazily. A pawn only gets a record the first time something is actually **written** to
|
||||||
difference is not cosmetic.
|
it — a crime, a search that found something, a reform change. Simply **reading** a pawn's disposition
|
||||||
|
never creates one.
|
||||||
|
|
||||||
```csharp
|
That distinction matters because Propensity's Nurture reads a pawn's reform score constantly, on every
|
||||||
// Creates a blank record on first ask. Use on a WRITE path.
|
pawn on the map, as part of judging their mood. If merely checking disposition created a record, every
|
||||||
public static CriminalRecord For(Pawn p);
|
mood check would stamp a criminal file onto every innocent pawn on the map. Because reading only peeks
|
||||||
|
— and tolerates a pawn having no record at all — the innocent stay off the books.
|
||||||
|
|
||||||
// Returns null if no record exists yet. Use on a READ path that must not create.
|
The rule the whole suite follows: **something happened → a record is created and written; just looking
|
||||||
public static CriminalRecord PeekFor(Pawn p);
|
→ no record is created.**
|
||||||
```
|
|
||||||
|
|
||||||
- **`For(pawn)`** guarantees a record — if the pawn has none, it makes a blank one and stores it. Call
|
|
||||||
this when you are about to *write* something ("record a crime"), because you need a record to write
|
|
||||||
to.
|
|
||||||
- **`PeekFor(pawn)`** returns the existing record or `null`. Call this on a *read* path that should
|
|
||||||
not leave a trail. The archetypal caller is `Propensity.Nurture`: it wants to *read* `reform` if a
|
|
||||||
record exists, but merely asking about a pawn's disposition must not conjure a criminal file for an
|
|
||||||
innocent. If `Nurture` used `For`, every mood check would stamp a record onto every pawn on the map.
|
|
||||||
|
|
||||||
Both are `static` and both are null-safe — a null pawn (or a game with no component yet) returns
|
|
||||||
`null` rather than throwing.
|
|
||||||
|
|
||||||
> The rule of thumb: **write with `For`, read with `PeekFor`.** The only reason `Nurture` can be
|
|
||||||
> called on all pawns constantly without littering the save with empty records is that it peeks.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Persistence
|
## Persistence
|
||||||
|
|
||||||
`GameComponent_CriminalRecords` is the single source of truth for the whole game. The design note is
|
Records save with your game and reload with it, and there's exactly one shared store for the whole
|
||||||
explicit: *"modules never keep their own per-pawn crime state, they read and write here, which is what
|
colony — no mod keeps its own private copy, which is what keeps them all agreeing with each other.
|
||||||
keeps them agreeing with each other."*
|
|
||||||
|
|
||||||
It stores records in a `Dictionary<Pawn, CriminalRecord>` and serialises them with RimWorld's Scribe:
|
On load, that store cleans itself up. Three kinds of record get dropped:
|
||||||
|
|
||||||
```csharp
|
1. **Records of pawns who are gone** — dead or removed. They take their records with them.
|
||||||
Scribe_Collections.Look(ref records, "records",
|
2. **Corrupt entries** — a guard against a broken save.
|
||||||
LookMode.Reference, LookMode.Deep, ref tmpPawns, ref tmpRecords);
|
3. **Blank records** — anything still blank. There's no reason to keep a file that records nothing.
|
||||||
```
|
|
||||||
|
|
||||||
The keys save as **references** (a pawn is saved elsewhere; the record just points at them) and the
|
The upshot: **records are cheap and self-cleaning.** Anything that never got a real mark written to it
|
||||||
values save **deep** (the record's fields are written inline). Each `CriminalRecord.ExposeData`
|
simply evaporates on the next load, so the save never bloats with one empty file per pawn ever glanced
|
||||||
scribes its own eight fields with their defaults, so a save omits any field still at its default.
|
at.
|
||||||
|
|
||||||
### Post-load cleanup
|
|
||||||
|
|
||||||
On `PostLoadInit`, the component prunes the dictionary:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
records.RemoveAll(kv => kv.Key == null || kv.Value == null || kv.Value.IsBlank);
|
|
||||||
```
|
|
||||||
|
|
||||||
Three things get dropped:
|
|
||||||
|
|
||||||
1. **Null keys** — a pawn who has been removed or garbage-collected. *"Dead/removed pawns take their
|
|
||||||
records with them; a null key would throw later."* This is defensive: a stale key would crash a
|
|
||||||
later lookup.
|
|
||||||
2. **Null values** — corruption guard.
|
|
||||||
3. **Blank records** — anything `IsBlank` is true for. There is no reason to persist a file that
|
|
||||||
records nothing, and pruning them keeps the save from accumulating one empty record per pawn ever
|
|
||||||
examined.
|
|
||||||
|
|
||||||
The upshot: **records are cheap and self-cleaning.** You can `For(pawn)` freely on a write path
|
|
||||||
without worrying about bloat, because anything that never got a real mark written to it evaporates on
|
|
||||||
the next load.
|
|
||||||
|
|
||||||
### The singleton
|
|
||||||
|
|
||||||
The component keeps a private `static instance` set both in its constructor and re-set on
|
|
||||||
`PostLoadInit`, which is what lets `For` and `PeekFor` be static entry points reachable from anywhere
|
|
||||||
without threading a reference through every caller. If no game is loaded (no component), both return
|
|
||||||
`null` safely.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+34
-34
@@ -4,11 +4,11 @@
|
|||||||
institution does to the people inside it, and the crime, policing, and justice that put them there.*
|
institution does to the people inside it, and the crime, policing, and justice that put them there.*
|
||||||
|
|
||||||
Core is the engine every other Institution mod runs on. **On its own it changes nothing you can
|
Core is the engine every other Institution mod runs on. **On its own it changes nothing you can
|
||||||
see** — no items, no jobs, no UI, no XML. It ships one small assembly and is completely inert until
|
see** — no items, no jobs, no new menus. It sits completely quiet until another Institution mod is
|
||||||
another Institution mod is installed on top of it. You install Core only because something else asks
|
installed on top of it. You install Core only because something else asks for it.
|
||||||
for it.
|
|
||||||
|
|
||||||
This wiki documents what that engine actually computes, down to the exact numbers.
|
This wiki explains what that hidden engine actually does once a mod above it puts it to work — down to
|
||||||
|
the exact numbers, because knowing how the numbers move is half the fun of running a prison.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -19,29 +19,29 @@ This wiki documents what that engine actually computes, down to the exact number
|
|||||||
|
|
||||||
That single sentence is the whole suite. Colonists, prisoners, and slaves drift toward crime on a
|
That single sentence is the whole suite. Colonists, prisoners, and slaves drift toward crime on a
|
||||||
continuous spectrum; a policing and justice layer discovers, catches, punishes, reforms, or paroles
|
continuous spectrum; a policing and justice layer discovers, catches, punishes, reforms, or paroles
|
||||||
them. Core is where the *spectrum* lives as code — before anyone is caught, before there is a record
|
them. Core is where the *spectrum* itself lives — before anyone is caught, before there is a record to
|
||||||
to read, before there is a warden to search them.
|
read, before there is a warden to search them.
|
||||||
|
|
||||||
## Why a shared engine
|
## Why a shared engine
|
||||||
|
|
||||||
The propensity idea did not start abstract. It was written twice, concretely, in two different mods:
|
The propensity idea didn't start out abstract. It grew up twice, in two different mods: one deciding
|
||||||
the piss spree's `WouldFoulPeople` (which drunk, miserable pawn starts a mess) and the breakout's
|
which drunk, miserable pawn starts a mess, and one deciding which prisoner whittles a shiv the moment a
|
||||||
`WouldArmSelf` (which prisoner whittles a shiv when the door opens). Both asked the same real
|
door opens. Both were asking the same real question — *would **this** pawn do it?* — and each answered
|
||||||
question — *would **this** pawn do it?* — and both answered it their own way.
|
it its own way, which meant they could disagree about who was dangerous.
|
||||||
|
|
||||||
Core exists so they stop disagreeing. When the contraband system, the justice system, and the gang
|
Core exists so they stop disagreeing. When the contraband system, the justice system, and the gang
|
||||||
system all read one disposition engine, they agree about who is dangerous instead of each computing
|
system all read one disposition engine, they agree about who is dangerous instead of each judging it
|
||||||
it from scratch and drifting apart. One pawn who is "the scary one" is the scary one to every module
|
separately and drifting apart. The pawn who is "the scary one" is the scary one to every part of the
|
||||||
at once. That coherence — not any single number — is the point of a shared substrate.
|
suite at once. That coherence — not any single number — is the point of a shared foundation.
|
||||||
|
|
||||||
Core carries exactly the three things every Institution mod reads and none should own alone, and
|
Core carries exactly the three things every Institution mod needs and none should own alone, and
|
||||||
nothing else:
|
nothing else:
|
||||||
|
|
||||||
| Pillar | Class | Answers | Page |
|
| Pillar | Answers | Page |
|
||||||
|---|---|---|---|
|
|---|---|---|
|
||||||
| **Propensity** | `Propensity` | *Would this pawn do it?* (nature × nurture, seeded) | [Propensity](Propensity.md) |
|
| **Propensity** | *Would this pawn do it?* (nature × nurture) | [Propensity](Propensity.md) |
|
||||||
| **Criminal record** | `CriminalRecord` | *What has this pawn actually done?* (one per-pawn history) | [Criminal Record](Criminal-Record.md) |
|
| **Criminal record** | *What has this pawn actually done?* (one history per pawn) | [Criminal Record](Criminal-Record.md) |
|
||||||
| **Secured context** | `SecuredContexts` | *What kind of hold is this pawn under, and who may search them?* | [Secured Context](Secured-Context.md) |
|
| **Secured context** | *What kind of hold is this pawn under, and who may search them?* | [Secured Context](Secured-Context.md) |
|
||||||
|
|
||||||
The distinction between the first two is the spine of the whole design: **propensity is suspicion, a
|
The distinction between the first two is the spine of the whole design: **propensity is suspicion, a
|
||||||
record is fact.** Propensity says "this pawn *seems* dangerous"; a record says "this pawn *has*
|
record is fact.** Propensity says "this pawn *seems* dangerous"; a record says "this pawn *has*
|
||||||
@@ -51,22 +51,23 @@ instead of on vibes.
|
|||||||
## The pages of this wiki
|
## The pages of this wiki
|
||||||
|
|
||||||
- **[Propensity](Propensity.md)** — nature × nurture in full: the trait-weight table, the nurture
|
- **[Propensity](Propensity.md)** — nature × nurture in full: the trait-weight table, the nurture
|
||||||
multiplier ladder, the `Would()` formula with worked examples, and why the roll is seeded per pawn.
|
multiplier ladder, the disposition formula with worked examples, and why each pawn's roll is fixed
|
||||||
- **[Criminal Record](Criminal-Record.md)** — every field, who writes and reads each, the
|
rather than re-rolled.
|
||||||
`For` vs `PeekFor` distinction, how it persists, and why `reform` is the pivot of the suite.
|
- **[Criminal Record](Criminal-Record.md)** — everything a record remembers, who fills in each part,
|
||||||
- **[Secured Context](Secured-Context.md)** — the Free / Prisoner / Slave kinds, `Of()` and
|
how it persists across saves, and why *reform* is the pivot of the whole suite.
|
||||||
`OnMap()`, `IsHeld` vs `CanConceal`, and why the suite keys off this instead of "prisoner."
|
- **[Secured Context](Secured-Context.md)** — the Free / Prisoner / Slave kinds, what counts as
|
||||||
|
"held" versus what counts as "able to hide something," and why the suite keys off this instead of
|
||||||
|
"is a prisoner."
|
||||||
- **[Treatment Engine](Treatment-Engine.md)** — the shared rehabilitation maths: mark a condition
|
- **[Treatment Engine](Treatment-Engine.md)** — the shared rehabilitation maths: mark a condition
|
||||||
treatable, reduce it by skill × facility quality per session, and build a recovery track toward
|
treatable, reduce it a little each session by skill and facility quality, and build a recovery track
|
||||||
discharge. Ward's psychiatric care and Justice's reform share this one engine.
|
toward discharge. Ward's psychiatric care and Justice's reform run on this one engine.
|
||||||
- **[Modder API](Modder-API.md)** — the public surface any mod can call, and the one seam
|
- **[Modder API](Modder-API.md)** — the "for modders" page: the tools any mod can call to build on
|
||||||
(`Propensity.DeterrenceFactor`) that lets a justice layer feed back into disposition without Core
|
Core. Players can skip it.
|
||||||
ever depending on it.
|
|
||||||
|
|
||||||
## The suite
|
## The suite
|
||||||
|
|
||||||
Each mod stands alone as an install; together they form one system. Core is the leaf everything else
|
Each mod stands alone as an install; together they form one system. Core sits underneath everything
|
||||||
depends on.
|
else.
|
||||||
|
|
||||||
| Mod | What it adds | Needs |
|
| Mod | What it adds | Needs |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -79,9 +80,8 @@ depends on.
|
|||||||
|
|
||||||
## Requirements & load order
|
## Requirements & load order
|
||||||
|
|
||||||
- **RimWorld 1.6.** Core references only the base game — no Harmony, no DLC, no other mod.
|
- **RimWorld 1.6.** Core needs only the base game — no Harmony, no DLC, no other mod.
|
||||||
- Load Core **before** any other Institution mod (`loadAfter` Ludeon.RimWorld only).
|
- Load Core **before** any other Institution mod in your mod list.
|
||||||
- `packageId`: `flan.institution.core`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ Core is a library. It has no XML, no defs, no UI — its entire purpose is to be
|
|||||||
Institution mods and by yours. This page is the public surface, with the one seam that matters most
|
Institution mods and by yours. This page is the public surface, with the one seam that matters most
|
||||||
(`Propensity.DeterrenceFactor`) explained in full.
|
(`Propensity.DeterrenceFactor`) explained in full.
|
||||||
|
|
||||||
|
> **This page is for modders.** Every other page in this wiki is written for players; this one is the
|
||||||
|
> technical surface for building on Core. If you just want to know how the mod plays, start at the
|
||||||
|
> [Home](Home.md) page and the [Propensity](Propensity.md) / [Criminal Record](Criminal-Record.md) /
|
||||||
|
> [Secured Context](Secured-Context.md) / [Treatment Engine](Treatment-Engine.md) pages instead.
|
||||||
|
|
||||||
Everything here is verified against the three source files in `Source/Core/`.
|
Everything here is verified against the three source files in `Source/Core/`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+112
-165
@@ -1,58 +1,47 @@
|
|||||||
# Propensity — nature × nurture
|
# Propensity — nature × nurture
|
||||||
|
|
||||||
`Propensity` is the suite's disposition engine. It answers one question and only one: *"would
|
Propensity is the suite's disposition engine. It answers one question and only one: *"would **this**
|
||||||
**this** pawn do it?"* — never *"would a prisoner,"* never *"would a colonist."* Every behaviour in
|
pawn do it?"* — never *"would a prisoner,"* never *"would a colonist."* Every behaviour in the suite
|
||||||
the suite that hinges on a pawn's character routes through here, so the crime system, contraband
|
that hinges on a pawn's character comes back to this, so crime, contraband brewing, escape arming, and
|
||||||
brewing, escape arming, and gang recruitment all read the same answer instead of each guessing.
|
gang recruitment all read the same answer instead of each guessing.
|
||||||
|
|
||||||
The model is deliberately old-fashioned: **nature × nurture.**
|
The model is deliberately old-fashioned: **nature × nurture.**
|
||||||
|
|
||||||
- **Nature** is a fixed property of the person — their traits. Almost nobody is strongly disposed.
|
- **Nature** is a fixed property of the person — their traits. Almost nobody is strongly disposed, and
|
||||||
This does not change over a pawn's life.
|
this never changes over a pawn's life.
|
||||||
- **Nurture** is what the colony has done *to* them — mood, mistreatment, unmet needs, the shadow a
|
- **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.
|
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
|
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
|
line; a monster kept content and deterred may never act. Both of those stories are possible by design.
|
||||||
are possible.
|
|
||||||
|
|
||||||
Everything on this page is verified against `Source/Core/Propensity.cs`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Nature — who they are
|
## Nature — who they are
|
||||||
|
|
||||||
```
|
Every pawn starts from a **baseline of 0.05** — the floor of the human condition in this model:
|
||||||
Nature(pawn) : float in [0, 1]
|
everyone is capable of *something*, most people barely. From there, each trait the pawn has adds or
|
||||||
n = 0.05 // baseline — everyone has a little
|
subtracts, and the total is held to the 0–1 range (never below 0, never above 1).
|
||||||
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
|
### Trait weights
|
||||||
|
|
||||||
Traits are read **reflectively by defName** via `DefDatabase<TraitDef>.GetNamedSilentFail`. If a
|
Traits are matched **by name**, so if a trait's mod isn't installed that entry is simply skipped.
|
||||||
trait's mod is not installed, that entry is silently skipped rather than throwing a hard reference —
|
That's how Core uses Vanilla Traits Expanded's dark traits when VTE is present without *requiring* VTE
|
||||||
which is how Core consumes Vanilla Traits Expanded's dark traits without *depending* on VTE.
|
to be installed.
|
||||||
|
|
||||||
| Trait | Weight | Source | Note |
|
| Trait | Weight | Source | Note |
|
||||||
|---|---:|---|---|
|
|---|---:|---|---|
|
||||||
| Psychopath | **+0.45** | Vanilla | no empathy; the heaviest single input |
|
| 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 |
|
| 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 |
|
| Greedy | **+0.25** | Vanilla | wants more than their share |
|
||||||
| Abrasive | **+0.15** | Vanilla | friction with everyone |
|
| Abrasive | **+0.15** | Vanilla | friction with everyone |
|
||||||
| Ascetic | **−0.15** | Vanilla | wants little, takes little |
|
| Ascetic | **−0.15** | Vanilla | wants little, takes little |
|
||||||
| Kind | **−0.30** | Vanilla | the strongest pull *down* |
|
| Kind | **−0.30** | Vanilla | the strongest pull *down* |
|
||||||
|
|
||||||
The design note in the source is explicit: *"We CONSUME Vanilla Traits Expanded's
|
Core adds no traits of its own — it reads the ones the game and your other mods already provide.
|
||||||
kleptomaniac/pyromaniac as inputs here rather than rebuild them; vanilla's own dark traits count
|
Vanilla's own dark traits count right alongside VTE's.
|
||||||
too."* Core does not add traits of its own — it reads the ones the ecosystem already has.
|
|
||||||
|
|
||||||
### Worked Nature values
|
### Worked Nature values
|
||||||
|
|
||||||
@@ -60,205 +49,163 @@ Because weights simply add and then clamp, Nature is easy to read by hand:
|
|||||||
|
|
||||||
| Pawn | Arithmetic | Nature |
|
| Pawn | Arithmetic | Nature |
|
||||||
|---|---|---:|
|
|---|---|---:|
|
||||||
| Ordinary pawn (none of the above) | `0.05` | **0.05** |
|
| Ordinary pawn (none of the above) | 0.05 | **0.05** |
|
||||||
| Kind pawn | `0.05 − 0.30 = −0.25` → clamp | **0.00** |
|
| Kind pawn | 0.05 − 0.30 = −0.25 → clamp | **0.00** |
|
||||||
| A single Greedy trait | `0.05 + 0.25` | **0.30** |
|
| A single Greedy trait | 0.05 + 0.25 | **0.30** |
|
||||||
| Psychopath | `0.05 + 0.45` | **0.50** |
|
| Psychopath | 0.05 + 0.45 | **0.50** |
|
||||||
| Psychopath **and** Kind | `0.05 + 0.45 − 0.30` | **0.20** |
|
| Psychopath **and** Kind | 0.05 + 0.45 − 0.30 | **0.20** |
|
||||||
| Greedy + Abrasive | `0.05 + 0.25 + 0.15` | **0.45** |
|
| 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** |
|
| 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`.
|
(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
|
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;
|
Most rolled pawns sit at or near the `0.05` floor. That's intended: a colony full of ordinary people
|
||||||
a rare few are strongly inclined."* A colony full of ordinary people is supposed to be mostly safe on
|
is supposed to be mostly safe on nature alone. What makes them dangerous is nurture.
|
||||||
nature alone. What makes them dangerous is nurture.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Nurture — what you have done to them
|
## Nurture — what you have done to them
|
||||||
|
|
||||||
```
|
Nurture is a **multiplier**. An ordinary pawn under ordinary conditions sits at **1.0**. The number
|
||||||
Nurture(pawn) : float (a multiplier, >= ~0.5 in normal play)
|
rises as things go wrong and falls as they go right. Each lever below stacks on top of the last.
|
||||||
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
|
### The multiplier ladder
|
||||||
|
|
||||||
| Clause | Factor | Applies when | Stacks? |
|
| Lever | Factor | Applies when | Stacks? |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **Despair** | ×2.5 | `mood < 0.20` | mutually exclusive with "badly kept" |
|
| **Despair** | ×2.5 | mood below 20% | mutually exclusive with "badly kept" |
|
||||||
| **Badly kept** | ×1.6 | `0.20 ≤ mood < 0.35` | mutually exclusive with "despair" |
|
| **Badly kept** | ×1.6 | mood 20–35% | mutually exclusive with "despair" |
|
||||||
| **Held & unhappy** | ×1.4 | `IsHeld` **and** `mood < 0.40` | on top of the mood clause |
|
| **Held & unhappy** | ×1.4 | held **and** mood below 40% | on top of the mood lever |
|
||||||
| **Prisonization** | ×`Clamp(1 − reform×0.5, 0.4, 2.0)` | `reform ≠ 0` | on top |
|
| **Prisonization** | × (depends on reform, see below) | the pawn has a non-zero reform score | on top |
|
||||||
| **Deterrence** | ×`DeterrenceFactor(pawn)` | always (neutral `1.0` in Core alone) | 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 two mood levers are either/or: a pawn is either in despair *or* badly kept, never both. "Held &
|
||||||
The "held & unhappy" clause is separate and multiplies again — so a mistreated prisoner in despair
|
unhappy" is separate and multiplies again — so a mistreated prisoner in despair compounds
|
||||||
compounds `2.5 × 1.4 = 3.5` before anything else. The source calls that exactly what it is: *"a badly
|
`2.5 × 1.4 = 3.5` before anything else. That is a badly run cell.
|
||||||
run cell."*
|
|
||||||
|
|
||||||
### Prisonization — the reform lever
|
### 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 score | Meaning | Factor |
|
||||||
reform factor = Clamp(1 - reform * 0.5, 0.4, 2.0)
|
|
||||||
```
|
|
||||||
|
|
||||||
| `reform` | Meaning | Factor |
|
|
||||||
|---:|---|---:|
|
|---:|---|---:|
|
||||||
| **+1.0** | fully rehabilitated | 0.50 |
|
| **+1.0** | fully rehabilitated | 0.50 |
|
||||||
| +0.5 | improving | 0.75 |
|
| +0.5 | improving | 0.75 |
|
||||||
| 0 | untouched (clause skipped) | *1.00* |
|
| 0 | untouched (no effect) | *1.00* |
|
||||||
| −0.5 | hardening | 1.25 |
|
| −0.5 | hardening | 1.25 |
|
||||||
| **−1.0** | prisonized | 1.50 |
|
| **−1.0** | prisonized | 1.50 |
|
||||||
| ≥ +1.2 | (over-reformed) | clamp floor **0.40** |
|
| beyond +1.2 | (over-reformed) | floors at **0.40** |
|
||||||
| ≤ −2.0 | (utterly broken) | clamp ceiling **2.00** |
|
| 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
|
Reform is nominally a 0-to-1 rehabilitation score, but harsh punishment can drive it **negative** —
|
||||||
**negative** — the source discusses `reform < 0` ("hardened, prisonized") in as many words. Within the
|
that's the "hardened, prisonized" end. Within the realistic −1…+1 range the factor spans 0.5…1.5; the
|
||||||
realistic range `[−1, +1]` the factor spans `0.5 … 1.5`; the `0.4 / 2.0` clamps only bite at extremes
|
0.4 / 2.0 limits only bite at the extremes, catching a pawn who's been endlessly punished or endlessly
|
||||||
outside that, catching a pawn who has been endlessly punished or endlessly rehabilitated.
|
rehabilitated.
|
||||||
|
|
||||||
The important design property: Core only ever **reads** `reform` here. Nothing in Core moves it. The
|
Propensity only *reads* this score — nothing in Core moves it. The Justice layer's discipline and
|
||||||
Justice layer's `Discipline` and `Parole` are what nudge it up or down — which means
|
parole are what nudge it up or down, which is how institutionalization and recidivism enter the game
|
||||||
*institutionalization and recidivism become the suite's without a parallel mechanic.* A pawn who was
|
without a separate mechanic. A pawn broken by a brutal prison stays broken (nurture ×1.5) after
|
||||||
broken by a brutal prison stays broken (nurture ×1.5) after release; a pawn genuinely reformed stays
|
release; a pawn genuinely reformed stays calmer (×0.5) for good. The scar rides on one number.
|
||||||
calmer (×0.5) for good. The scar is carried by one float.
|
|
||||||
|
|
||||||
### Deterrence — the climate of order
|
### Deterrence — the climate of order
|
||||||
|
|
||||||
The final `× DeterrenceFactor(pawn)` is the seam that lets the whole colony's climate feed back into
|
The final lever multiplies by the colony's overall **climate of order**. **With only Core installed
|
||||||
each pawn's disposition. **In Core alone it is neutral — a flat `1.0`** — because Core does not know
|
it's neutral — a flat 1.0** — because Core by itself doesn't know what deterrence is. Install
|
||||||
what deterrence is. When Institution: Justice is loaded, it fills this in: a well-policed colony pulls
|
**Institution: Justice** and it fills this in: a well-policed colony pulls the factor below 1 and
|
||||||
the factor below `1` and deters everyone a little; a lawless one pushes it above `1` and emboldens
|
deters everyone a little; a lawless one pushes it above 1 and emboldens them. That's what makes
|
||||||
them. That is what makes catching and punishing *one* pawn matter to the disposition of *the rest*.
|
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
|
With only Core installed, this lever does nothing, and that is correct. The technical details of how a
|
||||||
[Modder API](Modder-API.md) page. For now: with Core installed by itself, this clause does nothing,
|
mod fills in deterrence are on the [Modder API](Modder-API.md) page.
|
||||||
and that is correct.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Would — the full roll
|
## Would — the full roll
|
||||||
|
|
||||||
`Nature` and `Nurture` are ingredients. `Would` is the meal: the actual yes/no for a specific
|
Nature and Nurture are ingredients. The full roll is the meal: the actual yes/no for a specific
|
||||||
behaviour at a specific base rate.
|
behaviour at a specific base rate. For a given behaviour, the chance works out to:
|
||||||
|
|
||||||
```
|
> **chance = base rate × (0.1 + Nature × 2) × Nurture**, capped at 85%.
|
||||||
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:
|
Three things are happening there:
|
||||||
|
|
||||||
1. **`baseChance`** is the caller's dial — the base rate of *this* behaviour for an average pawn
|
1. **Base rate** is the behaviour's own dial — how likely an *average* pawn is to do this particular
|
||||||
(a rare act passes a small number, a common one a larger). Core does not decide it; the module
|
thing (a rare act uses a small number, a common one a larger). Core doesn't decide it; whichever
|
||||||
asking the question does.
|
system is asking does.
|
||||||
2. **`(0.1 + Nature × 2)`** is the nature amplifier. It spans **×0.1** (a saint, Nature 0) to
|
2. **(0.1 + Nature × 2)** is the nature amplifier. It spans **×0.1** (a saint, Nature 0) to **×2.1**
|
||||||
**×2.1** (Nature 1). At the common `0.05` floor it is `×0.2`. So a floor pawn is one-fifth as
|
(Nature 1). At the common `0.05` floor it's **×0.2** — so an ordinary pawn is one-fifth as likely
|
||||||
likely as the base rate; a maxed monster over twice as likely — *before* nurture.
|
as the base rate, while a maxed monster is over twice as likely — *before* nurture.
|
||||||
3. **`× Nurture`** applies circumstance on top, and `min(cap, …)` caps the whole thing at `0.85` by
|
3. **× Nurture** applies circumstance on top, and the whole thing is capped at **85%** by default.
|
||||||
default. Nobody is ever a dead certainty; there is always slack. A caller who wants a harder or
|
Nobody is ever a dead certainty; there's always slack. A behaviour that wants a harder or softer
|
||||||
softer ceiling passes their own `cap`.
|
ceiling can set its own cap.
|
||||||
|
|
||||||
### Worked example 1 — an ordinary colonist, content
|
### 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
|
||||||
nature term = 0.1 + 0.05 * 2 = 0.2
|
- chance = 0.10 × 0.2 × 1.0 = 0.02 → **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
|
Two percent — and, critically, **fixed**. For this pawn and this question it's a settled 2% coin that
|
||||||
that either comes up or does not; it is not re-flipped every tick.
|
either comes up or doesn't; it isn't re-flipped every moment.
|
||||||
|
|
||||||
### Worked example 2 — a mistreated psychopath prisoner
|
### Worked example 2 — a mistreated psychopath prisoner
|
||||||
|
|
||||||
A Psychopath + Bloodlust prisoner (Nature `0.85`), mood `0.15` (despair ×2.5), held and unhappy
|
A Psychopath + Bloodlust prisoner (Nature 0.85), mood 15% (despair ×2.5), held and unhappy (×1.4),
|
||||||
(×1.4), `reform = −0.5` (hardening → ×1.25), Core-only so deterrence `1.0`. Base rate `0.10`:
|
reform −0.5 (hardening → ×1.25), Core-only so deterrence 1.0. Base rate 10%:
|
||||||
|
|
||||||
```
|
- nature term = 0.1 + 0.85 × 2 = 1.8
|
||||||
nature term = 0.1 + 0.85 * 2 = 1.8
|
- nurture = 2.5 × 1.4 × 1.25 = 4.375
|
||||||
nurture = 2.5 * 1.4 * 1.25 = 4.375
|
- chance = 0.10 × 1.8 × 4.375 = 0.7875 → **~79%**
|
||||||
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
|
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
|
### 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
|
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`:
|
lawless colony where Justice has set deterrence to 1.4. Base rate 10%:
|
||||||
|
|
||||||
```
|
- nature term = 0.1 + 1.0 × 2 = 2.1
|
||||||
nature term = 0.1 + 1.0 * 2 = 2.1
|
- nurture = 2.5 × 1.4 × 1.4 = 4.9
|
||||||
nurture = 2.5 * 1.4 * 1.4 = 4.9
|
- raw chance = 0.10 × 2.1 × 4.9 = 1.029
|
||||||
raw chance = 0.10 * 2.1 * 4.9 = 1.029
|
- capped at **85%**
|
||||||
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
|
The raw product blew past 100%; the cap reins it to 85%. Even here, a 15% sliver of "not today"
|
||||||
today" survives. That sliver is deliberate — it keeps the worst pawn a character with a bad day
|
survives. That sliver is deliberate — it keeps the worst pawn a character with a bad day coming, not a
|
||||||
coming, not a scripted event.
|
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.
|
||||||
|
|
||||||
```
|
**Save/reload stability.** Because the answer is tied to the pawn's permanent identity and the specific
|
||||||
seed = pawn.thingIDNumber ^ salt
|
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
|
**A character, not a moment-to-moment lottery.** If the game flipped a fresh coin every tick, any pawn
|
||||||
important design decision on this page, and it is worth being precise about why.
|
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
|
**Live circumstance still bites.** The roll freezes the *identity*, not the *situation*. Nurture is
|
||||||
fixed salt — not from the live RNG stream — asking "would they?" gives the same answer before and
|
recomputed live, so as mood collapses or reform hardens, the same pawn's chance climbs and the same
|
||||||
after a save/reload, as long as the inputs (traits, mood, reform) are the same. You cannot scum a
|
fixed coin can flip from "no" to "yes." The pawn who wouldn't have acted last month acts now — not
|
||||||
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.
|
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
|
**Different questions, independent answers.** Each behaviour is its own separate question, so the
|
||||||
independent. The source puts it plainly: *"a man who would pocket a shiv is not therefore a man who
|
answers don't leak into each other: a pawn who would pocket a shiv is not therefore a pawn who would
|
||||||
would inform on his cellmate."* One pawn can be reliably one kind of trouble and reliably not another,
|
inform on a cellmate. One pawn can be reliably one kind of trouble and reliably not another, and that
|
||||||
and that pattern is stable across the whole game.
|
pattern is stable across the whole game.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+45
-110
@@ -1,29 +1,18 @@
|
|||||||
# Secured Context — the kind of hold a pawn is under
|
# Secured Context — the kind of hold a pawn is under
|
||||||
|
|
||||||
The suite has a rule that looks small and is load-bearing: **nothing keys off "is this a prisoner."**
|
The suite has a rule that looks small and is load-bearing: **nothing keys off "is this a prisoner."**
|
||||||
It keys off `SecuredContext` — the *kind of hold* a pawn is under. That indirection is why a ward
|
It keys off the *kind of hold* a pawn is under. That one step of indirection is why a ward patient, a
|
||||||
patient, a slave, and (later) anyone inside a secure zone get the same concealment, search, schedule,
|
slave, and (later) anyone inside a secure zone get the same concealment, search, schedule, and needs
|
||||||
and needs machinery for free, without any module hard-coding a pawn status.
|
machinery for free, without any feature hard-coding a single pawn status.
|
||||||
|
|
||||||
`SecuredContext` reduces a pawn to one of three kinds and the question "who, if anyone, may search
|
A pawn's secured context boils down to one of three kinds and the question "who, if anyone, may search
|
||||||
them." Everything downstream reads that, so a new module works across every context the day it is
|
them." Everything downstream reads that, so a new feature works across every kind of hold the day it's
|
||||||
written, and a new *context* works across every existing module the day it is added.
|
written, and a new *kind of hold* works across every existing feature the day it's added.
|
||||||
|
|
||||||
Verified against `Source/Core/SecuredContext.cs`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The three kinds
|
## The three kinds
|
||||||
|
|
||||||
```csharp
|
|
||||||
public enum SecuredKind
|
|
||||||
{
|
|
||||||
Free, // a free colonist
|
|
||||||
Prisoner, // a prisoner of the colony
|
|
||||||
Slave, // a slave (Ideology)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Kind | Who it is | May search them | Can conceal? | Held against their will? |
|
| Kind | Who it is | May search them | Can conceal? | Held against their will? |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| **Free** | a free colonist | *no one* — until a secure-area or policing layer grants standing | **yes** — they can hoard | no |
|
| **Free** | a free colonist | *no one* — until a secure-area or policing layer grants standing | **yes** — they can hoard | no |
|
||||||
@@ -32,121 +21,67 @@ public enum SecuredKind
|
|||||||
|
|
||||||
Two subtleties fall out of this table:
|
Two subtleties fall out of this table:
|
||||||
|
|
||||||
- **Free colonists can still conceal.** A free colonist "can hoard contraband, but no one has
|
- **Free colonists can still conceal.** A free colonist can hoard contraband, but no one has standing
|
||||||
standing to search them until a secure-area or policing layer grants it." Freedom is not innocence —
|
to search them until a secure-area or policing layer grants it. Freedom isn't innocence — it's only
|
||||||
it is only the absence of an authority permitted to check. That is why `CanConceal` (below) includes
|
the absence of an authority allowed to check.
|
||||||
the free.
|
|
||||||
- **Prisoner and Slave differ only in who holds the keys.** Both are "held"; the search authority is
|
- **Prisoner and Slave differ only in who holds the keys.** Both are "held"; the search authority is
|
||||||
the warden for one and the overseer for the other. Downstream modules that only care "is someone
|
the warden for one and the overseer for the other. Features that only care "is someone entitled to
|
||||||
entitled to search this pawn" read `IsHeld` and never branch on which.
|
search this pawn" read whether the pawn is *held* and never branch on which.
|
||||||
|
|
||||||
## The struct
|
## Two questions, two axes
|
||||||
|
|
||||||
`SecuredContext` is a small `readonly struct` — the kind, the pawn, and two computed predicates.
|
The context answers two different questions, and picking the right one is the whole skill of using it:
|
||||||
|
|
||||||
```csharp
|
| Question | True for | Ask it when you care about… |
|
||||||
public readonly struct SecuredContext
|
|
||||||
{
|
|
||||||
public readonly SecuredKind Kind;
|
|
||||||
public readonly Pawn Pawn;
|
|
||||||
|
|
||||||
// Held against their will -- prisoner or slave. Excludes a free colonist.
|
|
||||||
public bool IsHeld => Kind == SecuredKind.Prisoner || Kind == SecuredKind.Slave;
|
|
||||||
|
|
||||||
// Anyone with something to hide -- includes free colonists (who may hoard).
|
|
||||||
public bool CanConceal => Pawn != null;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The two predicates split the world along two different axes, and picking the right one is the whole
|
|
||||||
skill of using this type:
|
|
||||||
|
|
||||||
| Predicate | True for | Ask it when you care about… |
|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `IsHeld` | Prisoner, Slave | **custody** — who is under the colony's control, who can be disciplined, whose cell can be searched |
|
| **Is this pawn held?** | Prisoner, Slave | **custody** — who is under the colony's control, who can be disciplined, whose cell can be searched |
|
||||||
| `CanConceal` | Free, Prisoner, Slave (any real pawn) | **contraband** — who could be *hiding* something, regardless of status |
|
| **Could this pawn be hiding something?** | Free, Prisoner, Slave (any real pawn) | **contraband** — who could be *hiding* something, regardless of status |
|
||||||
|
|
||||||
For example, [Propensity](Propensity.md)'s Nurture uses `IsHeld` in its "held & unhappy compounds"
|
For example, [Propensity](Propensity.md)'s Nurture uses *held* in its "held & unhappy compounds" lever
|
||||||
clause — it only wants to pile the ×1.4 penalty on a pawn the colony actually holds and mistreats, not
|
— it only wants to pile the ×1.4 penalty on a pawn the colony actually holds and mistreats, not on a
|
||||||
on a grumpy free colonist. A warden-search feature, by contrast, would gate on `CanConceal` to decide
|
grumpy free colonist. A warden-search feature, by contrast, cares who *could* be hiding something to
|
||||||
who is even worth checking, and then on standing to decide whether it is *allowed* to.
|
decide who's even worth checking, and then checks standing to decide whether it's *allowed* to.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## `Of` — one pawn to a context
|
## How a pawn gets classified
|
||||||
|
|
||||||
```csharp
|
Any pawn resolves to one context, or to **nothing at all** if the suite doesn't model them:
|
||||||
public static SecuredContext? Of(Pawn p)
|
|
||||||
```
|
|
||||||
|
|
||||||
`Of` maps a pawn to a context, or **`null`** if the pawn has none. The order of the checks matters and
|
- Animals, mechs, and the dead are simply not the suite's business — they resolve to nothing, and
|
||||||
is worth reading literally:
|
every sweep and check skips them without a special case.
|
||||||
|
- A pawn is tested as **prisoner** first, then **slave**, then **free colonist**. So a pawn who is
|
||||||
|
somehow both a colonist and imprisoned is classified by their *hold* first.
|
||||||
|
|
||||||
```
|
Keeping "nothing at all" separate from "a free pawn we track" is deliberate: there's a real difference
|
||||||
if p is not humanlike, or p is dead -> null (not one of ours)
|
between "a free pawn we watch" and "a pawn we don't model at all," and collapsing them would let
|
||||||
if p.IsPrisonerOfColony -> Prisoner
|
non-colony pawns leak into colony machinery. A single sweep can walk every prisoner, slave, and
|
||||||
if p.IsSlaveOfColony -> Slave
|
colonist on a map in one pass, skipping everything that resolves to nothing.
|
||||||
if p.IsFreeColonist -> Free
|
|
||||||
otherwise -> null
|
|
||||||
```
|
|
||||||
|
|
||||||
- **The guard comes first.** Animals, mechs, and the dead are simply not the suite's business — they
|
|
||||||
return `null`, and every `OnMap` loop and downstream check skips them without a special case.
|
|
||||||
- **Prisoner and Slave are tested before Free.** A pawn who is somehow both a colonist and imprisoned
|
|
||||||
is classified by their *hold* first. The nullable return is the clean signal for "this pawn is
|
|
||||||
outside the model," so callers pattern-match `HasValue` rather than defaulting to some "Free-ish"
|
|
||||||
status.
|
|
||||||
|
|
||||||
Returning `SecuredContext?` (nullable) rather than a `Free` fallback is deliberate: there is a real
|
|
||||||
difference between "a free pawn we track" and "a pawn we do not model at all," and collapsing them
|
|
||||||
would let non-colony pawns leak into colony machinery.
|
|
||||||
|
|
||||||
## `OnMap` — every context on a map
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public static IEnumerable<SecuredContext> OnMap(Map map)
|
|
||||||
```
|
|
||||||
|
|
||||||
`OnMap` yields a context for every spawned pawn that has one — prisoners, slaves, and colonists,
|
|
||||||
skipping everything `Of` returns `null` for. It is a lazy `yield` iterator, so a caller can enumerate
|
|
||||||
"everyone the suite cares about on this map" in one loop:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
foreach (SecuredContext ctx in SecuredContexts.OnMap(map))
|
|
||||||
{
|
|
||||||
if (ctx.IsHeld) { /* consider only the held */ }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
A `null` map yields nothing (an empty sequence), so callers do not need to null-check before looping.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Why not just "prisoner"?
|
## Why not just "prisoner"?
|
||||||
|
|
||||||
This is the design decision the whole page exists to justify, so it is worth stating plainly. If every
|
This is the design decision the whole page exists to justify. If every feature checked "is this pawn a
|
||||||
module checked `pawn.IsPrisonerOfColony` directly, then:
|
prisoner" directly, then:
|
||||||
|
|
||||||
- adding **slaves** to a feature would mean editing every module;
|
- adding **slaves** to a feature would mean revisiting every feature;
|
||||||
- adding a **ward patient** mode would mean editing every module;
|
- adding a **ward patient** mode would mean revisiting every feature;
|
||||||
- adding a future **secure-zone** concept would mean editing every module.
|
- adding a future **secure-zone** concept would mean revisiting every feature.
|
||||||
|
|
||||||
By routing all of them through one predicate, a new hold-kind is added *once*, in Core, and every
|
By routing all of them through one question — *what kind of hold is this?* — a new kind of hold is
|
||||||
existing module inherits it. The concealment, search, and needs machinery never has to learn what a
|
added *once*, and every existing feature inherits it. The concealment, search, and needs machinery
|
||||||
slave is — it only ever asked `IsHeld` and `CanConceal`.
|
never has to learn what a slave is; it only ever asked whether the pawn is *held* and whether they
|
||||||
|
*could conceal*.
|
||||||
|
|
||||||
### The deliberately-absent Ward kind
|
### The deliberately-absent Ward kind
|
||||||
|
|
||||||
There is no `SecuredKind.WardPatient`, and its absence is intentional. From the source:
|
There's no separate "ward patient" kind, and its absence is intentional. A committed ward patient
|
||||||
|
**is** a prisoner in the game's own sense — Institution: Ward builds its committed-patient mode on top
|
||||||
> *"a committed patient IS a prisoner in vanilla's sense (Ward rides the prisoner rail), so it is
|
of vanilla's prisoner system — so such a pawn already counts as **Prisoner** here, with no special case
|
||||||
> `SecuredKind.Prisoner` here and needs no special case. Contraband does not depend on Ward."*
|
and no dependency from Core (or Contraband) onto Ward. Every feature treats a ward patient exactly as
|
||||||
|
it treats any prisoner. The right amount of new work to support Ward's hold semantics was zero, and
|
||||||
Because Institution: Ward implements a committed patient *on top of* vanilla's prisoner rail, such a
|
that's the payoff of keying off the kind of hold instead of the label.
|
||||||
pawn already returns `Prisoner` from `Of` — no new kind, no dependency from Core (or Contraband) onto
|
|
||||||
Ward, and every module treats a ward patient exactly as it treats any prisoner. The right amount of
|
|
||||||
new code to support Ward's hold semantics was zero, and that is the payoff of keying off the context
|
|
||||||
instead of the label.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+55
-120
@@ -1,61 +1,40 @@
|
|||||||
# The Treatment Engine — shared rehabilitation
|
# The Treatment Engine — shared rehabilitation
|
||||||
|
|
||||||
*`TreatmentProgram` + `TreatableConditionExtension`, in `Source/Core/TreatmentProgram.cs`.*
|
*The shared maths behind every "fix a held pawn over time" system in the suite.*
|
||||||
|
|
||||||
Core carries one more thing every "fix a held pawn over time" system needs and none should own alone:
|
Core carries one more thing every rehabilitation-style system needs and none should own alone: the
|
||||||
the maths of a **treatment programme**. Sustained attention in a secured facility reduces a marked
|
maths of a **treatment programme**. Sustained attention in a secured facility reduces a marked
|
||||||
condition, the room's quality helps or hurts, and once there is nothing left to reduce a **recovery
|
condition, the room's quality helps or hurts, and once there's nothing left to reduce a **recovery
|
||||||
track** builds toward a dischargeable state.
|
track** builds toward a state where the pawn can be discharged.
|
||||||
|
|
||||||
It exists for the same reason the propensity engine does. Two systems want the identical loop pointed
|
It exists for the same reason the propensity engine does. Two systems want the identical loop pointed
|
||||||
at different states — **Ward** treats the mental illness that got a pawn committed; **Justice** wants
|
at different things — **Ward** treats the mental illness that got a pawn committed; **Justice**
|
||||||
to rehabilitate the disposition that got one imprisoned — and without a shared engine each would grow
|
rehabilitates the disposition that got one imprisoned — and without a shared engine each would grow its
|
||||||
its own copy and drift. Core owns **only the maths**. Which interaction mode enrols a pawn, which
|
own copy and drift apart. Core owns **only the maths**. Which interaction enrols a pawn, which
|
||||||
hediffs and thoughts carry the flavour, which jobs run the sessions, and which alert fires on
|
conditions and moods carry the flavour, which jobs run the sessions, and which alert fires on discharge
|
||||||
discharge all stay in the consuming mod.
|
all live in the mod using it.
|
||||||
|
|
||||||
> The engine does something concrete when there is a real condition to work through, and is a
|
> The engine does something only when there's a real condition to work through, and does nothing
|
||||||
> **no-op** otherwise. A colony that marks nothing treatable never notices it exists.
|
> otherwise. A colony that never marks anything treatable never notices it exists.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The two pieces
|
## How treatment plays out
|
||||||
|
|
||||||
### `TreatableConditionExtension` — marking a condition
|
A condition has to be **marked treatable** before a programme can touch it (Ward marks mental illness
|
||||||
|
this way; a modder or player can mark anything — see the [Modder API](Modder-API.md) page). Once a
|
||||||
|
condition is marked, treatment runs in sessions:
|
||||||
|
|
||||||
A `DefModExtension` you attach to a `HediffDef` to declare it something a programme can reduce.
|
1. **Each completed session removes a slice of the condition's severity.** The base amount is **0.12
|
||||||
|
severity per session**, *before* it's scaled by the treating pawn's skill, the facility's quality,
|
||||||
```xml
|
and how much of the session that patient actually gets. When a condition's severity reaches zero
|
||||||
<HediffDef Name="SomeDisorder">
|
it's gone.
|
||||||
<modExtensions>
|
2. **A full course is several sessions by design.** The programme has to be *sustained* — you can't
|
||||||
<li Class="Contraband.TreatableConditionExtension">
|
fix a pawn in a single visit.
|
||||||
<reductionPerSession>0.12</reductionPerSession>
|
3. **Once nothing is left to treat, continued care builds a recovery track** from 0 toward 1
|
||||||
</li>
|
(discharge-ready). Fix the illness first, then stabilise.
|
||||||
</modExtensions>
|
4. **The recovery track decays on its own if you stop.** Recovery you stop maintaining slips back down
|
||||||
</HediffDef>
|
— which is what makes the whole thing a loop you keep up, not a one-time unlock.
|
||||||
```
|
|
||||||
|
|
||||||
| Field | Default | Meaning |
|
|
||||||
|---|---:|---|
|
|
||||||
| `reductionPerSession` | `0.12` | Severity removed per **completed** session, *before* the caller's skill / facility / share scale it. |
|
|
||||||
|
|
||||||
A full course is several sessions by design — the programme has to be **sustained**, not run once. The
|
|
||||||
extension carries no flavour; it says only *how fast this condition yields*. Ward attaches it to
|
|
||||||
mental illness (via a soft, conditional patch on Rim Disorders' depression, anxiety, PTSD, OCD);
|
|
||||||
anything a mod or a player marks is treated identically, so opting a hediff in is a one-line patch.
|
|
||||||
|
|
||||||
### `TreatmentProgram` — the maths
|
|
||||||
|
|
||||||
A static, null-safe class. It holds no state of its own — it reads and mutates hediffs on the pawn.
|
|
||||||
|
|
||||||
| Call | Returns | What it does |
|
|
||||||
|---|---|---|
|
|
||||||
| `TreatableConditions(pawn)` | `List<Hediff>` | every marked hediff on the pawn that still has severity |
|
|
||||||
| `HasTreatableCondition(pawn)` | `bool` | is there any condition left to work through? |
|
|
||||||
| `ReduceConditions(pawn, strength)` | `void` | lower every marked condition by `reductionPerSession × strength`; remove any that reach ~0 |
|
|
||||||
| `FacilityQuality(pawn)` | `float 0.5..1.5` | how much the pawn's room helps or hurts |
|
|
||||||
| `AdvanceRecovery(pawn, recoveryDef, amount)` | `float` | advance (creating if absent) a recovery hediff, clamped to `1.0`; returns the new severity |
|
|
||||||
| `RecoveryLevel(pawn, recoveryDef)` | `float 0..1` | current level of that recovery track, or `0` |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -63,96 +42,52 @@ A static, null-safe class. It holds no state of its own — it reads and mutates
|
|||||||
|
|
||||||
### Facility quality
|
### Facility quality
|
||||||
|
|
||||||
```
|
The room the treatment happens in scales every session between **0.5×** (grim) and **1.5×**
|
||||||
room == null OR psychologically outdoors → 0.75 (a poor makeshift facility, not zero)
|
(excellent):
|
||||||
otherwise → Clamp(0.6 + impressiveness / 120, 0.5, 1.5)
|
|
||||||
```
|
|
||||||
|
|
||||||
| Room impressiveness | Factor |
|
| Room | Factor |
|
||||||
|---|---:|
|
|---|---:|
|
||||||
| 0 (bare) | `0.60` |
|
| Bare (0 impressiveness) | 0.60 |
|
||||||
| ~48 (decent) | `1.00` |
|
| Decent (~48 impressiveness) | 1.00 |
|
||||||
| ≥108 (impressive) | `1.50` (capped) |
|
| Impressive (108+ impressiveness) | 1.50 (capped) |
|
||||||
| outdoors / none | `0.75` |
|
| Outdoors / no room | 0.75 |
|
||||||
|
|
||||||
The clamp is deliberate: a grim, filthy, cramped facility heals worse, a calm clean one better, but a
|
The limits are deliberate: a grim, filthy, cramped facility heals worse and a calm clean one better,
|
||||||
palace can't trivialise the labour the programme costs (1.5× ceiling), and even nowhere is a poor
|
but a palace can't trivialise the work the programme costs (1.5× ceiling), and even treating a pawn out
|
||||||
makeshift room, not a hard zero.
|
in the open is a poor makeshift room (0.75×), not a hard zero.
|
||||||
|
|
||||||
### Reducing a condition
|
### Reducing a condition
|
||||||
|
|
||||||
```
|
Each marked condition drops by **0.12 × the session's combined strength** per session, and vanishes
|
||||||
h.Severity -= reductionPerSession × strength // per marked hediff
|
when it hits zero. That combined strength is the treating pawn's skill × the facility quality above ×
|
||||||
if h.Severity <= 0.001 → remove it
|
the patient's share of the session — so all three inputs matter, and a good therapist in a good room
|
||||||
```
|
working one-on-one is worth many times a poor one splitting attention in a bad room.
|
||||||
|
|
||||||
`strength` is the **caller's** combined factor — skill × facility quality × this pawn's share of the
|
### Building recovery
|
||||||
session. The engine does not define it; the consumer does, so a one-on-one session at full skill in a
|
|
||||||
good room reduces far more than a distracted share in a squalid one.
|
|
||||||
|
|
||||||
### Advancing recovery
|
The recovery track is a plain 0-to-1 progress bar toward discharge. Continued care adds to it (capped
|
||||||
|
at 1.0) and — crucially — it carries a slow decay, so it slips back down without sustained attention.
|
||||||
```
|
The mod using the engine decides what reaching the top actually *unlocks* (Ward: a "ready for
|
||||||
recovery = pawn's hediff of recoveryDef (created at 0.001 if absent)
|
discharge" alert).
|
||||||
recovery.Severity = min(1.0, recovery.Severity + amount)
|
|
||||||
```
|
|
||||||
|
|
||||||
The recovery track is a plain `0..1` severity the consumer supplies the def for (Ward's
|
|
||||||
`Ward_Recovery`). Core only advances it; the consumer decides what its top stage *unlocks* (Ward: a
|
|
||||||
"ready for discharge" alert) and — crucially — gives the hediff a **negative `severityPerDay`** so it
|
|
||||||
**decays without sustained attention**. Recovery you stop maintaining slips back; that decay is what
|
|
||||||
makes the programme a loop rather than a one-time unlock.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The consumer contract
|
## Who uses it
|
||||||
|
|
||||||
Core draws a hard line at "only the maths". A consumer owns everything with flavour:
|
|
||||||
|
|
||||||
| Core owns (the maths) | The consumer owns (the flavour) |
|
|
||||||
|---|---|
|
|
||||||
| `TreatableConditions` / `HasTreatableCondition` | which hediffs are marked treatable |
|
|
||||||
| `ReduceConditions` | the job/interaction that runs a session |
|
|
||||||
| `FacilityQuality` | the room role that makes a "facility" |
|
|
||||||
| `AdvanceRecovery` / `RecoveryLevel` | the recovery hediff def + what its stable stage unlocks |
|
|
||||||
| — | the discharge alert, the thoughts, the decay rate |
|
|
||||||
|
|
||||||
The idiomatic session, drawn from Ward's `JobDriver_PsychiatricCare` (the reference consumer):
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using Contraband;
|
|
||||||
|
|
||||||
float skill = 0.7f * social + 0.3f * medicine; // the consumer's own blend
|
|
||||||
float quality = TreatmentProgram.FacilityQuality(patient);
|
|
||||||
float strength = (0.5f + skill / 20f * 0.5f) * quality * share; // 1.0 share for the primary patient
|
|
||||||
|
|
||||||
// 1. Reduce the actual condition first.
|
|
||||||
TreatmentProgram.ReduceConditions(patient, strength);
|
|
||||||
|
|
||||||
// 2. Only once nothing is left to treat does recovery advance -- fix the illness, then stabilise.
|
|
||||||
if (!TreatmentProgram.HasTreatableCondition(patient))
|
|
||||||
{
|
|
||||||
TreatmentProgram.AdvanceRecovery(patient, MyDefOf.RecoveryHediff, 0.15f * quality * share);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
"Reduce the condition, *then* build recovery" is the shape Ward uses, but it is a convention, not a
|
|
||||||
rule Core enforces — a rehabilitation consumer with no clinical condition to clear (Justice's reform)
|
|
||||||
can advance a recovery track from the first session and read `RecoveryLevel` to gate a parole.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## The two consumers
|
|
||||||
|
|
||||||
- **[Institution: Ward](https://git.onetick.ninja/flan/rimworld-ward)** — psychiatric care. Marks
|
- **[Institution: Ward](https://git.onetick.ninja/flan/rimworld-ward)** — psychiatric care. Marks
|
||||||
mental-illness hediffs treatable, runs warden counselling sessions, builds `Ward_Recovery` toward a
|
mental-illness conditions treatable (Rim Disorders' depression, anxiety, PTSD, and OCD, when that mod
|
||||||
"ready for discharge" alert. The reference implementation.
|
is present), runs warden counselling sessions, and builds a recovery track toward a "ready for
|
||||||
- **Institution: Justice** — rehabilitation. Its `reform` score is the disposition axis punishment
|
discharge" alert. The reference example.
|
||||||
|
- **Institution: Justice** — rehabilitation. Its *reform* score is the disposition axis punishment
|
||||||
moves; the recovery track is the natural home for a *sustained rehabilitation programme* that gates
|
moves; the recovery track is the natural home for a *sustained rehabilitation programme* that gates
|
||||||
parole on the same shared engine, so the two systems agree on "getting better" instead of each
|
parole on the same shared engine, so the two systems agree on "getting better" instead of each
|
||||||
inventing it.
|
inventing it.
|
||||||
|
|
||||||
The point, exactly as with propensity: one engine, two states, no drift.
|
"Reduce the condition, *then* build recovery" is the shape Ward uses, but it isn't forced: a
|
||||||
|
rehabilitation system with no clinical condition to clear (like Justice's reform) can build a recovery
|
||||||
|
track from the very first session and read its level to gate a parole.
|
||||||
|
|
||||||
|
The point, exactly as with propensity: one engine, two uses, no drift.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+52
-84
@@ -4,29 +4,29 @@
|
|||||||
|
|
||||||
You already build a minimum wing and a max block. You already put the psychopath behind steel and the
|
You already build a minimum wing and a max block. You already put the psychopath behind steel and the
|
||||||
teenage pickpocket behind a wooden door. Classification does not replace those walls, cells and locks —
|
teenage pickpocket behind a wooden door. Classification does not replace those walls, cells and locks —
|
||||||
it answers the question vanilla never does: **who belongs where.** It reads the one shared
|
it answers the question vanilla never does: **who belongs where.** It reads the one shared record every
|
||||||
`CriminalRecord` every module writes to and turns it into a single verdict, so the crime system, the
|
part of the suite writes to and turns it into a single verdict, so the crime system, the search, the
|
||||||
search, the escape and the classifier all agree on how dangerous a pawn is, because they read the same
|
escape and the classifier all agree on how dangerous a pawn is, because they read the same number.
|
||||||
number.
|
|
||||||
|
|
||||||
Grade drives everything downstream: search frequency, privileges, escape risk, and — critically —
|
Grade drives everything downstream: search frequency, privileges, escape risk, and — critically —
|
||||||
parole eligibility ([Parole](Parole.md) gates on grade).
|
parole eligibility ([Parole](Parole.md) gates on grade).
|
||||||
|
|
||||||
> The isolation *toll* of a max/solitary placement is Prisoner Realism's (good) sim. Justice only
|
> The isolation *toll* of a max/solitary placement is Prisoner Realism's (good) sim. Corrections only
|
||||||
> decides the placement.
|
> decides the placement.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The tiers
|
## The tiers
|
||||||
|
|
||||||
`SecurityGrade` is four bands, low to high — the tiers players already build, named:
|
Every held pawn falls into one of four security grades, low to high — the tiers you already build,
|
||||||
|
named:
|
||||||
|
|
||||||
| Grade | Risk score | Reading |
|
| Grade | Risk score | Reading |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Minimum** | `< 0.35` | Barely a disposition, clean record. Open dorm, light watch. |
|
| **Minimum** | below 0.35 | Barely a disposition, clean record. Open dorm, light watch. |
|
||||||
| **Medium** | `0.35 – 0.79` | A real record or a real nature. Proper cell, routine search. The parole ceiling. |
|
| **Medium** | 0.35 – 0.79 | A real record or a real nature. Proper cell, routine search. The parole ceiling. |
|
||||||
| **Maximum** | `0.80 – 1.49` | Repeat offender or proven flight risk. Steel, solitary door, frequent search. |
|
| **Maximum** | 0.80 – 1.49 | Repeat offender or proven flight risk. Steel, solitary door, frequent search. |
|
||||||
| **Supermax** | `≥ 1.5` | The headline cases. Escapes plus crimes plus a dark nature. Never let them near a wall. |
|
| **Supermax** | 1.5 and up | The headline cases. Escapes plus crimes plus a dark nature. Never let them near a wall. |
|
||||||
|
|
||||||
Note the ceiling at **Medium**: [Parole](Parole.md) will only consider a pawn graded Medium or below.
|
Note the ceiling at **Medium**: [Parole](Parole.md) will only consider a pawn graded Medium or below.
|
||||||
Grade is therefore not just a placement hint — it is the gate a pawn has to fall back through before
|
Grade is therefore not just a placement hint — it is the gate a pawn has to fall back through before
|
||||||
@@ -36,34 +36,27 @@ they can be released.
|
|||||||
|
|
||||||
## The risk score
|
## The risk score
|
||||||
|
|
||||||
Risk is a `0..~3` number: baseline disposition plus everything on the record, minus reform earned.
|
Risk is a number from 0 to about 3: baseline disposition, plus everything on the record, minus reform
|
||||||
|
earned. Each thing a prisoner has done adds a fixed amount:
|
||||||
|
|
||||||
```
|
| What it counts | Adds to risk | Why this weight |
|
||||||
risk = Nature(pawn) * 0.5 // who they are, before they have done anything
|
|
||||||
+ escapeAttempts * 0.35 // a proven flight risk is the headline
|
|
||||||
+ crimesCommitted * 0.20
|
|
||||||
+ timesCaught * 0.15
|
|
||||||
+ contrabandMade * 0.10
|
|
||||||
- max(0, reform) * 0.4 // genuine reform earns a downgrade
|
|
||||||
risk = max(0, risk) // never negative
|
|
||||||
```
|
|
||||||
|
|
||||||
| Term | Weight | Why this weight |
|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `Nature` (0..1) | `× 0.5` | Disposition alone can carry a pawn to Medium (Nature 0.7+), but never past it on its own. Character is a suspicion, not a conviction. |
|
| **Nature** (0 to 1) | nature × 0.5 | Disposition alone can carry a pawn to Medium (nature 0.7+), but never past it on its own. Character is a suspicion, not a conviction. |
|
||||||
| `escapeAttempts` | `× 0.35` | The heaviest per-event term. A pawn who *runs* is the one who ends up armed in your base. One attempt is worth nearly two crimes. |
|
| **Each escape attempt** | +0.35 | The heaviest per-event term. A pawn who *runs* is the one who ends up armed in your base. One attempt is worth nearly two crimes. |
|
||||||
| `crimesCommitted` | `× 0.20` | The staple. Steady, cumulative. |
|
| **Each crime committed** | +0.20 | The staple. Steady, cumulative. |
|
||||||
| `timesCaught` | `× 0.15` | Caught contraband is worse than merely suspected. |
|
| **Each time caught** | +0.15 | Caught contraband is worse than merely suspected. |
|
||||||
| `contrabandMade` | `× 0.10` | The lightest term — making a shiv is common; escaping with one is not. |
|
| **Each contraband item made** | +0.10 | The lightest term — making a shiv is common; escaping with one is not. |
|
||||||
| `reform` (if > 0) | `− 0.4` | The only term that can *lower* the grade. At most −0.4 (reform capped at 1.0). |
|
| **Reform earned** | up to −0.4 | The only thing that *lowers* the grade. At most −0.4, since reform caps at 1.0. |
|
||||||
|
|
||||||
|
The total is never allowed below zero.
|
||||||
|
|
||||||
Two facts fall out of the numbers, and both matter for how you play:
|
Two facts fall out of the numbers, and both matter for how you play:
|
||||||
|
|
||||||
- **`timesSearched` is not in the formula.** Searching a pawn does not make them more dangerous —
|
- **Searching a prisoner does not raise their risk.** Only *finding* something does — a search that
|
||||||
only *finding* something (`timesCaught`) does. Search freely; it costs the prisoner no grade.
|
turns up nothing costs the prisoner no grade. Search freely.
|
||||||
- **Reform can shift at most 0.4 of risk.** At `reform = 1.0`, the downgrade is `−0.4`. So a pawn
|
- **Reform can shift at most 0.4 of risk.** At full reform (1.0), the downgrade is −0.4. So a pawn
|
||||||
whose record *without reform* already totals `≥ 1.2` can never fall to Medium (`< 0.8`) no matter
|
whose record *without reform* already totals 1.2 or more can never fall to Medium (below 0.8) no
|
||||||
how thoroughly they reform — and therefore can **never be paroled**. A prolific offender is a
|
matter how thoroughly they reform — and therefore can **never be paroled**. A prolific offender is a
|
||||||
life sentence. You will hold them, work them, or execute them; you will not release them. This is a
|
life sentence. You will hold them, work them, or execute them; you will not release them. This is a
|
||||||
deliberate property of the weights, not an accident.
|
deliberate property of the weights, not an accident.
|
||||||
|
|
||||||
@@ -71,45 +64,34 @@ Two facts fall out of the numbers, and both matter for how you play:
|
|||||||
|
|
||||||
## Worked example — grading a record
|
## Worked example — grading a record
|
||||||
|
|
||||||
**Bram**, an Abrasive raider. Nature = `0.05 + 0.15 = 0.20`. He has three crimes, one escape attempt,
|
**Bram**, an Abrasive raider, has a nature of 0.20. He has three crimes, one escape attempt, was caught
|
||||||
was caught twice, made two shivs, and has not been reformed (`reform = 0`).
|
twice, made two shivs, and has not been reformed. Add it up:
|
||||||
|
|
||||||
```
|
| Source | Contribution |
|
||||||
risk = 0.20 * 0.5 = 0.100 (nature)
|
|---|---|
|
||||||
+ 1 * 0.35 = 0.350 (escape — the headline)
|
| nature 0.20 × 0.5 | 0.100 |
|
||||||
+ 3 * 0.20 = 0.600 (crimes)
|
| 1 escape × 0.35 | 0.350 (the headline) |
|
||||||
+ 2 * 0.15 = 0.300 (caught)
|
| 3 crimes × 0.20 | 0.600 |
|
||||||
+ 2 * 0.10 = 0.200 (contraband)
|
| caught twice × 0.15 | 0.300 |
|
||||||
- 0 * 0.4 = 0.000 (no reform)
|
| 2 shivs × 0.10 | 0.200 |
|
||||||
─────────
|
| no reform | 0.000 |
|
||||||
total = 1.550 → ≥ 1.5 → SUPERMAX
|
| **total** | **1.550** → Supermax |
|
||||||
```
|
|
||||||
|
|
||||||
Now treat him. Say patient handling and good conduct lift him to `reform = 0.40`:
|
Now treat him. Say patient handling and good conduct lift his reform to 0.40. That subtracts
|
||||||
|
0.40 × 0.4 = 0.16, dropping his risk to **1.39 → Maximum.** He falls a band.
|
||||||
|
|
||||||
```
|
But notice: his reform-*less* risk is 1.55, well above 1.2. Even at perfect reform (1.0) he lands at
|
||||||
risk = 1.550 - (0.40 * 0.4) = 1.550 - 0.160 = 1.390 → MAXIMUM
|
1.55 − 0.40 = 1.15 — still Maximum. **Bram is beyond parole for the rest of his life.** His record
|
||||||
```
|
decided that, not his disposition.
|
||||||
|
|
||||||
He drops a band. But notice: his reform-less risk is `1.55`, well above `1.2`. Even at perfect
|
Contrast **Cass**, a Kind pickpocket whose gentle nature floors her nature term at 0. She has one
|
||||||
`reform = 1.0` he lands at `1.55 − 0.40 = 1.15` — still Maximum. **Bram is beyond parole for the rest
|
crime, was caught once, no escapes, no contraband, no reform yet. Her risk is
|
||||||
of his life.** His record decided that, not his disposition.
|
(1 × 0.20) + (1 × 0.15) = **0.35 → Medium**, exactly on the line.
|
||||||
|
|
||||||
Contrast **Cass**, a Kind pickpocket. Nature = `clamp01(0.05 − 0.30) = 0`. One crime, caught once, no
|
Give Cass the standard treatment up to reform 0.30. That subtracts 0.30 × 0.4 = 0.12, dropping her to
|
||||||
escape, no contraband, `reform = 0`.
|
**0.23 → Minimum.** Cass is now Minimum, and — being Medium-or-below with reform at least 0.30 —
|
||||||
|
**paroleable.** Same institution, two futures, and the record told you which was which before you had
|
||||||
```
|
to guess.
|
||||||
risk = 0 + 0 + (1*0.20) + (1*0.15) + 0 - 0 = 0.350 → MEDIUM (exactly on the line)
|
|
||||||
```
|
|
||||||
|
|
||||||
Give Cass the standard treatment to `reform = 0.30`:
|
|
||||||
|
|
||||||
```
|
|
||||||
risk = 0.350 - (0.30 * 0.4) = 0.350 - 0.120 = 0.230 → MINIMUM
|
|
||||||
```
|
|
||||||
|
|
||||||
Cass is now Minimum, and — being Medium-or-below with `reform ≥ 0.30` — **paroleable**. Same
|
|
||||||
institution, two futures, and the record told you which was which before you had to guess.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -117,31 +99,17 @@ institution, two futures, and the record told you which was which before you had
|
|||||||
|
|
||||||
- **Segregate by grade, not by vibe.** Sort your cells to the four bands and put a pawn where their
|
- **Segregate by grade, not by vibe.** Sort your cells to the four bands and put a pawn where their
|
||||||
score, not your hunch, says. The whole suite reads the record; you can too.
|
score, not your hunch, says. The whole suite reads the record; you can too.
|
||||||
- **Escapes are the alarm.** One escape attempt (`+0.35`) will jump a middling pawn a band. If a pawn
|
- **Escapes are the alarm.** One escape attempt (+0.35) will jump a middling pawn a band. If a pawn
|
||||||
crosses into Maximum after a break attempt, believe it — treat them as the flight risk the number
|
crosses into Maximum after a break attempt, believe it — treat them as the flight risk the number
|
||||||
says they are.
|
says they are.
|
||||||
- **A clean nature is not a clean record.** A Kind pawn who has escaped twice still grades Maximum;
|
- **A clean nature is not a clean record.** A Kind pawn who has escaped twice still grades Maximum;
|
||||||
disposition is only half the score.
|
disposition is only half the score.
|
||||||
- **Watch the parole ceiling.** If you intend to release someone, keep their reform-less risk under
|
- **Watch the parole ceiling.** If you intend to release someone, keep their reform-less risk under
|
||||||
`1.2` — mostly that means limiting how deep their record gets *before* you start treating them. A
|
1.2 — mostly that means limiting how deep their record gets *before* you start treating them. A pawn
|
||||||
pawn you let rack up escapes and catches has sentenced themselves.
|
you let rack up escapes and catches has sentenced themselves.
|
||||||
- **Reform is the only lever that lowers a grade.** See [Discipline & Reform](Discipline-and-Reform.md)
|
- **Reform is the only lever that lowers a grade.** See [Discipline & Reform](Discipline-and-Reform.md)
|
||||||
for how to move it, and [Parole](Parole.md) for where the grade gate lands.
|
for how to move it, and [Parole](Parole.md) for where the grade gate lands.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## For modders
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
SecurityGrade grade = Classification.Grade(pawn); // the verdict
|
|
||||||
float score = Classification.Risk(pawn); // the raw 0..~3 number
|
|
||||||
```
|
|
||||||
|
|
||||||
Both are pure reads — `Risk` uses `GameComponent_CriminalRecords.PeekFor` and never creates a record.
|
|
||||||
`SecurityGrade` is an ordered enum (`Minimum < Medium < Maximum < Supermax`), so `grade <= Medium`
|
|
||||||
comparisons work as written. The type lives in the `Contraband` namespace (Justice was extracted from
|
|
||||||
Institution: Contraband and kept the shared namespace).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Part of the **Institution** suite for RimWorld 1.6: Core · Contraband · Policing · Corrections · Gangs · Ward.*
|
*Part of the **Institution** suite for RimWorld 1.6: Core · Contraband · Policing · Corrections · Gangs · Ward.*
|
||||||
|
|||||||
@@ -2,62 +2,57 @@
|
|||||||
|
|
||||||
*Punishment as an action, with a self-complete payoff — and the seat of institutionalization.*
|
*Punishment as an action, with a self-complete payoff — and the seat of institutionalization.*
|
||||||
|
|
||||||
> This page is the **maths** — what a disciplinary act does to reform and order. What *drives* it in
|
> This page is the **numbers** — what a disciplinary act does to reform and order. What *drives* it in
|
||||||
> play (a warden's rehabilitation sessions, and neglect that hardens) is [Rehabilitation](Rehabilitation.md).
|
> play (a warden's rehabilitation sessions, and neglect that hardens) is [Rehabilitation](Rehabilitation.md).
|
||||||
|
|
||||||
Punishment in Justice does two things at once, on two different axes:
|
Punishment does two things at once, on two different axes:
|
||||||
|
|
||||||
1. **It deters the colony.** Order seen to be done raises the climate of order for everyone —
|
1. **It deters the colony.** Order seen to be done raises the climate of order for everyone: any act of
|
||||||
`RecordPunishment`, `+0.12`. See [Deterrence](Deterrence.md).
|
discipline adds **+0.12** to the colony's order. See [Deterrence](Deterrence.md).
|
||||||
2. **It reshapes the punished.** A harsh hand *prisonizes* — the pawn hardens, `reform` falls, their
|
2. **It reshapes the punished.** A harsh hand *prisonizes* — the pawn hardens, reform falls, their
|
||||||
disposition climbs for good. A corrective hand *rehabilitates* — `reform` rises, their disposition
|
disposition climbs for good. A corrective hand *rehabilitates* — reform rises, their disposition
|
||||||
cools for good.
|
cools for good.
|
||||||
|
|
||||||
Both effects are Justice's own, so discipline is never inert. Prisoner Realism owns the passive *toll*
|
Both effects belong to Corrections, so discipline is never inert. Prisoner Realism owns the passive
|
||||||
of isolation (a good sim of how solitary *feels*); Justice owns the *order* it produces and the
|
*toll* of isolation (a good sim of how solitary *feels*); Corrections owns the *order* it produces and
|
||||||
persistent *shift* it leaves on the pawn. When both mods are present, PR's solitary deterioration
|
the persistent *shift* it leaves on the pawn. When both mods are present, PR's solitary deterioration
|
||||||
deepens the felt experience on top — it does not replace this.
|
deepens the felt experience on top — it does not replace this.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The two hands
|
## The two hands
|
||||||
|
|
||||||
```
|
Every disciplinary act moves the prisoner's **reform** score, in one of two directions:
|
||||||
Discipline.Punish(pawn, harsh):
|
|
||||||
Justice.RecordPunishment(pawn.Map) // +0.12 colony climate (Deterrence)
|
|
||||||
reform = Clamp(reform + (harsh ? -0.15 : +0.10), -1, +1) // the persistent shift
|
|
||||||
```
|
|
||||||
|
|
||||||
| Call | Reform delta | Meaning |
|
| The hand | Reform change | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `Punish(pawn, harsh: true)` | **−0.15** | Beatings, deprivation, the hard hand. Hardens — "prisonization." |
|
| **Harsh** | **−0.15** | Beatings, deprivation, the hard hand. Hardens — "prisonization." |
|
||||||
| `Punish(pawn, harsh: false)` | **+0.10** | Correction, structure, rewarded good conduct. Rehabilitates. |
|
| **Corrective** | **+0.10** | Correction, structure, rewarded good conduct. Rehabilitates. |
|
||||||
|
|
||||||
`reform` lives on the shared record, clamped to `[−1, +1]`:
|
Either way, the act also adds **+0.12** to the colony's order — the colony sees order done, whoever it
|
||||||
|
was done to.
|
||||||
|
|
||||||
| `reform` | Reading |
|
Reform lives on the shared record, clamped between −1 and +1:
|
||||||
|
|
||||||
|
| Reform | Reading |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `+1.0` | Fully rehabilitated |
|
| **+1.0** | Fully rehabilitated |
|
||||||
| `0.0` | Untouched — as they came in |
|
| **0.0** | Untouched — as they came in |
|
||||||
| `−1.0` | Fully hardened, institutionalized |
|
| **−1.0** | Fully hardened, institutionalized |
|
||||||
|
|
||||||
Note the asymmetry the other way from Deterrence: **harsh (−0.15) moves faster than gentle (+0.10).**
|
Note the asymmetry: **harsh (−0.15) moves faster than gentle (+0.10).** It takes three corrective acts
|
||||||
It takes three corrective acts to build `+0.30` of reform; two harsh acts to tear down `−0.30`. Damage
|
to build +0.30 of reform; two harsh acts to tear down −0.30. Damage is quicker than repair — as it
|
||||||
is quicker than repair — as it should be.
|
should be.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How reform becomes disposition
|
## How reform becomes disposition
|
||||||
|
|
||||||
Reform is not a status effect or a mood buff. It is read straight back into `Propensity.Nurture` (in
|
Reform is not a status effect or a mood buff. It feeds straight back into a pawn's **disposition** —
|
||||||
Core), where it multiplies a pawn's whole disposition:
|
Core's underlying "would they act out?" multiplier — scaling their whole disposition up or down and
|
||||||
|
keeping it there. The size of that scaling is set entirely by reform:
|
||||||
|
|
||||||
```
|
| Reform | Disposition factor | Effect |
|
||||||
if reform != 0:
|
|
||||||
Nurture *= Clamp(1 - reform * 0.5, 0.4, 2)
|
|
||||||
```
|
|
||||||
|
|
||||||
| `reform` | Nurture factor | Effect |
|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| −1.00 (hardened) | **1.500** | +50% more disposed — permanently |
|
| −1.00 (hardened) | **1.500** | +50% more disposed — permanently |
|
||||||
| −0.30 | 1.150 | +15% |
|
| −0.30 | 1.150 | +15% |
|
||||||
@@ -67,78 +62,50 @@ if reform != 0:
|
|||||||
| +0.30 (parole threshold) | 0.850 | −15% |
|
| +0.30 (parole threshold) | 0.850 | −15% |
|
||||||
| +1.00 (rehabilitated) | **0.500** | −50% less disposed — permanently |
|
| +1.00 (rehabilitated) | **0.500** | −50% less disposed — permanently |
|
||||||
|
|
||||||
Given reform is clamped to `[−1, +1]`, the factor spans `0.5×` to `1.5×`; the `0.4`/`2` clamp is a
|
Because reform is clamped between −1 and +1, the factor spans 0.5× to 1.5×. **This is the whole
|
||||||
safety rail that reform alone never reaches. **This is the whole trick.** Institutionalization and
|
trick.** Institutionalization and recidivism are not a parallel mechanic bolted on the side — they are
|
||||||
recidivism are not a parallel mechanic bolted on the side — they are *one number the propensity engine
|
*one number the disposition already reads*. A hardened pawn is not flagged "recidivist"; they simply
|
||||||
already reads*. A hardened pawn is not flagged "recidivist"; they simply carry a `−reform` that makes
|
carry a negative reform that makes every "would they?" roll for the rest of their life come up hot.
|
||||||
every "would they?" roll for the rest of their life come up hot. Discipline and Parole are what *move*
|
Discipline and Parole are what *move* reform; disposition only *reads* it — which is how
|
||||||
reform; Nurture only *reads* it — which is how institutionalization becomes Justice's without a second
|
institutionalization emerges without a second system.
|
||||||
system.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Worked example — two sentences
|
## Worked example — two sentences
|
||||||
|
|
||||||
Take a fresh prisoner, `reform = 0`, badly kept: mood `0.30`, held. From Core, before reform, their
|
Take a fresh prisoner with reform 0, badly kept: mood 0.30, held. Before any reform, their disposition
|
||||||
Nurture is already `1 × 1.6 (mood) × 1.4 (held & unhappy) = 2.24`, and say the colony sits at baseline
|
is already running hot — a bad mood (×1.6) and a held-and-unhappy cell (×1.4) combine, and with the
|
||||||
so `DeterrenceFactor = 1.05`, giving Nurture `≈ 2.35`.
|
colony at baseline the pawn sits around **2.35× disposition**.
|
||||||
|
|
||||||
**The hard hand.** You beat them into line — three harsh punishments:
|
**The hard hand.** You beat them into line — three harsh punishments take reform to −0.45. At that
|
||||||
|
reform the disposition factor is 1.225, so their disposition is now 2.35 × 1.225 ≈ **2.88 — +22% hotter
|
||||||
```
|
than when they arrived**, forever, independent of mood. You have made them more criminal, not less.
|
||||||
reform: 0 → -0.15 → -0.30 → -0.45
|
This is prisonization: the sentence itself became the cause. (You did buy +0.36 of colony order along
|
||||||
Nurture factor at reform -0.45 = 1 - (-0.45 * 0.5) = 1.225
|
the way — order today, at the cost of the pawn's tomorrow.)
|
||||||
```
|
|
||||||
|
|
||||||
Their disposition is now `2.35 × 1.225 ≈ 2.88` — **+22% hotter than when they arrived**, forever,
|
|
||||||
independent of mood. You have made them more criminal, not less. This is prisonization: the sentence
|
|
||||||
itself became the cause. (You did buy `+0.36` of colony climate along the way — order today, at the
|
|
||||||
cost of the pawn's tomorrow.)
|
|
||||||
|
|
||||||
**The corrective hand.** Instead you structure and reward — three gentle punishments *and* you fix
|
**The corrective hand.** Instead you structure and reward — three gentle punishments *and* you fix
|
||||||
their conditions (recreation, decent cell) so mood recovers to `0.55`:
|
their conditions (recreation, decent cell) so mood recovers to 0.55. Reform rises to +0.30, and the
|
||||||
|
recovered mood drops the "badly kept" and "held-and-unhappy" penalties entirely. The disposition factor
|
||||||
```
|
at +0.30 reform is 0.85, so their disposition is now roughly **0.89 — below neutral.** The same pawn who
|
||||||
reform: 0 → +0.10 → +0.20 → +0.30
|
would have been 2.88 disposed is now 0.89. And at reform 0.30 with a low enough grade they have crossed
|
||||||
mood 0.55 → no mood multiplier, not held-&-unhappy → those factors drop out
|
the [Parole](Parole.md) threshold. Same institution, opposite outcomes — decided by which hand you used.
|
||||||
Nurture factor at reform +0.30 = 1 - (0.30 * 0.5) = 0.85
|
|
||||||
```
|
|
||||||
|
|
||||||
Now Nurture is roughly `1 × 0.85 × 1.05 (deterrence) ≈ 0.89` — **below neutral.** The same pawn who
|
|
||||||
would have been `2.88` disposed is now `0.89`. And at `reform = 0.30` with a low enough grade they
|
|
||||||
have crossed the [Parole](Parole.md) threshold. Same institution, opposite outcomes — decided by which
|
|
||||||
hand you used.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How to play with discipline
|
## How to play with discipline
|
||||||
|
|
||||||
- **Harsh is a lever, not a punishment button.** It buys colony order *now* (`+0.12` climate) at the
|
- **Harsh is a lever, not a punishment button.** It buys colony order *now* (+0.12) at the price of the
|
||||||
price of the pawn's disposition *forever* (`−0.15` reform → hotter Nurture). Use it when you need the
|
pawn's disposition *forever* (−0.15 reform → hotter disposition). Use it when you need the deterrence
|
||||||
deterrence and never intend to release the pawn — a Supermax lifer you are only ever going to hold.
|
and never intend to release the pawn — a Supermax lifer you are only ever going to hold.
|
||||||
- **Gentle is how anyone gets out.** Parole needs `reform ≥ 0.30`; only the corrective hand builds it.
|
- **Gentle is how anyone gets out.** Parole needs reform 0.30 or higher; only the corrective hand
|
||||||
Three gentle acts is the floor.
|
builds it. Three gentle acts is the floor.
|
||||||
- **Damage compounds; repair is slow.** `−0.15` vs `+0.10` means a pawn you brutalize early is
|
- **Damage compounds; repair is slow.** −0.15 vs +0.10 means a pawn you brutalize early is expensive to
|
||||||
expensive to bring back — and a hardened pawn commits more crime, which sinks the climate, which
|
bring back — and a hardened pawn commits more crime, which sinks the climate, which makes *everyone*
|
||||||
makes *everyone* worse. Don't harden pawns you might want later.
|
worse. Don't harden pawns you might want later.
|
||||||
- **Conditions and discipline stack.** Reform shifts disposition permanently; mood, neglect and
|
- **Conditions and discipline stack.** Reform shifts disposition permanently; mood, neglect and
|
||||||
deterrence shift it live (see [Regime](Regime.md) and [Deterrence](Deterrence.md)). A reformed pawn
|
deterrence shift it live (see [Regime](Regime.md) and [Deterrence](Deterrence.md)). A reformed pawn
|
||||||
in a well-run wing is calm on two axes at once.
|
in a well-run wing is calm on two axes at once.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## For modders
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
Discipline.Punish(pawn, harsh: true); // -0.15 reform, +0.12 climate
|
|
||||||
Discipline.Punish(pawn, harsh: false); // +0.10 reform, +0.12 climate
|
|
||||||
```
|
|
||||||
|
|
||||||
`Punish` uses `GameComponent_CriminalRecords.For` (creates the record if absent) and clamps `reform`
|
|
||||||
to `[−1, +1]`. It always fires `RecordPunishment` on the pawn's map, so *any* act of discipline moves
|
|
||||||
the colony climate regardless of which hand you use — the colony sees order done, whoever it was done
|
|
||||||
to. The class lives in the `Contraband` namespace (shared since the extraction from Contraband).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Part of the **Institution** suite for RimWorld 1.6: Core · Contraband · Policing · Corrections · Gangs · Ward.*
|
*Part of the **Institution** suite for RimWorld 1.6: Core · Contraband · Policing · Corrections · Gangs · Ward.*
|
||||||
|
|||||||
+15
-14
@@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
Vanilla RimWorld's prisoners are interchangeable mood-boxes; a "maximum security" wing is a decoration
|
Vanilla RimWorld's prisoners are interchangeable mood-boxes; a "maximum security" wing is a decoration
|
||||||
the sim never acknowledges, and a warden's work never changes who a pawn becomes. **Corrections turns
|
the sim never acknowledges, and a warden's work never changes who a pawn becomes. **Corrections turns
|
||||||
the shared record into a regime you run** — it grades every held pawn from what they have actually done,
|
the prison into a regime you run** — it grades every held pawn from what they have actually done,
|
||||||
moves a *reform* score that decides whether a sentence hardens or heals, gates release on it, and gives
|
tracks a *reform* score that decides whether a sentence hardens or heals a prisoner, gates release on
|
||||||
prisoners the recreation need vanilla flatly denies them.
|
it, and gives prisoners the recreation need vanilla flatly denies them.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -15,31 +15,32 @@ prisoners the recreation need vanilla flatly denies them.
|
|||||||
|
|
||||||
| Page | What it covers |
|
| Page | What it covers |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **[Classification](Classification.md)** | Grades held pawns `Minimum`..`Supermax` from a risk score over the record; wall-mounted security markers grade a whole wing; misplacement flags. |
|
| **[Classification](Classification.md)** | Grades held pawns Minimum..Supermax from a risk score over their record; wall-mounted security markers grade a whole wing; misplacement alerts. |
|
||||||
| **[Discipline & Reform](Discipline-and-Reform.md)** | Punishment moves the reform score — harden ("prisonized") vs. rehabilitate. The seat of institutionalization and recidivism. |
|
| **[Discipline & Reform](Discipline-and-Reform.md)** | Punishment moves the reform score — harden ("prisonized") vs. rehabilitate. The seat of institutionalization and recidivism. |
|
||||||
| **[Rehabilitation](Rehabilitation.md)** | The warden work that drives the arc: a rehabilitate mode + counselling sessions move reform and colony order, a parole alert + release, neglect that hardens. |
|
| **[Rehabilitation](Rehabilitation.md)** | The warden work that drives the arc: counselling sessions move reform and colony order, a parole alert and release, neglect that hardens. |
|
||||||
| **[Parole](Parole.md)** | Release a reformed, low-grade prisoner as a free colonist; with Ideology, the execution stance sets the bar. Nested, primed for a future parole panel. |
|
| **[Parole](Parole.md)** | Release a reformed, low-grade prisoner as a free colonist; with Ideology, your execution stance sets the bar. |
|
||||||
| **[Regime](Regime.md)** | Prisoner recreation (Yard/Lockup schedule via Prison Labor's timetable), visitation, cell-safety. |
|
| **[Regime](Regime.md)** | Prisoner recreation (Yard/Lockup schedule via Prison Labor's timetable), visitation, cell-safety. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The one shared record
|
## One shared record
|
||||||
|
|
||||||
Corrections keeps no per-pawn crime state of its own. It reads and writes the single `CriminalRecord`
|
Corrections keeps no crime history of its own. Every held pawn carries a single record that
|
||||||
that **Institution: Core** holds — the same record Contraband, Policing, and the gang systems read.
|
**Institution: Core** maintains — the same record Contraband, Policing, and the gang systems all read
|
||||||
Corrections authors the `reform` / `pardoned` columns via Discipline and Parole, and grades and gates
|
and write. Discipline and Parole write the *reform* and *pardon* onto it; classification grades and the
|
||||||
over all the counters its siblings feed. Reform and rehabilitation run on Core's shared **treatment
|
release gate read back over every counter its siblings feed in. Reform and rehabilitation run on Core's
|
||||||
engine**, the same one Ward's psychiatric care uses.
|
shared treatment system, the same one Ward's psychiatric care uses — so a prisoner's whole arc, from
|
||||||
|
first crime to parole, is one continuous history rather than a pile of separate flags.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Requirements & load order
|
## Requirements & load order
|
||||||
|
|
||||||
- **RimWorld 1.6**
|
- **RimWorld 1.6**
|
||||||
- **Institution: Core** — required (the propensity/record/treatment engine)
|
- **Institution: Core** — required (the disposition, record, and treatment systems)
|
||||||
- **Institution: Policing** — required (the shared climate of order: discipline and rehab nudge it, the
|
- **Institution: Policing** — required (the shared climate of order: discipline and rehab nudge it, the
|
||||||
inspect pane reads it)
|
inspect pane reads it)
|
||||||
- **Harmony** — required (Regime's prisoner-recreation patch)
|
- **Harmony** — required (Regime's prisoner-recreation feature)
|
||||||
- Load **after** Core, Policing, and Harmony.
|
- Load **after** Core, Policing, and Harmony.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+48
-86
@@ -2,78 +2,62 @@
|
|||||||
|
|
||||||
*Release, driven by what the pawn has become.*
|
*Release, driven by what the pawn has become.*
|
||||||
|
|
||||||
> This page is the release **decision** (`CanRelease`). The warden work that acts on it — the
|
> This page is the release **decision**. The warden work that acts on it — the "Ready for parole" alert
|
||||||
> "Ready for parole" alert and the release job that frees a parolee back into the colony — is
|
> and the release that frees a parolee back into the colony — is [Rehabilitation](Rehabilitation.md).
|
||||||
> [Rehabilitation](Rehabilitation.md).
|
|
||||||
|
|
||||||
Parole is the end of the arc, and it is deliberately a *decision*, not a mechanic that rebuilds the
|
Parole is the end of the arc, and it is deliberately a *decision*, not a mechanic that rebuilds the
|
||||||
game's release flow. It asks one question — **is this pawn ready?** — and answers it from two things
|
game's release flow. It asks one question — **is this pawn ready?** — and answers it from two things
|
||||||
already on the record: the reform the sentence earned (or failed to earn), and the grade the record
|
already on the record: the reform the sentence earned (or failed to earn), and the grade the record
|
||||||
warrants. Both are self-complete reads; nothing here re-simulates a pawn's future.
|
warrants.
|
||||||
|
|
||||||
> The *return* outcome — a grateful ally, a vengeful raider — is Prisoner Realism's Recidivism, a good
|
> The *return* outcome — a grateful ally, a vengeful raider — is Prisoner Realism's Recidivism, a good
|
||||||
> sim Justice does not rebuild. Justice's mirror is the parolee who **stays** and may reoffend,
|
> sim Corrections does not rebuild. Corrections' mirror is the parolee who **stays** and may reoffend,
|
||||||
> carrying their record into the colony (a Justice follow-up).
|
> carrying their record into the colony.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The gate
|
## The gate
|
||||||
|
|
||||||
```
|
A prisoner can be paroled only when **all three** of these hold:
|
||||||
CanRelease(pawn):
|
|
||||||
rec = record for pawn (peek, no create)
|
|
||||||
if rec == null: return true // nothing on record to hold them
|
|
||||||
if rec.pardoned: return false // already released
|
|
||||||
return rec.reform >= 0.30
|
|
||||||
&& Classification.Grade(pawn) <= SecurityGrade.Medium
|
|
||||||
```
|
|
||||||
|
|
||||||
Three conditions, all of which must hold:
|
|
||||||
|
|
||||||
| Condition | Threshold | Why |
|
| Condition | Threshold | Why |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Not already pardoned | `pardoned == false` | Release is a one-way flag; you can't re-parole. |
|
| Not already pardoned | never released before | Release is one-way; you can't re-parole. |
|
||||||
| Genuinely reformed | `reform ≥ 0.30` | Proof the sentence *healed* rather than hardened. Three corrective acts minimum. |
|
| Genuinely reformed | reform 0.30 or higher | Proof the sentence *healed* rather than hardened. Three corrective acts minimum. |
|
||||||
| Low enough grade | `Grade ≤ Medium` (risk `< 0.8`) | Reform of attitude is not enough — the *record* must have cooled to something you'd let out. |
|
| Low enough grade | grade Medium or below (risk under 0.8) | Reform of attitude is not enough — the *record* must have cooled to something you'd let out. |
|
||||||
|
|
||||||
A pawn with **no record at all** is releasable trivially (there is nothing to hold them). Everyone
|
A pawn with **no record at all** is releasable trivially (there is nothing to hold them). Everyone else
|
||||||
else has to earn all three.
|
has to earn all three.
|
||||||
|
|
||||||
The grade gate and the reform gate interact in a way worth internalizing. Because reform subtracts at
|
The grade gate and the reform gate interact in a way worth internalizing. Because reform subtracts at
|
||||||
most `0.4` from risk ([Classification](Classification.md)), a pawn whose reform-*less* risk is `≥ 1.2`
|
most 0.4 from risk ([Classification](Classification.md)), a pawn whose reform-*less* risk is 1.2 or more
|
||||||
can never reach Medium — so **no amount of reform will ever parole a prolific offender.** Parole is for
|
can never reach Medium — so **no amount of reform will ever parole a prolific offender.** Parole is for
|
||||||
pawns whose record stayed shallow enough that treatment can pull them back under the ceiling. Let a
|
pawns whose record stayed shallow enough that treatment can pull them back under the ceiling. Let a pawn
|
||||||
pawn rack up escapes and catches and you have quietly converted them into a lifer, whatever their
|
rack up escapes and catches and you have quietly converted them into a lifer, whatever their attitude.
|
||||||
attitude.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Release
|
## Release
|
||||||
|
|
||||||
```
|
Releasing a parolee marks a **pardon** on their record. Vanilla does the actual freeing; the pardon is
|
||||||
Release(pawn):
|
Corrections' memory that this pawn was let out after genuine reform, so reintegration can grant a clean
|
||||||
record.pardoned = true
|
slate and the pawn won't be offered for parole twice. A modest faction-goodwill / relationship nudge for
|
||||||
```
|
a well-handled release is the immediate payoff.
|
||||||
|
|
||||||
`Release` sets one flag: `pardoned`. Vanilla does the actual freeing; the flag is Justice's memory
|
|
||||||
that this pawn was let out after genuine reform, so reintegration can grant a clean slate and
|
|
||||||
`CanRelease` won't offer them twice. A modest faction-goodwill / relationship nudge for a well-handled
|
|
||||||
release is the immediate payoff, wired when the release job lands.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The whole arc
|
## The whole arc
|
||||||
|
|
||||||
The pawn moves through every Justice module in turn, and each one writes the record the next one reads:
|
The pawn moves through every stage in turn, and each one writes the record the next one reads:
|
||||||
|
|
||||||
| Stage | Module | What moves | Record touched |
|
| Stage | Where it lives | What moves | What's recorded |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 1. Crime | [Deterrence](Deterrence.md) | Climate `−0.08`; the colony runs hot | `crimesCommitted++`, `lastCrimeTick` |
|
| 1. Crime | [Deterrence](Deterrence.md) | Climate −0.08; the colony runs hot | a crime, and when it happened |
|
||||||
| 2. Caught & held | [Classification](Classification.md) | A grade is assigned from the record | reads all counters |
|
| 2. Caught & held | [Classification](Classification.md) | A grade is assigned from the record | reads every counter |
|
||||||
| 3. Discipline | [Discipline](Discipline-and-Reform.md) | `reform` moves ±; climate `+0.12` | `reform` |
|
| 3. Discipline | [Discipline](Discipline-and-Reform.md) | Reform moves ±; climate +0.12 | reform |
|
||||||
| 4. Reform | [Discipline](Discipline-and-Reform.md) → Core | Disposition cools as reform rises | `reform` (read by Nurture) |
|
| 4. Reform | [Discipline](Discipline-and-Reform.md) → Core | Disposition cools as reform rises | reform (read by disposition) |
|
||||||
| 5. Regrade | [Classification](Classification.md) | Reform subtracts risk; grade falls | reads `reform` |
|
| 5. Regrade | [Classification](Classification.md) | Reform subtracts risk; grade falls | reads reform |
|
||||||
| 6. **Parole** | this page | Gate opens when reform ≥ 0.30 **and** grade ≤ Medium | reads `reform`, `pardoned`; sets `pardoned` |
|
| 6. **Parole** | this page | Gate opens at reform 0.30 **and** grade Medium or below | reads reform and pardon; sets pardon |
|
||||||
|
|
||||||
It is a loop, not a line: the crime that opens stage 1 also cools the colony's climate, which the *next*
|
It is a loop, not a line: the crime that opens stage 1 also cools the colony's climate, which the *next*
|
||||||
pawn's disposition reads — so one pawn's whole sentence is faintly present in every other pawn's odds.
|
pawn's disposition reads — so one pawn's whole sentence is faintly present in every other pawn's odds.
|
||||||
@@ -82,46 +66,37 @@ pawn's disposition reads — so one pawn's whole sentence is faintly present in
|
|||||||
|
|
||||||
## Worked example — a sentence that ends in release
|
## Worked example — a sentence that ends in release
|
||||||
|
|
||||||
**Cass**, a Kind pickpocket. Nature `= clamp01(0.05 − 0.30) = 0`. She arrives with one crime, caught
|
**Cass**, a Kind pickpocket whose gentle nature floors her nature term at 0. She arrives with one crime,
|
||||||
once, no escapes, no contraband, `reform = 0`.
|
caught once, no escapes, no contraband, no reform yet. Her risk is (1 × 0.20) + (1 × 0.15) = **0.35 →
|
||||||
|
Medium.** She is already Medium-or-below, so the *grade* gate is met — but her reform is 0, so she is not
|
||||||
|
yet releasable.
|
||||||
|
|
||||||
```
|
You give her the corrective hand three times (structure, rewarded conduct) and fix her cell so she
|
||||||
Grade at intake:
|
isn't stewing. Reform rises to 0.30, which subtracts 0.30 × 0.4 = 0.12 from her risk, dropping her to
|
||||||
risk = 0 + 0 + (1*0.20) + (1*0.15) + 0 - 0 = 0.35 → MEDIUM
|
**0.23 → Minimum.** Now all three gates are met — reform at least 0.30, Minimum is below Medium, and
|
||||||
CanRelease? reform 0 < 0.30 → NO
|
she's never been pardoned — so **Cass is paroleable.** Pardon her and she walks.
|
||||||
```
|
|
||||||
|
|
||||||
She is already Medium-or-below, so the *grade* gate is met — but her `reform` is `0`. You give her the
|
Contrast **Bram** from the Classification page, whose reform-less risk of 1.55 keeps him at Maximum even
|
||||||
corrective hand three times (structure, rewarded conduct), and fix her cell so she isn't stewing:
|
at full reform — the grade gate never opens for him, so he can never be paroled. Two prisoners, the same
|
||||||
|
treatment, and the record decides which one you can ever let go.
|
||||||
```
|
|
||||||
reform: 0 → +0.10 → +0.20 → +0.30
|
|
||||||
Grade now: risk = 0.35 - (0.30 * 0.4) = 0.23 → MINIMUM
|
|
||||||
CanRelease? reform 0.30 ≥ 0.30 AND Minimum ≤ Medium AND not pardoned → YES
|
|
||||||
```
|
|
||||||
|
|
||||||
`Release(Cass)` sets `pardoned = true`. She walks. Contrast **Bram** from the Classification page,
|
|
||||||
whose reform-less risk of `1.55` keeps him at Maximum even at `reform = 1.0` — `CanRelease` returns
|
|
||||||
`false` for Bram forever, because the grade gate never opens. Two prisoners, the same treatment, and
|
|
||||||
the record decides which one you can ever let go.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## A note on record cleanup
|
## A note on record cleanup
|
||||||
|
|
||||||
A pawn whose record is `IsBlank` — no crimes, no escapes, no contraband, no catches, `reform == 0`,
|
A pawn with a completely empty record — no crimes, no escapes, no contraband, no catches, no reform, no
|
||||||
not pardoned — is dropped on save/load (Core prunes blank and dead-pawn records). A *pardoned* pawn is
|
pardon — is forgotten on save/load (Core prunes blank and dead-pawn records). A *pardoned* pawn is
|
||||||
therefore **not** blank and their record persists: the pardon is remembered. This is why a released
|
therefore **not** empty, and their record persists: the pardon is remembered. This is why a released
|
||||||
parolee who stays and reoffends still carries their history — Justice does not forget that they were
|
parolee who stays and reoffends still carries their history — Corrections does not forget that they were
|
||||||
once let out.
|
once let out.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Ideology-flavoured thresholds
|
## Ideology-flavoured thresholds
|
||||||
|
|
||||||
Precepts layer on top of this baseline, not under it: an execution-favouring ideoligion paroles
|
Precepts layer on top of this baseline, not under it: an execution-favouring ideoligion paroles rarely,
|
||||||
rarely, a lenient one readily. `CanRelease` is the substrate-native baseline those precepts modulate —
|
a lenient one readily. The gate above is the baseline those precepts modulate — the floor beneath the
|
||||||
the floor beneath the flavour, so the decision is coherent whether or not Ideology is installed.
|
flavour, so the decision is coherent whether or not Ideology is installed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -129,25 +104,12 @@ the floor beneath the flavour, so the decision is coherent whether or not Ideolo
|
|||||||
|
|
||||||
- **Parole is earned, not granted.** You cannot release your way out of a deep record. Decide early
|
- **Parole is earned, not granted.** You cannot release your way out of a deep record. Decide early
|
||||||
whether a pawn is a keep-or-release case and treat (gently) the ones you mean to free.
|
whether a pawn is a keep-or-release case and treat (gently) the ones you mean to free.
|
||||||
- **`reform ≥ 0.30` is three gentle acts, minimum.** Nothing faster reaches it. Start early.
|
- **Reform 0.30 is three gentle acts, minimum.** Nothing faster reaches it. Start early.
|
||||||
- **Keep the record shallow if you want the option.** Every escape (`+0.35`) and catch (`+0.15`) you
|
- **Keep the record shallow if you want the option.** Every escape (+0.35) and catch (+0.15) you let
|
||||||
let accumulate pushes a pawn toward the unparoleable side of the `1.2` line.
|
accumulate pushes a pawn toward the unparoleable side of the 1.2 line.
|
||||||
- **A parolee who stays is a Justice case, not a farewell.** They carry their record; if conditions
|
- **A parolee who stays is a Corrections case, not a farewell.** They carry their record; if conditions
|
||||||
sour, their disposition (still reading their history) can put them back on it.
|
sour, their disposition (still reading their history) can put them back on it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## For modders
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
if (Parole.CanRelease(pawn)) // pure read: reform, grade, pardon
|
|
||||||
Parole.Release(pawn); // sets pardoned = true
|
|
||||||
```
|
|
||||||
|
|
||||||
`CanRelease` peeks (never creates a record) and treats a missing record as releasable. `Release` uses
|
|
||||||
`For` (creates if absent) and is idempotent — a second call is a no-op once `pardoned` is set. Both
|
|
||||||
live in the `Contraband` namespace (shared since Justice's extraction from Contraband).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Part of the **Institution** suite for RimWorld 1.6: Core · Contraband · Policing · Corrections · Gangs · Ward.*
|
*Part of the **Institution** suite for RimWorld 1.6: Core · Contraband · Policing · Corrections · Gangs · Ward.*
|
||||||
|
|||||||
+34
-75
@@ -3,93 +3,62 @@
|
|||||||
*The daily life of the held — and the one lever vanilla withholds.*
|
*The daily life of the held — and the one lever vanilla withholds.*
|
||||||
|
|
||||||
Most of what a prison regime needs, vanilla already gives you. The schedule is the Restrict tab. The
|
Most of what a prison regime needs, vanilla already gives you. The schedule is the Restrict tab. The
|
||||||
mood-driven disposition already flows through `Propensity.Nurture` — a neglected prisoner runs hotter
|
mood-driven disposition already flows through the colony's underlying systems — a neglected prisoner
|
||||||
with no new machinery. So Regime does not rebuild any of that. It adds the single thing vanilla flatly
|
runs hotter with no new machinery. So Regime does not rebuild any of that. It adds the single thing
|
||||||
refuses to model: **recreation.**
|
vanilla flatly refuses to model: **recreation.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## What vanilla does, and what Regime flips
|
## What vanilla does, and what Regime flips
|
||||||
|
|
||||||
Vanilla denies prisoners the **Joy** (recreation) need outright — the need is flagged `colonistsOnly`
|
Vanilla denies prisoners the **Joy** (recreation) need outright — the need is flagged colonists-only, so
|
||||||
and `neverOnPrisoner`, so it simply never appears on a prisoner. A caged pawn has no recreation bar,
|
it simply never appears on a prisoner. A caged pawn has no recreation bar, cannot take joy from
|
||||||
cannot take joy from anything, and never suffers for its absence.
|
anything, and never suffers for its absence.
|
||||||
|
|
||||||
Regime flips exactly that one bit on, via a small Harmony postfix — the same approach the good, popular
|
Regime flips exactly that one bit on — the same approach the good, popular **Prisoner Recreation** mod
|
||||||
**Prisoner Recreation** mod takes:
|
takes. It grants the recreation need to **humanlike prisoners of your colony** and nothing else: animals,
|
||||||
|
guests, slaves and colonists are untouched, only the recreation need is affected, and it only ever
|
||||||
```csharp
|
*adds* the need where vanilla withheld it — it never removes a need another mod granted.
|
||||||
[HarmonyPatch(typeof(Pawn_NeedsTracker), "ShouldHaveNeed")]
|
|
||||||
static class Patch_PrisonerRecreation
|
|
||||||
{
|
|
||||||
static void Postfix(NeedDef nd, Pawn ___pawn, ref bool __result)
|
|
||||||
{
|
|
||||||
if (__result || nd?.defName != "Joy") return; // only touch Joy, only when vanilla said no
|
|
||||||
if (___pawn?.RaceProps?.Humanlike == true
|
|
||||||
&& ___pawn.IsPrisonerOfColony)
|
|
||||||
__result = true; // humanlike prisoners get the need
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Three guards, in order:
|
|
||||||
|
|
||||||
1. **`__result` already true** — vanilla or another patch already granted the need. Do nothing. This
|
|
||||||
is what makes the patch *additive*: it only ever turns a `false` into `true`, never the reverse.
|
|
||||||
2. **`nd.defName != "Joy"`** — this patch is about recreation and nothing else. Every other need is
|
|
||||||
left exactly as vanilla decided.
|
|
||||||
3. **humanlike prisoner-of-colony** — animals, guests, slaves and colonists are untouched; only a
|
|
||||||
humanlike prisoner of *your* colony gains the need.
|
|
||||||
|
|
||||||
That is the entire mechanism. Enabling recreation is trivial — once the need exists, vanilla does all
|
That is the entire mechanism. Enabling recreation is trivial — once the need exists, vanilla does all
|
||||||
the rest (joy sources, the recreation bar, the mood effects of a full or empty one). The *value* is
|
the rest (joy sources, the recreation bar, the mood effects of a full or empty one). The *value* is not
|
||||||
not the plumbing; it is what an empty bar now costs.
|
the plumbing; it is what an empty bar now costs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Why a need you can neglect is the point
|
## Why a need you can neglect is the point
|
||||||
|
|
||||||
Giving prisoners recreation is not a mercy toggle — it is a **lever with two ends**, and the down end
|
Giving prisoners recreation is not a mercy toggle — it is a **lever with two ends**, and the down end is
|
||||||
is the one that matters to the suite. Once a prisoner *has* the Joy need, they can be **starved** of
|
the one that matters to the suite. Once a prisoner *has* the Joy need, they can be **starved** of it, and
|
||||||
it, and vanilla's own machinery then does the work:
|
vanilla's own machinery then does the work: no recreation lowers Joy, which lowers mood, and a low mood
|
||||||
|
raises the pawn's disposition — their odds on every "would they?" roll.
|
||||||
|
|
||||||
```
|
Every one of those steps already exists. Regime only opens the first door; mood is wired to disposition
|
||||||
no recreation → Joy falls → mood falls → Propensity.Nurture rises → higher propensity
|
in Core, so a prisoner who gets no yard time stews, their mood sinks, and their disposition climbs.
|
||||||
```
|
From [Deterrence](Deterrence.md) and [Discipline & Reform](Discipline-and-Reform.md) you already know
|
||||||
|
disposition is the multiplier the whole system keys off; Regime is what lets *neglect* feed it.
|
||||||
|
|
||||||
Every one of those arrows already exists. Regime only opens the first door; mood is wired to
|
Concretely, a prisoner's mood pushes their disposition into these brackets:
|
||||||
disposition in Core, so a prisoner who gets no yard time stews, their mood sinks, and their Nurture —
|
|
||||||
and with it their odds on every "would they?" roll — climbs. From [Deterrence](Deterrence.md) and
|
|
||||||
[Discipline & Reform](Discipline-and-Reform.md) you already know Nurture is the multiplier the whole
|
|
||||||
system keys off; Regime is what lets *neglect* feed it.
|
|
||||||
|
|
||||||
Concretely, in Core's `Nurture`:
|
| Prisoner condition | Disposition contribution |
|
||||||
|
|
||||||
| Prisoner condition | Nurture contribution |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| Mood `< 0.20` (recreation-starved, at the floor of despair) | `× 2.5` |
|
| Mood below 0.20 (recreation-starved, at the floor of despair) | ×2.5 |
|
||||||
| Mood `< 0.35` (badly kept) | `× 1.6` |
|
| Mood below 0.35 (badly kept) | ×1.6 |
|
||||||
| Held **and** mood `< 0.40` (a badly run cell) | additional `× 1.4` |
|
| Held **and** mood below 0.40 (a badly run cell) | an additional ×1.4 |
|
||||||
|
|
||||||
A prisoner you give no recreation is a prisoner you are pushing up those brackets. Regime turns "I
|
A prisoner you give no recreation is a prisoner you are pushing up those brackets. Regime turns "I
|
||||||
didn't bother building a rec room" from a non-event into a disposition cost you pay on every roll.
|
didn't bother building a rec room" from a non-event into a disposition cost you pay on every roll.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Idempotent with Prisoner Recreation
|
## Works alongside Prisoner Recreation
|
||||||
|
|
||||||
If you also run the **Prisoner Recreation** mod, nothing breaks. Both mods do the same thing — force
|
If you also run the **Prisoner Recreation** mod, nothing breaks. Both mods do the same thing — grant the
|
||||||
`ShouldHaveNeed` to return `true` for prisoner Joy — and because Regime's postfix only acts when
|
recreation need to prisoners — and because Regime only acts when the need isn't already granted,
|
||||||
`__result` is still `false`, the two are simply *two postfixes that force the same `true`*. Whichever
|
whichever runs first grants it and the other sees it already done. Running both is harmless. Running
|
||||||
runs first grants the need; the second sees it already granted and returns immediately. Running both is
|
either alone is sufficient. There is no double-need, no conflict, no load-order sensitivity between them.
|
||||||
harmless. Running either alone is sufficient. There is no double-need, no conflict, no load-order
|
|
||||||
sensitivity between them.
|
|
||||||
|
|
||||||
Regime announces itself at startup so you can confirm it loaded:
|
Regime prints a short startup message so you can confirm it loaded and prisoner recreation is enabled.
|
||||||
|
|
||||||
```
|
|
||||||
[Institution: Justice] Regime: prisoner recreation enabled.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -97,8 +66,8 @@ Regime announces itself at startup so you can confirm it loaded:
|
|||||||
|
|
||||||
Yard time, safety needs, and visitation are *content* on this foundation, not new machinery. Each of
|
Yard time, safety needs, and visitation are *content* on this foundation, not new machinery. Each of
|
||||||
them is just another thing that moves a prisoner's mood — and mood is already wired to disposition — so
|
them is just another thing that moves a prisoner's mood — and mood is already wired to disposition — so
|
||||||
they need only defs and jobs, not new systems. When they land, they will make a well-run regime feel
|
they build on the same spine that this one recreation change put in place. When they land, they make a
|
||||||
richer; the mechanical spine is already here in this one postfix.
|
well-run regime feel richer without adding new systems underneath.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -108,7 +77,7 @@ richer; the mechanical spine is already here in this one postfix.
|
|||||||
their disposition sits near baseline. A cheap horseshoe pin or chess table earns its keep in crimes
|
their disposition sits near baseline. A cheap horseshoe pin or chess table earns its keep in crimes
|
||||||
that don't happen.
|
that don't happen.
|
||||||
- **Neglect is a choice with a number.** Leaving prisoners with nothing to do isn't neutral — it drives
|
- **Neglect is a choice with a number.** Leaving prisoners with nothing to do isn't neutral — it drives
|
||||||
mood into the `×1.6` and `×2.5` Nurture brackets, and a hotter prisoner reoffends, which cools your
|
mood into the ×1.6 and ×2.5 disposition brackets, and a hotter prisoner reoffends, which cools your
|
||||||
colony's climate of order, which heats *everyone*. The rec room is deterrence you build once.
|
colony's climate of order, which heats *everyone*. The rec room is deterrence you build once.
|
||||||
- **Recreation and reform stack.** Regime cools disposition *live* (through mood); reform cools it
|
- **Recreation and reform stack.** Regime cools disposition *live* (through mood); reform cools it
|
||||||
*permanently* (through [Discipline & Reform](Discipline-and-Reform.md)). A reformed prisoner in a
|
*permanently* (through [Discipline & Reform](Discipline-and-Reform.md)). A reformed prisoner in a
|
||||||
@@ -117,14 +86,4 @@ richer; the mechanical spine is already here in this one postfix.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## For modders
|
|
||||||
|
|
||||||
Harmony ID `flan.institution.justice`, a single postfix on `Pawn_NeedsTracker.ShouldHaveNeed`. It is
|
|
||||||
the suite's *only* Harmony-using code — Contraband dropped its Harmony dependency after Justice was
|
|
||||||
split out. The patch class lives in the `Contraband` namespace (shared across the suite since the
|
|
||||||
extraction). The postfix is strictly additive (`false → true` only), so it is safe to stack with any
|
|
||||||
other mod that grants prisoner needs.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Part of the **Institution** suite for RimWorld 1.6: Core · Contraband · Policing · Corrections · Gangs · Ward.*
|
*Part of the **Institution** suite for RimWorld 1.6: Core · Contraband · Policing · Corrections · Gangs · Ward.*
|
||||||
|
|||||||
+64
-69
@@ -1,112 +1,108 @@
|
|||||||
# Rehabilitation — the post-arrest loop, wired
|
# Rehabilitation
|
||||||
|
|
||||||
*The warden work that finally drives discipline, reform, and parole. Source in
|
*The warden work that drives discipline, reform, and parole.*
|
||||||
`Source/Justice/Corrections/`.*
|
|
||||||
|
|
||||||
Classification, discipline, deterrence, and parole always had their **maths** — a grade over the
|
Classification, discipline, deterrence, and parole set the rules — a grade over the record, a reform
|
||||||
record, a reform score punishment moves, an order climate, a release decision. What they lacked was a
|
score punishment moves, an order climate, a release decision. Rehabilitation is what a warden actually
|
||||||
**trigger**: nothing in play called them. Reform never moved, so classification never re-graded and
|
*does* day to day to move a prisoner through them. Without it, reform sits still, classification never
|
||||||
parole never fired; the only thing that raised the colony's order was a punishment nothing invoked.
|
re-grades, and parole never fires; with it, the whole post-arrest arc comes alive.
|
||||||
The post-arrest half of the loop was real code with no way to run it.
|
|
||||||
|
|
||||||
Rehabilitation is that trigger. It gives corrections the same treatment Ward gave psychiatric care —
|
It gives corrections the same shape Ward gives psychiatric care — a **prisoner interaction mode**,
|
||||||
a **prisoner interaction mode**, **warden jobs**, an **alert**, and an inspect readout — so a warden
|
**warden work**, an **alert**, and an **inspect readout** — so a warden reforms a prisoner over time and
|
||||||
actually reforms a prisoner over time and paroles them when they have turned a corner.
|
paroles them when they have turned a corner.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The two modes
|
## The two modes
|
||||||
|
|
||||||
Both are non-exclusive prisoner interaction modes (the shape Prison Labor and Ward use), so they
|
Both are non-exclusive prisoner interaction modes (the same kind Prison Labor and Ward use), so they
|
||||||
stack with each other and with recruit / reduce-resistance.
|
stack with each other and with recruit / reduce-resistance.
|
||||||
|
|
||||||
| Mode | What a warden does | Result |
|
| Mode | What a warden does | Result |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Rehabilitate** | Runs counselling sessions on the prisoner | Reform rises, the reform track fills, colony order rises |
|
| **Rehabilitate** | Runs counselling sessions on the prisoner | Reform rises, the reform bar fills, colony order rises |
|
||||||
| **Parole when reformed** | Releases the prisoner **once they are eligible** | A paroled colonist, back in the colony |
|
| **Parole when reformed** | Releases the prisoner **once they are eligible** | A paroled colonist, back in the colony |
|
||||||
|
|
||||||
Set **Rehabilitate** alone to run the programme and parole by hand when the alert fires; set **both**
|
Set **Rehabilitate** alone to run the programme and parole by hand when the alert fires; set **both**
|
||||||
for a hands-off "reform, then release" pipeline. A prisoner flagged for parole who is *not yet*
|
for a hands-off "reform, then release" pipeline. A prisoner flagged for parole who is *not yet* eligible
|
||||||
eligible is simply not acted on — the warden waits.
|
is simply not acted on — the warden waits.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## A rehabilitation session
|
## A rehabilitation session
|
||||||
|
|
||||||
A warden walks to the prisoner and counsels them for ~1250 ticks (about 20 in-game minutes), Social
|
A warden walks to the prisoner and counsels them for about 20 in-game minutes, with Social as the
|
||||||
being the active skill. On completion (never on interruption — the credit is a follow-on toil), one
|
active skill. On completion (never on interruption — you only earn credit for a finished session), one
|
||||||
session does three things, in `CorrectionsUtility.RunReformSession`:
|
session does three things:
|
||||||
|
|
||||||
1. **Corrective discipline** — `Discipline.Punish(prisoner, harsh: false)`: reform **+0.10**, and the
|
1. **Corrective discipline** — a gentle disciplinary act: reform **+0.10**, and it raises the colony's
|
||||||
punishment is recorded, which raises the colony's **order** by `+0.12`. This is the deterrence-up
|
**order** by **+0.12**. This is the deterrence-up half that punishment alone never triggered —
|
||||||
half that punishment alone never triggered — visible corrective justice deters everyone else.
|
visible corrective justice deters everyone else.
|
||||||
2. **The reform track** — `TreatmentProgram.AdvanceRecovery` advances `Institution_Reforming` on
|
2. **The reform bar** — the visible progress bar fills, by **0.34 × cell quality × skill** per session,
|
||||||
Core's shared treatment engine, by `0.34 × cell quality × skill`. It is the visible progress bar,
|
and it **decays −0.08/day**, so rehabilitation has to be *sustained*.
|
||||||
and it **decays** (`−0.08/day`), so rehabilitation has to be *sustained*.
|
|
||||||
3. **A memory** — the prisoner feels counselled (a small mood lift).
|
3. **A memory** — the prisoner feels counselled (a small mood lift).
|
||||||
|
|
||||||
| Input | Range | Source |
|
| Input | Range | Depends on |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| skill factor | `0.5 … 1.0` | warden's Social level |
|
| skill factor | 0.5 … 1.0 | the warden's Social level |
|
||||||
| cell quality | `0.5 … 1.5` | `TreatmentProgram.FacilityQuality` (room impressiveness) |
|
| cell quality | 0.5 … 1.5 | the cell's impressiveness |
|
||||||
| reform / session | `+0.10` | `Discipline.Punish` (fixed) |
|
| reform per session | +0.10 | fixed |
|
||||||
| order / session | `+0.12` | `Justice.RecordPunishment` |
|
| order per session | +0.12 | fixed |
|
||||||
|
|
||||||
Reform reaches the parole bar (`≥ 0.30`) in roughly **three completed sessions**, faster than a bad
|
Reform reaches the parole bar (0.30) in roughly **three completed sessions** — faster than a bad cell or
|
||||||
cell or a poor counsellor fills the visible track — so the "Ready for parole" alert keys off the real
|
a poor counsellor fills the visible bar. So the "Ready for parole" alert keys off the real gate, not the
|
||||||
gate (`Parole.CanRelease`), not the bar.
|
bar you see filling.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Neglect — the teeth
|
## Neglect — the teeth
|
||||||
|
|
||||||
A prisoner you flag for rehabilitation and then never attend **hardens**, exactly as a ward you don't
|
A prisoner you flag for rehabilitation and then never attend **hardens**, exactly as a ward you don't
|
||||||
staff does. `MapComponent_ReformNeglect` measures the days since the last real session; past **two
|
staff does. Once more than **two days** pass since the last real session, reform falls **−0.15/day** and
|
||||||
days** of neglect, reform falls `−0.15/day` and they resent it (a mood hit). The hardening lowers
|
they resent it (a mood hit). This hardening lowers reform *directly*, not as an act of visible justice —
|
||||||
reform *directly*, not through `Discipline.Punish` — neglect is passive prisonization, not an act of
|
neglect is passive prisonization, so unlike a session it does **not** raise the colony's order.
|
||||||
visible justice, so unlike a session it must **not** raise the colony's order.
|
|
||||||
|
|
||||||
Attend the prisoner and the clock resets; flag them and walk away and they slide back toward the
|
Attend the prisoner and the clock resets; flag them and walk away and they slide back toward the record
|
||||||
record that put them in. "A reform programme you don't staff is worse than none."
|
that put them in. "A reform programme you don't staff is worse than none."
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Parole — the release that stays
|
## Parole — the release that stays
|
||||||
|
|
||||||
When a rehabilitated prisoner clears `Parole.CanRelease` — reform `≥ 0.30`, security grade
|
When a rehabilitated prisoner clears the parole gate — reform 0.30 or higher, security grade Medium or
|
||||||
`≤ Medium`, not already pardoned — the **Ready for parole** alert names them. A warden set to Parole
|
below, not already pardoned — the **Ready for parole** alert names them. A warden set to Parole then
|
||||||
then processes the release (`CorrectionsUtility.ReleaseOnParole`):
|
processes the release:
|
||||||
|
|
||||||
- marks the **pardon** on the record;
|
- marks the **pardon** on the record;
|
||||||
- clears the prisoner hold so a former colonist is a **free colonist again** — a parolee who *stays*
|
- clears the prisoner hold so a former prisoner becomes a **free colonist again** — a parolee who
|
||||||
in the colony carrying their record, not vanilla's release that ejects them from the map;
|
*stays* in the colony carrying their record, not vanilla's release that ejects them from the map;
|
||||||
- a grateful memory, and a small **+0.03** to order (a lawful release is visible justice too).
|
- a grateful memory, and a small **+0.03** to order (a lawful release is visible justice too).
|
||||||
|
|
||||||
The parolee keeps their `CriminalRecord`, so if they reoffend the [policing](Policing.md) loop catches
|
The parolee keeps their record, so if they reoffend the [policing](Policing.md) loop catches them again
|
||||||
them again — recidivism, closing back onto the start.
|
— recidivism, closing back onto the start.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Discipline — the other lever
|
## Discipline — the other lever
|
||||||
|
|
||||||
Rehabilitation is the carrot; **discipline** is the stick, and its opposite. Set a prisoner to
|
Rehabilitation is the carrot; **discipline** is the stick, and its opposite. Set a prisoner to
|
||||||
**Discipline** and a warden puts them in **solitary**: harsh discipline (`Discipline.Punish`) *hardens*
|
**Discipline** and a warden puts them in **solitary**: harsh discipline *hardens* them — reform falls —
|
||||||
them — reform falls — yet raises the colony's order (visible harsh justice deters the rest), and it
|
yet raises the colony's order (visible harsh justice deters the rest), and it stings: a mood hit and an
|
||||||
stings: a mood hit and an on-edge *in solitary* status. It fires at most once per term (the status
|
on-edge *in solitary* status. It fires at most once per term (the status blocks re-discipline for about
|
||||||
gates re-discipline for about two days), so it holds order without cratering reform in a single day.
|
two days), so it holds order without cratering reform in a single day. The tension is the point: lean on
|
||||||
The tension is the point: lean on discipline to keep order and you make the punished harder to reform.
|
discipline to keep order and you make the punished harder to reform.
|
||||||
|
|
||||||
## Classification enforcement
|
## Classification enforcement
|
||||||
|
|
||||||
Classification always computed a **security grade** (`Minimum`..`Supermax`); now it is actionable:
|
Classification always computed a **security grade** (Minimum..Supermax); now it is actionable:
|
||||||
|
|
||||||
- **The grade shows** on every prisoner's inspect pane — the game tells you who is dangerous instead of
|
- **The grade shows** on every prisoner's inspect pane — the game tells you who is dangerous instead of
|
||||||
leaving you to guess.
|
leaving you to guess.
|
||||||
- **Wing-routing.** A wall-mounted **security marker** grades the cell block it stands in
|
- **Wing-routing.** A wall-mounted **security marker** grades the cell block it stands in
|
||||||
(`Minimum`..`Supermax`, set with a gizmo). One marker covers a *whole block* — a prisoner takes the
|
(Minimum..Supermax, set with a gizmo). One marker covers a *whole block* — a prisoner takes the grade
|
||||||
grade of the nearest marker in range, so you tag a corridor once rather than cluttering every cell. A
|
of the nearest marker in range, so you tag a corridor once rather than cluttering every cell. A
|
||||||
**"Dangerous prisoner unsecured" alert** then flags any prisoner held in a wing too weak for their
|
**"Dangerous prisoner unsecured" alert** then flags any prisoner held in a wing too weak for their
|
||||||
grade, and the inspect pane shows their wing and a `MISPLACED` warning — move them to a stronger
|
grade, and the inspect pane shows their wing and a **MISPLACED** warning — move them to a stronger
|
||||||
wing. An ungraded wing falls back to flagging an unsegregated Maximum/Supermax pawn.
|
wing. An ungraded wing falls back to flagging an unsegregated Maximum/Supermax pawn.
|
||||||
- **Cell safety:** a prisoner caged with a dangerous one suffers a mood penalty. Segregate your
|
- **Cell safety:** a prisoner caged with a dangerous one suffers a mood penalty. Segregate your
|
||||||
dangerous prisoners, or their cellmates pay for it — the grade made to matter on the *housing* side,
|
dangerous prisoners, or their cellmates pay for it — the grade made to matter on the *housing* side,
|
||||||
@@ -117,9 +113,9 @@ Classification always computed a **security grade** (`Minimum`..`Supermax`); now
|
|||||||
Beyond punishment and reform, the held have a **daily life**:
|
Beyond punishment and reform, the held have a **daily life**:
|
||||||
|
|
||||||
- **Yard / Lockup schedule.** Two timetable blocks set a held pawn's day: **Yard** allows recreation
|
- **Yard / Lockup schedule.** Two timetable blocks set a held pawn's day: **Yard** allows recreation
|
||||||
(with the prisoner Joy need Regime enables, they head out to a rec area), **Lockup** confines them to
|
(with the prisoner recreation need Regime enables, they head out to a rec area), **Lockup** confines
|
||||||
the cell and stews their mood. The timetable itself is **Prison Labor's** — this reads it when PL is
|
them to the cell and stews their mood. The timetable itself is **Prison Labor's** — Corrections reads
|
||||||
present and is inert without it, so PL stays compatible: read, never fought.
|
it when PL is present and does nothing without it, so PL stays compatible.
|
||||||
- **Visitors.** A prisoner or ward patient with family or a bond gets **visitors** — a friendly party
|
- **Visitors.** A prisoner or ward patient with family or a bond gets **visitors** — a friendly party
|
||||||
walks in under the game's own visitor AI and congregates by their cell, and the break from isolation
|
walks in under the game's own visitor AI and congregates by their cell, and the break from isolation
|
||||||
lifts their mood. Thematically strongest for a ward patient whose family comes to see them.
|
lifts their mood. Thematically strongest for a ward patient whose family comes to see them.
|
||||||
@@ -128,28 +124,27 @@ Beyond punishment and reform, the held have a **daily life**:
|
|||||||
|
|
||||||
## Seeing it
|
## Seeing it
|
||||||
|
|
||||||
Every colony prisoner's inspect pane now carries the corrections readout (a Harmony postfix on
|
Every colony prisoner's inspect pane now carries the corrections readout:
|
||||||
`Pawn.GetInspectString`, via Justice's existing Regime patch — not a contested method):
|
|
||||||
|
|
||||||
> *Security grade: Maximum* · *In solitary* · *Rehabilitation: reforming (40%) — neglected (3d)* ·
|
> *Security grade: Maximum* · *In solitary* · *Rehabilitation: reforming (40%) — neglected (3d)* ·
|
||||||
> *reformed — ready for parole*
|
> *reformed — ready for parole*
|
||||||
|
|
||||||
The **grade** shows for every held pawn (classification's information pillar); *in solitary* while a
|
The **grade** shows for every held pawn; *in solitary* while a discipline term runs; the
|
||||||
discipline term runs; the **rehabilitation** line for a pawn set to reform. And because the colony's
|
**rehabilitation** line for a pawn set to reform. And because the colony's **order climate** otherwise
|
||||||
**order climate** otherwise has no readout at all, the same pane surfaces it when it is far enough from
|
has no readout at all, the same pane surfaces it when it is far enough from ordinary to matter —
|
||||||
ordinary to matter — *Colony order: low (28%) — crime pays here* / *high (71%) — order holds*.
|
*Colony order: low (28%) — crime pays here* / *high (71%) — order holds*.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## What this closes
|
## What this closes
|
||||||
|
|
||||||
With rehabilitation wired, the whole post-arrest arc runs in play: **discipline** moves reform,
|
With rehabilitation in play, the whole post-arrest arc runs: **discipline** moves reform,
|
||||||
**classification** re-grades as reform rises, **deterrence** climbs on visible justice (not just
|
**classification** re-grades as reform rises, **deterrence** climbs on visible justice (not just falls
|
||||||
falls on crime), **parole** releases the reformed, and Core's **treatment engine** carries the reform
|
on crime), **parole** releases the reformed, and Core's **treatment** carries the reform bar the same
|
||||||
track the same way it carries Ward's recovery. The loop closes back onto disposition — catching,
|
way it carries Ward's recovery. The loop closes back onto disposition — catching, reforming, and
|
||||||
reforming, and releasing one pawn moves the climate every other pawn is judged against.
|
releasing one pawn moves the climate every other pawn is judged against.
|
||||||
|
|
||||||
Gated by `InstitutionSettings.justice`, like the rest of the corrections layer.
|
The whole corrections layer can be toggled off in the mod's settings, like the rest of the suite.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -3,48 +3,33 @@
|
|||||||
*Gangs grow out of who pawns **already are** — and the counter-play is to keep the wrong pawns apart.*
|
*Gangs grow out of who pawns **already are** — and the counter-play is to keep the wrong pawns apart.*
|
||||||
|
|
||||||
A gang in this mod is never invented from nowhere. It crystallises along the bonds a colony already
|
A gang in this mod is never invented from nowhere. It crystallises along the bonds a colony already
|
||||||
contains: shared faction, shared faith, or real friendship. This page covers the formation rule — what
|
contains: shared faction, shared faith, or real friendship. This page covers the formation rule — which
|
||||||
`SharesAffiliation` checks and why the *same room* requirement matters — and then the segregation
|
bonds count and why the *same room* requirement matters — and then the segregation counter-play that
|
||||||
counter-play that turns those same bonds against the network.
|
turns those same bonds against the network.
|
||||||
|
|
||||||
## The formation rule
|
## The formation rule
|
||||||
|
|
||||||
On each 5000-tick check, the gang component walks the eligible `crew` (every spawned humanlike that
|
On each 5000-tick pass, the game walks everyone currently over the join bar (Nature × Nurture ≥ 0.9)
|
||||||
currently meets the `Nature × Nurture ≥ 0.9` join bar) and tries to pair each unaffiliated member with
|
and tries to pair each unaffiliated pawn with a mate. A pawn pairs up with someone who is, at that
|
||||||
a mate:
|
moment, **in the same room** *and* someone they **already share a bond with**.
|
||||||
|
|
||||||
```
|
|
||||||
mate = a pawn q in crew such that:
|
|
||||||
q != p
|
|
||||||
AND q.GetRoom() == p.GetRoom() -- same room, right now
|
|
||||||
AND SharesAffiliation(p, q) -- a bond they already have
|
|
||||||
if mate exists: Enlist(p, mate)
|
|
||||||
```
|
|
||||||
|
|
||||||
Two conditions, both required: they must be **in the same room** at the moment of the check, **and**
|
Two conditions, both required: they must be **in the same room** at the moment of the check, **and**
|
||||||
they must **share an affiliation**. A disposed pawn does not fall in with a stranger across the map; it
|
they must **share an affiliation**. A disposed pawn does not fall in with a stranger across the map; it
|
||||||
falls in with someone it is standing next to *and* already connected to.
|
falls in with someone it is standing next to *and* already connected to.
|
||||||
|
|
||||||
`Enlist` then binds them: if either already runs with a gang, the other joins that gang; otherwise a
|
Pairing binds them together: if either already runs with a gang, the other joins that gang; otherwise a
|
||||||
fresh gang id is minted. So gangs accrete — a new member pairing with an existing member is absorbed
|
fresh gang is founded. So gangs accrete — a new member pairing with an existing member is absorbed
|
||||||
into the existing crew rather than starting a rival one.
|
into the existing crew rather than starting a rival one.
|
||||||
|
|
||||||
### What counts as an affiliation
|
### What counts as an affiliation
|
||||||
|
|
||||||
```
|
|
||||||
SharesAffiliation(a, b) is true if ANY of:
|
|
||||||
a.Faction != null AND a.Faction == b.Faction -- same faction
|
|
||||||
Ideology active AND a.Ideo == b.Ideo (both non-null) -- same ideoligion
|
|
||||||
a.relations.OpinionOf(b) >= 20 -- a genuine friendship
|
|
||||||
```
|
|
||||||
|
|
||||||
| Bond | Condition | Notes |
|
| Bond | Condition | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Same faction** | `a.Faction == b.Faction` (non-null) | your colonists share one; captured raiders share theirs |
|
| **Same faction** | both belong to the same (non-empty) faction | your colonists share one; captured raiders share theirs |
|
||||||
| **Same ideoligion** | `a.Ideo == b.Ideo`, only if the Ideology DLC is active | guarded by `ModsConfig.IdeologyActive` |
|
| **Same ideoligion** | both follow the same ideoligion, only if the Ideology DLC is active | needs Ideology enabled |
|
||||||
| **Friendship** | `OpinionOf(b) >= 20` | a real positive relationship, not mere acquaintance |
|
| **Friendship** | an opinion of at least **+20** of each other | a real positive relationship, not mere acquaintance |
|
||||||
|
|
||||||
The design intent, from the source:
|
The design intent:
|
||||||
|
|
||||||
> Gangs grow out of who pawns ALREADY are, not out of nowhere — so a prisoner's gang on the outside is
|
> Gangs grow out of who pawns ALREADY are, not out of nowhere — so a prisoner's gang on the outside is
|
||||||
> their old faction/friends, and rival factions run as rival gangs.
|
> their old faction/friends, and rival factions run as rival gangs.
|
||||||
@@ -57,9 +42,9 @@ random groupings over the top.
|
|||||||
|
|
||||||
Follow the rule to its conclusion. Two captured raiders from **different** hostile factions each share
|
Follow the rule to its conclusion. Two captured raiders from **different** hostile factions each share
|
||||||
an affiliation with *their own* side but not with each other. Housed in the same wing, each pairs up
|
an affiliation with *their own* side but not with each other. Housed in the same wing, each pairs up
|
||||||
along its own faction line — and now you have **two gangs**. By the definition on the
|
along its own faction line — and now you have **two gangs**. As the
|
||||||
[Rivalry and Fights](Rivalry-and-Fights.md) page, members of two different gangs are automatically
|
[Rivalry and Fights](Rivalry-and-Fights.md) page explains, members of two different gangs are
|
||||||
**rivals**, and rival-gang violence between them is booked as a crime.
|
automatically **rivals**, and rival-gang violence between them is booked as a crime.
|
||||||
|
|
||||||
So the affiliation rule and the rivalry rule are two ends of one idea: **who bands together** and **who
|
So the affiliation rule and the rivalry rule are two ends of one idea: **who bands together** and **who
|
||||||
is opposed** both derive from pre-existing loyalties. Mix hostile factions or clashing ideoligions in
|
is opposed** both derive from pre-existing loyalties. Mix hostile factions or clashing ideoligions in
|
||||||
@@ -70,39 +55,31 @@ them. This is a housing decision with mechanical teeth.
|
|||||||
|
|
||||||
Here is where the formation rule and the network rule meet, and where the rest of the suite earns its
|
Here is where the formation rule and the network rule meet, and where the rest of the suite earns its
|
||||||
keep. A gang can only *do* anything — resupply itself — if its members can **reach** each other.
|
keep. A gang can only *do* anything — resupply itself — if its members can **reach** each other.
|
||||||
`MoveWithin` requires `SameGang` **and**, for two co-located pawns, a walkable path
|
Passing contraband requires two things: the pawns must be in the same gang, **and** two pawns both on
|
||||||
(`CanReach(..., Touch, Danger.Deadly)`). Break the path and you break the network:
|
the map must have a walkable path between them. Break the path and you break the network.
|
||||||
|
|
||||||
```
|
The design calls this out as the intended answer to the whole mod:
|
||||||
MoveWithin(from, to):
|
|
||||||
...
|
|
||||||
if from.Spawned && to.Spawned && !from.CanReach(to, Touch, Deadly):
|
|
||||||
return false -- kept apart: the segregation counter-play
|
|
||||||
```
|
|
||||||
|
|
||||||
The source calls this out as the intended answer to the whole mod:
|
|
||||||
|
|
||||||
> The counter-play is the rest of the suite: Classification SEGREGATES rivals into different wings, and
|
> The counter-play is the rest of the suite: Classification SEGREGATES rivals into different wings, and
|
||||||
> a network whose members cannot REACH each other is starved.
|
> a network whose members cannot REACH each other is starved.
|
||||||
|
|
||||||
**Classification** lives in **Institution: Justice**. It grades pawns by risk and lets you sort them
|
**Classification** lives in **Institution: Justice**. It grades pawns by risk and lets you sort them
|
||||||
into separate, walled wings. Two gangmates graded into different wings, with no walkable route between
|
into separate, walled wings. Two gangmates graded into different wings, with no walkable route between
|
||||||
them, fail the reach gate on every network pass. The gang still *exists* on the membership map — they
|
them, fail the reach test on every network pass. The gang still *exists* — they are still the same crew,
|
||||||
are still the same crew, still rivals of the other crew — but it can no longer pass a shiv from a
|
still rivals of the other crew — but it can no longer pass a shiv from a holder to a have-not. **A
|
||||||
holder to a have-not. **A network that cannot reach itself is a network that cannot supply itself.**
|
network that cannot reach itself is a network that cannot supply itself.**
|
||||||
|
|
||||||
This is the elegant part of the design: the very bonds that formed the gang (`SharesAffiliation`) tell
|
This is the elegant part of the design: the very bonds that formed the gang tell you *who to keep
|
||||||
you *who to keep apart*, and the reach requirement (`CanReach`) makes keeping them apart actually
|
apart*, and the reach requirement makes keeping them apart actually starve the supply chain. You do not
|
||||||
starve the supply chain. You do not disband a gang; you **partition the graph** until its edges carry
|
disband a gang; you **cut it off from itself** until nothing can move along it.
|
||||||
nothing.
|
|
||||||
|
|
||||||
## How to play it
|
## How to play it
|
||||||
|
|
||||||
- **Read affiliations before you house pawns.** Same-faction and same-ideoligion prisoners will band
|
- **Read affiliations before you house pawns.** Same-faction and same-ideoligion prisoners will band
|
||||||
together; hostile factions in one wing will form *rival* gangs and fight. Sort deliberately.
|
together; hostile factions in one wing will form *rival* gangs and fight. Sort deliberately.
|
||||||
- **Segregate by Classification, not by hope.** Putting rivals in "different areas" is not enough — the
|
- **Segregate by Classification, not by hope.** Putting rivals in "different areas" is not enough —
|
||||||
reach test cares about a *walkable path*. A shared corridor is a supply line. Use genuinely separate,
|
resupply only needs a *walkable path* between two gangmates. A shared corridor is a supply line. Use
|
||||||
walled wings.
|
genuinely separate, walled wings.
|
||||||
- **Starve, don't chase.** You will rarely delete a gang outright. The durable win is to keep its
|
- **Starve, don't chase.** You will rarely delete a gang outright. The durable win is to keep its
|
||||||
members unable to reach one another so the network dries up, while keeping mood and deterrence high so
|
members unable to reach one another so the network dries up, while keeping mood and deterrence high so
|
||||||
few pawns cross the join bar in the first place (see [Joining](Joining.md)).
|
few pawns cross the join bar in the first place (see [Joining](Joining.md)).
|
||||||
@@ -112,4 +89,3 @@ nothing.
|
|||||||
|
|
||||||
---
|
---
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs (this). Each mod stands alone; together they are one system.*
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs (this). Each mod stands alone; together they are one system.*
|
||||||
**
|
|
||||||
|
|||||||
+20
-21
@@ -23,7 +23,7 @@ If you run **Prisoner Realism**, its *Ringleader* system already models a domina
|
|||||||
unrest and mood contagion through a wing. That system is good, and Gangs does not touch it. Ringleader
|
unrest and mood contagion through a wing. That system is good, and Gangs does not touch it. Ringleader
|
||||||
owns *influence and mood*.
|
owns *influence and mood*.
|
||||||
|
|
||||||
Gangs owns the thing Ringleader has no equivalent for: **the network as a logistics graph.** Contraband
|
Gangs owns the thing Ringleader has no equivalent for: **the network as a supply chain.** Contraband
|
||||||
physically changes hands along social lines. A stash on one pawn is a stash available to the whole
|
physically changes hands along social lines. A stash on one pawn is a stash available to the whole
|
||||||
gang. The two systems are complementary — run both. Ringleader tells you *who stirs the pot*; Gangs
|
gang. The two systems are complementary — run both. Ringleader tells you *who stirs the pot*; Gangs
|
||||||
tells you *how the shivs get around*.
|
tells you *how the shivs get around*.
|
||||||
@@ -36,7 +36,7 @@ dependencies:
|
|||||||
|
|
||||||
| It asks... | ...and the answer comes from |
|
| It asks... | ...and the answer comes from |
|
||||||
|---|---|
|
|---|---|
|
||||||
| *Who is disposed enough to join?* | **Institution: Core** — `Nature × Nurture`, the shared propensity engine |
|
| *Who is disposed enough to join?* | **Institution: Core** — the propensity engine that scores each pawn's Nature and Nurture |
|
||||||
| *What do they move?* | **Institution: Contraband** — concealment, stashes, search, and the bent-warden supply route |
|
| *What do they move?* | **Institution: Contraband** — concealment, stashes, search, and the bent-warden supply route |
|
||||||
| *Where does a fight get booked, and how are rivals kept apart?* | **Institution: Justice** — the crime/deterrence loop, and Classification's segregation |
|
| *Where does a fight get booked, and how are rivals kept apart?* | **Institution: Justice** — the crime/deterrence loop, and Classification's segregation |
|
||||||
|
|
||||||
@@ -45,10 +45,11 @@ Contraband's stashes, and Justice's policing all cash out at once.
|
|||||||
|
|
||||||
## A healthy colony grows few gangs — or none
|
## A healthy colony grows few gangs — or none
|
||||||
|
|
||||||
This is the thesis, and it is enforced in the numbers, not just the flavour. Membership is gated at
|
This is the thesis, and it is enforced in the numbers, not just the flavour. Membership is gated at a
|
||||||
`Nature × Nurture ≥ 0.9` — a high bar on purpose. A disposed pawn who is **well-kept and
|
Nature × Nurture score of **0.9 or higher** — a high bar on purpose. A disposed pawn who is
|
||||||
well-policed** does not band up: their nurture multiplier stays low, and deterrence pulls it lower
|
**well-kept and well-policed** does not band up: their nurture multiplier stays low, and deterrence
|
||||||
still. It takes a foul streak (nature) *and* a badly-run situation (nurture) at the same time.
|
pulls it lower still. It takes a foul streak (nature) *and* a badly-run situation (nurture) at the
|
||||||
|
same time.
|
||||||
|
|
||||||
So a gang problem is a **symptom**. It is the game telling you that a wing is mistreated, under-policed,
|
So a gang problem is a **symptom**. It is the game telling you that a wing is mistreated, under-policed,
|
||||||
or both. The fix is never "fight the gang system" — it is to run a better prison. Feed them, give them
|
or both. The fix is never "fight the gang system" — it is to run a better prison. Feed them, give them
|
||||||
@@ -58,32 +59,30 @@ recreation, keep deterrence high, segregate rivals, and the networks starve on t
|
|||||||
|
|
||||||
## The pages
|
## The pages
|
||||||
|
|
||||||
- **[Joining](Joining.md)** — `WouldJoin = Nature × Nurture ≥ 0.9`: why the bar is high, why good
|
- **[Joining](Joining.md)** — why the join bar (Nature × Nurture ≥ 0.9) is high, why good treatment and
|
||||||
treatment and deterrence keep pawns *out*, and how the 5000-tick check works for every kind of pawn.
|
deterrence keep pawns *out*, and how membership is re-checked for every kind of pawn.
|
||||||
- **[Networks and Smuggling](Networks-and-Smuggling.md)** — `MoveWithin`: how a holder resupplies a
|
- **[Networks and Smuggling](Networks-and-Smuggling.md)** — how a holder resupplies a needy gangmate,
|
||||||
needy gangmate, the *same-gang + can-reach* rule, why this defeats a single search, and how a bent
|
the *same-gang-and-can-reach* rule, why this defeats a single search, and how a bent warden refills a
|
||||||
warden refills a starved network from outside.
|
starved network from outside.
|
||||||
- **[Rivalry and Fights](Rivalry-and-Fights.md)** — `AreRivals` and `RecordFight`: how a gang fight is
|
- **[Rivalry and Fights](Rivalry-and-Fights.md)** — how a gang fight is booked as a crime through
|
||||||
booked as a crime through Justice, why inside-the-wire and out-on-the-street are the same offence,
|
Justice, why inside-the-wire and out-on-the-street are the same offence, and how it feeds deterrence.
|
||||||
and how it feeds deterrence.
|
- **[Affiliations and Segregation](Affiliations-and-Segregation.md)** — shared faction, faith, or
|
||||||
- **[Affiliations and Segregation](Affiliations-and-Segregation.md)** — `SharesAffiliation` plus
|
friendship plus *same room* as the formation rule, why rival factions become rival gangs, and how
|
||||||
*same room* as the formation rule, why rival factions become rival gangs, and how Classification's
|
Classification's segregation is the counter-play that starves a network which cannot reach itself.
|
||||||
segregation is the counter-play that starves a network which cannot reach itself.
|
|
||||||
|
|
||||||
## The suite
|
## The suite
|
||||||
|
|
||||||
Part of the **Institution** suite of RimWorld 1.6 mods:
|
Part of the **Institution** suite of RimWorld 1.6 mods:
|
||||||
|
|
||||||
- **Institution: Core** — the propensity engine (`Nature × Nurture`), criminal records, secured context.
|
- **Institution: Core** — the propensity engine (Nature × Nurture), criminal records, secured context.
|
||||||
- **Institution: Contraband** — concealment, improvised shivs, tunnels, warden search and corruption.
|
- **Institution: Contraband** — concealment, improvised shivs, tunnels, warden search and corruption.
|
||||||
- **Institution: Justice** — Classification, Deterrence, Discipline, Parole, Regime.
|
- **Institution: Justice** — Classification, Deterrence, Discipline, Parole, Regime.
|
||||||
- **Institution: Gangs** — this mod: joining, networks/smuggling, rivalry/fights.
|
- **Institution: Gangs** — this mod: joining, networks/smuggling, rivalry/fights.
|
||||||
|
|
||||||
Sibling projects: **Foul Play** (the vessel/substance framework and the "Piss Nuke") and **Ward** (the
|
Sibling projects: **Foul Play** (the vessel/substance framework and the "Piss Nuke") and **Ward** (a
|
||||||
test harness and a ward/treatment prison mode).
|
ward/treatment prison mode).
|
||||||
|
|
||||||
Each mod stands alone as an install; together they form one system.
|
Each mod stands alone as an install; together they form one system.
|
||||||
|
|
||||||
---
|
---
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs (this). Each mod stands alone; together they are one system.*
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs (this). Each mod stands alone; together they are one system.*
|
||||||
**
|
|
||||||
|
|||||||
+34
-55
@@ -3,48 +3,39 @@
|
|||||||
*How Gangs decides who runs with a crew — and why a well-run colony keeps almost everyone out.*
|
*How Gangs decides who runs with a crew — and why a well-run colony keeps almost everyone out.*
|
||||||
|
|
||||||
Membership is not random, and it is not a mood event. A pawn joins a gang only when their **disposition
|
Membership is not random, and it is not a mood event. A pawn joins a gang only when their **disposition
|
||||||
and their circumstances line up at once** — the same `Nature × Nurture` engine that drives every other
|
and their circumstances line up at once** — the same Nature × Nurture propensity that drives every
|
||||||
behaviour in the Institution suite. This page covers the exact test, why the bar is set where it is,
|
other behaviour in the Institution suite. This page covers the exact test, why the bar is set where it
|
||||||
and how you keep pawns on the right side of it.
|
is, and how you keep pawns on the right side of it.
|
||||||
|
|
||||||
## The rule
|
## The rule
|
||||||
|
|
||||||
```
|
A pawn joins when their **Nature multiplied by their Nurture reaches 0.9 or higher**. Only humanlike
|
||||||
WouldJoin(pawn) == Propensity.Nature(pawn) * Propensity.Nurture(pawn) >= JoinThreshold
|
pawns are ever eligible — animals and mechs never join.
|
||||||
JoinThreshold = 0.9
|
|
||||||
```
|
|
||||||
|
|
||||||
Two gates before the maths even runs:
|
Beyond that it is one line of arithmetic: multiply the pawn's **Nature** by their **Nurture** and
|
||||||
|
compare to **0.9**. There is no dice roll here. The same pawn in the same situation always gives the
|
||||||
| Guard | Effect |
|
same answer — join, or don't. (That makes membership different from one-off acts like a piss spree,
|
||||||
|---|---|
|
which *do* roll the dice each time. Gang membership is a standing condition, so it uses the raw product
|
||||||
| `pawn == null` | never joins |
|
with no randomness.)
|
||||||
| not `RaceProps.Humanlike` | never joins — animals and mechs are out |
|
|
||||||
|
|
||||||
Then it is one line: multiply the pawn's **Nature** by their **Nurture** and compare to **0.9**. There
|
|
||||||
is no dice roll here. `WouldJoin` is a deterministic threshold on the two propensity scores, so the
|
|
||||||
same pawn in the same situation always gives the same answer — join, or don't. (This is different from
|
|
||||||
Core's `Propensity.Would(...)`, which *is* a seeded random roll used for one-off acts like a piss
|
|
||||||
spree. Gang membership is a standing condition, so it uses the raw product.)
|
|
||||||
|
|
||||||
### The two halves
|
### The two halves
|
||||||
|
|
||||||
Both terms come from **Institution: Core** — see Core's own documentation for the full tables — but you
|
Both terms come from **Institution: Core** — see Core's own documentation for the full tables — but you
|
||||||
need the shape of them to understand the bar:
|
need the shape of them to understand the bar:
|
||||||
|
|
||||||
- **Nature** (`0..1`) is *who the pawn is*: a base of `0.05`, plus trait weights, clamped to `[0, 1]`.
|
- **Nature** (`0` to `1`) is *who the pawn is*: a base of `0.05`, plus trait weights, floored at `0`
|
||||||
Psychopath `+0.45`, Bloodlust `+0.35`, Kleptomaniac `+0.40`, Greedy `+0.25`, Abrasive `+0.15`;
|
and capped at `1`. Psychopath `+0.45`, Bloodlust `+0.35`, Kleptomaniac `+0.40`, Greedy `+0.25`,
|
||||||
and it goes **down** for Kind `−0.30` or Ascetic `−0.15`. This is fixed at pawn creation and barely
|
Abrasive `+0.15`; and it goes **down** for Kind `−0.30` or Ascetic `−0.15`. This is fixed at pawn
|
||||||
moves.
|
creation and barely moves.
|
||||||
- **Nurture** (a multiplier, `~≥ 0.4`, base `1.0`) is *the situation you put them in*: a floored mood
|
- **Nurture** (a multiplier, roughly `0.4` at the floor, `1.0` at baseline) is *the situation you put
|
||||||
multiplies it up hard (roughly `×2.5` under 0.20 mood, `×1.6` under 0.35), being held while
|
them in*: a floored mood multiplies it up hard (roughly `×2.5` under 0.20 mood, `×1.6` under 0.35),
|
||||||
miserable adds more (`×1.4`), a hardened record (negative reform) raises it, and — critically —
|
being held while miserable adds more (`×1.4`), a hardened record (negative reform) raises it, and —
|
||||||
**deterrence pulls it down** (`×Lerp(1.3, 0.7, deterrence)`, neutral `1.0` at baseline order, so a
|
critically — **deterrence pulls it down** (toward about `0.7` in a high-deterrence colony; a neutral,
|
||||||
high-deterrence colony scales nurture toward `0.7`). This is the half you control.
|
baseline order sits at `1.0`). This is the half you control.
|
||||||
|
|
||||||
## Why the bar is high
|
## Why the bar is high
|
||||||
|
|
||||||
`0.9` is a deliberately steep threshold. From the source, in its own words:
|
`0.9` is a deliberately steep threshold. In the design's own words:
|
||||||
|
|
||||||
> A high bar, on purpose: gangs are a symptom of a badly-run colony, not furniture. A healthy Rimworld
|
> A high bar, on purpose: gangs are a symptom of a badly-run colony, not furniture. A healthy Rimworld
|
||||||
> — content pawns, an orderly colony — grows few gangs, if any.
|
> — content pawns, an orderly colony — grows few gangs, if any.
|
||||||
@@ -63,7 +54,7 @@ the shape of the decision.
|
|||||||
|
|
||||||
| Pawn | Nature | Situation → Nurture | Product | Joins? |
|
| Pawn | Nature | Situation → Nurture | Product | Joins? |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| **Kind colonist**, ordinary life | `0.05 − 0.30 → 0.00` (clamped) | content, `≈ 1.0` | `0.00` | **No** — nature floors it |
|
| **Kind colonist**, ordinary life | `0.05 − 0.30`, floored at `0.00` | content, `≈ 1.0` | `0.00` | **No** — nature floors it |
|
||||||
| **Greedy prisoner**, content, policed | `0.30` | fed & high deterrence, `≈ 0.7` | `≈ 0.21` | **No** |
|
| **Greedy prisoner**, content, policed | `0.30` | fed & high deterrence, `≈ 0.7` | `≈ 0.21` | **No** |
|
||||||
| **Greedy prisoner**, starved & neglected | `0.30` | mood floored + held, `≈ 3.5` | `≈ 1.05` | **Yes** |
|
| **Greedy prisoner**, starved & neglected | `0.30` | mood floored + held, `≈ 3.5` | `≈ 1.05` | **Yes** |
|
||||||
| ...same pawn, but you raise deterrence | `0.30` | `× ≈ 0.7` → `≈ 2.45` | `≈ 0.74` | **No** — deterrence tipped them out |
|
| ...same pawn, but you raise deterrence | `0.30` | `× ≈ 0.7` → `≈ 2.45` | `≈ 0.74` | **No** — deterrence tipped them out |
|
||||||
@@ -74,44 +65,33 @@ you run the wing. The Kind colonist and the near-maxed psychopath are the fixed
|
|||||||
cannot join, the other essentially always will if you neglect them. Everyone in between is a policy
|
cannot join, the other essentially always will if you neglect them. Everyone in between is a policy
|
||||||
choice.
|
choice.
|
||||||
|
|
||||||
The mod's own integration test asserts exactly these poles: a psychopath-plus-bloodlust pawn with a
|
|
||||||
floored mood returns `WouldJoin == true`, and a Kind colonist in ordinary circumstance returns
|
|
||||||
`WouldJoin == false`.
|
|
||||||
|
|
||||||
## It works for every pawn, everywhere
|
## It works for every pawn, everywhere
|
||||||
|
|
||||||
There is no "prisoners only" special case. The comment is explicit:
|
There is no "prisoners only" special case: any pawn — colonist, prisoner, slave — can run with a crew,
|
||||||
|
wherever they are.
|
||||||
|
|
||||||
> Any pawn — colonist, prisoner, slave — can, wherever they are.
|
Membership pays no attention to a pawn's secured status. A disposed **free colonist** can run with a
|
||||||
|
|
||||||
`WouldJoin` reads nothing about a pawn's secured status. A disposed **free colonist** can run with a
|
|
||||||
crew on the outside; a **prisoner** can run with theirs on the inside; a **slave** likewise. This is
|
crew on the outside; a **prisoner** can run with theirs on the inside; a **slave** likewise. This is
|
||||||
what lets a gang span the wall — an outside member and an inside member of the same crew (see
|
what lets a gang span the wall — an outside member and an inside member of the same crew (see
|
||||||
[Networks and Smuggling](Networks-and-Smuggling.md)). Being held raises nurture (misery), but it is not
|
[Networks and Smuggling](Networks-and-Smuggling.md)). Being held raises nurture (misery), but it is not
|
||||||
a requirement.
|
a requirement.
|
||||||
|
|
||||||
## The cadence: the 5000-tick check
|
## The cadence: every 5000 ticks
|
||||||
|
|
||||||
Membership is re-evaluated on a fixed interval by the gang map component:
|
Gang membership is re-evaluated on a fixed interval — every **5000 ticks**, which is about **2 in-game
|
||||||
|
hours** (a RimWorld day is 60,000 ticks), or roughly **80 seconds** of real time at normal (1×) speed.
|
||||||
|
On each pass, the game:
|
||||||
|
|
||||||
```
|
1. Gathers everyone on the map who currently meets the Nature × Nurture ≥ 0.9 join bar.
|
||||||
CheckInterval = 5000 ticks
|
2. Pairs up unaffiliated members who **share a room and a bond** into gangs (see
|
||||||
MapComponentTick: run only when TicksGame % 5000 == 0
|
|
||||||
```
|
|
||||||
|
|
||||||
**5000 ticks** is about **2 in-game hours** (a RimWorld day is 60,000 ticks), or roughly **80 seconds**
|
|
||||||
of real time at normal (1×) speed. On each fire, the component:
|
|
||||||
|
|
||||||
1. Gathers `crew` — every spawned humanlike on the map for whom `WouldJoin` is currently true.
|
|
||||||
2. Pairs up unaffiliated members of `crew` who **share a room and a bond** into gangs (see
|
|
||||||
[Affiliations and Segregation](Affiliations-and-Segregation.md)).
|
[Affiliations and Segregation](Affiliations-and-Segregation.md)).
|
||||||
3. Runs one round of network resupply inside each gang (see
|
3. Runs one round of network resupply inside each gang (see
|
||||||
[Networks and Smuggling](Networks-and-Smuggling.md)).
|
[Networks and Smuggling](Networks-and-Smuggling.md)).
|
||||||
|
|
||||||
Because the check re-reads `WouldJoin` every time, membership is *live*: a pawn whose situation improves
|
Because the join bar is re-checked from scratch every pass, membership is *live*: a pawn whose situation
|
||||||
past the point where the product drops back under `0.9` simply stops being eligible to form new bonds.
|
improves until the product drops back under `0.9` simply stops being eligible to form new bonds. The
|
||||||
The bar is not a one-time gate at recruitment — it is a standing condition the colony is continuously
|
bar is not a one-time gate at recruitment — it is a standing condition the colony is continuously graded
|
||||||
graded against.
|
against.
|
||||||
|
|
||||||
## How to keep pawns out
|
## How to keep pawns out
|
||||||
|
|
||||||
@@ -131,4 +111,3 @@ You do not fight the gang system. You lower nurture, which lowers the product, w
|
|||||||
|
|
||||||
---
|
---
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs (this). Each mod stands alone; together they are one system.*
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs (this). Each mod stands alone; together they are one system.*
|
||||||
**
|
|
||||||
|
|||||||
+43
-58
@@ -3,40 +3,33 @@
|
|||||||
*The one thing nothing else models: contraband that flows **between** pawns. A gang is a supply chain.*
|
*The one thing nothing else models: contraband that flows **between** pawns. A gang is a supply chain.*
|
||||||
|
|
||||||
This is the core of the mod. Everything else — who joins, who fights, who is kept apart — exists to
|
This is the core of the mod. Everything else — who joins, who fights, who is kept apart — exists to
|
||||||
serve or to break the network described here. A gang is not a mood aura; it is a **logistics graph**,
|
serve or to break the network described here. A gang is not a mood aura; it is a **supply chain**, and
|
||||||
and its edges carry contraband.
|
contraband flows along it.
|
||||||
|
|
||||||
## The move
|
## The move
|
||||||
|
|
||||||
The heart of it is one method, `MoveWithin(from, to)`: a gangmate who is holding a stash passes a piece
|
The heart of it is a single transfer: a gangmate who is holding a stash passes one piece of it to a
|
||||||
of it to a gangmate who has none. It returns `true` if something actually moved.
|
gangmate who has none. Here is everything that has to be true for a piece to move:
|
||||||
|
|
||||||
```
|
- both pawns are in the **same gang**;
|
||||||
MoveWithin(from, to):
|
- if both are on the map, the supplier has a **walkable path** to the customer (touch range, willing to
|
||||||
1. both non-null, and SameGang(from, to) -- else false
|
cross deadly danger to get there);
|
||||||
2. if both are Spawned: from must CanReach(to, -- the reach gate
|
- there is contraband in play on the map at all;
|
||||||
PathEndMode.Touch, Danger.Deadly) -- else false
|
- the supplier is actually holding at least one concealed item.
|
||||||
3. a Contraband tracker must exist on the map -- else false
|
|
||||||
4. 'from' must have at least one concealed item -- else false
|
When all of that holds, the supplier's first concealed item leaves their hands and arrives — still
|
||||||
5. take stash[0]:
|
hidden — on the customer.
|
||||||
tracker.Confiscate(from, item.def) -- leaves the supplier's hands
|
|
||||||
tracker.Conceal(to, item.def) -- arrives, hidden, in the customer's
|
|
||||||
return true
|
|
||||||
```
|
|
||||||
|
|
||||||
A few things worth reading carefully:
|
A few things worth reading carefully:
|
||||||
|
|
||||||
- **One item per move.** It takes `stash[0]` — the first concealed item on the supplier — and moves
|
- **One item per move.** It moves the first concealed item on the supplier, and exactly that. It is a
|
||||||
exactly that. It is a redistribution, not a duplication: the item leaves `from` (`Confiscate`) and
|
redistribution, not a duplication: the item leaves the supplier and arrives concealed on the customer.
|
||||||
arrives concealed on `to` (`Conceal`). The gang's total stash is unchanged; only *who holds it*
|
The gang's total stash is unchanged; only *who holds it* changes.
|
||||||
changes.
|
- **Same gang, always.** Both pawns must belong to the same gang. There is no smuggling to a stranger —
|
||||||
- **Same gang, always.** `SameGang` requires both pawns to hold the same non-zero gang id. There is no
|
the network only moves along membership.
|
||||||
smuggling to a stranger — the network only moves along membership.
|
- **The reach requirement only applies to pawns both on the map.** Two pawns physically present must
|
||||||
- **The reach gate only applies to co-located pawns.** The `CanReach` check is guarded by
|
have a walkable path between them. If a member is off the active map, the reach check is skipped —
|
||||||
`from.Spawned && to.Spawned`. Two pawns both physically on the map must have a walkable path
|
which is what allows a gang to reach across the wall to a member who is not on the map right now.
|
||||||
(`Touch` range, willing to cross `Deadly` danger) between them. If a member is not spawned, the
|
|
||||||
reach check is skipped — which is what allows a gang to reach across the wall to a member who is off
|
|
||||||
the active map.
|
|
||||||
|
|
||||||
## Why this defeats a single search
|
## Why this defeats a single search
|
||||||
|
|
||||||
@@ -45,61 +38,54 @@ tick a box: *cell searched, no contraband.* But the shiv was on Prisoner C the w
|
|||||||
next 5000-tick network pass it will move to whoever is out. Search C tomorrow and it may already be on
|
next 5000-tick network pass it will move to whoever is out. Search C tomorrow and it may already be on
|
||||||
A again.
|
A again.
|
||||||
|
|
||||||
From the source comment, plainly:
|
Put plainly:
|
||||||
|
|
||||||
> A gangmate holding a stash supplies one who has none, so a search that turns up nothing on one
|
> A gangmate holding a stash supplies one who has none, so a search that turns up nothing on one
|
||||||
> prisoner has not cleaned out the wing.
|
> prisoner has not cleaned out the wing.
|
||||||
|
|
||||||
A single search is a snapshot of one node in a graph that reshuffles itself. This is the whole reason
|
A single search is a snapshot of one pawn in a network that reshuffles itself. This is the whole reason
|
||||||
contraband-as-a-network is worth modelling: **the unit of contraband is the wing, not the pawn.** To
|
contraband-as-a-network is worth modelling: **the unit of contraband is the wing, not the pawn.** To
|
||||||
clean it out you have to either search faster than it moves, or — far better — break the graph.
|
clean it out you have to either search faster than it moves, or — far better — break the network.
|
||||||
|
|
||||||
## The automatic resupply pass
|
## The automatic resupply pass
|
||||||
|
|
||||||
You do not call `MoveWithin` by hand; the gang component does it on the 5000-tick check. After forming
|
You never trigger a transfer by hand; the game runs it automatically on the 5000-tick pass. After
|
||||||
gangs for the pass, it runs one resupply round **per gang**:
|
forming gangs for the pass, it runs one resupply round **per gang**: it finds the first member who *is*
|
||||||
|
hiding contraband and the first member who is *not*, and if both exist, moves one item from the holder
|
||||||
```
|
to the empty-handed member.
|
||||||
for each gang among the eligible crew:
|
|
||||||
holder = first member who IS hiding contraband
|
|
||||||
needy = first member who is NOT hiding contraband
|
|
||||||
if holder and needy both exist:
|
|
||||||
MoveWithin(holder, needy)
|
|
||||||
```
|
|
||||||
|
|
||||||
So on each interval, every gang that has both a haves-member and a have-not-member performs **one**
|
So on each interval, every gang that has both a haves-member and a have-not-member performs **one**
|
||||||
transfer, moving a single item from a holder to someone empty-handed. Over several ticks the effect is
|
transfer, moving a single item from a holder to someone empty-handed. Over several passes the effect is
|
||||||
that a gang tends to keep its members supplied and to spread a stash out — which is exactly what makes
|
that a gang tends to keep its members supplied and to spread a stash out — which is exactly what makes
|
||||||
a scattershot search miss it. (Only members who currently meet the join bar participate in this
|
a scattershot search miss it. (Only members who currently meet the join bar take part in this automatic
|
||||||
automatic pass; the resupply loop draws from the same `crew` used for formation.)
|
pass.)
|
||||||
|
|
||||||
## The cross-wall move
|
## The cross-wall move
|
||||||
|
|
||||||
Because `WouldJoin` and `SameGang` care nothing about a pawn's secured status, a gang can have a
|
Because neither the join bar nor gang membership cares about a pawn's secured status, a gang can have a
|
||||||
**free** member and a **held** member. If the free member is holding the stash, the network resupplies
|
**free** member and a **held** member. If the free member is holding the stash, the network resupplies
|
||||||
*into* the prison — the outside man passes to the inside man. The mod's integration test builds exactly
|
*into* the prison — the outside man passes to the inside man. A free colonist and a prisoner can be in
|
||||||
this: a free psychopath colonist and a prisoner in one gang, the stash on the free member, and
|
one gang with the stash on the free member; the transfer succeeds, and afterwards the prisoner is hiding
|
||||||
`MoveWithin(free, prisoner)` succeeds — after which the prisoner is hiding contraband and the free
|
contraband and the colonist is not. Your prison's contraband problem is not sealed inside your prison.
|
||||||
colonist is not. Your prison's contraband problem is not sealed inside your prison.
|
|
||||||
|
|
||||||
## Resupply from outside: the bent warden
|
## Resupply from outside: the bent warden
|
||||||
|
|
||||||
`MoveWithin` only *redistributes* what a gang already has. So what happens when you finally search the
|
The transfer only *redistributes* what a gang already has. So what happens when you finally search the
|
||||||
whole wing on the same day and strip every member clean — the graph has no more edges to carry? The
|
whole wing on the same day and strip every member clean — there is nothing left to pass around? The
|
||||||
gang is starved. It stays starved until contraband **re-enters** from outside.
|
gang is starved. It stays starved until contraband **re-enters** from outside.
|
||||||
|
|
||||||
That external source is not part of Gangs; it is **Institution: Contraband's** corruption route. A
|
That external source is not part of Gangs; it is **Institution: Contraband's** corruption route. A
|
||||||
warden's honesty varies by personality (a Greedy warden has a price), and a bent warden can *smuggle
|
warden's honesty varies by personality (a Greedy warden has a price), and a bent warden can *smuggle
|
||||||
contraband in to a prisoner* — `Corruption.Smuggle(warden, prisoner, tracker)` plants a concealed item
|
contraband in to a prisoner*, planting a concealed item directly. That single planted item is then all
|
||||||
directly. That single seeded item is then all the network needs: one holder, and the 5000-tick pass
|
the network needs: one holder, and the next 5000-tick pass spreads it back out across everyone who can
|
||||||
spreads it back out across everyone who can reach.
|
reach.
|
||||||
|
|
||||||
So the two halves of the supply picture are:
|
So the two halves of the supply picture are:
|
||||||
|
|
||||||
| Mechanism | Owner | What it does |
|
| Mechanism | Owner | What it does |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `MoveWithin` | **Gangs** | moves existing contraband *between* gangmates who can reach each other |
|
| Passing contraband between gangmates | **Gangs** | moves existing contraband *between* gangmates who can reach each other |
|
||||||
| `Corruption.Smuggle` | **Contraband** | injects *new* contraband from outside via a corruptible warden |
|
| Warden smuggling | **Contraband** | injects *new* contraband from outside via a corruptible warden |
|
||||||
|
|
||||||
Cut both and a gang genuinely dries up. Cut only the redistribution and a bent warden re-seeds it; cut
|
Cut both and a gang genuinely dries up. Cut only the redistribution and a bent warden re-seeds it; cut
|
||||||
only the warden and any stash you missed keeps circulating.
|
only the warden and any stash you missed keeps circulating.
|
||||||
@@ -109,12 +95,11 @@ only the warden and any stash you missed keeps circulating.
|
|||||||
- **Search the whole wing at once**, not one pawn at a time. A partial sweep is a snapshot the network
|
- **Search the whole wing at once**, not one pawn at a time. A partial sweep is a snapshot the network
|
||||||
routes around.
|
routes around.
|
||||||
- **Segregate rivals into different, unreachable wings** (Classification, in Institution: Justice). The
|
- **Segregate rivals into different, unreachable wings** (Classification, in Institution: Justice). The
|
||||||
reach gate is the lever — a network whose members cannot walk to each other cannot pass anything.
|
reach requirement is the lever — a network whose members cannot walk to each other cannot pass
|
||||||
This is the primary counter-play; see
|
anything. This is the primary counter-play; see
|
||||||
[Affiliations and Segregation](Affiliations-and-Segregation.md).
|
[Affiliations and Segregation](Affiliations-and-Segregation.md).
|
||||||
- **Clean up your wardens.** If searches never seem to finish the job, you may have a Greedy warden
|
- **Clean up your wardens.** If searches never seem to finish the job, you may have a Greedy warden
|
||||||
re-seeding the wing. Corruption is the resupply valve; personality is the fix.
|
re-seeding the wing. Corruption is the resupply valve; personality is the fix.
|
||||||
|
|
||||||
---
|
---
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs (this). Each mod stands alone; together they are one system.*
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs (this). Each mod stands alone; together they are one system.*
|
||||||
**
|
|
||||||
|
|||||||
+31
-49
@@ -9,27 +9,22 @@ suite can *see* and *police*.
|
|||||||
|
|
||||||
## Who is a rival
|
## Who is a rival
|
||||||
|
|
||||||
```
|
The rule is exactly as blunt as it sounds: **two pawns in two different gangs are rivals.** Both must
|
||||||
AreRivals(a, b):
|
actually be in a gang, and it must be a *different* gang for each. As the design puts it:
|
||||||
ga = GangOf(a); gb = GangOf(b)
|
|
||||||
return ga != 0 && gb != 0 && ga != gb
|
|
||||||
```
|
|
||||||
|
|
||||||
The definition is exactly as blunt as it reads: **two pawns in two different gangs are rivals.** Both
|
|
||||||
must actually be in a gang (a non-zero id), and the ids must differ. From the source:
|
|
||||||
|
|
||||||
> Two pawns of DIFFERENT gangs are rivals — inside the wire or out on the street.
|
> Two pawns of DIFFERENT gangs are rivals — inside the wire or out on the street.
|
||||||
|
|
||||||
Note what is *not* required. There is no separate "hostility" flag, no rival-declaration event, no
|
Note what is *not* required. There is no separate "hostility" flag, no rival-declaration event, no
|
||||||
threshold to cross. The moment two crews exist, their members are rivals of one another. Rivalry is a
|
threshold to cross. The moment two crews exist, their members are rivals of one another. Rivalry is a
|
||||||
structural fact about the membership map, not a mood or a relationship value.
|
structural fact about who is in which gang, not a mood or a relationship value.
|
||||||
|
|
||||||
Two consequences fall straight out of that:
|
Two consequences fall straight out of that:
|
||||||
|
|
||||||
- **Non-members are nobody's rival.** A pawn with gang id `0` — everyone in a healthy colony — is never
|
- **Non-members are nobody's rival.** A pawn in no gang — everyone in a healthy colony — is never a
|
||||||
a rival to anyone, because `AreRivals` requires both ids non-zero. No gangs, no rivalry.
|
rival to anyone, because rivalry needs *both* pawns to be in a gang. No gangs, no rivalry.
|
||||||
- **Same gang, never rivals.** Members of one crew fail the `ga != gb` test. Within a gang there is no
|
- **Same gang, never rivals.** Members of one crew are in the *same* gang, so they are not rivals.
|
||||||
rivalry to book; there is the network (see [Networks and Smuggling](Networks-and-Smuggling.md)).
|
Within a gang there is no rivalry to book; there is the network (see
|
||||||
|
[Networks and Smuggling](Networks-and-Smuggling.md)).
|
||||||
|
|
||||||
Where do two *different* gangs come from in the first place? From the affiliations pawns already have —
|
Where do two *different* gangs come from in the first place? From the affiliations pawns already have —
|
||||||
rival factions and rival ideoligions form into separate crews. That is the subject of
|
rival factions and rival ideoligions form into separate crews. That is the subject of
|
||||||
@@ -38,44 +33,33 @@ with the colony's existing social fault lines, so **rival factions run as rival
|
|||||||
|
|
||||||
## A fight is a crime
|
## A fight is a crime
|
||||||
|
|
||||||
The payoff of tracking rivalry is `RecordFight`. When a rival-gang attack happens, it is not treated as
|
The payoff of tracking rivalry is what happens when rivals fight. When a rival-gang attack happens, it
|
||||||
generic brawling — it is booked as a crime through the same Justice pipeline as any other offence.
|
is not treated as generic brawling — it is booked as a crime through the same Justice pipeline as any
|
||||||
|
other offence.
|
||||||
|
|
||||||
```
|
Only rival-gang violence counts: the two pawns must be in different gangs for anything to be recorded.
|
||||||
RecordFight(attacker, victim):
|
A scuffle between gangmates, or between two non-members, is not a *gang* fight and is not booked. When
|
||||||
if attacker == null || victim == null: return
|
it *is* rival-gang violence, the whole event is recorded as a crime against the **attacker**, and that
|
||||||
if !AreRivals(attacker, victim): return -- only rival-gang violence counts
|
does three things at once (they live in Institution: Justice, but this is what Gangs is leaning on):
|
||||||
Justice.RecordCrime(attacker)
|
|
||||||
```
|
|
||||||
|
|
||||||
So the guard is precise: the two pawns must be **rivals** (different gangs) for anything to be recorded.
|
| Effect of booking a gang fight | Where it lands |
|
||||||
A scuffle between gangmates, or between two non-members, is not a *gang* fight and is not booked here.
|
|
||||||
When it *is* rival-gang violence, the whole event routes through one call — `Justice.RecordCrime`,
|
|
||||||
against the **attacker** — and that single call does three things at once (it lives in Institution:
|
|
||||||
Justice, but this is what Gangs is leaning on):
|
|
||||||
|
|
||||||
| Effect of `Justice.RecordCrime(attacker)` | Owner |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| `crimesCommitted` on the attacker's record goes up | Core's `CriminalRecord` |
|
| the attacker's crime count goes up | their criminal record (Core) |
|
||||||
| `lastCrimeTick` is stamped to now | Core's `CriminalRecord` |
|
| the time of the crime is stamped to now | their criminal record (Core) |
|
||||||
| the colony's **deterrence** nudges **down** (a crime happened; order slipped) | Justice's `MapComponent_Deterrence` |
|
| the colony's **deterrence** nudges **down** (a crime happened; order slipped) | Justice's deterrence system |
|
||||||
|
|
||||||
The mod's integration test confirms the loop end to end: it builds two rival gangs, records a fight,
|
|
||||||
and asserts both that the attacker and victim *are* rivals and that the attacker's `crimesCommitted`
|
|
||||||
went up as a result.
|
|
||||||
|
|
||||||
## Inside the wire and out on the street are the same offence
|
## Inside the wire and out on the street are the same offence
|
||||||
|
|
||||||
This is the design point the source is emphatic about:
|
This is the design point the mod is emphatic about:
|
||||||
|
|
||||||
> A gang fight is a CRIME — it goes on the attacker's record and moves the colony's climate through
|
> A gang fight is a CRIME — it goes on the attacker's record and moves the colony's climate through
|
||||||
> the same Justice loop as any other, so it can be investigated, attributed and policed. Rival-gang
|
> the same Justice loop as any other, so it can be investigated, attributed and policed. Rival-gang
|
||||||
> violence inside a prison and out on the street are the same offence.
|
> violence inside a prison and out on the street are the same offence.
|
||||||
|
|
||||||
There is no separate code path for a prison-yard shanking versus a colonists' brawl in the dining
|
There is no separate rule for a prison-yard shanking versus a colonists' brawl in the dining room. A
|
||||||
room. `RecordFight` reads only `AreRivals` and calls `Justice.RecordCrime`. A free colonist who is a
|
fight is booked purely on whether the two pawns are rivals. A free colonist who is a gang member
|
||||||
gang member attacking a rival is booked identically to a prisoner doing the same in a cell block. The
|
attacking a rival is booked identically to a prisoner doing the same in a cell block. The gang system
|
||||||
gang system does not care which side of the wall the violence is on — only that it was between rivals.
|
does not care which side of the wall the violence is on — only that it was between rivals.
|
||||||
|
|
||||||
This is what makes gang violence *legible* to the rest of the suite. A fight is not a one-off flavour
|
This is what makes gang violence *legible* to the rest of the suite. A fight is not a one-off flavour
|
||||||
event that scrolls past in the log; it is a record entry with an attributed perpetrator and a timestamp,
|
event that scrolls past in the log; it is a record entry with an attributed perpetrator and a timestamp,
|
||||||
@@ -85,19 +69,18 @@ which means it can be investigated, blamed on a specific pawn, and answered.
|
|||||||
|
|
||||||
Here is the loop that closes the whole suite:
|
Here is the loop that closes the whole suite:
|
||||||
|
|
||||||
1. A rival-gang fight fires `RecordFight` → `Justice.RecordCrime(attacker)`.
|
1. A rival-gang fight is booked as a crime against the attacker.
|
||||||
2. That nudges the colony's **deterrence down**. Order has visibly slipped.
|
2. That nudges the colony's **deterrence down**. Order has visibly slipped.
|
||||||
3. Lower deterrence *raises* the nurture multiplier for **every** disposed pawn on the map
|
3. Lower deterrence *raises* the nurture multiplier for **every** disposed pawn on the map (less
|
||||||
(deterrence scales nurture via `×Lerp(1.3, 0.7, deterrence)` — less deterrence, bigger multiplier).
|
deterrence means a bigger multiplier).
|
||||||
4. Higher nurture pushes more pawns over the `Nature × Nurture ≥ 0.9` join bar (see
|
4. Higher nurture pushes more pawns over the Nature × Nurture ≥ 0.9 join bar (see
|
||||||
[Joining](Joining.md)).
|
[Joining](Joining.md)).
|
||||||
5. More members → more rivals → more fights available to be booked.
|
5. More members → more rivals → more fights available to be booked.
|
||||||
|
|
||||||
Left unanswered, gang violence is self-reinforcing: each booked fight makes the next one likelier. The
|
Left unanswered, gang violence is self-reinforcing: each booked fight makes the next one likelier. The
|
||||||
brake is the other half of Justice — **punishment raises deterrence back up** (`RecordPunishment`
|
brake is the other half of Justice — **punishment raises deterrence back up**, which lowers nurture,
|
||||||
nudges it up), which lowers nurture, which drops borderline pawns back under the bar. The integration
|
which drops borderline pawns back under the bar. The seesaw works both ways: punishing a pawn raises the
|
||||||
test checks exactly this seesaw: a punishment raises the deterrence level, and a subsequent crime
|
deterrence level, and a fresh crime lowers it again.
|
||||||
lowers it again.
|
|
||||||
|
|
||||||
## How to play it
|
## How to play it
|
||||||
|
|
||||||
@@ -113,4 +96,3 @@ lowers it again.
|
|||||||
|
|
||||||
---
|
---
|
||||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs (this). Each mod stands alone; together they are one system.*
|
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs (this). Each mod stands alone; together they are one system.*
|
||||||
**
|
|
||||||
|
|||||||
+53
-90
@@ -2,22 +2,22 @@
|
|||||||
|
|
||||||
*The connection that turns the pipeline into a cycle.*
|
*The connection that turns the pipeline into a cycle.*
|
||||||
|
|
||||||
This is the module that makes policing **govern** rather than merely clean up. Everything else in
|
This is the part that makes policing **govern** rather than merely clean up. Everything else in Justice
|
||||||
Justice reacts to one pawn: this reactor's *state* is the whole colony, and it feeds **back** into
|
reacts to one pawn: this reacts to the *whole colony*, and it feeds **back** into every pawn's
|
||||||
every pawn's disposition. Crime that goes unanswered emboldens everyone a little; visible justice
|
disposition. Crime that goes unanswered emboldens everyone a little; visible justice deters everyone a
|
||||||
deters everyone a little. The reason to punish is not just this prisoner — it is the message it sends
|
little. The reason to punish is not just this prisoner — it is the message it sends the rest, and here
|
||||||
the rest, and here that message is a number they all read.
|
that message is a number they all read.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The climate of order
|
## The climate of order
|
||||||
|
|
||||||
Every map carries a `MapComponent_Deterrence` holding one value:
|
Every colony carries one order value:
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Range** | `0.0` (lawless) … `1.0` (iron) |
|
| **Range** | 0.0 (lawless) … 1.0 (iron) |
|
||||||
| **Baseline** | `0.5` — an ordinary colony |
|
| **Baseline** | 0.5 — an ordinary colony |
|
||||||
| **Below baseline** | crime pays; dispositions run **hot** |
|
| **Below baseline** | crime pays; dispositions run **hot** |
|
||||||
| **Above baseline** | order holds; dispositions run **cool** |
|
| **Above baseline** | order holds; dispositions run **cool** |
|
||||||
|
|
||||||
@@ -25,50 +25,40 @@ It is nudged by two events and, left alone, forgets.
|
|||||||
|
|
||||||
### The nudges
|
### The nudges
|
||||||
|
|
||||||
| Event | Source | Nudge | Written by |
|
| Event | Nudge |
|
||||||
|---|---|---|---|
|
|---|---|
|
||||||
| A crime, unanswered | `Justice.RecordCrime(perp)` | **−0.08** | Justice, Gangs (fights) |
|
| A crime, unanswered | **−0.08** |
|
||||||
| Visible justice done | `Justice.RecordPunishment(map)` | **+0.12** | Justice (`Discipline.Punish`) |
|
| Visible justice done (a punishment) | **+0.12** |
|
||||||
|
|
||||||
Both clamp the result to `[0, 1]`. Note the asymmetry: **punishment (+0.12) outweighs crime (−0.08)
|
Both stay clamped to the 0–1 range. Note the asymmetry: **punishment (+0.12) outweighs crime (−0.08)
|
||||||
by 1.5×.** A colony that answers every offence does not merely break even — it ratchets *above*
|
by 1.5×.** A colony that answers every offence does not merely break even — it ratchets *above*
|
||||||
baseline into "order holds" territory. This is the deterrence dividend, and it is deliberate: justice
|
baseline into "order holds" territory. This is the deterrence dividend, and it is deliberate: justice
|
||||||
seen to be done buys more order than the crime cost.
|
seen to be done buys more order than the crime cost.
|
||||||
|
|
||||||
`RecordCrime` also writes the record — it increments `crimesCommitted` and stamps `lastCrimeTick` on
|
The same crime that moves the climate also marks the culprit's record — the offence that shifts the
|
||||||
the perpetrator — so the same event that moves the climate is the event classification and parole
|
colony's mood is the one classification and parole will remember later. A punishment moves only the
|
||||||
later see. `RecordPunishment` moves only the climate; the *record* side of punishment (the reform
|
climate; the reform side of a punishment — how the offender themselves changes — is
|
||||||
shift) is [Discipline](Discipline-and-Reform.md)'s job.
|
[Discipline](Discipline-and-Reform.md)'s job.
|
||||||
|
|
||||||
### The drift
|
### The drift
|
||||||
|
|
||||||
```
|
Memory fades. With nothing happening, the climate creeps back toward ordinary at about **0.0015 every
|
||||||
every 2500 ticks: level = MoveTowards(level, 0.5, 0.0015)
|
in-game while (roughly one nudge every 2500 ticks)** — which works out to:
|
||||||
```
|
|
||||||
|
|
||||||
Memory fades. With nothing happening, the climate creeps back toward ordinary at **0.0015 per step,
|
**drift per in-game day ≈ 0.036**
|
||||||
one step every 2500 ticks** — which works out to:
|
|
||||||
|
|
||||||
```
|
So a single unanswered crime (−0.08) takes roughly `0.08 / 0.036 ≈ 2.2 days` to heal on its own — or
|
||||||
drift per in-game day = (60000 / 2500) * 0.0015 = 24 * 0.0015 = 0.036 / day
|
one punishment (+0.12) to over-answer instantly. An iron colony you stop maintaining slides back to
|
||||||
```
|
baseline over about two weeks; it does not stay stern for free.
|
||||||
|
|
||||||
So a single unanswered crime (`−0.08`) takes roughly `0.08 / 0.036 ≈ 2.2 days` to heal on its own —
|
|
||||||
or one punishment (`+0.12`) to over-answer instantly. An iron colony you stop maintaining slides back
|
|
||||||
to baseline over about two weeks; it does not stay stern for free.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The feedback — how it reaches every pawn
|
## The feedback — how it reaches every pawn
|
||||||
|
|
||||||
This is the loop. The climate is read back into `Propensity.Nurture` (in Core) as a multiplier:
|
This is the loop. The climate is read back into every colonist's upbringing as a multiplier on their
|
||||||
|
propensity:
|
||||||
|
|
||||||
```
|
| Order level | Factor | Effect on every pawn's disposition |
|
||||||
DeterrenceFactor(pawn) = Lerp(1.3, 0.7, level) // = 1.3 - 0.6 * level
|
|
||||||
Nurture(pawn) = ... * DeterrenceFactor(pawn)
|
|
||||||
```
|
|
||||||
|
|
||||||
| `level` | Factor | Effect on every pawn's disposition |
|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 0.00 (lawless) | **1.300** | +30% hotter — crime pays, everyone feels it |
|
| 0.00 (lawless) | **1.300** | +30% hotter — crime pays, everyone feels it |
|
||||||
| 0.30 | 1.120 | +12% |
|
| 0.30 | 1.120 | +12% |
|
||||||
@@ -79,55 +69,42 @@ Nurture(pawn) = ... * DeterrenceFactor(pawn)
|
|||||||
|
|
||||||
Two things to notice:
|
Two things to notice:
|
||||||
|
|
||||||
- **Baseline is the neutral point, by design.** A default colony sits at `0.5`, which is a factor of
|
- **Baseline is the neutral point, by design.** A default colony sits at 0.5, which is a factor of
|
||||||
exactly `1.0` — ordinary order neither inflames nor calms. That symmetry is deliberate. If baseline
|
exactly 1.0 — ordinary order neither inflames nor calms. That symmetry is deliberate. If baseline
|
||||||
ran hot (as an earlier tuning did), ordinary order would nudge propensity up, breed a little more
|
ran hot, ordinary order would nudge propensity up, breed a little more crime, drop order, and feed a
|
||||||
crime, drop order, and feed a slow runaway. Neutral-at-baseline means only a colony that lets order
|
slow runaway. Neutral-at-baseline means only a colony that lets order *slide below* ordinary earns
|
||||||
*slide below* ordinary earns the hot multiplier, and only one that pushes *above* it earns the calm.
|
the hot multiplier, and only one that pushes *above* it earns the calm.
|
||||||
- **The swing is `0.7×` to `1.3×`, symmetric around `1.0`** — a `±30%` spread applied to *everyone's*
|
- **The swing is 0.7× to 1.3×, symmetric around 1.0** — a ±30% spread applied to *everyone's*
|
||||||
nurture at once. This is the multiplier that makes catching one pawn matter to the disposition of
|
disposition at once. This is the multiplier that makes catching one pawn matter to the disposition of
|
||||||
the rest.
|
the rest.
|
||||||
|
|
||||||
### PolicingBootstrap — reconnecting the loop across the mod boundary
|
### When it applies
|
||||||
|
|
||||||
Core keeps `Propensity.DeterrenceFactor` as a neutral seam — a `Func<Pawn,float>` that returns `1f`
|
This bending of behaviour only happens while **Policing is installed**. Core on its own reads a flat,
|
||||||
until something fills it. Core alone never has to know Justice exists; that one seam is what lets Core
|
neutral 1.0 — colonists are indifferent to order — and only Policing wires the climate into their
|
||||||
stay a dependency-free leaf while the deterrence loop runs *through* it.
|
disposition. A colonist who is off the map (in a caravan, in transit) has no local climate to read, so
|
||||||
|
they fall back to neutral too.
|
||||||
At startup, `PolicingBootstrap` fills the seam:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
Propensity.DeterrenceFactor = pawn => {
|
|
||||||
var d = pawn?.Map?.GetComponent<MapComponent_Deterrence>();
|
|
||||||
return d != null ? Mathf.Lerp(1.3f, 0.7f, d.Level) : 1f; // neutral (1.0) at baseline 0.5
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
So **only when Justice is installed** does the climate bend disposition; without it, Core reads a flat
|
|
||||||
`1.0` and pawns are indifferent to order. This is the deterrence feedback loop, reconnected across the
|
|
||||||
mod boundary without Core ever depending on the justice layer. (If the pawn is off-map — caravan, in
|
|
||||||
transit — there is no map component, and the factor falls back to a neutral `1.0`.)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Worked example — a day at the institution
|
## Worked example — a day at the institution
|
||||||
|
|
||||||
The colony starts at baseline `0.5` (factor `1.05`). Over one day:
|
The colony starts at baseline 0.5 (factor 1.05). Over one day:
|
||||||
|
|
||||||
| Step | Event | `level` | Factor |
|
| Step | Event | Order level | Factor |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| start | — | 0.50 | 1.050 |
|
| start | — | 0.50 | 1.050 |
|
||||||
| 1 | Prisoner A shanks a guard (crime, unanswered) | 0.42 | 1.106 |
|
| 1 | Prisoner A shanks a guard (crime, unanswered) | 0.42 | 1.106 |
|
||||||
| 2 | Prisoner B foments trouble (crime, unanswered) | 0.34 | 1.162 |
|
| 2 | Prisoner B foments trouble (crime, unanswered) | 0.34 | 1.162 |
|
||||||
| 3 | Warden punishes A (`RecordPunishment`) | 0.46 | 1.078 |
|
| 3 | Warden punishes A | 0.46 | 1.078 |
|
||||||
| 4 | Warden punishes B (`RecordPunishment`) | 0.58 | 0.994 |
|
| 4 | Warden punishes B | 0.58 | 0.994 |
|
||||||
| overnight | nothing happens, drift ≈ −0.036 toward 0.5 | 0.544 | 1.019 |
|
| overnight | nothing happens, drift ≈ −0.036 toward 0.5 | 0.544 | 1.019 |
|
||||||
|
|
||||||
Two crimes cost `−0.16`; two punishments returned `+0.24`. The colony ends the day at `0.58` —
|
Two crimes cost −0.16; two punishments returned +0.24. The colony ends the day at 0.58 — **above**
|
||||||
**above** where it started — because punishment out-answers crime. Every pawn's disposition dipped
|
where it started — because punishment out-answers crime. Every pawn's disposition dipped hotter as the
|
||||||
hotter as the crimes landed (`1.05 → 1.16`) and then cooled below neutral once order was reasserted
|
crimes landed (1.05 → 1.16) and then cooled below neutral once order was reasserted (0.994). By morning
|
||||||
(`0.994`). By morning the drift has begun erasing the gain, and if nothing keeps the pressure on, the
|
the drift has begun erasing the gain, and if nothing keeps the pressure on, the colony sinks back to its
|
||||||
colony sinks back to its slightly-hot baseline over the next couple of weeks.
|
slightly-hot baseline over the next couple of weeks.
|
||||||
|
|
||||||
That arc — hot when crime pays, cool when justice is seen, forgetful when neither happens — is the
|
That arc — hot when crime pays, cool when justice is seen, forgetful when neither happens — is the
|
||||||
whole reason discipline is not inert. You are not punishing a pawn; you are setting the temperature of
|
whole reason discipline is not inert. You are not punishing a pawn; you are setting the temperature of
|
||||||
@@ -140,27 +117,13 @@ the room.
|
|||||||
- **Answer crime, visibly and promptly.** Each punishment is worth 1.5 crimes of order. A colony that
|
- **Answer crime, visibly and promptly.** Each punishment is worth 1.5 crimes of order. A colony that
|
||||||
reliably punishes climbs *above* baseline and cools everyone; a colony that lets offences slide
|
reliably punishes climbs *above* baseline and cools everyone; a colony that lets offences slide
|
||||||
sinks below and heats everyone — a spiral, because hotter pawns commit more crime.
|
sinks below and heats everyone — a spiral, because hotter pawns commit more crime.
|
||||||
- **Don't coast on a past crackdown.** The climate drifts back to baseline at `0.036/day`. Order is a
|
- **Don't coast on a past crackdown.** The climate drifts back to baseline at 0.036/day. Order is a
|
||||||
maintenance cost, not a one-time purchase.
|
maintenance cost, not a one-time purchase.
|
||||||
- **Push past baseline if you want calm.** Neutral disposition needs `level ≈ 0.571`; ordinary
|
- **Push past baseline if you want calm.** Neutral disposition needs an order level of about 0.571;
|
||||||
(`0.5`) still runs 5% hot. A steady rhythm of caught-and-punished offences is what holds you there.
|
ordinary (0.5) still runs 5% hot. A steady rhythm of caught-and-punished offences is what holds you
|
||||||
- **A lawless spell is self-feeding.** At `level 0.3` every pawn is `1.19×` more disposed; more crime
|
there.
|
||||||
follows, driving the level lower still. Break the cycle with punishments, not patience.
|
- **A lawless spell is self-feeding.** At an order level of 0.3 every pawn is 1.19× more disposed; more
|
||||||
|
crime follows, driving the level lower still. Break the cycle with punishments, not patience.
|
||||||
---
|
|
||||||
|
|
||||||
## For modders
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
Justice.RecordCrime(perpetrator); // -0.08 climate, +1 crime, stamps lastCrimeTick
|
|
||||||
Justice.RecordPunishment(map); // +0.12 climate
|
|
||||||
float level = map.GetComponent<MapComponent_Deterrence>().Level; // raw 0..1
|
|
||||||
```
|
|
||||||
|
|
||||||
`RecordCrime` is the writer any module calls when a pawn does something the colony would police —
|
|
||||||
Institution: Gangs calls it on rival fights, for instance. `RecordPunishment` is called by
|
|
||||||
`Discipline.Punish`. The component and statics live in the `Contraband` namespace (shared across the
|
|
||||||
suite since Justice's extraction from Contraband).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+13
-12
@@ -4,8 +4,8 @@
|
|||||||
RimWorld 1.6 mods.*
|
RimWorld 1.6 mods.*
|
||||||
|
|
||||||
Vanilla RimWorld keeps a rap sheet no one reads, and your own colonists never break the law. Policing
|
Vanilla RimWorld keeps a rap sheet no one reads, and your own colonists never break the law. Policing
|
||||||
gives the colony a **climate of law and order** over the shared criminal record: colonists offend on a
|
gives the colony a **climate of law and order**: colonists offend on a spectrum of criminal
|
||||||
seeded propensity spectrum, the law catches them, and catching one changes the disposition of the rest.
|
propensity, the law catches them, and catching one changes how the rest behave.
|
||||||
|
|
||||||
> **Catching and punishing one pawn changes the disposition of the rest.**
|
> **Catching and punishing one pawn changes the disposition of the rest.**
|
||||||
|
|
||||||
@@ -18,33 +18,34 @@ reason to punish an offender is the message it sends everyone else quietly decid
|
|||||||
|
|
||||||
| Page | What it covers |
|
| Page | What it covers |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **[Policing](Policing.md)** | Colony crime on the propensity spectrum, witnesses and the constable work type, the weighed arrest and resistance, prison riots, the recidivism alert. |
|
| **[Policing](Policing.md)** | Colony crime on the propensity spectrum, witnesses and the constable's investigation, the weighed arrest and resisting it, prison riots, and the recidivism alert. |
|
||||||
| **[Deterrence](Deterrence.md)** | The colony's climate of order (0 lawless … 1 iron), moved by crime and punishment, drifting to baseline, fed back into every pawn's disposition through Core's seam. |
|
| **[Deterrence](Deterrence.md)** | The colony's climate of order (0 lawless … 1 iron), moved by crime and punishment, drifting back to baseline, and fed back into every colonist's disposition. |
|
||||||
|
|
||||||
At load, **`PolicingBootstrap`** fills the neutral deterrence seam Core leaves open — reconnecting the
|
The climate of order only steers your colonists while Policing is installed. Core on its own keeps the
|
||||||
feedback loop across the mod boundary without Core ever depending on the policing layer.
|
records but leaves behaviour untouched; add Policing and the feedback loop comes alive — visible
|
||||||
|
justice starts calming the colony, unanswered crime starts emboldening it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The one shared record
|
## The one shared record
|
||||||
|
|
||||||
Policing keeps no per-pawn crime state of its own. It reads and writes the single `CriminalRecord` that
|
Policing keeps no crime record of its own. It reads and writes the single criminal record that
|
||||||
**Institution: Core** holds for every pawn — the same record Contraband, Corrections, and the gang
|
**Institution: Core** holds for every colonist — the same rap sheet Contraband, Corrections, and the
|
||||||
systems read. `RecordCrime` writes the `crimesCommitted` / `lastCrimeTick` columns; Corrections grades
|
gang systems read. A crime here adds to that record; Corrections grades and reforms over the same
|
||||||
and reforms over the same numbers.
|
history.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Requirements & load order
|
## Requirements & load order
|
||||||
|
|
||||||
- **RimWorld 1.6**
|
- **RimWorld 1.6**
|
||||||
- **Institution: Core** — required (the propensity/record engine)
|
- **Institution: Core** — required (the propensity and record system)
|
||||||
- **Harmony** — required
|
- **Harmony** — required
|
||||||
- Load **after** Core and Harmony.
|
- Load **after** Core and Harmony.
|
||||||
|
|
||||||
The prison half — classification, discipline, reform, parole, regime — lives in **Institution:
|
The prison half — classification, discipline, reform, parole, regime — lives in **Institution:
|
||||||
Corrections**, which depends on this layer for the shared climate of order. A future **Judiciary** layer
|
Corrections**, which depends on this layer for the shared climate of order. A future **Judiciary** layer
|
||||||
(a court that tries and sentences) is reserved as a Core seam; today an arrest imprisons directly.
|
— a court that tries and sentences — is planned; for now an arrest sends the culprit straight to a cell.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+24
-29
@@ -12,18 +12,17 @@ deterrence, discipline, parole, regime — without colony crime.
|
|||||||
|
|
||||||
## The loop
|
## The loop
|
||||||
|
|
||||||
```
|
**crime → witnessed / investigated → weighed arrest (resist?) → prison → … → parole → recidivism**
|
||||||
[crime] → [witnessed / investigated] → [weighed arrest (resist?)] → prison → … → parole → [recidivism]
|
|
||||||
```
|
|
||||||
|
|
||||||
Every stage reads the shared engine (`Propensity`, the `CriminalRecord`, the deterrence meter), so
|
Every stage draws on the same shared systems — each colonist's propensity, the one shared criminal
|
||||||
policing is not a bolt-on — it feeds the same record classification grades on and discipline reforms.
|
record, the climate of order — so policing is not a bolt-on: the record it writes is the record
|
||||||
|
classification grades on and discipline reforms.
|
||||||
|
|
||||||
## Crime — and why small colonies stay honest
|
## Crime — and why small colonies stay honest
|
||||||
|
|
||||||
Every ~40 seconds, each free colonist gets a seeded roll to offend. The base rate is low (`0.06`) and
|
Every ~40 seconds, each free colonist gets a hidden roll to offend. The base chance is low (about
|
||||||
multiplied hard by nature × nurture — almost nobody with an ordinary disposition ever does anything.
|
6% per roll) and it is multiplied hard by nature × nurture — almost nobody with an ordinary
|
||||||
But it is *also* multiplied by **crime cover**:
|
disposition ever does anything. But it is *also* multiplied by **crime cover**:
|
||||||
|
|
||||||
| Free colonists | Crime cover |
|
| Free colonists | Crime cover |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -41,8 +40,7 @@ kleptomaniac steals, a bloodlusty pawn assaults, a pyromaniac vandalizes).
|
|||||||
|
|
||||||
## Consequences — the stakes
|
## Consequences — the stakes
|
||||||
|
|
||||||
A crime leaves a mark the colony can *see* (all wrapped defensively — a consequence that finds no
|
A crime leaves a mark the colony can *see*:
|
||||||
target never breaks anything):
|
|
||||||
|
|
||||||
- **Theft** lifts a small stack of the colony's goods and the thief **pockets it**. RimWorld has no
|
- **Theft** lifts a small stack of the colony's goods and the thief **pockets it**. RimWorld has no
|
||||||
per-person property (stockpiles are communal), so theft is theft from the common store — but the
|
per-person property (stockpiles are communal), so theft is theft from the common store — but the
|
||||||
@@ -58,30 +56,28 @@ target never breaks anything):
|
|||||||
## Witnesses and investigation
|
## Witnesses and investigation
|
||||||
|
|
||||||
A crime is not equally solvable. Colonists who were near enough to **see** it are witnesses, and each
|
A crime is not equally solvable. Colonists who were near enough to **see** it are witnesses, and each
|
||||||
is a partial lead — a witnessed crime starts up to `0.8` of the way to solved before anyone lifts a
|
is a partial lead — a witnessed crime starts up to 80% of the way to solved before anyone lifts a
|
||||||
finger; an unseen one is a **cold case** worked from nothing.
|
finger; an unseen one is a **cold case** worked from nothing.
|
||||||
|
|
||||||
Investigation then accrues evidence toward `1.0` (naming the culprit) two ways:
|
Investigation then builds evidence toward a full, named solve two ways:
|
||||||
|
|
||||||
- **Ambient**, scaled by how many colonists the colony can spare (`~0.34` per check per unit of
|
- **Ambient** progress, scaled by how many colonists the colony can spare — a background trickle from
|
||||||
effort — a few investigated windows).
|
everyone keeping an eye out, faster when the colony has people to spare.
|
||||||
- **The police work type.** Assign a colonist to *policing* and they walk to open **crime scenes** and
|
- **The policing work type.** Assign a colonist to *policing* and they walk to open **crime scenes**
|
||||||
work them, adding a chunk of evidence scaled by their Intellectual + Social skill. A sharp constable
|
and work them, adding a chunk of evidence scaled by their Intellectual + Social skill. A sharp
|
||||||
cracks cases fast; a colony that spares no one lets them go cold.
|
constable cracks cases fast; a colony that spares no one lets them go cold.
|
||||||
|
|
||||||
## The weighed arrest
|
## The weighed arrest
|
||||||
|
|
||||||
A solved case does **not** mean an automatic arrest. Fellow-colonist cops will not gut the colony to
|
A solved case does **not** mean an automatic arrest. Fellow-colonist cops will not gut the colony to
|
||||||
jail someone over a petty crime — the arrest is a cost/benefit (`ShouldArrest`):
|
jail someone over a petty crime — the arrest is a cost/benefit call, and it goes ahead only when both
|
||||||
|
are true: the colony can **afford a prison**, and the crime is **serious enough** to clear a bar that
|
||||||
```
|
rises with how valuable the culprit is.
|
||||||
arrest ⇔ colony can afford a prison AND severity ≥ 1.5 + value × 3
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Severity** — the crime's kind (theft 1 · vandalism 1.5 · assault 2.5), escalated by the culprit's
|
- **Severity** — the crime's kind (theft 1 · vandalism 1.5 · assault 2.5), escalated by the culprit's
|
||||||
record (a rap sheet and prior escapes compound).
|
record (a rap sheet and prior escapes compound).
|
||||||
- **Value** — 0..1 from the culprit's best skills. A star colonist raises the bar to ~4.5; an
|
- **Value** — 0..1 from the culprit's best skills. The bar sits at about 1.5 for an expendable pawn
|
||||||
expendable one leaves it at ~1.5.
|
and climbs by three times the culprit's value — so a star colonist raises it to about 4.5.
|
||||||
- **Capacity** — **below ~5 colonists, or with no prison bed, the colony arrests no one.** A prisoner
|
- **Capacity** — **below ~5 colonists, or with no prison bed, the colony arrests no one.** A prisoner
|
||||||
plus a warden would cripple a small colony. Capacity ramps up as the colony grows.
|
plus a warden would cripple a small colony. Capacity ramps up as the colony grows.
|
||||||
|
|
||||||
@@ -90,22 +86,21 @@ paid, unanswered, erodes the climate of order a little more (crime emboldens).
|
|||||||
|
|
||||||
## Resisting arrest
|
## Resisting arrest
|
||||||
|
|
||||||
The disposed do not go quietly (`WouldResist`, seeded nature × nurture):
|
The disposed do not go quietly (seeded by nature × nurture):
|
||||||
|
|
||||||
- **Comply** → taken in cleanly (they become a prisoner of the colony — imprisonment is direct, and it
|
- **Comply** → taken in cleanly (they become a prisoner of the colony — imprisonment is direct, and it
|
||||||
survives guest-management mods like Hospitality).
|
survives guest-management mods like Hospitality).
|
||||||
- **Resist** → the violent turn **berserk**, the rest **flee**. A botched arrest becomes a crisis the
|
- **Resist** → the violent turn **berserk**, the rest **flee**. A botched arrest becomes a crisis the
|
||||||
colony must handle the hard way, and openly defying arrest emboldens the lawless.
|
colony must handle the hard way, and openly defying arrest emboldens the lawless.
|
||||||
- **Subdued** → a resister the colony beats down is then **imprisoned** (auto-capture) — the resist
|
- **Subdued** → a resister the colony beats down is then **imprisoned** — the resist path resolves to a
|
||||||
path resolves to a cell once the colony wins the confrontation. One who flees the map gets away.
|
cell once the colony wins the confrontation. One who flees the map gets away.
|
||||||
|
|
||||||
## Prison riots
|
## Prison riots
|
||||||
|
|
||||||
When the climate of order **collapses** (deterrence at the floor) in a colony holding prisoners, a
|
When the climate of order **collapses** (deterrence at the floor) in a colony holding prisoners, a
|
||||||
neglected prison boils over into a **riot**: disposed prisoners turn violent together, and the unrest
|
neglected prison boils over into a **riot**: disposed prisoners turn violent together, and the unrest
|
||||||
shatters order further. A well-run colony (high deterrence) never sees one — a riot is the thing a
|
shatters order further. A well-run colony (high deterrence) never sees one — a riot is the thing a
|
||||||
badly-run institution *earns*. It ties the deterrence meter, the propensity engine, and the prison
|
badly-run institution *earns*.
|
||||||
population into one emergent event.
|
|
||||||
|
|
||||||
## Recidivism, made visible
|
## Recidivism, made visible
|
||||||
|
|
||||||
|
|||||||
+111
-202
@@ -4,20 +4,17 @@ How involuntary psychiatric commitment actually works in play: how a colonist be
|
|||||||
what the treatment station does, how a room turns into a ward, who does the counselling, what the
|
what the treatment station does, how a room turns into a ward, who does the counselling, what the
|
||||||
treatment buys, and what happens if you commit someone and then forget about them.
|
treatment buys, and what happens if you commit someone and then forget about them.
|
||||||
|
|
||||||
Every def and class name below is the real one from the mod source. The `Ward_` prefix marks a
|
|
||||||
def; the C# class names are the workers behind them.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The loop in one paragraph
|
## The loop in one paragraph
|
||||||
|
|
||||||
You **arrest** a colonist (or otherwise take a prisoner) and set their interaction mode to
|
You **arrest** a colonist (or otherwise take a prisoner) and set their interaction mode to
|
||||||
**psychiatric care**. You build a **treatment station** in their cell, which turns the room into a
|
**psychiatric care**. You build a **treatment station** in their cell, which turns the room into a
|
||||||
**psychiatric ward**. A warden walks over, sits, and **counsels** them; each session adds a little
|
**psychiatric ward**. A warden walks over, sits, and **counsels** them; each session builds up their
|
||||||
`Ward_UnderTreatment` severity, which **lowers their mental-break threshold**. That severity
|
**treatment**, which **lowers their mental-break threshold** so they break less often. That progress
|
||||||
**decays** at 0.15/day, so the effect fades unless wardens keep attending. Counselling also gives a
|
**fades** over about a week if nobody keeps attending, so it has to be sustained. Counselling also
|
||||||
small mood lift (`Ward_Counselled`). Neglect a committed patient — leave them with no active treatment
|
gives the patient a small mood lift. Neglect a committed patient — leave them with no ongoing
|
||||||
hediff — and once a day they gain `Ward_Neglected`, a mood *penalty* that drags them back toward the
|
treatment — and once a day they pick up a **neglected** mood penalty that drags them back toward the
|
||||||
break that got them committed. Treat them and they stabilise; forget them and they don't. That
|
break that got them committed. Treat them and they stabilise; forget them and they don't. That
|
||||||
feedback loop is the entire mod.
|
feedback loop is the entire mod.
|
||||||
|
|
||||||
@@ -25,34 +22,25 @@ feedback loop is the entire mod.
|
|||||||
|
|
||||||
## Step 1 — Becoming a ward patient
|
## Step 1 — Becoming a ward patient
|
||||||
|
|
||||||
Commitment reuses vanilla's arrest → prisoner transition. You don't invent a new pawn state; you take
|
Commitment reuses vanilla's arrest → prisoner transition. You don't get a new pawn state; you take a
|
||||||
a prisoner (arrest your own colonist, or a raider) and then flip on a **non-exclusive interaction
|
prisoner (arrest your own colonist, or a captured raider) and then switch on a new interaction mode.
|
||||||
mode**.
|
|
||||||
|
|
||||||
### `Ward_PsychiatricCare` — the interaction mode
|
### Psychiatric care — the interaction mode
|
||||||
|
|
||||||
| Field | Value | Why |
|
In the prisoner's interaction dropdown you'll find a new option, **psychiatric care**, sitting between
|
||||||
|---|---|---|
|
*Reduce resistance* and *Release*. A few things make it behave the way it does:
|
||||||
| Def type | `PrisonerInteractionModeDef` | A Def, not the compiled `GuestStatus` enum — that's the whole point |
|
|
||||||
| `defName` | `Ward_PsychiatricCare` | |
|
|
||||||
| `label` | "psychiatric care" | Shown in the prisoner's interaction dropdown |
|
|
||||||
| `listOrder` | `150` | Sits between vanilla's `ReduceResistance` (100) and `Release` (200) |
|
|
||||||
| `isNonExclusiveInteraction` | `true` | **Stacks** with recruit/convert/work instead of stealing the radio button |
|
|
||||||
| `mustBeAwake` | `false` | You can flag a downed or sleeping patient for care; the *work giver* decides when to actually treat |
|
|
||||||
| `allowOnWildMan` | `false` | A wild man isn't a commitment case |
|
|
||||||
| `allowInClassicIdeoMode` | `true` | Available without Ideology's ideoligion system |
|
|
||||||
|
|
||||||
**Non-exclusive is load-bearing.** Vanilla's recruit/convert/enslave/release/execute modes are
|
- **It stacks.** Unlike recruit / convert / enslave / release / execute — which are one-at-a-time
|
||||||
mutually exclusive — you pick one from a radio group. Psychiatric care is modelled on vanilla's own
|
choices — psychiatric care is a toggle that layers *on top* of whatever else you've picked, the
|
||||||
*Bloodfeed* and *Study* modes, which are toggles layered *on top* of the exclusive choice. So a
|
same way vanilla's *Bloodfeed* and *Study* toggles do. So a patient can be set to **recruit AND
|
||||||
patient can be set to **recruit AND psychiatric care at once**: you counsel the sad colonist toward
|
psychiatric care at once**: you counsel a sad captured colonist toward stability while also chipping
|
||||||
stability while also chipping at their resistance. It also means Ward doesn't have to ship a
|
at their resistance.
|
||||||
cross-product of `workAndPsychiatricCare` variants to coexist with *Prison Labor*'s work modes — both
|
- **You can flag a downed or sleeping patient.** Setting the mode doesn't require them to be awake —
|
||||||
just toggle on.
|
the warden decides when to actually treat.
|
||||||
|
- **Not for wild men**, and it works fine **without Ideology's ideoligion system**.
|
||||||
|
|
||||||
The harness proves this on a live pawn: after `ToggleNonExclusiveInteraction(Ward_PsychiatricCare,
|
Because the mode stacks, it also lets Ward coexist cleanly with work-your-prisoners mods like *Prison
|
||||||
true)` and then `SetExclusiveInteraction(AttemptRecruit)`, **both** modes report enabled
|
Labor* — see **Compatibility**.
|
||||||
(`live.stacksWithRecruit = True`).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -60,144 +48,78 @@ true)` and then `SetExclusiveInteraction(AttemptRecruit)`, **both** modes report
|
|||||||
|
|
||||||
Flagging a patient for care does nothing on its own. Treatment happens *at a station, in the
|
Flagging a patient for care does nothing on its own. Treatment happens *at a station, in the
|
||||||
patient's own room*. Without one, a patient flagged for care in an ordinary cell simply goes
|
patient's own room*. Without one, a patient flagged for care in an ordinary cell simply goes
|
||||||
untreated — which is the point of the neglect thought (Step 6).
|
untreated — which is the point of the neglect penalty (Step 6).
|
||||||
|
|
||||||
### `Ward_TreatmentStation` — the building
|
### The treatment station — the building
|
||||||
|
|
||||||
> *"A desk, a chair bolted to the floor, and a locked cabinet of sedatives. A warden set to
|
> *"A desk, a chair bolted to the floor, and a locked cabinet of sedatives. A warden set to
|
||||||
> psychiatric care will use it to counsel patients held in this room."*
|
> psychiatric care will use it to counsel patients held in this room."*
|
||||||
|
|
||||||
| Field | Value | Why |
|
| What | Value |
|
||||||
|---|---|---|
|
|---|---|
|
||||||
| `defName` | `Ward_TreatmentStation` | |
|
| Cost | **40 Steel + 2 medicine** — deliberately cheap |
|
||||||
| Parent | `BuildingBase` | Ordinary passable furniture |
|
| Build time | Quick |
|
||||||
| Cost | **40 Steel + 2 Industrial medicine** | Deliberately cheap |
|
| Research needed | **Medicine Production** — the one gate |
|
||||||
| `WorkToBuild` | `1600` | A quick build |
|
| Size | A two-tile desk; pawns squeeze past it, so it doesn't wall off a cell |
|
||||||
| `MaxHitPoints` | `120` | |
|
| Notes | It burns |
|
||||||
| `researchPrerequisites` | `MedicineProduction` | The one research gate |
|
|
||||||
| `size` | `(2,1)` | A two-tile desk |
|
|
||||||
| `graphicClass` | `Graphic_Single` | One texture, not a rotation set — nothing to keep in sync |
|
|
||||||
| `designationCategory` | `Misc` | |
|
|
||||||
| `passability` | `PassThroughOnly`, `pathCost 60` | Pawns squeeze past it; it doesn't wall a cell |
|
|
||||||
| `Flammability` | `1.0` | It burns |
|
|
||||||
|
|
||||||
**It's cheap on purpose.** The real cost of running a ward is not the 40 steel. It is the **warden
|
**It's cheap on purpose.** The real cost of running a ward is not the 40 steel. It's the **warden
|
||||||
hours** you spend counselling and the **labour you give up** by not putting the patient to work. The
|
hours** you spend counselling and the **labour you give up** by not putting the patient to work. The
|
||||||
building is just the gate that says "this room is set up to treat people."
|
building is just the gate that says "this room is set up to treat people."
|
||||||
|
|
||||||
|
And the locked cabinet isn't only flavour: a patient sliding toward a break faster than counselling
|
||||||
|
can steady them is the moment a **sedative** earns its keep — something to calm a patient who's close
|
||||||
|
to going over the edge.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 3 — The room becomes a ward
|
## Step 3 — The room becomes a ward
|
||||||
|
|
||||||
Once a treatment station stands in a room that already holds prisoner beds, the room's *role* changes
|
Once a treatment station stands in a room that already holds prisoner beds, the room's role changes
|
||||||
from prison cell to psychiatric ward.
|
from prison cell to **psychiatric ward**. Vanilla already has Prison Cell, Prison Barracks and
|
||||||
|
Hospital; the ward is the fourth sibling. Two things are worth knowing:
|
||||||
|
|
||||||
### `Ward_PsychWard` — the room role
|
- **It's gated on the station.** A room only reads as a ward if it actually contains a treatment
|
||||||
|
station *and* at least one prisoner bed. Without that, an ordinary prison cell full of beds keeps
|
||||||
| Field | Value |
|
its normal Prison Cell role, untouched. Build the station and it's a ward; don't and nothing
|
||||||
|---|---|
|
changes.
|
||||||
| Def type | `RoomRoleDef` (`workerClass` is a public field — no patch needed) |
|
- **It's still a prison cell.** Re-labelling the role does not release anyone. A ward is a prison cell
|
||||||
| `defName` | `Ward_PsychWard` |
|
*and* a ward at the same time, so containment, food delivery, and prison breaks all keep working.
|
||||||
| `label` | "psychiatric ward" |
|
|
||||||
| `workerClass` | `Ward.RoomRoleWorker_PsychWard` |
|
|
||||||
| `relatedStats` | Impressiveness, Cleanliness, Space |
|
|
||||||
|
|
||||||
Vanilla already ships `PrisonCell`, `PrisonBarracks` and `Hospital` on exactly this rail; the ward is
|
|
||||||
the fourth sibling. The scoring worker, `RoomRoleWorker_PsychWard.GetScore`, is where the care goes:
|
|
||||||
|
|
||||||
```
|
|
||||||
score = 0 if stations == 0 OR prisonerBeds == 0
|
|
||||||
score = 1_000_000 × prisonerBeds otherwise
|
|
||||||
```
|
|
||||||
|
|
||||||
Two design decisions are baked into that formula:
|
|
||||||
|
|
||||||
- **Gated on a station.** The score is **zero** unless the room actually contains a
|
|
||||||
`Ward_TreatmentStation` *and* at least one prisoner bed. This is deliberate: without the gate, any
|
|
||||||
ordinary prison cell full of prisoner beds would start scoring as a ward and quietly steal the
|
|
||||||
`PrisonCell` role from vanilla — a compatibility bug dressed up as a feature. Build the station and
|
|
||||||
it's a ward; don't and your cells keep their vanilla role, untouched.
|
|
||||||
- **1e6, not a hard-coded constant.** Vanilla's room roles score in the `~1e5` range. The ward must
|
|
||||||
*outscore* `PrisonCell` for the same room or it would read as a cell block. Multiplying by
|
|
||||||
1,000,000 clears that range without pinning to a magic number a future RimWorld patch might move.
|
|
||||||
(The harness's `FakeDLC` fixture deliberately adds a greedy competing role at `1e5 × beds`, and the
|
|
||||||
ward still wins the room.)
|
|
||||||
|
|
||||||
**Crucially, it is still a prison cell.** Re-labelling the role does not release anyone. `Room.isPrisonCell`
|
|
||||||
is a cached field set only when the room's shape changes — never derived from the role — so a ward is
|
|
||||||
a prison cell *and* a ward simultaneously. Containment, food delivery, and prison breaks all keep
|
|
||||||
working. This is the pair of claims the harness holds together: `room.roleIsWard = True` **and**
|
|
||||||
`room.stillPrisonCellAsWard = True`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 4 — The warden does the work
|
## Step 4 — The warden does the work
|
||||||
|
|
||||||
### `Ward_WardenPsychiatricCare` — the work giver
|
Counselling is **warden work**. Assign it the way you assign any warden task, and a warden will walk
|
||||||
|
over and treat the patient. A few rules govern when a session can start:
|
||||||
|
|
||||||
| Field | Value | Why |
|
- The warden needs to be able to **talk and hear** — a mute or deaf warden can't counsel.
|
||||||
|---|---|---|
|
- The patient must be **awake, not downed, and not mid-break** — you can't counsel someone who's
|
||||||
| `defName` | `Ward_WardenPsychiatricCare` | |
|
unconscious or already spiralling.
|
||||||
| `giverClass` | `Ward.WorkGiver_Warden_PsychiatricCare` | A **new** class extending `WorkGiver_Warden` |
|
- There must be a **treatment station in the patient's own room** (not out in the open). You can't
|
||||||
| `workType` | `Warden` | Assigned like any other warden work |
|
treat someone through a wall from a station two cells over.
|
||||||
| `verb` / `gerund` | "treat" / "treating" | |
|
- Its priority sits **above chatting** but **below feeding** — an untreated patient deteriorates, a
|
||||||
| `priorityInType` | `70` | Above chat (60), below feeding — an untreated patient deteriorates, a bored one doesn't |
|
bored one doesn't — so wardens won't skip meals to run a session.
|
||||||
| `requiredCapacities` | Talking, Hearing | A mute or deaf warden can't counsel |
|
|
||||||
|
|
||||||
The class extends `WorkGiver_Warden` and its `JobOnThing` refuses the job unless **all** of these
|
### A session
|
||||||
hold:
|
|
||||||
|
|
||||||
1. The pawn is a prisoner of the colony being taken care of (`ShouldTakeCareOfPrisoner`).
|
- **Length:** about **20 in-game minutes**, with a progress bar while it runs.
|
||||||
2. `IsInteractionEnabled(Ward_PsychiatricCare)` — asked, not "is this *the* mode," because the mode is
|
- It **fails out** if the patient despawns, is forbidden, falls asleep, or breaks partway through.
|
||||||
non-exclusive and the patient may also be set to recruit or forced labour.
|
- On completion it applies the treatment, gives the patient a "counselled" mood memory, runs a
|
||||||
3. The patient is **awake, not in a mental state, and not downed** — counselling someone mid-break or
|
deep-talk conversation (so the relationship builds and the *next* session lands harder), and awards
|
||||||
unconscious isn't counselling.
|
the warden **60 Social XP**.
|
||||||
4. The warden can reserve the patient.
|
|
||||||
5. A `Ward_TreatmentStation` exists **in the patient's own room** (not `PsychologicallyOutdoors`), is
|
|
||||||
reservable, and isn't forbidden. You can't treat someone through a wall from the station two
|
|
||||||
blocks over.
|
|
||||||
|
|
||||||
**Being a new class is the entire compatibility story** (see The Compat Harness for the proof):
|
Because psychiatric care is its own separate warden job rather than a rewrite of vanilla's, it doesn't
|
||||||
|
collide with mods that modify the built-in warden interactions — again, **Compatibility**.
|
||||||
- *Custom Prisoner Interactions* prefixes/postfixes the **named** vanilla warden givers (`_Chat`,
|
|
||||||
`_Convert`, `_Enslave`, `_ReleasePrisoner`). It literally cannot see `WorkGiver_Warden_PsychiatricCare`,
|
|
||||||
so it can't break it.
|
|
||||||
- *Prison Commons* postfixes `WorkGiver_Warden.ShouldSkip` on the **base** class. Ward's giver
|
|
||||||
inherits that method and doesn't override it, so Ward respects prison-commons areas **for free**,
|
|
||||||
without Ward knowing Prison Commons exists.
|
|
||||||
|
|
||||||
### `Ward_ProvidePsychiatricCare` — the job
|
|
||||||
|
|
||||||
| Field | Value |
|
|
||||||
|---|---|
|
|
||||||
| `defName` | `Ward_ProvidePsychiatricCare` |
|
|
||||||
| `driverClass` | `Ward.JobDriver_PsychiatricCare` |
|
|
||||||
| `casualInterruptible` | `false` |
|
|
||||||
|
|
||||||
> Note: the `JobDef` and the `PrisonerInteractionModeDef` deliberately have **different** defNames.
|
|
||||||
> `[DefOf]` binds fields to defs by name, so two defs sharing one name couldn't both be reached from
|
|
||||||
> the `WardDefOf` class.
|
|
||||||
|
|
||||||
The driver, `JobDriver_PsychiatricCare`, walks the warden to the patient and runs a counselling toil:
|
|
||||||
|
|
||||||
- **Session length:** `SessionTicks = 1200` — about **20 in-game minutes**.
|
|
||||||
- The wait toil uses `activeSkill = Social` and shows a progress bar; it fails out if the patient
|
|
||||||
despawns, is forbidden, falls asleep, or enters a mental state partway through.
|
|
||||||
- On completion it applies treatment, grants the counselled mood memory, fires a `DeepTalk` social
|
|
||||||
interaction (so the relationship builds and the *next* session lands harder), and awards **60 Social
|
|
||||||
XP** to the warden.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 5 — What treatment buys: `Ward_UnderTreatment`
|
## Step 5 — What treatment buys
|
||||||
|
|
||||||
Each completed session adds severity to a single hediff. The severity is **skill-scaled**:
|
Each completed session adds to the patient's treatment, and how much depends on the warden's **Social
|
||||||
|
skill**:
|
||||||
|
|
||||||
```
|
| Warden Social skill | Treatment gained per session |
|
||||||
gain = 0.20 + (wardenSocialLevel / 20) × 0.30 → range 0.20 .. 0.50 per session
|
|
||||||
```
|
|
||||||
|
|
||||||
| Warden Social skill | Severity gained per session |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| 0 | 0.20 |
|
| 0 | 0.20 |
|
||||||
| 5 | 0.275 |
|
| 5 | 0.275 |
|
||||||
@@ -205,100 +127,88 @@ gain = 0.20 + (wardenSocialLevel / 20) × 0.30 → range 0.20 .. 0.50 per
|
|||||||
| 15 | 0.425 |
|
| 15 | 0.425 |
|
||||||
| 20 | 0.50 |
|
| 20 | 0.50 |
|
||||||
|
|
||||||
The floor is deliberate: even an unskilled warden buys **0.20** per session. A bad ward is *neglect*,
|
The floor is deliberate: even an unskilled warden buys **0.20** a session. A bad ward is *neglect*,
|
||||||
not *zero* — a clumsy counsellor is still worth something. Severity is capped at `maxSeverity = 1.0`.
|
not *zero* — a clumsy counsellor is still worth something. Treatment tops out at **1.0**, a full
|
||||||
|
course.
|
||||||
|
|
||||||
### The hediff
|
**What it does:** while treatment is active it **lowers the patient's mental-break threshold by
|
||||||
|
0.10** — a lower threshold means they break at a worse mood, i.e. **break less often**.
|
||||||
|
|
||||||
| Field | Value | Why |
|
**Decay is the mechanic.** Treatment bleeds off at **0.15 per day**, so a full course fades back to
|
||||||
|---|---|---|
|
nothing in **about a week** if nobody keeps attending. A patient treated once and then ignored slides
|
||||||
| `defName` | `Ward_UnderTreatment` | |
|
right back. Sustained counselling keeps it topped up and the break threshold suppressed; stopping lets
|
||||||
| `hediffClass` | `HediffWithComps` | |
|
it fade. Treatment is a *programme*, not a one-time cure.
|
||||||
| `label` | "under psychiatric care" | |
|
|
||||||
| `isBad` | `false` | It's a good hediff; won't be treated as an injury |
|
|
||||||
| `scenarioCanAdd` | `false` | Can't be granted by scenario editor |
|
|
||||||
| `maxSeverity` | `1.0` | |
|
|
||||||
| **Decay** | `severityPerDay = −0.15` | Roughly a week from a full course back to nothing, unattended |
|
|
||||||
| **Effect** | `MentalBreakThreshold −0.10` (stage 0) | A *negative* offset means the pawn breaks at a **lower** mood — i.e. **breaks less often** |
|
|
||||||
|
|
||||||
**Decay is the mechanic.** Because severity bleeds off at 0.15/day, a patient treated once and then
|
> The −0.10 break-threshold figure is a first pass and not yet heavily playtested — expect it to be
|
||||||
ignored slides right back. Sustained treatment keeps the hediff topped up and the break threshold
|
> tuned.
|
||||||
suppressed; stopping lets it fade. Treatment is a *programme*, not a one-time cure. (The
|
|
||||||
`MentalBreakThreshold −0.10` value is a first-guess, not yet playtested — see the "unplaytested"
|
|
||||||
caveats in the README.)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 6 — The other half: neglect
|
## Step 6 — The other half: neglect
|
||||||
|
|
||||||
A ward you don't staff is not a neutral place to keep someone. It is a *worse* one. Without a cost,
|
A ward you don't staff is not a neutral place to keep someone. It's a *worse* one. Without a cost,
|
||||||
"arrest the sad colonist and park them" would be a free way to remove a problem pawn from play. It
|
"arrest the sad colonist and park them" would be a free way to remove a problem pawn from play. It
|
||||||
isn't free.
|
isn't free.
|
||||||
|
|
||||||
### `MapComponent_WardNeglect`
|
**Once per in-game day**, Ward checks every prisoner flagged for psychiatric care. Any patient who
|
||||||
|
isn't currently under treatment — meaning no warden has been near them in days — picks up a
|
||||||
|
**neglected** mood penalty. (The check keys off active treatment, and since treatment decays on its
|
||||||
|
own, its absence is the tell that the patient has genuinely been left alone, not merely that nobody
|
||||||
|
happens to be counselling them this exact minute.)
|
||||||
|
|
||||||
Once per **in-game day** (`CheckIntervalTicks = 60000`), this component scans every spawned prisoner
|
### The two moods
|
||||||
of the colony. For each prisoner flagged for `Ward_PsychiatricCare` who does **not** currently have a
|
|
||||||
`Ward_UnderTreatment` hediff, it grants the `Ward_Neglected` thought.
|
|
||||||
|
|
||||||
The absence of the treatment hediff is the signal *by design*: because the hediff decays on its own,
|
| Mood | Value | Duration | Stacks to | Meaning |
|
||||||
its absence means **no warden has been near this patient in days** — not merely that they aren't being
|
|
||||||
counselled at this exact instant.
|
|
||||||
|
|
||||||
### The two thoughts
|
|
||||||
|
|
||||||
| Thought | Mood | Duration | Stack | Meaning |
|
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `Ward_Counselled` | **+6** | 2 days | up to 3 | "Someone sat with me and listened. It helped, a little." |
|
| **Counselled** | **+6** | 2 days | 3 | "Someone sat with me and listened. It helped, a little." |
|
||||||
| `Ward_Neglected` | **−8** | 3 days | up to 4 | "They locked me in here for my own good and then forgot about me." |
|
| **Neglected** | **−8** | 3 days | 4 | "They locked me in here for my own good and then forgot about me." |
|
||||||
|
|
||||||
Neglect is asymmetric — a −8 penalty stacking four deep (−32) badly outweighs three counselling lifts
|
Neglect is **asymmetric** — a −8 penalty stacking four deep (−32) badly outweighs three counselling
|
||||||
(+18). That's intentional: a neglected patient's mood falls, which raises their break risk, which is
|
lifts (+18). That's intentional: a neglected patient's mood falls, which raises their break risk,
|
||||||
exactly the crisis you committed them to avoid. The ward can *manufacture* the break it was built to
|
which is exactly the crisis you committed them to avoid. A ward can *manufacture* the break it was
|
||||||
prevent. Staff it, or don't build it.
|
built to prevent. Staff it, or don't build it.
|
||||||
|
|
||||||
> **Known rough edge (from the README's "Open" list):** neglect currently keys off "no
|
> **Known rough edge:** neglect keys off "not currently under treatment," so a patient treated once a
|
||||||
> `Ward_UnderTreatment` hediff." A patient treated once a week technically dodges the neglect thought
|
> week can technically dodge the penalty on the single day their treatment runs out. It's a documented
|
||||||
> on the single day the hediff expires. A real last-treated timestamp would be cleaner. Documented,
|
> quirk, not yet smoothed.
|
||||||
> not yet fixed.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How it stacks with recruitment (and everything else)
|
## How it stacks with recruitment (and everything else)
|
||||||
|
|
||||||
Because `Ward_PsychiatricCare` is non-exclusive, it layers onto whatever else you've set:
|
Because psychiatric care is a non-exclusive toggle, it layers onto whatever else you've set:
|
||||||
|
|
||||||
| You want to… | Set | Result |
|
| You want to… | Set | Result |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Just stabilise a broken colonist | Psychiatric care | Wardens counsel; break threshold drops |
|
| Just stabilise a broken colonist | Psychiatric care | Wardens counsel; break threshold drops |
|
||||||
| Talk a captured raider down *and* recruit them | Psychiatric care **+** Attempt recruit | Both run — counselling lifts mood while resistance is chipped |
|
| Talk a captured raider down *and* recruit them | Psychiatric care **+** Attempt recruit | Both run — counselling lifts mood while resistance is chipped |
|
||||||
| Convert *and* treat | Psychiatric care **+** convert | Both toggle on |
|
| Convert *and* treat | Psychiatric care **+** convert | Both toggle on |
|
||||||
| Work a patient *and* treat them | Psychiatric care **+** Prison Labor's work mode | Both apply. Working a fragile patient is bleak — and it's your call to make |
|
| Work a patient *and* treat them | Psychiatric care **+** Prison Labor's work mode | Both apply. Working a fragile patient is bleak — and it's your call |
|
||||||
|
|
||||||
The last row is the darkest option the mod exposes, and it exposes it on purpose: Ward doesn't
|
The last row is the darkest option the mod exposes, and it exposes it on purpose: Ward doesn't forbid
|
||||||
forbid working a patient, it just makes the trade-off visible.
|
working a patient, it just makes the trade-off visible.
|
||||||
|
|
||||||
Because a ward patient is a genuine vanilla prisoner, everything the wider Institution suite does to
|
Because a ward patient is a genuine vanilla prisoner, everything the wider Institution suite does to
|
||||||
prisoners applies to them unchanged — a committed patient can be classified, disciplined, paroled,
|
prisoners applies to them unchanged — a committed patient can be classified, disciplined, paroled,
|
||||||
searched, and can conceal or improvise contraband. Ward writes no integration code for any of that;
|
searched, and can conceal or improvise contraband. See **Home** for the suite map and **Compatibility**
|
||||||
the patient satisfies "held pawn" and the rest follows. See **Home** for the suite map and **The
|
for how Ward stays out of other mods' way.
|
||||||
Compat Harness** for how all of it is verified in one running game.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick reference — every Ward def
|
## Quick reference — the numbers
|
||||||
|
|
||||||
| defName | Type | Class (if any) | Key numbers |
|
| Thing | Number |
|
||||||
|---|---|---|---|
|
|---|---|
|
||||||
| `Ward_PsychiatricCare` | PrisonerInteractionModeDef | — | non-exclusive; listOrder 150 |
|
| Treatment station cost | 40 steel + 2 medicine; needs Medicine Production research |
|
||||||
| `Ward_TreatmentStation` | ThingDef | `Building` | 40 steel + 2 medicine; WorkToBuild 1600; MedicineProduction research |
|
| Counselling session | ~20 in-game minutes |
|
||||||
| `Ward_PsychWard` | RoomRoleDef | `RoomRoleWorker_PsychWard` | score = 1e6 × prisonerBeds (station-gated) |
|
| Treatment per session | 0.20 (unskilled) → 0.50 (Social 20) |
|
||||||
| `Ward_WardenPsychiatricCare` | WorkGiverDef | `WorkGiver_Warden_PsychiatricCare` | priority 70; Talking + Hearing |
|
| Treatment cap | 1.0 (a full course) |
|
||||||
| `Ward_ProvidePsychiatricCare` | JobDef | `JobDriver_PsychiatricCare` | 1200-tick session; +60 Social XP |
|
| Treatment decay | −0.15/day (~a week from full to nothing) |
|
||||||
| `Ward_UnderTreatment` | HediffDef | `HediffWithComps` | +0.20–0.50/session; −0.15/day decay; −0.10 break threshold |
|
| Break-threshold effect | −0.10 while treated (breaks less often) |
|
||||||
| `Ward_Counselled` | ThoughtDef | `Thought_Memory` | +6 mood, 2 days, stack 3 |
|
| Counselled mood | +6, 2 days, stacks to 3 |
|
||||||
| `Ward_Neglected` | ThoughtDef | `Thought_Memory` | −8 mood, 3 days, stack 4 |
|
| Neglected mood | −8, 3 days, stacks to 4 |
|
||||||
| — | MapComponent | `MapComponent_WardNeglect` | daily scan (60000 ticks) |
|
| Neglect check | once per in-game day |
|
||||||
|
| Warden reward | 60 Social XP per session |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -309,4 +219,3 @@ Institution: Ward is one mod in the **Institution** suite of RimWorld 1.6 mods
|
|||||||
and **Foul Play**. Each stands alone; together they interlock. A committed patient rides the same
|
and **Foul Play**. Each stands alone; together they interlock. A committed patient rides the same
|
||||||
prisoner rail the rest of the suite polices, so a psychiatric ward is also a secure context where a
|
prisoner rail the rest of the suite polices, so a psychiatric ward is also a secure context where a
|
||||||
patient can conceal and improvise contraband exactly as a prisoner can.
|
patient can conceal and improvise contraband exactly as a prisoner can.
|
||||||
|
|
||||||
|
|||||||
+31
-50
@@ -16,66 +16,48 @@ as though nothing happened. Mental breaks are episodic and amnesiac — nothing
|
|||||||
Ward adds the missing state, and the room to put it in. A colonist you arrest can be set to
|
Ward adds the missing state, and the room to put it in. A colonist you arrest can be set to
|
||||||
**psychiatric care**. Wardens counsel them at a **treatment station**. A room with prisoner beds
|
**psychiatric care**. Wardens counsel them at a **treatment station**. A room with prisoner beds
|
||||||
and a treatment station is a **psychiatric ward**, not a prison cell. Treatment lowers the patient's
|
and a treatment station is a **psychiatric ward**, not a prison cell. Treatment lowers the patient's
|
||||||
mental-break threshold while it lasts — and it decays, so it has to be *sustained*. A ward you don't
|
mental-break threshold while it lasts — and it fades, so it has to be *sustained*. A ward you don't
|
||||||
staff is worse than no ward at all.
|
staff is worse than no ward at all.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The third state, without a fourth enum slot
|
## The third state RimWorld doesn't have
|
||||||
|
|
||||||
The reason "commit a colonist" isn't a stock feature is a single compiled fact. `RimWorld.GuestStatus`
|
The reason "commit a colonist" isn't a stock button is simple: vanilla only knows two kinds of held
|
||||||
is a three-value enum:
|
person — a free colonist and a prisoner. There's no built-in slot for someone who is neither free nor
|
||||||
|
guilty, which is why other mods that keep a not-quite-colonist around (like *Hospitality* and its
|
||||||
|
guests) have to work so hard to maintain them.
|
||||||
|
|
||||||
```
|
**Ward takes the cheap road.** A committed pawn *is* a prisoner — arresting your own colonist already
|
||||||
Guest = 0
|
does that. Ward just adds a new **way to handle** that prisoner: a "psychiatric care" mode you toggle
|
||||||
Prisoner = 1
|
on, sitting alongside vanilla's recruit / convert / release options. Nothing exotic — it's the same
|
||||||
Slave = 2
|
kind of custom prisoner mode other prison mods already add.
|
||||||
```
|
|
||||||
|
|
||||||
A mod **cannot add a fourth value** to a compiled enum. That one constraint drives every design
|
So committing someone is entirely ordinary from the game's point of view:
|
||||||
decision in Ward — and in the rest of the ecosystem. It is why *Hospitality* carries dozens of
|
|
||||||
Harmony patches to maintain a pawn who is in your colony but is not your colonist: the enum has no
|
|
||||||
slot for one, so it runs a shadow guest system and defends it by patching bed validity, allowed
|
|
||||||
areas, the work JobGiver and the mental-state handler.
|
|
||||||
|
|
||||||
**Ward doesn't pay that tax.** A committed pawn *is* a prisoner, in vanilla's own sense of the word —
|
- **psychiatric care** — a new prisoner interaction mode you switch on
|
||||||
arresting your own colonist already performs the colonist→prisoner transition. Ward only adds a new
|
- **the psychiatric ward** — a new room role a cell earns once it holds a treatment station
|
||||||
**mode** to hold them under, and `PrisonerInteractionModeDef` is a **Def, not an enum**. Adding one
|
- **counselling** — a new warden job, handled like any other warden work
|
||||||
is XML plus, at most, a worker class. This is not a trick; it is the established pattern. *Prison
|
- the **treatment station**, the **recovery**, the **neglect** — new content layered on top
|
||||||
Labor* already ships seven custom interaction modes of its own.
|
|
||||||
|
|
||||||
So the *state itself* costs **zero patches** — it is all defs:
|
Because Ward *adds* things rather than rewriting how vanilla prisoners behave, it sits quietly next to
|
||||||
|
the popular psych and prison mods instead of fighting them. Which mods, and why, is on the
|
||||||
| Extension point | What Ward adds | Patches needed |
|
**Compatibility** page.
|
||||||
|---|---|---|
|
|
||||||
| `PrisonerInteractionModeDef` | `Ward_PsychiatricCare` (non-exclusive) | 0 |
|
|
||||||
| `RoomRoleDef` (`workerClass` is public) | `Ward_PsychWard` | 0 |
|
|
||||||
| `WorkGiverDef` (`giverClass`) | `Ward_WardenPsychiatricCare` | 0 |
|
|
||||||
| `JobDef`, `HediffDef`, `ThoughtDef`, `ThingDef` | treatment, recovery, neglect, the station | 0 |
|
|
||||||
|
|
||||||
Ward *does* use Harmony where a patch is the cleaner tool (a patient's inspect line, and more), so it
|
|
||||||
is not a zero-patch mod. Its compatibility is about **which** methods it avoids: it touches none of
|
|
||||||
the contested methods the popular psych/prison mods fight over, and never prefixes
|
|
||||||
`MentalStateHandler.TryStartMentalState`. The full argument — which mods it was verified against and
|
|
||||||
why it can't collide with them — is on **The Compat Harness** page.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Riding the prisoner rail (why the suite cares)
|
## Riding the prisoner rail (why the suite cares)
|
||||||
|
|
||||||
The design choice that makes Ward *cheap* also makes it *interesting* inside the wider Institution
|
The choice that makes Ward *cheap* also makes it *interesting* inside the wider Institution suite. A
|
||||||
suite. A committed patient is a genuine vanilla prisoner. That means they are a **secure context** in
|
committed patient is a genuine vanilla prisoner. That means they're a **held pawn** in exactly the
|
||||||
exactly the sense the rest of the suite understands: a pawn who is held, searchable, and capable of
|
sense the rest of the suite understands: someone who can be searched, and who can conceal and
|
||||||
concealing and improvising contraband. A patient in a psych ward can hoard a shiv or brew something
|
improvise contraband. A patient in a psych ward can hoard a shiv or brew something foul in a smuggled
|
||||||
foul in a smuggled vessel for **free**, because the contraband, search, corruption and gang systems
|
vessel, because the contraband, search, corruption and gang systems already work on any held
|
||||||
were written against "held pawn," and a ward patient satisfies that predicate without Ward writing a
|
prisoner — and a ward patient is one, for free.
|
||||||
line of integration code.
|
|
||||||
|
|
||||||
The ward is a prison cell that happens to also be a ward. `Room.isPrisonCell` is a cached field
|
**And re-labelling a cell as a ward never lets anyone out.** A ward is a prison cell that also happens
|
||||||
written only when the room's shape changes — it is **not** derived from the room's role — so
|
to be a ward — the two roles coexist. Containment, food delivery, and prison breaks all keep working
|
||||||
re-labelling a cell as a ward cannot break containment, food delivery, or prison breaks. The compat
|
exactly as they did before you built the station.
|
||||||
harness asserts exactly this: a built ward is both `roleIsWard = True` **and** `stillPrisonCellAsWard
|
|
||||||
= True`. The two claims pull against each other, and both have to hold.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -84,8 +66,8 @@ harness asserts exactly this: a built ward is both `roleIsWard = True` **and** `
|
|||||||
| Page | What's on it |
|
| Page | What's on it |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Home** (this page) | What Ward is, the third-state problem, the suite |
|
| **Home** (this page) | What Ward is, the third-state problem, the suite |
|
||||||
| **Commitment** | How commitment works in play: the mode, station, room role, work giver, job, hediff, thoughts, neglect, and how it stacks with recruitment — with every real def name and number |
|
| **Commitment** | How commitment works in play: the care mode, the station, the ward room, the warden's job, treatment, recovery, neglect, and how it stacks with recruitment — with the real numbers |
|
||||||
| **The Compat Harness** | The in-game test tool this repo carries: booting real headless RimWorld with the whole suite + the most-subscribed Workshop mods (~24 active), the dependency-ordered build, the `WARDTEST` / `PNTEST` / `CBTEST` / `FPBRIDGE` assertion families, and how to run it |
|
| **Compatibility** | Which popular psych and prison mods Ward plays nicely with, and why it doesn't fight them |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -99,14 +81,13 @@ conceal and improvise contraband exactly as a prisoner can, because they ride th
|
|||||||
|
|
||||||
| Mod | What it is |
|
| Mod | What it is |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Institution: Ward** (this) | Involuntary psychiatric commitment — the third pawn state, neither free nor guilty. Also carries the suite's in-game compat harness. |
|
| **Institution: Ward** (this) | Involuntary psychiatric commitment — the third pawn state, neither free nor guilty. |
|
||||||
| **Institution: Core** | The shared engine — propensity (nature × nurture), the criminal record, secured context. |
|
| **Institution: Core** | The shared engine — propensity (nature × nurture), the criminal record, secured context. |
|
||||||
| **Institution: Contraband** | The physical smuggling loop — conceal, improvise shivs, dig Prison-Architect tunnels, warden search, warden corruption. |
|
| **Institution: Contraband** | The physical smuggling loop — conceal, improvise shivs, dig Prison-Architect tunnels, warden search, warden corruption. |
|
||||||
| **Institution: Justice** | Corrections & policing — classification, deterrence, discipline, parole, regime. |
|
| **Institution: Justice** | Corrections & policing — classification, deterrence, discipline, parole, regime. |
|
||||||
| **Institution: Gangs** | Gangs as contraband economies — joining, smuggling networks, rivalry, fights-as-crime. Also the home of the suite's cross-module `CBTEST` integration test. |
|
| **Institution: Gangs** | Gangs as contraband economies — joining, smuggling networks, rivalry, fights-as-crime. |
|
||||||
| **Foul Play** | The vessel + substance + throw framework, and the "Piss Nuke" flagship; bridges to Core + Contraband when both are present. |
|
| **Foul Play** | The vessel + substance + throw framework, and the "Piss Nuke" flagship; bridges to Core + Contraband when both are present. |
|
||||||
|
|
||||||
Reference sibling mods by name — the mods are separate repositories and cross-repo links break.
|
Reference sibling mods by name — the mods are separate repositories and cross-repo links break.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+26
-271
@@ -1,281 +1,36 @@
|
|||||||
# The Compat Harness
|
# Compatibility
|
||||||
|
|
||||||
`Tools/run-compat-test.sh` is not a unit test. It is a **documented tool** that boots a *real,
|
Ward is built to sit alongside your existing mod list rather than elbow it aside. The trick is that
|
||||||
headless RimWorld* with the entire Institution suite and the most-subscribed relevant Workshop mods
|
Ward mostly **adds** things — a new "psychiatric care" prisoner mode, a new building, a new room role,
|
||||||
loaded **at once**, plays the game at maximum speed for a few thousand ticks, and greps the log for
|
new jobs — instead of rewriting how vanilla prisoners already behave. And a committed patient isn't a
|
||||||
assertions that the mods it built actually work — and don't silently eat each other.
|
new kind of pawn: they're an ordinary vanilla prisoner with an extra "treat me" flag switched on. So
|
||||||
|
any mod that already works with prisoners keeps working with a ward patient, and Ward stays out of its
|
||||||
|
way.
|
||||||
|
|
||||||
Ward carries this harness for the whole suite because Ward is where the compatibility argument lives:
|
Here's how Ward gets along with the popular psych and prison mods:
|
||||||
Ward's central claim is "I add defs, I patch nothing, so I can't collide with your mod list," and a
|
|
||||||
claim like that can only be *believed* from static analysis. It has to be *run* to be proven.
|
|
||||||
|
|
||||||
---
|
| Mod | How it coexists with Ward |
|
||||||
|
|
||||||
## Why a running game, not static analysis
|
|
||||||
|
|
||||||
Static analysis got the design most of the way. Dumping the Harmony attributes out of every mod
|
|
||||||
assembly shows **where** two mods want to patch the same vanilla method. What it cannot show is
|
|
||||||
whether that collision actually **bites**:
|
|
||||||
|
|
||||||
- Two mods can both postfix a method and be completely fine.
|
|
||||||
- Or one mod's **prefix returns `false`**, which short-circuits the original method *and every other
|
|
||||||
prefix* — silently eating another mod's patch. **Nothing crashes.** A feature just quietly stops
|
|
||||||
working.
|
|
||||||
|
|
||||||
The canonical example the harness guards: *Hospitality* prefixes
|
|
||||||
`MentalStateHandler.TryStartMentalState`. If Ward ever added a second prefix there that returned
|
|
||||||
`false`, Hospitality's would silently never run, and there would be no error to notice. So the harness
|
|
||||||
doesn't read code — it interrogates the **live Harmony patch table inside a running RimWorld** and
|
|
||||||
asserts who owns each contested method.
|
|
||||||
|
|
||||||
The same failure mode, in a different shape, threatens Foul Play: the "Piss Nuke" psychotic-spree
|
|
||||||
feature is delivered by **XPath PatchOperations** into `MentalStateNonCritical` (and the escape/duty
|
|
||||||
defs). If another mod reshapes those defs, the XPath silently stops matching, the feature dies, and —
|
|
||||||
again — nothing errors. Only booting the whole stack and watching the spree actually happen catches
|
|
||||||
it.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What it loads (~24 active mods)
|
|
||||||
|
|
||||||
The full-stack run activates the base game + three DLC, two libraries, the popular psych/prison
|
|
||||||
Workshop mods, and the whole Institution suite — **24 active mods** by default, **25** with the
|
|
||||||
`FakeDLC` fixture switched on.
|
|
||||||
|
|
||||||
| Group | Mods |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| Base + DLC | RimWorld Core, Royalty, Ideology, Biotech |
|
| **Hospitality** | Hospitality manages *guests*; Ward manages *prisoners*. They ride two completely separate rails and never touch. |
|
||||||
| Libraries | Harmony, HugsLib |
|
| **Prison Labor** | Psychiatric care is a toggle, so you can put a patient **to work *and* in treatment at the same time**. No conflict, no either/or. |
|
||||||
| Guest/prisoner rail | Hospitality, Prison Labor, Locks, Prison Commons, Custom Prisoner Interactions, Prisoner Realism, Prisoner Recreation |
|
| **Custom Prisoner Interactions** | That mod tweaks the built-in warden interactions (chat, convert, enslave, release). Ward's counselling is a *separate* job of its own, so the two don't overlap. |
|
||||||
| Mental health | Psychology (unofficial), Rim Disorders, More Mental Breaks, Restraints |
|
| **Prison Commons** | Ward's warden work automatically respects prison-commons areas — you get that for free, without either mod knowing about the other. |
|
||||||
| The big neighbour | Dubs Bad Hygiene (632k subscribers — the one Foul Play deliberately does *not* duplicate) |
|
| **Psychology (unofficial) · Rim Disorders · More Mental Breaks** | These reshape the mental-break and mental-illness machinery. Ward doesn't touch that machinery — it just nudges the break *threshold* the way a mood effect would — so they all stack. |
|
||||||
| Institution suite | Core, Contraband, Justice, Gangs, Foul Play, **Ward** |
|
| **Prisoner Realism** | It changes how the game decides what counts as a prison cell. Ward's ward role sits on top of that without breaking containment — a ward is still a prison cell. |
|
||||||
| Optional fixture | FakeDLC (stands in for Anomaly/Odyssey; only with `FAKE_DLC=1`) |
|
| **Locks · Restraints · Prisoner Recreation** | All fine. They touch prisoner behaviours Ward leaves alone. |
|
||||||
|
| **Dubs Bad Hygiene** | No overlap at all. |
|
||||||
|
| **Royalty · Ideology · Biotech** | Ward works with or without the DLC. Psychiatric care is available even if you don't run Ideology's ideoligions. |
|
||||||
|
|
||||||
The Workshop mods are read from a persistent, ID-keyed cache (default
|
The general rule: because Ward *adds* rather than *overrides*, it should also coexist with prison and
|
||||||
`/home/dev/rimworld-ref/mods-cache`, keyed by Workshop file id). The cache lives on the home
|
psych mods that aren't on this list. If you ever find one it genuinely fights, that's a bug worth
|
||||||
partition, not `/tmp`, because a scratchpad reap once took the whole stack down with it. Rebuild it
|
reporting — Ward's whole design is built around not doing that.
|
||||||
from an installed run with `Tools/rebuild-mods-cache.sh`.
|
|
||||||
|
|
||||||
Two sharp compat targets in that list are worth calling out, because they are exactly the ones that
|
|
||||||
*could* break Ward:
|
|
||||||
|
|
||||||
- **Prisoner Realism** postfixes `Verse.Room.IsPrisonCell` — and Ward introduces a room role that can
|
|
||||||
outrank `PrisonCell`. Statically, `isPrisonCell` is a cached field written only by
|
|
||||||
`Notify_RoomShapeChanged` and is **not** derived from the role, so Ward can't break it. The harness
|
|
||||||
proves that at runtime, on a room it builds and re-roles live.
|
|
||||||
- **Hospitality** owns the whole "a pawn in the colony who is not a colonist" surface, including that
|
|
||||||
`TryStartMentalState` prefix. Ward rides the *prisoner* rail; Hospitality rides the *guest* rail;
|
|
||||||
the harness asserts they never touch.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## The dependency-ordered build
|
|
||||||
|
|
||||||
The Institution suite is split across separate assemblies with real cross-references, so they must
|
|
||||||
**build** and **load** in dependency order or the references won't resolve. Core is the dependency-free
|
|
||||||
leaf everything else reads.
|
|
||||||
|
|
||||||
### Build order (what the script compiles, and why)
|
|
||||||
|
|
||||||
| # | Built | Depends on (to compile) | SelfTest? | Notes |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| 1 | **Institution: Core** | base game only | no | The propensity / criminal-record / secured-context engine. Built even in Core-only, because everything below references its DLL. |
|
|
||||||
| 2 | **Institution: Justice** | Core | no | Classification, deterrence, discipline, parole, regime. Built even in Core-only (the bridge references it). |
|
|
||||||
| 3 | **Institution: Contraband** | Core | no | Concealment, search, tunnels. Builds **clean** now — see "Where CBTEST lives" below. |
|
|
||||||
| 4 | **Foul Play** (+ bridge) | Core, Contraband, its own DLL | **yes** (`PNTEST`) | The vessel/substance/throw framework and the Piss Nuke flagship. Its optional Contraband **bridge** is compiled against the Core/Contraband/FoulPlay DLLs. |
|
|
||||||
| 5 | **Institution: Gangs** | Core, Contraband, Justice | **yes** (`CBTEST`) | The social capstone — the only assembly that references *every* other Institution mod, which is why the cross-module integration harness lives here. |
|
|
||||||
| 6 | **Institution: Ward** | base game only (harness: + Harmony) | **separate assembly** (`WARDTEST`) | Ship `Ward.dll` carries no harness (Harmony ships as a normal dependency; the harness does not). The `WARDTEST` harness is its own project, `Source/Ward.CompatTest` → `Ward.CompatTest.dll`, built to a scratch dir and dropped beside the ship DLL at test time. |
|
|
||||||
|
|
||||||
### Load order (the `activeMods` list)
|
|
||||||
|
|
||||||
Harmony first (it says so itself), then the base game + DLC, then HugsLib, then the Workshop mods,
|
|
||||||
then the Institution suite **in dependency order**, and **Ward dead last**:
|
|
||||||
|
|
||||||
```
|
|
||||||
Core → Contraband → Justice → Foul Play → Gangs → Ward
|
|
||||||
```
|
|
||||||
|
|
||||||
Contraband and Justice each need only Core (their relative order doesn't matter). Foul Play's bridge
|
|
||||||
needs Contraband **active**, so Foul Play loads after it. Gangs is the capstone and needs Core +
|
|
||||||
Contraband + Justice. **Ward loads last on purpose** — its Harmony conflict report reads the live
|
|
||||||
patch table, so every other mod must have had its chance to patch *before* Ward looks.
|
|
||||||
|
|
||||||
The script self-checks the config, not just the mod: every Workshop mod copied into `Mods/` has its
|
|
||||||
real `packageId` cross-checked against `activeMods`, because copying a mod in without activating it
|
|
||||||
would silently shrink the stack and still pass. It also lints every def XML first (`Tools/lint-xml.py`) —
|
|
||||||
a malformed comment makes RimWorld drop a def *silently*, and that's not worth a 15-minute headless
|
|
||||||
run to discover.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Where CBTEST lives now (Gangs, SelfTest)
|
|
||||||
|
|
||||||
The suite's cross-module integration test — the `CBTEST` family, which drives the Prison-Architect
|
|
||||||
tunnel dig→search→breach loop and the full Core/Justice/Gangs interaction — used to live in
|
|
||||||
Contraband. It **moved into Gangs**, because Gangs is the one assembly that already references every
|
|
||||||
Institution mod (Core, Contraband, Justice). Consequences:
|
|
||||||
|
|
||||||
- **Contraband now builds clean** — no `SelfTest`, no test code in the shipped `Contraband.dll`.
|
|
||||||
- **Gangs** is built with `-p:SelfTest=true`, which compiles its integration `GameComponent` in. That
|
|
||||||
build leaves test code in `Assemblies/InstitutionGangs.dll`, so the Gangs repo must be rebuilt
|
|
||||||
clean before its shipped DLL is committed.
|
|
||||||
- **Foul Play** is likewise built `-p:SelfTest=true` for its own `PNTEST` harness, so its DLL must
|
|
||||||
be rebuilt clean before it is shipped.
|
|
||||||
- **Ward** no longer works that way. Its harness is a **separate assembly**
|
|
||||||
(`Source/Ward.CompatTest` → `Ward.CompatTest.dll`), never compiled into `Ward.dll`. There is no
|
|
||||||
`SelfTest` flag on the ship project and nothing to "rebuild clean" — a build of `Ward.csproj` is
|
|
||||||
physically incapable of emitting the harness or a Harmony reference. `run-compat-test.sh` builds
|
|
||||||
the harness to a scratch dir and installs `Ward.CompatTest.dll` beside the clean ship DLL, where
|
|
||||||
RimWorld loads both. A CI guard additionally fails any push whose committed `Ward.dll` carries
|
|
||||||
`HarmonyLib`/`CompatTest` symbols. (Gangs and Foul Play still rely on the rebuild-clean
|
|
||||||
discipline; Ward removed that failure mode outright after a SelfTest `Ward.dll` shipped by
|
|
||||||
accident.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## The assertion families
|
|
||||||
|
|
||||||
Everything is written to the log with a family tag, and the verdict section greps for each one. A run
|
|
||||||
with no `WARDTEST` lines at all is a hard failure ("NO ASSERTIONS RAN").
|
|
||||||
|
|
||||||
| Tag | Owner | What it proves |
|
|
||||||
|---|---|---|
|
|
||||||
| **`WARDTEST`** | Ward (`Source/Ward.CompatTest/CompatTest.cs`) | Ward loaded, patches no *contested* method, and works on a live prisoner |
|
|
||||||
| **`PNTEST`** | Foul Play self-test | The Piss Nuke / vessel / substance / ferment content works under the stack |
|
|
||||||
| **`FPBRIDGE`** | Foul Play ↔ Contraband bridge | The optional bridge loaded (and *only* when Contraband is present) and merged the two frameworks |
|
|
||||||
| **`CBTEST`** | Gangs self-test | The whole Institution loop — tunnel, search, classification, discipline, parole, corruption, gangs, regime — runs end-to-end on a live map |
|
|
||||||
| *(`PNCOMBAT`)* | Foul Play | Grepped alongside PNTEST for the combat/throw path |
|
|
||||||
|
|
||||||
### `WARDTEST` — Ward's own checks
|
|
||||||
|
|
||||||
Split between a `[StaticConstructorOnStartup]` (static def checks, "did Ward *load*") and a
|
|
||||||
`GameComponent` that runs at tick 60+ (the live checks, because the static-ctor order between mods is
|
|
||||||
undefined and Contraband injects its escape JobGivers from its *own* static ctor):
|
|
||||||
|
|
||||||
- **Def load:** `def.interactionMode` (and `.nonExclusive`), `def.roomRole` (and `.workerResolves`),
|
|
||||||
`def.workGiver` (and `.classResolves`), `def.job`, `def.hediff`, `def.station`,
|
|
||||||
`station.graphicLoaded` (a missing texture is a magenta box and a log line, not a crash — so it's
|
|
||||||
checked on purpose).
|
|
||||||
- **Vanilla intact:** `vanilla.prisonerModes.intact` — `MaintainOnly`, `AttemptRecruit`,
|
|
||||||
`ReduceResistance`, `Release`, `Execution` all still exist (a defName collision would let the game
|
|
||||||
boot while quietly removing your ability to execute or release a prisoner).
|
|
||||||
- **The central claim:** `ward.patchesNoContestedMethod` — the harness enumerates every vanilla method
|
|
||||||
where a psych/prison mod *could* collide (`SetGuestStatus`, `IsValidBedFor`, `InAllowedArea`,
|
|
||||||
`TryStartMentalState`, `MentalBreaker.TryDoRandomMoodCausedMentalBreak`, `WorkGiver_Warden.ShouldSkip`,
|
|
||||||
`WorkGiver_Warden_Chat.JobOnThing`, …), reads the live Harmony patch owners, and asserts **none of
|
|
||||||
them is owned by Ward.** The moment Ward grows a Harmony patch on a contested method, this fails.
|
|
||||||
- **The specific one:** `ward.noPrefixOn.TryStartMentalState` — Ward must never add a *prefix* there,
|
|
||||||
or it silently eats Hospitality's.
|
|
||||||
- **Live usability:** `live.isPrisoner`, `live.psychCareEnabled`, `live.stacksWithRecruit` — a real
|
|
||||||
prisoner is generated, flagged for care via vanilla's *own* public API, and confirmed to stack with
|
|
||||||
`AttemptRecruit`, with Prison Labor / Custom Prisoner Interactions / Hospitality all loaded and
|
|
||||||
patching the guest tracker.
|
|
||||||
- **The built ward:** the component builds an actual walled, roofed room with a prisoner bed and a
|
|
||||||
station on the live map, forces a region/room rebuild, and asserts `room.proper`,
|
|
||||||
`room.plainCellNotStolen` (a bare cell keeps its vanilla role), `room.roleIsWard`, and — the
|
|
||||||
load-bearing one — `room.stillPrisonCellAsWard`.
|
|
||||||
- **Escape-tree order (`escape.armBeforeExit`, `escape.useContrabandBeforeExit`):** Ward also verifies
|
|
||||||
that Contraband's escape JobGivers land *before* `JobGiver_GotoTravelDestination` in the
|
|
||||||
`PrisonerEscape` think tree. A think node that *landed* is not a think node that can *run* — the tree
|
|
||||||
takes the first node that returns a job, and the travel-destination node returns one essentially
|
|
||||||
always, so anything appended after it is dead code that would pass a mere presence test. The harness
|
|
||||||
asserts **position**, because pre-order index is evaluation order.
|
|
||||||
- Terminated by `WARDTEST DONE`.
|
|
||||||
|
|
||||||
### `PNTEST` / `FPBRIDGE` — Foul Play
|
|
||||||
|
|
||||||
The Foul Play self-test proves the flagship content survives the stack: the carboy weapon def loads
|
|
||||||
(`def.weapon`), the spree think-tree XPath still matches (`thinktree.patched`), and the generic-vessel
|
|
||||||
refactor holds — runtime substance, label-shows-contents, the eight-substance catalogue, drink
|
|
||||||
consequences, mix ratios, pour capacity, water-douses-fire, blend round-trip through save/load,
|
|
||||||
fermentation dilution and spoilage, "only 100% ripe piss reads as a nuke," any pawn filling a vessel,
|
|
||||||
furniture dip/pour, and the autonomous cocktail on fill. The `FPBRIDGE` family fires only when
|
|
||||||
Contraband is present, and proves the optional bridge wired the carboy's brew-from-body worker, merged
|
|
||||||
the two propensity engines, and wired the content-tag delegates so a concealed vessel keeps its
|
|
||||||
substance.
|
|
||||||
|
|
||||||
### `CBTEST` — the suite integration test (in Gangs)
|
|
||||||
|
|
||||||
When Contraband is active, the Gangs self-test drives the entire loop on a live map and every one of
|
|
||||||
these must report `True`: `escape.registered`, `escape.foundWillingDigger`, `escape.digRatePositive`,
|
|
||||||
`escape.tickAdvancesTunnel`, `escape.recordsAttempt`, `escape.breachDestroysWall`,
|
|
||||||
`escape.emergedOutside`, `escape.toolConsumed`, `intake.concealsOnBodyContraband`,
|
|
||||||
`content.tagSurvivesRedeem`, `corruption.dispositionVaries`, `classification.gradeReflectsRecord`,
|
|
||||||
`prisonization.reshapesNurture`, `justice.deterrenceFeedback`, `discipline.hardens`,
|
|
||||||
`parole.releaseDecision`, `corruption.bentWardenSmuggles`, `gangs.smugglesAcrossWall`,
|
|
||||||
`gangs.fightIsCrime`, `gangs.membershipByDisposition`, `regime.prisonerRecreation`. Any `CBTEST … =
|
|
||||||
False`, or a `CBTEST HARNESS THREW`, fails the run.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How to run it
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Full stack: ~24 active mods, all DLC, the popular Workshop mods.
|
|
||||||
Tools/run-compat-test.sh /path/to/rimworld-install [/path/to/workshop-mods]
|
|
||||||
```
|
|
||||||
|
|
||||||
The second argument (the Workshop-mod cache) defaults to `/home/dev/rimworld-ref/mods-cache` or
|
|
||||||
`$RIMWORLD_MODS_CACHE`. The Institution sibling source dirs default to `$HOME/rimworld-<mod>` and can
|
|
||||||
be overridden with `INSTITUTIONCORE`, `INSTITUTIONJUSTICE`, `CONTRABAND`, `FOULPLAY`,
|
|
||||||
`INSTITUTIONGANGS`.
|
|
||||||
|
|
||||||
### The two modes — they fail in opposite directions
|
|
||||||
|
|
||||||
| Env var | What it does | Why it exists |
|
|
||||||
|---|---|---|
|
|
||||||
| `CORE_ONLY=1` | No DLC, no Workshop mods, no Contraband/Justice/Gangs — just **Foul Play + Ward on a bare Core install** | Coexisting with 24 mods proves nothing about the player who owns no DLC. A def that accidentally referenced a DLC thing would resolve happily in the full run and break for everyone else. This is the opposite test. |
|
|
||||||
| `FAKE_DLC=1` | Loads `Tools/fixtures/FakeDLC` — a *hostile* mod that does to Ward's surface everything an unknown DLC could: competing prisoner interaction modes, a competing room role (`~1e5 × beds`), and pokes the very think-tree/duty defs the Piss Nuke XPaths depend on | You own Royalty/Ideology/Biotech but **cannot** load Anomaly/Odyssey without owning them (a DLC's defs can't be downloaded otherwise). The fixture stands in for what can't be loaded, and it loads *before* the suite so its patches land first. |
|
|
||||||
|
|
||||||
`CORE_ONLY` is not achievable by editing `ModsConfig` (RimWorld re-activates any expansion whose
|
|
||||||
`Data/` folder is present) or by symlinks (Unity resolves the executable's real path back into the
|
|
||||||
original install). The script builds a **hard-link copy** of the game with the DLC `Data/` folders
|
|
||||||
deleted — the binary genuinely lives in a DLC-free tree, so its real path resolves there, at no disk
|
|
||||||
cost. In Core-only, `CB_ACTIVE=0` drops Contraband/Justice/Gangs from the load order too, so the pass
|
|
||||||
tests Foul Play + Ward genuinely standalone: the Contraband bridge must **not** load, yet the piss
|
|
||||||
content must still work. The verdict section asserts both directions — and a Core-only run that
|
|
||||||
quietly still had DLC loaded (which happened, twice, during development) is caught by grepping the
|
|
||||||
`loaded mods` line for `royalty|ideology|biotech`.
|
|
||||||
|
|
||||||
### Watching a run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Stream a run to a stable log on the home partition (survives /tmp reaps):
|
|
||||||
FAKE_DLC=1 Tools/harness-stream.sh /path/to/rimworld-install
|
|
||||||
|
|
||||||
# Follow it live from anywhere on the box (-F survives the log being recreated each run):
|
|
||||||
Tools/watch-harness.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Internally the run boots via `timeout 900 xvfb-run … RimWorldLinux -logfile … -quicktest`, forces
|
|
||||||
`TimeSpeed.Ultrafast` from the `GameComponent` (same per-tick logic, just no idle real-time between
|
|
||||||
ticks), and calls `Root.Shutdown()` once every assertion is written (~tick 8000; a 12000-tick watchdog
|
|
||||||
is the safety cap). The 900-second `timeout` is a backstop, not the normal exit.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Requirements — and why it's local, not CI
|
|
||||||
|
|
||||||
| Requirement | Why |
|
|
||||||
|---|---|
|
|
||||||
| A **licensed RimWorld install** with the `RimWorldLinux` executable | The harness boots the *real* game engine. There is no headless stub — the whole value is that vanilla's own room grid, think trees, and mental-state handler run for real. |
|
|
||||||
| **The Workshop mods**, in the ID-keyed cache | Fetched with `DepotDownloader -app 294100 -pubfile <id>` — which needs a Steam account that *owns* RimWorld. The mods can't be redistributed. |
|
|
||||||
| **xvfb** + Mesa software GL (`libgl1-mesa-dri`) | RimWorld is a Unity app that insists on a GL context even for `-quicktest`. `xvfb-run` gives it a headless framebuffer; `LIBGL_ALWAYS_SOFTWARE=1` / `llvmpipe` render on the CPU (slow, but no GPU needed). |
|
|
||||||
| **dotnet** | Builds all six Institution assemblies (net472 via Krafs reference assemblies — no game install needed to *compile*, only to *run*). |
|
|
||||||
|
|
||||||
Every one of those — a licensed game binary, non-redistributable Workshop content, a Steam login, a
|
|
||||||
GL-hungry Unity process — is a reason this cannot live in ordinary CI. It is a **local, on-demand
|
|
||||||
integration test**: you run it before a release, on a box that owns the game, and read the verdict.
|
|
||||||
The last line of a good run is `ALL COMPAT ASSERTIONS PASSED`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Part of the Institution suite
|
## Part of the Institution suite
|
||||||
|
|
||||||
The harness in this repo builds and tests the whole suite — **Institution: Core**, **Institution:
|
The Institution suite — **Institution: Core**, **Institution: Contraband**, **Institution: Justice**,
|
||||||
Contraband**, **Institution: Justice**, **Institution: Gangs**, **Foul Play**, and **Institution:
|
**Institution: Gangs**, **Foul Play**, and **Institution: Ward** — is a set of RimWorld 1.6 mods about
|
||||||
Ward** — in dependency order, alongside the most-subscribed psych/prison Workshop mods, in one running
|
what an institution does to the people inside it. Each stands alone; together they interlock. Ward's
|
||||||
game. Ward carries it because Ward's design *is* a compatibility claim, and a claim like that is worth
|
psychiatric ward is a secure context where a committed patient can conceal and improvise contraband
|
||||||
nothing until it's run.
|
exactly as a prisoner can, because they ride the same prisoner rail the rest of the suite polices.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user