From 8250dcb3698dbda456c003a0a8339db92d59c08f Mon Sep 17 00:00:00 2001 From: flan Date: Thu, 16 Jul 2026 02:18:25 +0000 Subject: [PATCH] Wiki: player-facing rewrite of every layer + add a scenario FAQ, linked from Home --- Wiki/FAQ.md | 145 ++++++++++ Wiki/Home.md | 10 +- Wiki/contraband/Compatibility.md | 148 +++++----- Wiki/contraband/Concealment-and-Intake.md | 277 ++++++++---------- Wiki/contraband/Corruption.md | 94 +++---- Wiki/contraband/Home.md | 94 +++---- Wiki/contraband/Improvised-Weapons.md | 187 ++++++------ Wiki/contraband/Tunnels.md | 140 ++++----- Wiki/contraband/Warden-Search.md | 211 +++++++------- Wiki/core/Criminal-Record.md | 193 +++++-------- Wiki/core/Home.md | 68 ++--- Wiki/core/Modder-API.md | 5 + Wiki/core/Propensity.md | 277 ++++++++---------- Wiki/core/Secured-Context.md | 155 +++------- Wiki/core/Treatment-Engine.md | 175 ++++-------- Wiki/corrections/Classification.md | 136 ++++----- Wiki/corrections/Discipline-and-Reform.md | 143 ++++------ Wiki/corrections/Home.md | 29 +- Wiki/corrections/Parole.md | 134 ++++----- Wiki/corrections/Regime.md | 109 +++---- Wiki/corrections/Rehabilitation.md | 133 +++++---- Wiki/gangs/Affiliations-and-Segregation.md | 78 ++--- Wiki/gangs/Home.md | 41 ++- Wiki/gangs/Joining.md | 89 +++--- Wiki/gangs/Networks-and-Smuggling.md | 101 +++---- Wiki/gangs/Rivalry-and-Fights.md | 80 ++---- Wiki/policing/Deterrence.md | 143 ++++------ Wiki/policing/Home.md | 25 +- Wiki/policing/Policing.md | 53 ++-- Wiki/ward/Commitment.md | 313 ++++++++------------- Wiki/ward/Home.md | 81 ++---- Wiki/ward/The-Compat-Harness.md | 297 ++----------------- 32 files changed, 1654 insertions(+), 2510 deletions(-) create mode 100644 Wiki/FAQ.md diff --git a/Wiki/FAQ.md b/Wiki/FAQ.md new file mode 100644 index 0000000..a35e3cc --- /dev/null +++ b/Wiki/FAQ.md @@ -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).* diff --git a/Wiki/Home.md b/Wiki/Home.md index 81745ba..a175154 100644 --- a/Wiki/Home.md +++ b/Wiki/Home.md @@ -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, 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 -install with a **checkbox per layer** (all on by default), owning no gameplay code itself — it is a -thin settings shim over the same separate, still-splittable feature DLLs. Pull it apart into its -layers again and nothing is lost. +**Institution** is the single mod that ships all of it — every layer bundled into one install with a +**checkbox per layer** (all on by default), so you run as much or as little of the suite as you like. + +> **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. --- diff --git a/Wiki/contraband/Compatibility.md b/Wiki/contraband/Compatibility.md index 6e49d5d..76ba42f 100644 --- a/Wiki/contraband/Compatibility.md +++ b/Wiki/contraband/Compatibility.md @@ -1,128 +1,116 @@ # Compatibility -*Contraband ships zero Harmony patches. It adds defs, subclasses a work-giver, mutates two duty -trees at startup, and polls. That is why it composes instead of colliding.* +*Contraband adds its own content and reads from the base game rather than rewriting it. That is why it +composes with other mods instead of colliding with them.* ## Requires: Institution: Core -Contraband declares a hard `modDependency` on **`flan.institution.core`** and loads after it. This -is not optional — after the split, the entire substrate Contraband reads lives in Core: +Contraband has a hard dependency on **Institution: Core** and loads after it. This is not optional — +after the split, everything Contraband reads lives in Core: | Core provides | Contraband uses it for | |---|---| -| `SecuredContext` / `SecuredContexts` / `CanConceal` / `IsHeld` | 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 | -| `CriminalRecord` + `GameComponent_CriminalRecords` | `timesSearched`, `timesCaught`, `escapeAttempts`, `contrabandMade` — written by search and acquisition, read by priority | +| Which pawns count as "in custody," and who can hide things | who counts as a concealer; who can be intaken, whittle, or dig | +| 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 | +| 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 -interplay that degrades gracefully when the other mod is absent. +Install Contraband without Core and it will not run. Everything else on this page is optional interplay +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 -**Institution: Justice**, so Contraband is Harmony-free again. Instead of patching, it: +Contraband adds its own content rather than rewriting the base game's. It: -- **Adds a new `WorkGiver`** (`WorkGiver_Warden_Search`) rather than patching a vanilla one. -- **Subclasses `WorkGiver_Warden`**, inheriting its behaviour rather than overriding it. -- **Mutates two `DutyDef` think trees at startup** via `StaticConstructorOnStartup` (not Harmony) — - an XML patch on a duty think tree silently no-ops in 1.6, so it is done in C#, deterministically. -- **Polls** every held pawn from a `MapComponent` for intake, improvisation, and tunnels — no hook - on the capture event is needed because the tick already visits every held pawn. +- **Adds a new warden job** (search) rather than changing an existing one. +- **Reuses vanilla warden behaviour** rather than overriding it, so anything that adjusts warden + behaviour on the base class flows through to searches for free. +- **Extends two escape-related behaviours at startup** so prisoners arm themselves and use what they + are hiding when they break — inserted at a specific point, and it logs a warning if that anchor is + 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 exception, not the rule. ## Foul Play — the carboy bridge -**Foul Play** (the "Piss Nuke" mod) stays its own mod with its own front door, and yet its **carboy -is 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: +**Foul Play** (the "Piss Nuke" mod) stays its own mod with its own front door, and yet its **carboy is +a first-class contraband item.** This is the flagship demonstration of Contraband's *"items are +content, subsystems are shared, mods are audiences"* principle, and it works two ways: -1. **Cross-assembly worker resolution.** `CompProperties_Contraband`'s `useWorker` / `escapeWorker` - fields are plain `Type`s, and RimWorld resolves def `Type` fields with - `GenTypes.GetTypeInAnyAssembly` — searching **every loaded assembly**. So the carboy declares - `CompProperties_Contraband` and points `useWorker` at a class that lives in *Foul Play's* assembly - (e.g. a throw-the-carboy worker). Contraband never has to know that class exists. -2. **The opaque content tag.** A carboy is not just "a jar" — it is a jar *of* a fermented blend, and - that blend has to survive being concealed. Foul Play sets Contraband's two delegates: - - ```csharp - 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 +1. **The carboy brings its own behaviour.** The carboy simply declares itself as contraband and points + at the throw-the-carboy behaviour that lives in *Foul Play*. Contraband never has to know that + behaviour exists — it just runs whatever the item brought with it. +2. **A concealed carboy keeps its contents.** A carboy is not just "a jar" — it is a jar *of* a + fermented blend, and that blend has to survive being concealed. When a carboy is intaken or + smuggled, Contraband captures a label for its contents blindly; on redemption it hands the label + 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 + captured and nothing breaks: a jar is just a jar. See [Concealment and Intake](Concealment-and-Intake.md). -The dependency runs one way only: Foul Play bridges *to* Contraband (and to Core) if they are -present; Contraband takes **no** dependency on Foul Play. +The dependency runs one way only: Foul Play bridges *to* Contraband (and to Core) if they are present; +Contraband takes **no** dependency on Foul Play. ## 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 ──▶ CriminalRecord ──▶ Justice reads - timesSearched (in Core) Classification (security grade) - timesCaught Deterrence (colony-wide signal) - escapeAttempts Discipline / Parole (reform) - contrabandMade -``` +| Contraband writes | Justice reads it into | +|---|---| +| times searched | Classification (security grade) | +| times caught | Deterrence (colony-wide signal) | +| escape attempts | Discipline / Parole (reform) | +| contraband made | | - **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 - higher security grade. -- **And it feeds back.** Justice's deterrence signal flows *back* through Core's propensity seam - (`Nurture`), nudging every future intake / improvise / dig roll a Contraband pawn makes. A prison - that catches and disciplines becomes a prison where fewer prisoners bother trying — without either - mod referencing the other. Run Contraband alone and the deterrence factor is a neutral 1.0; the - record fields still route the warden. + mid-tunnel racks up catches and escape attempts, which Justice's classification weighs into a higher + security grade. +- **And it feeds back.** Justice's deterrence signal flows *back* through Core's propensity, nudging + every future intake / improvise / dig roll a Contraband pawn makes. A prison that catches and + disciplines becomes a prison where fewer prisoners bother trying — without either mod referencing the + other. Run Contraband alone and deterrence is a neutral 1.0; the record still routes the warden. ## Institution: Gangs — the 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. -- A gang's outside members are the natural caller for the **reach-in corruption** primitive - (`Corruption.Smuggle`) — paying a bent warden to make a delivery. That primitive is exposed in - Contraband but has no in-repo trigger; 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 - closes through Core again. +- 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** — paying a bent + warden to make a delivery. That ability exists in Contraband but has no in-game trigger of its own; + 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 closes + through Core again. ## Prison mods the suite is tested against | 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 | -| **Custom Prisoner Interactions** | patches only the *named* vanilla warden givers (`_Chat`, `_Convert`, `_Enslave`, `_ReleasePrisoner`); it cannot see a brand-new giver | 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 | -| **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 | -| **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 | +| **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** | 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 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 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 coexist with Prisoner Recreation | not Contraband's concern post-split | ## Load order -``` -Ludeon.RimWorld -flan.institution.core ← required, loads before Contraband -flan.institution.contraband ← this mod -(Foul Play / Justice / Gangs — any order after Core; each bridges if present) -``` +1. Base game +2. **Institution: Core** — required, loads before Contraband +3. **Institution: Contraband** — this mod +4. **Foul Play / Justice / Gangs** — any order after Core; each bridges if present ## The general rule -If a mod adds prisoner behaviour by **patching vanilla warden givers or the escape duty tree by -name**, it will not see Contraband's additions, and Contraband will not see its — they pass each -other. If a mod adds a *new* `WorkGiver` or duty node of its own, it stacks. The one place to watch is -another mod that *also* rewrites the same two duty think trees (`PrisonerEscape`, -`PrisonerAssaultColony`) wholesale; Contraband inserts its nodes at a specific anchor and logs a -warning if that anchor is missing, so a load-order or conflict problem is visible in the log rather -than silent. +If a mod adds prisoner behaviour by **changing vanilla warden jobs or the escape flow by name**, it +will not see Contraband's additions, and Contraband will not see its — they pass each other harmlessly. +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 escape-related behaviours wholesale; Contraband inserts +its part at a specific point and logs a warning if that point is missing, so a load-order or conflict +problem is visible in the log rather than silent. --- *Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and Ward. Each stands alone; together they interlock.* -** diff --git a/Wiki/contraband/Concealment-and-Intake.md b/Wiki/contraband/Concealment-and-Intake.md index 3dd93ad..00600fb 100644 --- a/Wiki/contraband/Concealment-and-Intake.md +++ b/Wiki/contraband/Concealment-and-Intake.md @@ -4,98 +4,91 @@ it becomes real again.* 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. -> 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 +> The prison quietly tracks what each pawn is hiding. **Concealed contraband is not an item on the +> 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 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. -Concretely, a concealed item lives as a `ConcealedItem` row in a `List` on the map component. It -carries the def, a `progress` value, the material it ended up made of, an optional source it is -being harvested from, tunnel state if it is a pick, and an opaque `contentTag`. It holds **no -spawned object** anywhere in the world until one of two things happens: +A concealed item is only a hidden fact about a pawn: what it is, how far along it is, what material it +ended up made of, an optional source it is being whittled from, its tunnel state if it is a pick, and +whatever it holds inside. It has **no physical object** anywhere in the world until one of two things +happens: -| Moment | What happens | Method | -|---|---|---| -| The prisoner **uses** it | It materialises into their inventory as a real `Thing` | `TryRedeem` | -| A warden **finds** it | It is deleted from the list; it never becomes a `Thing` at all | `Confiscate` | +| Moment | What happens | +|---|---| +| The prisoner **uses** it | It materialises into their inventory as a real item | +| 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 -`Search` job only ever appeared when there was genuinely something to find, the mere *offer* of the -job would be the discovery. You have to be able to toss a clean cell and come up with nothing. +A corollary the search page leans on hard: **an empty search must still cost time.** If the search +job only ever appeared when there was genuinely something to find, the mere *offer* of the job would +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 -ever find it**. - -```csharp -[Flags] enum ConcealSite { None = 0, OnBody = 1, InCell = 2 } -``` +Every piece of contraband can hide in one of two places, and this decides **who can ever find it**: | Site | Holds | Found by | |---|---|---| -| `OnBody` | small: a shiv, pills, a phone | searching the **pawn** | -| `InCell` | bulky: a rifle, a carboy, a crude pick | tossing the **room** | +| **On the body** | small: a shiv, pills, a phone | searching the **pawn** | +| **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 -enum is split at all: a **constable** in Justice's now-shipping policing layer, frisking a colonist -named in a street crime, reaches only `OnBody`, so whatever is under their floorboards stays there -until somebody has grounds to search the *room*. The split between "who may search" and "what a -search reaches" is deliberate — see [Warden Search](Warden-Search.md). +A warden turning over a prisoner and their cell reaches both. The reason the two are split at all: a +**constable** in Justice's policing layer, frisking a colonist named in a street crime, only reaches +what is on the body, so whatever is under their floorboards stays there until somebody has grounds to +search the *room*. The split between "who may search" and "what a search reaches" is deliberate — see +[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 -is no hardcoded list of item names — there are three recognisers: +There is no hardcoded list of contraband item names. At the start of a game, the mod looks at every +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 - mod plugs in its own `acquireWorker` / `useWorker` / `escapeWorker` behaviour. -2. **Every drug** (`ThingDef.IsDrug`) — inferred automatically. -3. **Every weapon** (`ThingDef.IsWeapon`) that is not `destroyOnDrop` — inferred from **mass**. +1. **Explicit.** Anything a mod has deliberately marked as contraband. This is the opt-in, and it is + how a mod plugs in its own behaviour for how the item is acquired, used, or used to escape. +2. **Every drug** — recognised automatically. +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: -| Kind | Test | `concealability` | `hideIn` | +| Kind | Test | Concealability | Hides | |---|---|---|---| -| Hard drug | `drugCategory == Hard` | **0.75** | `OnBody \| InCell` | -| Other drug | any other `IsDrug` | **0.5** | `OnBody \| InCell` | -| Light weapon | `Mass ≤ 2.0` | **0.7** | `OnBody \| InCell` | -| Heavy weapon | `2.0 < Mass ≤ 10.0` | **0.35** | `InCell` only | -| Very heavy weapon | `Mass > 10.0` | — | **not contraband** (a minigun hides nowhere) | +| Hard drug | drug rated Hard | **0.75** | on body or in cell | +| Other drug | any other drug | **0.5** | on body or in cell | +| Light weapon | mass ≤ 2.0 kg | **0.7** | on body or in cell | +| Heavy weapon | 2.0 kg < mass ≤ 10.0 kg | **0.35** | in cell only | +| 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 -harder to find. Drugs and inferred weapons get **no `acquireWorker`** (you cannot synthesise yayo in -a bare cell, and you cannot conjure a rifle) and **no `useWorker`** — because vanilla already knows -what to do with them. `JobGiver_SatisfyChemicalNeed` takes a drug; an armed prisoner *is* the -payload. Getting the item into the cell is the entire problem, and that is all these routes solve. +**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 crafting route** (you cannot synthesise yayo in a +bare cell, and you cannot conjure a rifle) and **no special use behaviour** — because vanilla already +knows what to do with them. A prisoner takes a drug on their own; an armed prisoner *is* the payload. +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 -dictionary. Modded drugs and weapons are covered without ever being named. +Nothing here edits another mod's items. It is a one-time reading of each item's own stats. Modded +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 -every pawn who could be concealing something and does all the ongoing work: intake, tunnel progress, -and improvisation. If nothing on the map is self-acquirable and there are no escape tools registered, -it returns immediately and costs nothing. +The prison does its ongoing work on a heartbeat. Every **250 ticks** it visits every pawn who could +be concealing something and does all the ongoing work: intake, tunnel progress, and improvisation. If +nothing on the map can be self-made and no escape tools are in play, it does nothing and costs +nothing. -Polling — rather than patching — is a deliberate architecture choice. **The tick already visits -every held pawn**, so intake needs no Harmony hook on the capture event: a pawn who is in custody but -not yet in the `intakeProcessed` set is, by definition, one who was newly taken. The whole mod ships -zero Harmony patches, and this is a large part of why. +Because the heartbeat already visits every held pawn, intake needs no special trigger on the capture +event: a pawn who is in custody but has not yet been processed is, by definition, one who was newly +taken. ### Who counts as a concealer -`ContrabandUtility.Concealers(map)` is **not** just prisoners. It delegates to Core's -secured-context predicate and yields every pawn whose belongings are anyone's business — prisoners, -slaves, **and free colonists**: +The set of pawns being tracked is **not** just prisoners. It is every pawn whose belongings are +anyone's business — prisoners, slaves, ward patients, **and free colonists**: > 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. @@ -107,124 +100,102 @@ authority is not. ## Intake: the moment of capture 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 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 +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 carried gear is never intaken. -2. **Once.** The pawn's `thingIDNumber` is added to a `HashSet`. The set stores an `int`, not a - pawn reference, so it holds no ghost of a captive who has since died. A second pass finds them - already processed and does nothing. -3. **Even while downed.** Intake runs *before* the "must be awake" gate. The stash a captive walked - in with is hidden the moment they are in custody, not when they wake — everything *after* intake +2. **Once.** Each captive is processed a single time; a later pass finds them already done and moves + on. (It remembers by an ID, so it holds no ghost of a captive who has since died.) +3. **Even while downed.** Intake runs *before* the "must be awake" gate. The stash a captive walked 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. -4. **Only what can ride on a body.** `IsIntakeConcealable` accepts a thing only if its contraband - props include the `OnBody` flag. A shiv up a sleeve is intaken; a rifle or a suit of armour is - too bulky to palm and stays as ordinary strippable gear the player confiscates for free. +4. **Only what can ride on a body.** A thing can be intaken only if it is small enough to hide on the + body. A shiv up a sleeve is intaken; a rifle or a suit of armour is too bulky to palm and stays as + ordinary strippable gear the player confiscates for free. 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. - ``` - Propensity.Would(pawn, 0.6, SaltIntake ^ item.thingIDNumber) - ``` +When a pawn does hide an item, the real object is **removed from the game** and a hidden secret takes +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 - **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. +## Preserving contents without understanding them -When a pawn does hide an item, the real `Thing` is **destroyed** (equipment via -`DestroyEquipment`, inventory via `Destroy(Vanish)`) and a concealed secret is created in its place, -preserving what the item was made of (`stuff`) and — critically — its `contentTag`. +A concealed vessel is not just "a jar" — it is a jar *of* something (hooch, piss, a fermented blend), +and that something has to survive being hidden and come back when the item is used. Contraband stores +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 -blend), and that something has to survive being hidden and come back on redemption. Contraband -stores a single opaque string, `contentTag`, and **has no idea what it means**: - -```csharp -// A bridge mod (Foul Play) fills these in. Null (no bridge) = items have no contents. -ContrabandUtility.ContentTagOf; // Func — read on conceal -ContrabandUtility.ApplyContent; // Action — 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 +Fermentation itself lives in Foul Play, not here. A concealed vessel comes back out **fresh** and +ferments afterward like any other, which suits the theme: fresh-brewed piss is weak, and time is what +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 [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. -2. `ThingMaker.MakeThing(def, stuff)`. -3. If there was a `contentTag`, call `ApplyContent` to restore the contents. -4. `TryAdd` it to the pawn's inventory. If that fails, the thing is vanished and nothing is lost. -5. Remove the secret from the list. +1. It is made from the material it was whittled from (or the item's default material, if it was never + made from a material). +2. Anything it held inside is poured back in. +3. It is placed into the pawn's inventory. If that somehow fails, nothing is dropped and nothing is + lost. +4. The secret is cleared from the stash. -The trigger for redemption is the breakout duty tree. `ThinkTreeInjection` runs once at startup and -mutates two duty defs directly (an XML patch on a `DutyDef` think tree silently no-ops in 1.6, so it -is done in C#): +The trigger for this is a breakout. When a prisoner enters an escape or assault-the-colony state, they +will arm themselves and then use whatever they are hiding — the same moment a base-game prisoner would +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 | -|---|---|---| -| `PrisonerEscape` | `JobGiver_ArmSelf`, then `JobGiver_UseContraband` | **after** `JobGiver_TakeCombatEnhancingDrug` | -| `PrisonerAssaultColony` | `JobGiver_ArmSelf`, then `JobGiver_UseContraband` | **before** `JobGiver_AIFightEnemies` | +- **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 + stashed **weapon** only comes out for someone who would actually use it — 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 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 -hook Ludeon wrote and then left reading an always-empty inventory. For each thing a pawn is hiding: +## Confiscation, and the door every route comes through -- **Inert contraband** (a drug, a stashed rifle — no `useWorker`): just materialise it and get out - of the way. Vanilla takes the drug via `JobGiver_SatisfyChemicalNeed`; a weapon in inventory is a - weapon. But a stashed **weapon** only comes out for someone who would actually use it - (`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. +When a warden finds contraband, it is simply removed. It **never becomes a real item** — the warden +found 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.) -## 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 -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.) +## Saving -At the other end, `Conceal(prisoner, def, ...)` is the public door **every** supply route comes -through — intake calls it, a bent warden calls it ([Corruption](Corruption.md)), and a smuggling -gangmate ([Institution: Gangs](Compatibility.md)) calls it. It hands a prisoner something already -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. +Concealed contraband is saved with your game — the whole stash, the per-pawn search cooldowns, and who +has already been intaken. On load, any secret whose pawn or item no longer exists is quietly dropped: +prisoners who died or left take their secrets with them. ## Quick reference | 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 gate | `IsHeld` + `OnBody` | only held pawns, only body-hideable items | -| Hard-drug concealability | 0.75 | inferred | +| Intake gate | held + body-hideable | only pawns in custody, only items small enough to palm | +| Hard-drug concealability | 0.75 | inferred from being a hard drug | | Other-drug concealability | 0.50 | inferred | -| Light-weapon concealability | 0.70 | `Mass ≤ 2.0`, hides on body | -| Heavy-weapon concealability | 0.35 | `2.0 < Mass ≤ 10.0`, `InCell` only | -| Unhideable mass | > 10.0 | not contraband | +| Light-weapon concealability | 0.70 | mass ≤ 2.0 kg, hides on body | +| Heavy-weapon concealability | 0.35 | 2.0–10.0 kg, in cell only | +| Unhideable mass | > 10.0 kg | not contraband | --- *Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and Ward. Each stands alone; together they interlock.* -** diff --git a/Wiki/contraband/Corruption.md b/Wiki/contraband/Corruption.md index aa25b7d..5f48ce9 100644 --- a/Wiki/contraband/Corruption.md +++ b/Wiki/contraband/Corruption.md @@ -3,32 +3,22 @@ *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.* -The counter to contraband is the [warden search](Warden-Search.md) — but a search is not a machine. -It is a human being with traits and a mood, and Contraband models that human being on **two axes, -both read off the same vanilla traits, mood, and relationships — no new stat to maintain**: +The counter to contraband is the [warden search](Warden-Search.md) — but a search is not a machine. It +is a human being with traits and a mood, and Contraband models that human being on **two axes, both +read off the same vanilla traits, mood, and relationships — no new stat to maintain**: | Axis | Question | Effect on a search | |---|---|---| | **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 | -This is *"what turns 'the game vs the player' into 'the player's own guards, each with an angle.'"* -The prisoner you can control. The guard you assigned to watch them, you cannot. +This is *"what turns 'the game vs the player' into 'the player's own guards, each with an angle.'"* The +prisoner you can control. The guard you assigned to watch them, you cannot. ## Diligence — how hard they look -`WardenDisposition.Diligence(warden)` returns 0..1 and multiplies the whole find chance in -`JobDriver_SearchPawn`. A lax or miserable warden misses things a dutiful one would turn up. - -``` -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) -``` +Diligence runs 0 to 1 and multiplies the whole find chance. A lax or miserable warden misses things a +dutiful one would turn up. It starts from a baseline of **0.55** and moves with traits and mood: | Trait / factor | Δ Diligence | Reasoning | |---|---|---| @@ -41,22 +31,10 @@ d = clamp01(d) ## Corruption — their price -`WardenDisposition.Corruption(warden)` returns 0..1. In a search, a bent warden gets a flat chance -to **look away** on each item they would otherwise have a shot at finding: - -``` -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) -``` +Corruption also runs 0 to 1. In a search, a bent warden gets a flat chance to **look away** on each +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: | Trait / factor | Δ Corruption | Reasoning | |---|---|---| @@ -68,18 +46,17 @@ c = clamp01(c) ## The Kind warden is a double liability -Read the two formulas 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 — -the worst warden you can put on a contraband detail, and the least obvious one, because "kind" reads -as a virtue everywhere else in the game. Meanwhile **mood** cuts the same way on both axes: a -miserable warden looks *less* hard and is *more* temptable. A depressed guard is a sieve at both -ends. +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 — the +worst warden you can put on a contraband detail, and the least obvious one, because "kind" reads as a +virtue everywhere else in the game. Meanwhile **mood** cuts the same way on both axes: a miserable +warden looks *less* hard and is *more* temptable. A depressed guard is a sieve at both ends. ## Worked examples -Find chance for a hidden item is `baseFind × Diligence`, with a separate `lookAway = Corruption × -0.6` chance to ignore it entirely. Assume a mid-skill warden whose raw find chance on a shiv is -around 0.30 before disposition: +Find chance for a hidden item is roughly the base find chance × Diligence, with a separate look-away +chance (Corruption × 0.6) to ignore it entirely. Assume a mid-skill warden whose raw find chance on a +shiv is around 0.30 before disposition: | 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* -Corruption is not only "looks away." The `Corruption.Smuggle` primitive is the reaching-*in* half — a -guard who slips a prisoner contraband through the very same `Conceal` door a smuggler or a gangmate -uses: +Corruption is not only "looks away." The other half is reaching *in* — a bent guard who slips a +prisoner contraband through the very same door a smuggler or a gangmate uses. Only what can ride on a +body gets slipped across a handshake. Whether and when a guard does this is gated on that guard's +Corruption. -```csharp -ThingDef Corruption.Smuggle(warden, prisoner, tracker) -// picks a random OnBody-hideable contraband and Conceal()s it onto the prisoner; returns the def -``` - -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). +In Contraband on its own, this "smuggle in" ability exists but is 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 @@ -119,9 +90,9 @@ plugs into. See [Compatibility](Compatibility.md). brutally effective if you can stomach them. - **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. -- **Redundancy beats brilliance.** Because each search is one shot per prisoner per day and even a - good warden misses a well-hidden shiv, a *second* diligent warden over time matters more than a - single perfect search. +- **Redundancy beats brilliance.** Because each search is one shot per prisoner per day and even a good + warden misses a well-hidden shiv, a *second* diligent warden over time matters more than a single + perfect search. ## Quick reference @@ -132,10 +103,9 @@ plugs into. See [Compatibility](Compatibility.md). | Diligence mood scale | ×0.6 .. ×1.1 | miserable → content | | Corruption mood scale | ×1.6 .. ×0.7 | miserable → content | | 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 Ward. Each stands alone; together they interlock.* -** diff --git a/Wiki/contraband/Home.md b/Wiki/contraband/Home.md index 08e0ce4..757ea93 100644 --- a/Wiki/contraband/Home.md +++ b/Wiki/contraband/Home.md @@ -3,15 +3,15 @@ *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 -every mechanic with the real numbers pulled from the source, explains why each one works the way it -does, and tells you how to play with them — and against them. +every mechanic with the real numbers, explains why each one works the way it does, and tells you how +to play with them — and against them. > **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 -> secured-context predicate — now lives in **Institution: Core**, which this mod requires. The -> response systems (classification, deterrence, discipline, parole, regime) went to **Institution: -> Justice**, and the gang network to **Institution: Gangs**. If this page mentions "the record" or -> "a pawn's propensity," those are Core's; Contraband reads them. +> loop** of a prison. The engine it used to carry — a pawn's criminal propensity, the criminal +> record, and which pawns count as "in custody" — now lives in **Institution: Core**, which this mod +> requires. The response systems (classification, deterrence, discipline, parole, regime) went to +> **Institution: Justice**, and the gang network to **Institution: Gangs**. When this page mentions +> "the record" or "a pawn's propensity," those belong to Core; Contraband reads them. ## 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, > 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 -a disposed prisoner files a blade off his bunk, palms a twist of yayo on the way into a cell, or -starts digging. Contraband is where the spectrum becomes an object with edges. +Contraband is the half of that sentence you can *hold in your hand*. A propensity is abstract until a +disposed prisoner files a blade off his bunk, palms a twist of yayo on the way into a cell, or starts +digging. Contraband is where the spectrum becomes an object with edges. ## Vanilla built the demand and forgot the supply chain -RimWorld already ships a fully-built junkie. Raiders spawn addicted (`chemicalAddictionChance`, -`forcedAddictions`) and carrying combat drugs (`combatEnhancingDrugsChance`). You down one, capture -strips everything (`DropAndForbidEverything`), and now withdrawal grips them in the 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`). +RimWorld already ships a fully-built junkie. Raiders spawn addicted and carrying combat drugs. You +down one, capture strips everything, and now withdrawal grips them in the cell. A prisoner already +*seeks* a drug on their own, and already *ignores* the forbidden flag inside their own cell. -Put a drug within reach of an addicted prisoner and the base game makes them take it. Today. With no -new code. The consumer, the craving, the reach, the ignore-forbidden — all of it ships. +Put a drug within reach of an addicted prisoner and the base game makes them take it. Today. The +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 | |---|---| | A prisoner who wants contraband | Any route for contraband to reach the cell | | A warden with fifteen jobs | A sixteenth called *search* | -Search `Assembly-CSharp.dll` for `Contraband`, `Search`, `Confiscate`, `Smuggle`, `Frisk`, -`Shakedown` and you get **zero types**. The warden can chat, convert, feed, execute, enslave, -release, suppress — and cannot look in a pocket. Contraband builds exactly the missing two halves: -**supply** and **search**. It does not need to build the junkie, because Ludeon already did. +The base game's warden can chat, convert, feed, execute, enslave, release, suppress — and cannot look +in a pocket. Contraband builds exactly the missing two halves: **supply** and **search**. It does not +need to build the junkie, because Ludeon already did. ## 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 -told **nothing**. The item exists only as a row in `MapComponent_Contraband`'s private list, and it -becomes a real `Thing` at exactly two moments: +told **nothing**. The item does not become a real object in the world until exactly two moments: -- when the prisoner **uses** it (the secret is "redeemed" into their inventory), and -- when a warden **finds** it in a search (it is confiscated and never becomes a thing at all). +- 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 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 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 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 @@ -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) | 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 -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. -2. **Every drug** (`ThingDef.IsDrug`) — vanilla's, Vanilla Expanded's, mods that don't exist yet. - Hard drugs conceal better (0.75 vs 0.5). -3. **Every weapon** (`ThingDef.IsWeapon`) — concealability and hiding place derived from **mass**. A - knife rides on the body; a heavy gun goes under the floor; a minigun (over 10 kg) hides nowhere. +1. **Anything explicitly marked as contraband** — the opt-in, with pluggable behaviour a mod can + supply. +2. **Every drug** — vanilla's, Vanilla Expanded's, mods that don't exist yet. Hard drugs conceal + better (0.75 vs 0.5). +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 -[Concealment and Intake](Concealment-and-Intake.md) and [Compatibility](Compatibility.md). +It reads the item's own stats rather than editing anything. Nothing changes another mod's items. +Details in [Concealment and Intake](Concealment-and-Intake.md) and [Compatibility](Compatibility.md). ## Index -- **[Concealment and Intake](Concealment-and-Intake.md)** — the secret model, `ConcealSite`, the - three sources, intake on capture, redemption, and the opaque content-tag bridge. -- **[Improvised Weapons](Improvised-Weapons.md)** — the shiv whittled from furniture, material - carry-through, the damage tell, and who reaches for it in a breakout. -- **[Tunnels](Tunnels.md)** — the Prison-Architect dig under the perimeter, which pawns dig, and how - a search uncovers the shaft. +- **[Concealment and Intake](Concealment-and-Intake.md)** — the secret model, where a thing can hide, + the three kinds of contraband, intake on capture, redemption, and how a smuggled vessel keeps its + contents. +- **[Improvised Weapons](Improvised-Weapons.md)** — the shiv whittled from furniture, how its + material carries through, the damage tell, and who reaches for it in a breakout. +- **[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 - 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 find, and the bent warden who looks away or smuggles in. - **[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: Gangs** | gangs as contraband economies | Core, Contraband, Justice | | **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 Ward. Each stands alone; together they interlock.* -** diff --git a/Wiki/contraband/Improvised-Weapons.md b/Wiki/contraband/Improvised-Weapons.md index 5a0d536..6fb1820 100644 --- a/Wiki/contraband/Improvised-Weapons.md +++ b/Wiki/contraband/Improvised-Weapons.md @@ -1,70 +1,63 @@ # Improvised Weapons -*The acquisition route you cannot deny — because you are obliged to furnish the cell, and the -furniture is the raw material.* +*The supply route you cannot deny — because you are obliged to furnish the cell, and the furniture is +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, -or steel, or — if you built a nice prison — plasteel. Give a disposed prisoner a season of -unsupervised time with it and they will file a blade off it. **The prison's own furnishings are the -arsenal, and better furniture is worse.** +You have to give a prisoner a bed. RimWorld will nag you until you do. That bed is a frame of wood, or +steel, or — if you built a nice prison — plasteel. Give a disposed prisoner a season of unsupervised +time with it and they will file a blade off it. **The prison's own furnishings are the arsenal, and +better furniture is worse.** ## The shiv -`CB_Shiv` is a crude stabbing weapon: *"A blade filed off a bed frame and wrapped in cloth for a -grip. Barely a weapon — but a barely-a-weapon in a cell you thought was empty is a dead guard."* +The shiv is a crude stabbing weapon: *"A blade filed off a bed frame and wrapped in cloth for a grip. +Barely a weapon — but a barely-a-weapon in a cell you thought was empty is a dead guard."* | Stat | Value | |---|---| | Tech level | Neolithic | | Mass | 0.4 kg | -| `concealability` | **0.75** (hides very well — it rides on the body) | -| `hideIn` | `OnBody \| InCell` | +| Concealability | **0.75** (hides very well — it rides on the body) | +| Hides | on body or in cell | | Point (Stab) | power 7, cooldown 1.5 s | | Handle (Blunt) | power 5, cooldown 1.6 s | -| `acquireWorker` | `AcquireWorker_Improvise` | -| `useWorker` | none — an armed prisoner *is* the payload | +| Use | none — an armed prisoner *is* the payload | -It is **stuffable** (`stuffCategories`: Metallic, Woody, Stony), which is the entire point of the -harvest mechanic: a shiv's stats come from its material for free. A wood shiv is feeble; a plasteel -one is vicious. You never furnished a cell thinking of it as an armoury, but that is what it is. +Its stats come from its material: it can be made of metal, wood, or stone. A wood shiv is feeble; a +plasteel one is vicious. That is the whole point of the harvest mechanic — you never furnished a cell +thinking of it as an armoury, but that is what it is. ## 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 - shivs in the commons — *yet* (the concealment layer already tracks them; only the authority to act - on them is missing). -2. **Disposed.** A seeded propensity roll: - - ``` - 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 +1. **Held.** The pawn is a prisoner, slave, or ward patient. Free colonists do not whittle shivs in + the commons — *yet* (the concealment layer already tracks them; only the authority to act on them + is missing). +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 never does, and never would have, no matter how many times you reload. 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 -about *who they are and how you keep them*, and the tell it leaves is meant to point you at exactly -that pawn. +The base is deliberately low. Most prisoners are not making weapons. When one is, that is a fact about +*who they are and how you keep them*, and the tell it leaves is meant to point you at exactly that +pawn. ## 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. - 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 | |---|---| -| Made of `Stuff` | you cannot whittle a blade out of nothing | -| `useHitPoints` | it has to be damageable | -| Stuff category is **Metallic, Woody, or Stony** | cloth and leather cannot be filed into a blade | -| Reachable at `Touch` through `Danger.Deadly` | they have to get to it | +| Made of a material | you cannot whittle a blade out of nothing | +| Damageable | it has to take a beating | +| Made of metal, wood, or stone | cloth and leather cannot be filed into a blade | +| 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. 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 -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. -``` -damage = max(1, round(source.MaxHitPoints × 0.012)) // Blunt, ~1.2% of max HP per worked poll -``` - -The damage is not a side effect — it is the point. A gnawed bed is **probable cause**. The same -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. +The damage is not a side effect — it is the point. A gnawed bed is **probable cause**. The same tick +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 -wreckage and move on to the next**: if the source is destroyed before the shiv is finished, -`FindSource` looks for another, and if there is none, the route **stalls** (progress is kept, not -lost). A materially poor cell is a real defence — the prisoner cannot make progress with nothing to -file. +wreckage and move on to the next**: if the source is destroyed before the shiv is finished, they look +for another, and if there is none, the route **stalls** (progress is kept, not lost). A materially +poor cell is a real defence — the prisoner cannot make progress with nothing to file. ## How long, and the material it becomes -Progress uses the default rate — roughly **1.5 unsupervised days** of active work — jittered ±25% -each poll so no two shivs finish on the same schedule: +A shiv takes roughly **1.5 unsupervised days** of active work, jittered ±25% each poll so no two +shivs finish on the same schedule. -``` -progressPerPoll = 250 / (1.5 × 60000) × Rand.Range(0.75, 1.25) -``` +When it is finished, it happens **silently** — the player is told nothing. Two things happen: -When progress reaches 1, the shiv is finished, **silently** — the player is told nothing. Two things -happen: - -- **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 +- **Material carry-through.** The shiv is made of whatever the source was made of. A wood bed yields a + 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 the warden's future suspicion of this pawn and, if you run **Institution: Justice**, their security classification. ## 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 -`PrisonerAssaultColony` duty, `JobGiver_UseContraband` offers to materialise what they are hiding — -but a **weapon** only comes out for someone who would actually use it. That test is -`Disposition.WouldArmSelf`, and it is the same gate `JobGiver_ArmSelf` uses to decide whether an -escapee even bothers to pick a weapon off the floor. +A hidden shiv is inert until a breakout. When a prisoner enters an escape or assault-the-colony state, +they will use what they are hiding — but a **weapon** only comes out for someone who would actually +use it. That is the same test that decides whether an escapee even bothers to pick a weapon off the +floor. -This matters because **vanilla escapees never arm themselves at all.** -`JobGiver_PickUpOpportunisticWeapon` exists and is simply absent from the escape duty tree, so a -base-game prisoner walks out unarmed, always. Arming is genuinely new behaviour, so it is kept rare -enough that when it happens you recognise *who* did it. +This matters because **vanilla escapees never arm themselves at all.** The base game has the behaviour +for opportunistically grabbing a weapon, and simply leaves it out of the escape flow, so a base-game +prisoner walks out unarmed, always. Arming is genuinely new behaviour, so it is kept rare enough that +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 -picks up a rifle is not a rare event, it is a bug. Otherwise: +First a **hard gate**: a pawn who is incapable of violence never arms, ever — a pacifist who picks up +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. -``` -chance = ArmDisposition(pawn) × ArmCircumstance(pawn) -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 -``` +**Disposition** — who they are, fixed, nothing about today changes it. A base of 0.10, scaled by trait +and skill: | Factor | Value | |---|---| @@ -145,12 +121,12 @@ ArmDisposition = 0.10 (base) × trait × skill | Brawler | ×1.8 | | Wimp | ×0.3 | | 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 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 | |---|---|---| @@ -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 -`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 - fell within 45 tiles. Vanilla remembers it in `Pawn_MindState.droppedWeapon` and then never uses - the field for prisoners. *"A man walking back to the spot where his own rifle fell is a better - story than a man rummaging through your stockpile."* A prisoner does not care that you marked it - forbidden. -2. **Anything to hand** within 18 tiles — but only for a pawn over a higher disposition floor - (`WouldSeeded(pawn, 0.35, …)`). Rummaging a stockpile is a further step than grabbing your own gun - back. +1. **Their own dropped weapon** — the gun they carried when you downed them, still lying where it fell + within 45 tiles. The base game remembers where it fell and then never uses that for prisoners. *"A + man walking back to the spot where his own rifle fell is a better story than a man rummaging through + your stockpile."* A prisoner does not care that you marked it forbidden. +2. **Anything to hand** within 18 tiles — but only for a pawn over a higher disposition floor (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 -is the sort to use it does not leave it in his pocket. `JobGiver_UseContraband` redeems it and moves -it straight from inventory into the equipment slot. The pawn who would *not* arm keeps it hidden and -runs — and the shiv survives to be found in a later search, or used in a later break. +A concealed shiv slots into this the natural way: a prisoner who has been sitting on a blade and who is +the sort to use it does not leave it in his pocket — it goes straight into his hand. The pawn who +would *not* arm keeps it hidden and runs — and the shiv survives to be found in a later search, or +used in a later break. ## Not only shivs: the crude pick -`AcquireWorker_Improvise` is shared. The **crude pick** (`CB_DiggingTool`) is filed off furniture -exactly the same way — same disposition gate, same damage tell — but it carries an `escapeWorker` -instead of weapon stats, and its purpose is a tunnel rather than a fight. See [Tunnels](Tunnels.md). +The same whittling behaviour makes the **crude pick** — filed off furniture exactly the same way, with +the same disposition gate and the same damage tell — but its purpose is a tunnel rather than a fight. +See [Tunnels](Tunnels.md). ## Quick reference | Constant | Value | Meaning | |---|---|---| -| Improvise base chance | 0.10 | before nature × nurture; seeded per pawn | -| Damage per worked poll | ~1.2% of source max HP | Blunt; the visible tell | +| 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 | | 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 | -| Workable stuff | Metallic / Woody / Stony | cloth and leather cannot be whittled | -| Shiv `concealability` | 0.75 | hard to find on the body | +| Workable material | metal / wood / stone | cloth and leather cannot be whittled | +| Shiv concealability | 0.75 | hard to find on the body | | Arm chance cap | 0.85 | after disposition × circumstance | | Own-weapon reach | 45 tiles | their dropped weapon | | 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 Ward. Each stands alone; together they interlock.* -** diff --git a/Wiki/contraband/Tunnels.md b/Wiki/contraband/Tunnels.md index 8d84ead..18c828b 100644 --- a/Wiki/contraband/Tunnels.md +++ b/Wiki/contraband/Tunnels.md @@ -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 dig needs a tool. `CB_DiggingTool` is a crude pick, filed off the cell's own furniture exactly -the way a shiv is (`AcquireWorker_Improvise` — same disposition gate, same damage-to-furniture -tell; see [Improvised Weapons](Improvised-Weapons.md)). Where the shiv is a weapon, this is a *way -out*: it carries no combat stats and is never equipped. +The dig needs a tool. The crude pick is filed off the cell's own furniture exactly the way a shiv is — +same disposition gate, same damage-to-furniture tell; see +[Improvised Weapons](Improvised-Weapons.md). Where the shiv is a weapon, this is a *way out*: it +carries no combat stats and is never equipped. | 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."* | | Max HP | 60 | | Mass | 1.2 kg | -| `concealability` | **0.5** | -| `hideIn` | `InCell` only (too bulky to ride on a body) | -| `acquireWorker` | `AcquireWorker_Improvise` | -| `escapeWorker` | `EscapeTunnelWorker` | -| Deterioration | 2.0 | +| Concealability | **0.5** | +| Hides | in cell only (too bulky to ride on a body) | +| Deteriorates | yes | -Because it hides `InCell` only, a warden turning over the *room* can find the pick itself — and, far -more easily, the hole it is digging. +Because it can only hide in the cell, a warden turning over the *room* can find the pick itself — and, +far more easily, the hole it is digging. ## 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 -you dig your way OUT with, quietly, over days."* It is deliberately **not a Job**. There is no -supervised task a warden could walk in on and interrupt — only a secret that grows and a wall that -eventually gives. Progress is driven from `MapComponent_Contraband`'s poll (`TickTunnels`), the same -250-tick heartbeat that makes contraband, so *the same search that finds a shiv can find the tunnel*. +you dig your way OUT with, quietly, over days."* It is deliberately **not a job** you can catch a pawn +performing. There is no supervised task a warden could walk in on and interrupt — only a secret that +grows and a wall that eventually gives. The dig advances on the same 250-tick heartbeat that makes +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, -as a second, longer secret carried on the same `ConcealedItem` (`escapeProgress`, `escapeWall`). +The pick must first be **made** (or smuggled). Only once it is finished does the dig begin, as a +second, longer secret carried on the same hidden pick. ## 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). -2. **The disposition to dig for weeks:** - - ``` - 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. +1. **A person, and held** — prisoner, slave, or ward patient. +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. 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: > 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 -> with a filed-down pick and the disposition to use it can still, slowly, dig out — and nothing else -> in the ecosystem lets them. +> prisoners" mid-break bashing a door, and a committed ward patient is never that pawn. A patient with +> a filed-down pick and the disposition to use it can still, slowly, dig out — and nothing else in the +> ecosystem lets them. ## 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` -finds the **nearest wilderness** — the first standable cell, scanning radially outward from the -holder up to **60 tiles** (`MaxSearchRadius`), whose room *touches the map edge* and is not the room -they are held in. +The target is **not** the cell wall. *"You do not go through the wall, you go under it."* The pawn +aims for the **nearest wilderness** — the first standable spot, scanning outward up to **60 tiles**, +that opens onto the map edge and is not the room they are held in. 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 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. -- **A fully enclosed, map-locked base with no reachable edge has nowhere to surface.** `TryPlan` - fails, `ProgressPerCheck` returns 0, and the tunnel **stalls** — progress is kept, not lost. Seal - the map and the shaft simply waits. +- **A fully enclosed, map-locked base with no reachable edge has nowhere to surface.** The plan fails, + no progress is made, and the tunnel **stalls** — progress is kept, not lost. Seal the map and the + shaft simply waits. ### How long -Dig time scales with how deep the cell sits: - -``` -requiredDays = clamp(distanceToWilderness / 6, 3, 15) // CellsPerDay = 6 -progressPerPoll = 250 / (requiredDays × 60000) × Rand.Range(0.75, 1.25) -``` +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: | | Days | |---|---| @@ -97,53 +84,47 @@ ceiling of 15 keeps the deepest from being effectively forever. ## The tell: spoil -A dig has to go somewhere, and the dirt is where you notice it. Each dig tick, with a 50% seeded -chance (`OnDigTick`), the worker drops one tile of `Filth_Dirt` at the holder's position: +A dig has to go somewhere, and the dirt is where you notice it. Each dig tick, about half the time, +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. 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 -a genuine visual cue that someone is digging. +not so much that it spams filth or litters a clean cell every tick. If you floor your cells, spoil is a +genuine visual cue that someone is digging. ## 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 -`InCell` site, `ExtraDiscoverChance` is added on top of the tool's normal find chance: - -``` -extraChance = clamp01(escapeProgress) × 0.4 // up to +0.40 at a nearly-finished tunnel -``` +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. 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 -dig is logged the same as one that ran: on discovery, `CriminalRecord.escapeAttempts` is -incremented, which raises this pawn's future search priority sharply (a known tunneller is the -prisoner a warden checks first — see [Warden Search](Warden-Search.md)). The full find math is on the +that finds a shiv finds the hole"* — and the deeper the shaft, the harder it is to miss. A foiled dig +counts against the prisoner the same as one that ran: on discovery, their escape-attempt tally ticks +up, which raises this pawn's future search priority sharply (a known tunneller is the prisoner a warden +checks first — see [Warden Search](Warden-Search.md)). The full find math is on the [Warden Search](Warden-Search.md) page. ## 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. -2. **Breach the outer wall or fence** the tunnel comes up beside (`LastContainmentOnLine` — the - *outermost* impassable edifice or fence between the cell and the surfacing point, walking inward - from the exit). It is destroyed with `KillFinalize` — a visible hole the colony must repair, and - proof of how they got out. An open perimeter needs no breach. -3. **Record the attempt** (`escapeAttempts++`) — it happened whether or not they get clear of the - map. +2. **Breach the outer wall or fence** the tunnel comes up beside — the *outermost* impassable wall or + fence between the cell and the surfacing point. It is destroyed, leaving a visible hole the colony + must repair, and 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. 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 - up. -5. **Hand off to vanilla** (`PrisonBreakUtility.StartPrisonBreak`). Now that they are loose and - outside, the base game's prison-break flow — and anything layered on it, e.g. **Prisoner - Realism** — takes it from there. A pawn already outside who no longer qualifies as an in-prison - escapee simply flees on their own; the breach and the emergence are the real outcome regardless. + yard, with a little spoil dirt to mark where they came up. +5. **Hand off to vanilla.** Now that they are loose and outside, the base game's prison-break flow — + and anything layered on it, e.g. **Prisoner Realism** — takes it from there. A pawn already outside + who no longer qualifies as an in-prison 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 -and the pick are spent together — the `ConcealedItem` is removed. +and the pick are spent together. ## 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 | | **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 -the *last* one on the way out. +Note what does **not** help much: adding perimeter walls. The dig goes under them and only breaches the +*last* one on the way out. ## Quick reference | 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 | | Cells per day | 6 | dig speed vs. cell depth | -| Min / max dig time | 3 / 15 days | clamps on `distance / 6` | -| Spoil chance per dig tick | 50% | one tile of `Filth_Dirt` | -| Extra find chance | up to +0.40 | scales with `escapeProgress` | -| Pick `concealability` | 0.5 | `InCell` only | -| On discovery / breakout | `escapeAttempts++` | logged either way | +| Min / max dig time | 3 / 15 days | clamps on depth ÷ 6 | +| Spoil chance per dig tick | 50% | one tile of dirt filth | +| Extra find chance | up to +0.40 | scales with how far along the dig is | +| Pick concealability | 0.5 | in cell only | +| On discovery / breakout | escape attempt logged | counted either way | --- *Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and Ward. Each stands alone; together they interlock.* -** diff --git a/Wiki/contraband/Warden-Search.md b/Wiki/contraband/Warden-Search.md index f834e97..cc54943 100644 --- a/Wiki/contraband/Warden-Search.md +++ b/Wiki/contraband/Warden-Search.md @@ -4,177 +4,161 @@ which is exactly why it has to cost.* 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. -Contraband adds it as a **new** `WorkGiver`, not a patch on a vanilla one, and builds it around a -single hard rule: **the search must be able to come up empty.** +Searching a prisoner is not one of them, and no such job exists anywhere in the base game. Contraband +adds it as a **new** warden job, not a change to an existing one, and builds it around a single hard +rule: **the search must be able to come up empty.** ## 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 | -| `JobDriver_SearchPawn` | **What** is a search? | takes any pawn, searches any pawn | +| 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 | +| 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: -Police** module adds a *second* authority — a cop who may stop and frisk a **free colonist** — as a -new WorkGiver that reuses the same driver with a **narrower `Reach`** (a street frisk gets what is -`OnBody`, not what is under the floorboards). That module is an *addition*, not a rewrite, precisely -because "who may search" and "what a search reaches" were never welded together. +Police** module adds a *second* authority — a cop who may stop and frisk a **free colonist** — reusing +the same act with a **narrower reach** (a street frisk gets what is on the body, not what is under the +floorboards). That module is an *addition*, not a rewrite, precisely because "who may search" and +"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 | -| `priorityInType` | 40 | **below feeding** — a starving prisoner matters more than a hidden shiv | -| `requiredCapacities` | Manipulation, Sight | you search with your hands and eyes | -| `Prioritized` | **true** (in C#) | rank candidates by suspicion, not by distance | +| Work type | Warden | it is warden work | +| Priority within warden work | **below feeding** | a starving prisoner matters more than a hidden shiv | +| Capacities needed | Manipulation, Sight | you search with your hands and eyes | +| 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 -`WorkGiverDef` field, and an XML `` silently errors on load. That flag is what lets a -gnawed bed and a thick record send the warden to the *right* prisoner first instead of the nearest. +That last one — ranking by suspicion rather than distance — is what lets a gnawed bed and a thick +record send the warden to the *right* prisoner first. -`JobOnThing` will offer a search only if all of these hold: the warden should take care of this -prisoner, the target is a `Pawn`, the per-prisoner cooldown is up (`CanSearchNow`), the prisoner is -**awake, not downed, and not in a mental state**, and the warden can reserve them. +A search is offered only if all of these hold: the warden should be looking after this prisoner, the +per-prisoner cooldown is up, the prisoner is **awake, not downed, and not in a mental state**, and the +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 -> the cell is turned over.** If the job were only offered when there was something to find, the mere +> Whether the prisoner is actually hiding anything. **The warden does not know. Nobody knows until the +> 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 > pays for in warden-hours. An empty search has to be possible, and has to cost. ### The cooldown -`MapComponent_Contraband` won't let the same prisoner be turned over more than **once per 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. +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. ## Priority: the search reads the tells -`GetPriority` is where suspicion becomes routing. Everyone is worth a routine toss; the tells push a -prisoner up the queue: +Everyone is worth a routine toss; the tells push a prisoner up the queue. Priority is built up like +this: -``` -priority = 4 // baseline — everyone gets frisked - + 12 × bedDamageFraction // chewed furniture = probable cause - + min(8, timesCaught×2 + contrabandMade×0.5 + escapeAttempts×3) // the record -``` +- **Base 4** — a routine toss for every prisoner. +- **Up to +12 for a gnawed bed** — scaled by how damaged their owned bed is. A badly damaged bed + shoots to the top. +- **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 | |---|---|---| | 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 | -| `timesCaught` | ×2 | already caught with contraband before | -| `contrabandMade` | ×0.5 | has finished contraband in the past | -| `escapeAttempts` | ×3 | **the heaviest term** — a known tunneller is checked first, every time | +| Gnawed bed | up to +12 | how far below full HP their owned bed sits | +| Times caught | ×2 | already caught with contraband before | +| Contraband made | ×0.5 | has finished contraband in the past | +| 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 | -The record is read with `PeekFor` (a read-only lookup that never creates a record). The base of 4 for -everyone is not an accident — **innocent prisoners still get frisked**, because the search must be -able to come up empty or its mere offer would be the discovery. +The record is only read here, never created. The base of 4 for everyone is not an accident — +**innocent prisoners still get frisked**, because the search must be able to come up empty or its mere +offer would be the discovery. -**Worked example.** A prisoner whose bed sits at 40% HP (`missing = 0.6`), with 2 prior catches and -1 foiled tunnel: +**Worked example.** A prisoner whose bed sits at 40% HP (so 60% missing), with 2 prior catches and 1 +foiled tunnel: -``` -priority = 4 + 12×0.6 + min(8, 2×2 + 0 + 1×3) - = 4 + 7.2 + min(8, 7) - = 4 + 7.2 + 7 = 18.2 -``` +> 4 + (12 × 0.6) + min(8, 2×2 + 1×3) = 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 -straight for the one who has been busy. This closes the loop [Improvised Weapons](Improvised-Weapons.md) -opens: whittling a shiv chews the bed, the damage is probable cause, and the warden's *suspicion* — -not the player's eye — sends them to search. +straight for the one who has been busy. This closes the loop +[Improvised Weapons](Improvised-Weapons.md) opens: whittling a shiv chews the bed, the damage is +probable cause, and the warden's *suspicion* — not the player's eye — sends them to search. ## The act: what a search does -`JobDriver_SearchPawn` walks the searcher to the subject, runs a **900-tick** wait toil with a -progress bar, then resolves. Resolution always does two things, hit or miss: +The searcher walks to the subject, spends a **900-tick** turn with a progress bar, then resolves. +Resolution always does two things, hit or miss: -1. `MarkSearched(subject)` — starts the daily cooldown. -2. `timesSearched++` on the record — *"classification and the guard's future suspicion read how often - this pawn has been turned over."* +1. Starts the daily cooldown on that prisoner. +2. Ticks up how many times this pawn has been searched — *"classification and the guard's future + 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 -`OnBody | InCell`; a subclass can narrow it). If nothing is reachable, the job ends here — the time -was still spent, and the player is told nothing. +Then it goes through everything the subject is hiding that this search can **reach** (a full warden +search reaches both the body and the cell; a narrower future street-frisk reaches only the body). If +nothing is reachable, the job ends here — the time was still spent, and the player is told nothing. ### Find math, per hidden item -For each reachable secret: +For each reachable secret, the chance to find it is built like this: -``` -conceal = item.concealability // 0 = always found, 1 = never -chance = clamp( (0.35 + 0.03 × socialSkill) × (1 − conceal) × 2 , 0.02 , 0.95 ) - -if item is a tunnel in progress: - chance = clamp( chance + tunnelExtra , 0.02 , 0.98 ) // tunnelExtra up to +0.40 - -chance ×= WardenDisposition.Diligence(searcher) // the guard's thoroughness - -if Rand.Chance( WardenDisposition.Corruption(searcher) × 0.6 ): - continue // the warden saw it and said nothing — looked away - -found = Rand.Chance(chance) -``` +- Start from a **base of 0.35**, plus **0.03 for each level of the warden's Social skill**. +- Scale by how concealable the item is: the factor pivots at concealability 0.5. A drug at 0.5 is + 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 + finished shaft), and the ceiling rises. +- Multiply the whole thing by the warden's **Diligence** — a lax, kind, or miserable guard scales it + down. +- 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. +- The chance is clamped to between 0.02 and 0.95 (0.98 for a tunnel). Reading it in plain terms: -- **Skill raises the ceiling; concealability lowers it.** `BaseFindChance` is 0.35; each level of the - warden's **Social** skill adds 0.03. The `(1 − conceal) × 2` factor pivots at concealability 0.5 - (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 - is boosted (×1.3). -- **A good warden still misses a well-hidden shiv more often than not.** Worked: a level-8 Social - 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. +- **Skill raises the ceiling; concealability lowers it.** Each level of the warden's Social skill adds + a little; a well-hidden item takes a lot of it back. +- **A good warden still misses a well-hidden shiv more often than not.** A level-8 Social warden + searching for a 0.75-concealability shiv is already well under a coin-flip *before* Diligence scales + it down further. - **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 catches a dig in progress. -- **The warden's own thoroughness and price** enter last: `Diligence` multiplies the whole chance - down for a lax, kind, or miserable guard, and `Corruption` gives a bent guard a flat chance to look - the other way even on an item they would otherwise have found. Both are on the +- **The warden's own thoroughness and price** enter last: Diligence multiplies the whole chance down + for a lax, kind, or miserable guard, and Corruption gives a bent guard a flat chance to look the + other way even on an item they would otherwise have found. Both are on the [Corruption](Corruption.md) page. ### On a find -``` -Confiscate(subject, item.def) // it never becomes a Thing — nothing drops on the floor -timesCaught++ // raises future suspicion, feeds classification -if it was a tunnel: escapeAttempts++ // a foiled dig is a logged attempt, same as one that ran -``` +- The item is confiscated — it **never becomes a real object**, nothing drops on the floor. +- The prisoner's times-caught tally ticks up (raising future suspicion, feeding classification). +- If it was a tunnel, their escape-attempt tally ticks up too — a foiled dig counts the same as one + that ran. -and the player gets a message — *"{warden} searched {prisoner} and found hidden {item}"*, or the -tunnel variant. If nothing is found the player is told **nothing**: they do not learn there *was* -something, only that the warden's time was spent. +The player gets a message — *"{warden} searched {prisoner} and found hidden {item}"*, or the tunnel +variant. If nothing is found the player is told **nothing**: they do not learn there *was* something, +only that the warden's time was spent. ## 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: -| Field | Written when | Read by | +| Tally | Updated when | Read by | |---|---|---| -| `timesSearched` | every search, hit or miss | priority, suspicion, classification | -| `timesCaught` | a find | priority (×2), classification | -| `escapeAttempts` | a foiled tunnel | priority (×3, heaviest), classification | +| Times searched | every search, hit or miss | priority, suspicion, classification | +| Times caught | a find | priority (×2), 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 -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). ## Quick reference @@ -184,15 +168,14 @@ route the warden. See [Compatibility](Compatibility.md). | Base find chance | 0.35 | before skill and concealability | | Per Social level | +0.03 | skill raises the ceiling | | Find chance clamp | 0.02 .. 0.95 | (0.98 for a tunnel) | -| Search duration | 900 ticks | one wait toil, hit or miss | -| Search cooldown | 60000 ticks (1 day) | per prisoner | +| Search duration | 900 ticks | one turn, hit or miss | +| Search cooldown | 60,000 ticks (1 day) | per prisoner | | Priority base | 4 | every prisoner is worth a routine toss | -| Gnawed-bed weight | up to +12 | `1 − HP/maxHP` | -| Record weight | up to +8 | `timesCaught×2 + contrabandMade×0.5 + escapeAttempts×3` | -| Default `Reach` | `OnBody \| InCell` | narrowed by a future street-frisk subclass | +| Gnawed-bed weight | up to +12 | how far below full HP their bed sits | +| Record weight | up to +8 | caught ×2 + made ×0.5 + escapes ×3 | +| Default reach | body + cell | narrowed by a future street-frisk | --- *Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and Ward. Each stands alone; together they interlock.* -** diff --git a/Wiki/core/Criminal-Record.md b/Wiki/core/Criminal-Record.md index 044ca2c..6263798 100644 --- a/Wiki/core/Criminal-Record.md +++ b/Wiki/core/Criminal-Record.md @@ -2,163 +2,100 @@ 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 -kinds of thing. Propensity is computed fresh from who a pawn is and how they are treated; a record is -a durable log of what they have actually done, written once when it happens and read forever after. +kinds of thing. Propensity is computed fresh from who a pawn is and how they're treated; a record is a +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. -Classification grades on it, parole gates on it, the deterrence loop reacts to it, and reintegration -remembers it. Because there is exactly one record per pawn and everyone shares it, no two modules can -disagree about a pawn's past. - -Everything here is verified against `Source/Core/CriminalRecord.cs`. +There is exactly **one criminal record per pawn**, and the whole suite shares it. Classification grades +on it, parole gates on it, the deterrence loop reacts to it, and reintegration remembers it. Because +everyone reads the same record, no two parts of the suite can disagree about a pawn's past. --- -## The fields +## What a record remembers -`CriminalRecord` is a plain `IExposable` bag of counters plus two special values. Core defines the -fields and the storage; it does **not** write most of them — the modules that own each event do. +A record is a small set of counters plus two special marks. Core provides the record and keeps it +safe; the individual events are logged by whichever mod they happen in. -| Field | Type | Default | What it records | Who writes it | Who reads it | -|---|---|---:|---|---|---| -| `crimesCommitted` | `int` | `0` | count of committed crimes | Justice `RecordCrime` (also on gang fights) | classification risk score, deterrence | -| `escapeAttempts` | `int` | `0` | breakout attempts | Contraband (escape/tunnel logic) | classification risk score | -| `contrabandMade` | `int` | `0` | items brewed / whittled | Contraband (shivs, vessels) | classification risk score | -| `timesSearched` | `int` | `0` | how often searched | Contraband (warden search) | search prioritisation, audit | -| `timesCaught` | `int` | `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 | -| `reform` | `float` | `0` | rehabilitation score (see below) | Justice `Discipline`, `Parole` | **Propensity.Nurture**, parole gate, reintegration | -| `pardoned` | `bool` | `false` | granted a clean slate after genuine reform | Justice `Parole` (on release) | parole gate, reintegration | +| What it tracks | Starts at | Meaning | Tracked by | Used by | +|---|---:|---|---|---| +| Crimes committed | 0 | count of committed crimes | Justice (and gang fights) | classification risk score, deterrence | +| Escape attempts | 0 | breakout attempts | Contraband | classification risk score | +| Contraband made | 0 | items brewed / whittled | Contraband | classification risk score | +| Times searched | 0 | how often searched | Contraband (warden search) | search prioritisation | +| Times caught | 0 | searches that found something | Contraband (warden search) | classification risk score | +| Last crime | never | when the most recent crime happened | Justice | recency / cooldown checks | +| Reform | 0 | rehabilitation score (see below) | Justice (discipline, parole) | **Propensity's Nurture**, 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 -defaults to zero/false. +"Last crime" starts at *never* — a distinct value from "at the very first moment of the game." +Everything else starts at zero / no. -> **Note on ownership.** Core is the *vault*, not the *clerk*. It hands out records and persists them; -> the write logic ("a crime just happened, bump the counter") lives in the module where the event -> occurs, above Core. The columns above name where that logic lives in the suite — Core itself only -> defines the fields. +> **Who logs what.** Core is the *vault*, not the *clerk*. It hands out records and keeps them; the +> actual "a crime just happened, add one" logging lives in the mod where the event occurs. The columns +> above name which mod does that — Core itself only holds 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 -its own section. +Most of a record is inert tallies. **Reform** is the one entry that feeds back into behaviour, and it's +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: -``` -reform : float, nominally 0..1 but can go negative - raised by good treatment and good conduct - decays without either - read by Propensity.Nurture, the parole gate, and reintegration -``` +- **[Propensity](Propensity.md)'s Nurture** scales a pawn's disposition by their reform score — so a + genuinely rehabilitated pawn (reform above 0) is calmer *for good*, and a prisonized one (reform + below 0) is inflamed *for good*. This is how institutionalization and recidivism enter the game + without a separate mechanic bolted on. +- **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 -sentence's *outcome* is stored. Three systems lean on that one float: +The negative range isn't a bug — it's the "hardened" end of the spectrum, and Nurture is written to +expect it. -- **[Propensity](Propensity.md)'s Nurture** multiplies disposition by `Clamp(1 − reform×0.5, 0.4, 2)` - — 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. +## Pardoned and blank records -Although the field is documented as `0..1`, Justice's `Discipline` can drive it negative (harsh -punishment subtracts from it). That negative range is not a bug — it is the "hardened" end of the -spectrum, and `Nurture`'s clamp is written to expect it. +The **pardoned** mark flips on exactly once, after genuine reform, so reintegration can grant a clean +slate without erasing the history that earned it — the counters stay, but the pawn is marked forgiven. -## `pardoned` and `IsBlank` - -`pardoned` flips to `true` exactly once, after genuine reform, so reintegration can grant a clean -slate without erasing the history that earned it — the counters stay, but the pawn is marked -forgiven. - -`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. +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*: +times searched and the last-crime time aren't part of the test. A pawn who was searched and found clean +— searched but never caught, never a crime — still counts as blank. That's 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 -difference is not cosmetic. +Records are made lazily. A pawn only gets a record the first time something is actually **written** to +it — a crime, a search that found something, a reform change. Simply **reading** a pawn's disposition +never creates one. -```csharp -// Creates a blank record on first ask. Use on a WRITE path. -public static CriminalRecord For(Pawn p); +That distinction matters because Propensity's Nurture reads a pawn's reform score constantly, on every +pawn on the map, as part of judging their mood. If merely checking disposition created a record, every +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. -public static CriminalRecord PeekFor(Pawn p); -``` - -- **`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. +The rule the whole suite follows: **something happened → a record is created and written; just looking +→ no record is created.** --- ## Persistence -`GameComponent_CriminalRecords` is the single source of truth for the whole game. The design note is -explicit: *"modules never keep their own per-pawn crime state, they read and write here, which is what -keeps them agreeing with each other."* +Records save with your game and reload with it, and there's exactly one shared store for the whole +colony — no mod keeps its own private copy, which is what keeps them all agreeing with each other. -It stores records in a `Dictionary` and serialises them with RimWorld's Scribe: +On load, that store cleans itself up. Three kinds of record get dropped: -```csharp -Scribe_Collections.Look(ref records, "records", - LookMode.Reference, LookMode.Deep, ref tmpPawns, ref tmpRecords); -``` +1. **Records of pawns who are gone** — dead or removed. They take their records with them. +2. **Corrupt entries** — a guard against a broken save. +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 -values save **deep** (the record's fields are written inline). Each `CriminalRecord.ExposeData` -scribes its own eight fields with their defaults, so a save omits any field still at its default. - -### 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. +The upshot: **records are cheap and self-cleaning.** Anything that never got a real mark written to it +simply evaporates on the next load, so the save never bloats with one empty file per pawn ever glanced +at. --- diff --git a/Wiki/core/Home.md b/Wiki/core/Home.md index 037fa9f..f83b12e 100644 --- a/Wiki/core/Home.md +++ b/Wiki/core/Home.md @@ -4,11 +4,11 @@ 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 -see** — no items, no jobs, no UI, no XML. It ships one small assembly and is completely inert until -another Institution mod is installed on top of it. You install Core only because something else asks -for it. +see** — no items, no jobs, no new menus. It sits completely quiet until another Institution mod is +installed on top of it. You install Core only because something else asks 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 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 -to read, before there is a warden to search them. +them. Core is where the *spectrum* itself lives — before anyone is caught, before there is a record to +read, before there is a warden to search them. ## Why a shared engine -The propensity idea did not start abstract. It was written twice, concretely, in two different mods: -the piss spree's `WouldFoulPeople` (which drunk, miserable pawn starts a mess) and the breakout's -`WouldArmSelf` (which prisoner whittles a shiv when the door opens). Both asked the same real -question — *would **this** pawn do it?* — and both answered it their own way. +The propensity idea didn't start out abstract. It grew up twice, in two different mods: one deciding +which drunk, miserable pawn starts a mess, and one deciding which prisoner whittles a shiv the moment a +door opens. Both were asking the same real question — *would **this** pawn do it?* — and each answered +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 -system all read one disposition engine, they agree about who is dangerous instead of each computing -it from scratch and drifting apart. One pawn who is "the scary one" is the scary one to every module -at once. That coherence — not any single number — is the point of a shared substrate. +system all read one disposition engine, they agree about who is dangerous instead of each judging it +separately and drifting apart. The pawn who is "the scary one" is the scary one to every part of the +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: -| Pillar | Class | Answers | Page | -|---|---|---|---| -| **Propensity** | `Propensity` | *Would this pawn do it?* (nature × nurture, seeded) | [Propensity](Propensity.md) | -| **Criminal record** | `CriminalRecord` | *What has this pawn actually done?* (one per-pawn history) | [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) | +| Pillar | Answers | Page | +|---|---|---| +| **Propensity** | *Would this pawn do it?* (nature × nurture) | [Propensity](Propensity.md) | +| **Criminal record** | *What has this pawn actually done?* (one history per pawn) | [Criminal Record](Criminal-Record.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 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 - **[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. -- **[Criminal Record](Criminal-Record.md)** — every field, who writes and reads each, the - `For` vs `PeekFor` distinction, how it persists, and why `reform` is the pivot of the suite. -- **[Secured Context](Secured-Context.md)** — the Free / Prisoner / Slave kinds, `Of()` and - `OnMap()`, `IsHeld` vs `CanConceal`, and why the suite keys off this instead of "prisoner." + multiplier ladder, the disposition formula with worked examples, and why each pawn's roll is fixed + rather than re-rolled. +- **[Criminal Record](Criminal-Record.md)** — everything a record remembers, who fills in each part, + how it persists across saves, and why *reform* is the pivot of the whole suite. +- **[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 - treatable, reduce it by skill × facility quality per session, and build a recovery track toward - discharge. Ward's psychiatric care and Justice's reform share this one engine. -- **[Modder API](Modder-API.md)** — the public surface any mod can call, and the one seam - (`Propensity.DeterrenceFactor`) that lets a justice layer feed back into disposition without Core - ever depending on it. + treatable, reduce it a little each session by skill and facility quality, and build a recovery track + toward discharge. Ward's psychiatric care and Justice's reform run on this one engine. +- **[Modder API](Modder-API.md)** — the "for modders" page: the tools any mod can call to build on + Core. Players can skip it. ## The suite -Each mod stands alone as an install; together they form one system. Core is the leaf everything else -depends on. +Each mod stands alone as an install; together they form one system. Core sits underneath everything +else. | Mod | What it adds | Needs | |---|---|---| @@ -79,9 +80,8 @@ depends on. ## Requirements & load order -- **RimWorld 1.6.** Core references only the base game — no Harmony, no DLC, no other mod. -- Load Core **before** any other Institution mod (`loadAfter` Ludeon.RimWorld only). -- `packageId`: `flan.institution.core`. +- **RimWorld 1.6.** Core needs only the base game — no Harmony, no DLC, no other mod. +- Load Core **before** any other Institution mod in your mod list. --- diff --git a/Wiki/core/Modder-API.md b/Wiki/core/Modder-API.md index 6bb8df9..402ea03 100644 --- a/Wiki/core/Modder-API.md +++ b/Wiki/core/Modder-API.md @@ -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 (`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/`. --- diff --git a/Wiki/core/Propensity.md b/Wiki/core/Propensity.md index 66240c4..f95a418 100644 --- a/Wiki/core/Propensity.md +++ b/Wiki/core/Propensity.md @@ -1,58 +1,47 @@ # Propensity — nature × nurture -`Propensity` is the suite's disposition engine. It answers one question and only one: *"would -**this** pawn do it?"* — never *"would a prisoner,"* never *"would a colonist."* Every behaviour in -the suite that hinges on a pawn's character routes through here, so the crime system, contraband -brewing, escape arming, and gang recruitment all read the same answer instead of each guessing. +Propensity is the suite's disposition engine. It answers one question and only one: *"would **this** +pawn do it?"* — never *"would a prisoner,"* never *"would a colonist."* Every behaviour in the suite +that hinges on a pawn's character comes back to this, so crime, contraband brewing, escape arming, and +gang recruitment all read the same answer instead of each guessing. The model is deliberately old-fashioned: **nature × nurture.** -- **Nature** is a fixed property of the person — their traits. Almost nobody is strongly disposed. - This does not change over a pawn's life. +- **Nature** is a fixed property of the person — their traits. Almost nobody is strongly disposed, and + this never changes over a pawn's life. - **Nurture** is what the colony has done *to* them — mood, mistreatment, unmet needs, the shadow a sentence leaves. This is the half **you** control, and the half no other mod models. Good conditions pull nurture down; neglect and cruelty push it up. A saint left to rot can cross the -line; a monster kept content and deterred may never act. The engine is built so both of those stories -are possible. - -Everything on this page is verified against `Source/Core/Propensity.cs`. +line; a monster kept content and deterred may never act. Both of those stories are possible by design. --- ## Nature — who they are -``` -Nature(pawn) : float in [0, 1] - n = 0.05 // baseline — everyone has a little - for each listed trait the pawn has: - n += weight - return Clamp01(n) // never below 0, never above 1 -``` - -The baseline is `0.05`. That is the floor of the human condition in this model: everyone is capable -of *something*, most people barely. From there, traits add or subtract. +Every pawn starts from a **baseline of 0.05** — the floor of the human condition in this model: +everyone is capable of *something*, most people barely. From there, each trait the pawn has adds or +subtracts, and the total is held to the 0–1 range (never below 0, never above 1). ### Trait weights -Traits are read **reflectively by defName** via `DefDatabase.GetNamedSilentFail`. If a -trait's mod is not installed, that entry is silently skipped rather than throwing a hard reference — -which is how Core consumes Vanilla Traits Expanded's dark traits without *depending* on VTE. +Traits are matched **by name**, so if a trait's mod isn't installed that entry is simply skipped. +That's how Core uses Vanilla Traits Expanded's dark traits when VTE is present without *requiring* VTE +to be installed. | Trait | Weight | Source | Note | |---|---:|---|---| | Psychopath | **+0.45** | Vanilla | no empathy; the heaviest single input | -| Kleptomaniac | **+0.40** | Vanilla Traits Expanded | consumed, not rebuilt | +| Kleptomaniac | **+0.40** | Vanilla Traits Expanded | counted if VTE is installed | | Bloodlust | **+0.35** | Vanilla | enjoys violence | -| Pyromaniac | **+0.30** | Vanilla Traits Expanded | consumed, not rebuilt | +| Pyromaniac | **+0.30** | Vanilla Traits Expanded | counted if VTE is installed | | Greedy | **+0.25** | Vanilla | wants more than their share | | Abrasive | **+0.15** | Vanilla | friction with everyone | | Ascetic | **−0.15** | Vanilla | wants little, takes little | | Kind | **−0.30** | Vanilla | the strongest pull *down* | -The design note in the source is explicit: *"We CONSUME Vanilla Traits Expanded's -kleptomaniac/pyromaniac as inputs here rather than rebuild them; vanilla's own dark traits count -too."* Core does not add traits of its own — it reads the ones the ecosystem already has. +Core adds no traits of its own — it reads the ones the game and your other mods already provide. +Vanilla's own dark traits count right alongside VTE's. ### Worked Nature values @@ -60,205 +49,163 @@ Because weights simply add and then clamp, Nature is easy to read by hand: | Pawn | Arithmetic | Nature | |---|---|---:| -| Ordinary pawn (none of the above) | `0.05` | **0.05** | -| Kind pawn | `0.05 − 0.30 = −0.25` → clamp | **0.00** | -| A single Greedy trait | `0.05 + 0.25` | **0.30** | -| Psychopath | `0.05 + 0.45` | **0.50** | -| Psychopath **and** Kind | `0.05 + 0.45 − 0.30` | **0.20** | -| Greedy + Abrasive | `0.05 + 0.25 + 0.15` | **0.45** | -| Psychopath + Bloodlust + Kleptomaniac | `0.05 + 0.45 + 0.35 + 0.40 = 1.25` → clamp | **1.00** | +| Ordinary pawn (none of the above) | 0.05 | **0.05** | +| Kind pawn | 0.05 − 0.30 = −0.25 → clamp | **0.00** | +| A single Greedy trait | 0.05 + 0.25 | **0.30** | +| Psychopath | 0.05 + 0.45 | **0.50** | +| Psychopath **and** Kind | 0.05 + 0.45 − 0.30 | **0.20** | +| Greedy + Abrasive | 0.05 + 0.25 + 0.15 | **0.45** | +| Psychopath + Bloodlust + Kleptomaniac | 0.05 + 0.45 + 0.35 + 0.40 = 1.25 → clamp | **1.00** | -Two things fall out of this. First, the clamp is not decorative: a Kind pawn floors at exactly `0` +Two things fall out of this. First, the clamp isn't decorative: a Kind pawn floors at exactly `0` (nature can never make them *disposed*, only never disposed), and a stacked monster ceilings at `1`. Second, traits genuinely net against each other — a Kind Psychopath is a real, middling `0.20`, not a -contradiction the engine has to resolve by fiat. +contradiction. -Most rolled pawns sit at or near the `0.05` floor. That is intended: *"Most pawns sit near the floor; -a rare few are strongly inclined."* A colony full of ordinary people is supposed to be mostly safe on -nature alone. What makes them dangerous is nurture. +Most rolled pawns sit at or near the `0.05` floor. That's intended: a colony full of ordinary people +is supposed to be mostly safe on nature alone. What makes them dangerous is nurture. --- ## Nurture — what you have done to them -``` -Nurture(pawn) : float (a multiplier, >= ~0.5 in normal play) - m = 1.0 // ordinary circumstance - - mood = pawn.needs.mood.CurLevelPercentage (or 1.0 if none) - if mood < 0.20: m *= 2.5 // at the floor of despair - else if mood < 0.35: m *= 1.6 // badly kept - - if IsHeld(pawn) and mood < 0.40: m *= 1.4 // held AND unhappy compounds - - rec = criminal record (peek — does not create one) - if rec != null and rec.reform != 0: - m *= Clamp(1 - rec.reform * 0.5, 0.4, 2.0) // prisonization - - m *= DeterrenceFactor(pawn) // colony climate of order (1.0 in Core alone) - - return m -``` - -`1.0` is an ordinary pawn under ordinary conditions. The number rises as things go wrong and falls as -they go right. Each clause below is one lever. +Nurture is a **multiplier**. An ordinary pawn under ordinary conditions sits at **1.0**. The number +rises as things go wrong and falls as they go right. Each lever below stacks on top of the last. ### The multiplier ladder -| Clause | Factor | Applies when | Stacks? | +| Lever | Factor | Applies when | Stacks? | |---|---|---|---| -| **Despair** | ×2.5 | `mood < 0.20` | mutually exclusive with "badly kept" | -| **Badly kept** | ×1.6 | `0.20 ≤ mood < 0.35` | mutually exclusive with "despair" | -| **Held & unhappy** | ×1.4 | `IsHeld` **and** `mood < 0.40` | on top of the mood clause | -| **Prisonization** | ×`Clamp(1 − reform×0.5, 0.4, 2.0)` | `reform ≠ 0` | on top | -| **Deterrence** | ×`DeterrenceFactor(pawn)` | always (neutral `1.0` in Core alone) | on top | +| **Despair** | ×2.5 | mood below 20% | mutually exclusive with "badly kept" | +| **Badly kept** | ×1.6 | mood 20–35% | mutually exclusive with "despair" | +| **Held & unhappy** | ×1.4 | held **and** mood below 40% | on top of the mood lever | +| **Prisonization** | × (depends on reform, see below) | the pawn has a non-zero reform score | on top | +| **Deterrence** | × (the colony's climate of order) | always (neutral 1.0 with only Core) | on top | -The two mood clauses are an `if / else-if`: a pawn is either in despair *or* badly kept, never both. -The "held & unhappy" clause is separate and multiplies again — so a mistreated prisoner in despair -compounds `2.5 × 1.4 = 3.5` before anything else. The source calls that exactly what it is: *"a badly -run cell."* +The two mood levers are either/or: a pawn is either in despair *or* badly kept, never both. "Held & +unhappy" is separate and multiplies again — so a mistreated prisoner in despair compounds +`2.5 × 1.4 = 3.5` before anything else. That is a badly run cell. ### Prisonization — the reform lever -The `reform` clause deserves its own look, because it is where a sentence leaves a permanent mark. +The reform lever is where a sentence leaves a permanent mark. A pawn's **reform score** runs from +about −1 to +1 and turns into a multiplier: -``` -reform factor = Clamp(1 - reform * 0.5, 0.4, 2.0) -``` - -| `reform` | Meaning | Factor | +| Reform score | Meaning | Factor | |---:|---|---:| | **+1.0** | fully rehabilitated | 0.50 | | +0.5 | improving | 0.75 | -| 0 | untouched (clause skipped) | *1.00* | +| 0 | untouched (no effect) | *1.00* | | −0.5 | hardening | 1.25 | | **−1.0** | prisonized | 1.50 | -| ≥ +1.2 | (over-reformed) | clamp floor **0.40** | -| ≤ −2.0 | (utterly broken) | clamp ceiling **2.00** | +| beyond +1.2 | (over-reformed) | floors at **0.40** | +| beyond −2.0 | (utterly broken) | ceilings at **2.00** | -`reform` is nominally documented as `0..1`, but the punishment machinery can and does drive it -**negative** — the source discusses `reform < 0` ("hardened, prisonized") in as many words. Within the -realistic range `[−1, +1]` the factor spans `0.5 … 1.5`; the `0.4 / 2.0` clamps only bite at extremes -outside that, catching a pawn who has been endlessly punished or endlessly rehabilitated. +Reform is nominally a 0-to-1 rehabilitation score, but harsh punishment can drive it **negative** — +that's the "hardened, prisonized" end. Within the realistic −1…+1 range the factor spans 0.5…1.5; the +0.4 / 2.0 limits only bite at the extremes, catching a pawn who's been endlessly punished or endlessly +rehabilitated. -The important design property: Core only ever **reads** `reform` here. Nothing in Core moves it. The -Justice layer's `Discipline` and `Parole` are what nudge it up or down — which means -*institutionalization and recidivism become the suite's without a parallel mechanic.* A pawn who was -broken by a brutal prison stays broken (nurture ×1.5) after release; a pawn genuinely reformed stays -calmer (×0.5) for good. The scar is carried by one float. +Propensity only *reads* this score — nothing in Core moves it. The Justice layer's discipline and +parole are what nudge it up or down, which is how institutionalization and recidivism enter the game +without a separate mechanic. A pawn broken by a brutal prison stays broken (nurture ×1.5) after +release; a pawn genuinely reformed stays calmer (×0.5) for good. The scar rides on one number. ### Deterrence — the climate of order -The final `× DeterrenceFactor(pawn)` is the seam that lets the whole colony's climate feed back into -each pawn's disposition. **In Core alone it is neutral — a flat `1.0`** — because Core does not know -what deterrence is. When Institution: Justice is loaded, it fills this in: a well-policed colony pulls -the factor below `1` and deters everyone a little; a lawless one pushes it above `1` and emboldens -them. That is what makes catching and punishing *one* pawn matter to the disposition of *the rest*. +The final lever multiplies by the colony's overall **climate of order**. **With only Core installed +it's neutral — a flat 1.0** — because Core by itself doesn't know what deterrence is. Install +**Institution: Justice** and it fills this in: a well-policed colony pulls the factor below 1 and +deters everyone a little; a lawless one pushes it above 1 and emboldens them. That's what makes +catching and punishing *one* pawn matter to the disposition of *the rest*. -The full mechanics of the seam — and how to fill it from your own mod — are on the -[Modder API](Modder-API.md) page. For now: with Core installed by itself, this clause does nothing, -and that is correct. +With only Core installed, this lever does nothing, and that is correct. The technical details of how a +mod fills in deterrence are on the [Modder API](Modder-API.md) page. --- ## Would — the full roll -`Nature` and `Nurture` are ingredients. `Would` is the meal: the actual yes/no for a specific -behaviour at a specific base rate. +Nature and Nurture are ingredients. The full roll is the meal: the actual yes/no for a specific +behaviour at a specific base rate. For a given behaviour, the chance works out to: -``` -Would(pawn, baseChance, salt, cap = 0.85) : bool - chance = min(cap, baseChance * (0.1 + Nature(pawn) * 2) * Nurture(pawn)) - return Rand.ChanceSeeded( Clamp01(chance), pawn.thingIDNumber ^ salt ) -``` +> **chance = base rate × (0.1 + Nature × 2) × Nurture**, capped at 85%. -Three things are happening in that one line of arithmetic: +Three things are happening there: -1. **`baseChance`** is the caller's dial — the base rate of *this* behaviour for an average pawn - (a rare act passes a small number, a common one a larger). Core does not decide it; the module - asking the question does. -2. **`(0.1 + Nature × 2)`** is the nature amplifier. It spans **×0.1** (a saint, Nature 0) to - **×2.1** (Nature 1). At the common `0.05` floor it is `×0.2`. So a floor pawn is one-fifth as - likely as the base rate; a maxed monster over twice as likely — *before* nurture. -3. **`× Nurture`** applies circumstance on top, and `min(cap, …)` caps the whole thing at `0.85` by - default. Nobody is ever a dead certainty; there is always slack. A caller who wants a harder or - softer ceiling passes their own `cap`. +1. **Base rate** is the behaviour's own dial — how likely an *average* pawn is to do this particular + thing (a rare act uses a small number, a common one a larger). Core doesn't decide it; whichever + system is asking does. +2. **(0.1 + Nature × 2)** is the nature amplifier. It spans **×0.1** (a saint, Nature 0) to **×2.1** + (Nature 1). At the common `0.05` floor it's **×0.2** — so an ordinary pawn is one-fifth as likely + as the base rate, while a maxed monster is over twice as likely — *before* nurture. +3. **× Nurture** applies circumstance on top, and the whole thing is capped at **85%** by default. + Nobody is ever a dead certainty; there's always slack. A behaviour that wants a harder or softer + ceiling can set its own cap. ### Worked example 1 — an ordinary colonist, content -Base rate `0.10`, an ordinary pawn (Nature `0.05`), good mood so Nurture `1.0`: +Base rate 10%, an ordinary pawn (Nature 0.05), good mood so Nurture 1.0: -``` -nature term = 0.1 + 0.05 * 2 = 0.2 -chance = min(0.85, 0.10 * 0.2 * 1.0) = 0.02 → 2% -``` +- nature term = 0.1 + 0.05 × 2 = 0.2 +- chance = 0.10 × 0.2 × 1.0 = 0.02 → **2%** -Two percent, and — critically — **seeded**. For this pawn and this question it is a fixed 2% coin -that either comes up or does not; it is not re-flipped every tick. +Two percent — and, critically, **fixed**. For this pawn and this question it's a settled 2% coin that +either comes up or doesn't; it isn't re-flipped every moment. ### Worked example 2 — a mistreated psychopath prisoner -A Psychopath + Bloodlust prisoner (Nature `0.85`), mood `0.15` (despair ×2.5), held and unhappy -(×1.4), `reform = −0.5` (hardening → ×1.25), Core-only so deterrence `1.0`. Base rate `0.10`: +A Psychopath + Bloodlust prisoner (Nature 0.85), mood 15% (despair ×2.5), held and unhappy (×1.4), +reform −0.5 (hardening → ×1.25), Core-only so deterrence 1.0. Base rate 10%: -``` -nature term = 0.1 + 0.85 * 2 = 1.8 -nurture = 2.5 * 1.4 * 1.25 = 4.375 -chance = min(0.85, 0.10 * 1.8 * 4.375) = min(0.85, 0.7875) = 0.7875 → ~79% -``` +- nature term = 0.1 + 0.85 × 2 = 1.8 +- nurture = 2.5 × 1.4 × 1.25 = 4.375 +- chance = 0.10 × 1.8 × 4.375 = 0.7875 → **~79%** Same base rate as example 1, same engine — but nature and a badly-run cell have turned a 2% pawn into -a near-certainty. The player did that, clause by clause. +a near-certainty. The player did that, lever by lever. ### Worked example 3 — hitting the cap -A fully stacked monster (Nature clamps to `1.0`), in despair (×2.5) and held & unhappy (×1.4), in a -lawless colony where Justice has set deterrence to `1.4`. Base rate `0.10`: +A fully stacked monster (Nature clamps to 1.0), in despair (×2.5) and held & unhappy (×1.4), in a +lawless colony where Justice has set deterrence to 1.4. Base rate 10%: -``` -nature term = 0.1 + 1.0 * 2 = 2.1 -nurture = 2.5 * 1.4 * 1.4 = 4.9 -raw chance = 0.10 * 2.1 * 4.9 = 1.029 -chance = min(0.85, 1.029) = 0.85 → capped at 85% -``` +- nature term = 0.1 + 1.0 × 2 = 2.1 +- nurture = 2.5 × 1.4 × 1.4 = 4.9 +- raw chance = 0.10 × 2.1 × 4.9 = 1.029 +- capped at **85%** -The raw product blew past `1.0`; the cap reins it to `0.85`. Even here, a `0.15` sliver of "not -today" survives. That sliver is deliberate — it keeps the worst pawn a character with a bad day -coming, not a scripted event. +The raw product blew past 100%; the cap reins it to 85%. Even here, a 15% sliver of "not today" +survives. That sliver is deliberate — it keeps the worst pawn a character with a bad day coming, not a +scripted event. --- -## The seeded roll — characters, not a dice cup +## The fixed roll — characters, not a dice cup -The last argument of `Would` is a **salt**, and the seed is: +Each pawn's answer to "would they?" is **fixed** by two things: who the pawn is (their permanent +identity) and which behaviour is being asked about. It is not re-rolled from scratch each time. This is +the single most important design decision on this page, and it's worth being precise about why. -``` -seed = pawn.thingIDNumber ^ salt -``` +**Save/reload stability.** Because the answer is tied to the pawn's permanent identity and the specific +question — not to the live random stream — asking "would they?" gives the same answer before and after +a save/reload, as long as the inputs (traits, mood, reform) are the same. You can't scum a reload to +re-roll a pawn into a different person. *Who* they are doesn't shift under them on a reload. -`Rand.ChanceSeeded` turns that seed into a *deterministic* pass/fail. This is the single most -important design decision on this page, and it is worth being precise about why. +**A character, not a moment-to-moment lottery.** If the game flipped a fresh coin every tick, any pawn +would eventually do anything if you waited long enough. Fixing the roll instead makes "would this pawn +pocket a shiv?" a settled fact about that pawn under those conditions — what makes them a character, +not a dice cup. -**Save/reload stability.** Because the seed is derived from the pawn's stable `thingIDNumber` and a -fixed salt — not from the live RNG stream — asking "would they?" gives the same answer before and -after a save/reload, as long as the inputs (traits, mood, reform) are the same. You cannot scum a -reload to re-roll a pawn into a different person. *"WHO they are does not shift under them on a -save/reload."* - -**A character, not a per-tick lottery.** A naive implementation would flip a fresh coin every tick, -so any pawn eventually does anything if you wait long enough. Seeding instead makes "would this pawn -pocket a shiv?" a *fixed fact* about that pawn under those conditions — *"what makes them a character, -not a dice cup."* - -**Live circumstance still bites.** Seeding freezes the *identity*, not the *situation*. Nurture is -recomputed live, so as mood collapses or reform hardens, the same pawn's `chance` climbs and the same -seeded coin can flip from "no" to "yes." The pawn who would not have acted last month acts now — not +**Live circumstance still bites.** The roll freezes the *identity*, not the *situation*. Nurture is +recomputed live, so as mood collapses or reform hardens, the same pawn's chance climbs and the same +fixed coin can flip from "no" to "yes." The pawn who wouldn't have acted last month acts now — not because the dice changed, but because you let their world get worse. -**Different questions, different salts.** Each behaviour passes its own salt, so the rolls are -independent. The source puts it plainly: *"a man who would pocket a shiv is not therefore a man who -would inform on his cellmate."* One pawn can be reliably one kind of trouble and reliably not another, -and that pattern is stable across the whole game. +**Different questions, independent answers.** Each behaviour is its own separate question, so the +answers don't leak into each other: a pawn who would pocket a shiv is not therefore a pawn who would +inform on a cellmate. One pawn can be reliably one kind of trouble and reliably not another, and that +pattern is stable across the whole game. --- diff --git a/Wiki/core/Secured-Context.md b/Wiki/core/Secured-Context.md index 9311cdf..98d5c89 100644 --- a/Wiki/core/Secured-Context.md +++ b/Wiki/core/Secured-Context.md @@ -1,29 +1,18 @@ # 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."** -It keys off `SecuredContext` — the *kind of hold* a pawn is under. That indirection is why a ward -patient, a slave, and (later) anyone inside a secure zone get the same concealment, search, schedule, -and needs machinery for free, without any module hard-coding a pawn status. +It keys off the *kind of hold* a pawn is under. That one step of indirection is why a ward patient, a +slave, and (later) anyone inside a secure zone get the same concealment, search, schedule, and needs +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 -them." Everything downstream reads that, so a new module works across every context the day it is -written, and a new *context* works across every existing module the day it is added. - -Verified against `Source/Core/SecuredContext.cs`. +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 feature works across every kind of hold the day it's +written, and a new *kind of hold* works across every existing feature the day it's added. --- ## 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? | |---|---|---|---|---| | **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: -- **Free colonists can still conceal.** A free colonist "can hoard contraband, but no one has - standing to search them until a secure-area or policing layer grants it." Freedom is not innocence — - it is only the absence of an authority permitted to check. That is why `CanConceal` (below) includes - the free. +- **Free colonists can still conceal.** A free colonist can hoard contraband, but no one has standing + to search them until a secure-area or policing layer grants it. Freedom isn't innocence — it's only + the absence of an authority allowed to check. - **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 - entitled to search this pawn" read `IsHeld` and never branch on which. + the warden for one and the overseer for the other. Features that only care "is someone entitled to + 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 -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… | +| Question | 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 | -| `CanConceal` | Free, Prisoner, Slave (any real pawn) | **contraband** — who could be *hiding* something, regardless of status | +| **Is this pawn held?** | Prisoner, Slave | **custody** — who is under the colony's control, who can be disciplined, whose cell can be searched | +| **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" -clause — it only wants to pile the ×1.4 penalty on a pawn the colony actually holds and mistreats, not -on a grumpy free colonist. A warden-search feature, by contrast, would gate on `CanConceal` to decide -who is even worth checking, and then on standing to decide whether it is *allowed* to. +For example, [Propensity](Propensity.md)'s Nurture uses *held* in its "held & unhappy compounds" lever +— it only wants to pile the ×1.4 penalty on a pawn the colony actually holds and mistreats, not on a +grumpy free colonist. A warden-search feature, by contrast, cares who *could* be hiding something 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 -public static SecuredContext? Of(Pawn p) -``` +Any pawn resolves to one context, or to **nothing at all** if the suite doesn't model them: -`Of` maps a pawn to a context, or **`null`** if the pawn has none. The order of the checks matters and -is worth reading literally: +- Animals, mechs, and the dead are simply not the suite's business — they resolve to nothing, and + 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. -``` -if p is not humanlike, or p is dead -> null (not one of ours) -if p.IsPrisonerOfColony -> Prisoner -if p.IsSlaveOfColony -> Slave -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 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. +Keeping "nothing at all" separate from "a free pawn we track" is deliberate: there's a real difference +between "a free pawn we watch" and "a pawn we don't model at all," and collapsing them would let +non-colony pawns leak into colony machinery. A single sweep can walk every prisoner, slave, and +colonist on a map in one pass, skipping everything that resolves to nothing. --- ## Why not just "prisoner"? -This is the design decision the whole page exists to justify, so it is worth stating plainly. If every -module checked `pawn.IsPrisonerOfColony` directly, then: +This is the design decision the whole page exists to justify. If every feature checked "is this pawn a +prisoner" directly, then: -- adding **slaves** to a feature would mean editing every module; -- adding a **ward patient** mode would mean editing every module; -- adding a future **secure-zone** concept would mean editing every module. +- adding **slaves** to a feature would mean revisiting every feature; +- adding a **ward patient** mode would mean revisiting every feature; +- 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 -existing module inherits it. The concealment, search, and needs machinery never has to learn what a -slave is — it only ever asked `IsHeld` and `CanConceal`. +By routing all of them through one question — *what kind of hold is this?* — a new kind of hold is +added *once*, and every existing feature inherits it. The concealment, search, and needs machinery +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 -There is no `SecuredKind.WardPatient`, and its absence is intentional. From the source: - -> *"a committed patient IS a prisoner in vanilla's sense (Ward rides the prisoner rail), so it is -> `SecuredKind.Prisoner` here and needs no special case. Contraband does not depend on Ward."* - -Because Institution: Ward implements a committed patient *on top of* vanilla's prisoner rail, such a -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. +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 +of vanilla's prisoner system — so such a pawn already counts as **Prisoner** here, with no special case +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 +that's the payoff of keying off the kind of hold instead of the label. --- diff --git a/Wiki/core/Treatment-Engine.md b/Wiki/core/Treatment-Engine.md index 5827cd5..e54e66b 100644 --- a/Wiki/core/Treatment-Engine.md +++ b/Wiki/core/Treatment-Engine.md @@ -1,61 +1,40 @@ # 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: -the 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 -track** builds toward a dischargeable state. +Core carries one more thing every rehabilitation-style system needs and none should own alone: the +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's nothing left to reduce a **recovery +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 -at different states — **Ward** treats the mental illness that got a pawn committed; **Justice** wants -to rehabilitate the disposition that got one imprisoned — and without a shared engine each would grow -its own copy and drift. Core owns **only the maths**. Which interaction mode enrols a pawn, which -hediffs and thoughts carry the flavour, which jobs run the sessions, and which alert fires on -discharge all stay in the consuming mod. +at different things — **Ward** treats the mental illness that got a pawn committed; **Justice** +rehabilitates the disposition that got one imprisoned — and without a shared engine each would grow its +own copy and drift apart. Core owns **only the maths**. Which interaction enrols a pawn, which +conditions and moods carry the flavour, which jobs run the sessions, and which alert fires on discharge +all live in the mod using it. -> The engine does something concrete when there is a real condition to work through, and is a -> **no-op** otherwise. A colony that marks nothing treatable never notices it exists. +> The engine does something only when there's a real condition to work through, and does nothing +> 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. - -```xml - - -
  • - 0.12 -
  • -
    -
    -``` - -| 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` | 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` | +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, + and how much of the session that patient actually gets. When a condition's severity reaches zero + it's gone. +2. **A full course is several sessions by design.** The programme has to be *sustained* — you can't + fix a pawn in a single visit. +3. **Once nothing is left to treat, continued care builds a recovery track** from 0 toward 1 + (discharge-ready). Fix the illness first, then stabilise. +4. **The recovery track decays on its own if you stop.** Recovery you stop maintaining slips back down + — which is what makes the whole thing a loop you keep up, not a one-time unlock. --- @@ -63,96 +42,52 @@ A static, null-safe class. It holds no state of its own — it reads and mutates ### Facility quality -``` -room == null OR psychologically outdoors → 0.75 (a poor makeshift facility, not zero) -otherwise → Clamp(0.6 + impressiveness / 120, 0.5, 1.5) -``` +The room the treatment happens in scales every session between **0.5×** (grim) and **1.5×** +(excellent): -| Room impressiveness | Factor | +| Room | Factor | |---|---:| -| 0 (bare) | `0.60` | -| ~48 (decent) | `1.00` | -| ≥108 (impressive) | `1.50` (capped) | -| outdoors / none | `0.75` | +| Bare (0 impressiveness) | 0.60 | +| Decent (~48 impressiveness) | 1.00 | +| Impressive (108+ impressiveness) | 1.50 (capped) | +| Outdoors / no room | 0.75 | -The clamp is deliberate: a grim, filthy, cramped facility heals worse, a calm clean one better, but a -palace can't trivialise the labour the programme costs (1.5× ceiling), and even nowhere is a poor -makeshift room, not a hard zero. +The limits are deliberate: a grim, filthy, cramped facility heals worse and a calm clean one better, +but a palace can't trivialise the work the programme costs (1.5× ceiling), and even treating a pawn out +in the open is a poor makeshift room (0.75×), not a hard zero. ### Reducing a condition -``` -h.Severity -= reductionPerSession × strength // per marked hediff -if h.Severity <= 0.001 → remove it -``` +Each marked condition drops by **0.12 × the session's combined strength** per session, and vanishes +when it hits zero. That combined strength is the treating pawn's skill × the facility quality above × +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 -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. +### Building recovery -### Advancing recovery - -``` -recovery = pawn's hediff of recoveryDef (created at 0.001 if absent) -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 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 +discharge" alert). --- -## The consumer contract - -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 +## Who uses it - **[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 - "ready for discharge" alert. The reference implementation. -- **Institution: Justice** — rehabilitation. Its `reform` score is the disposition axis punishment + mental-illness conditions treatable (Rim Disorders' depression, anxiety, PTSD, and OCD, when that mod + is present), runs warden counselling sessions, and builds a recovery track toward a "ready for + 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 parole on the same shared engine, so the two systems agree on "getting better" instead of each 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. --- diff --git a/Wiki/corrections/Classification.md b/Wiki/corrections/Classification.md index 5f1d96e..ca637ee 100644 --- a/Wiki/corrections/Classification.md +++ b/Wiki/corrections/Classification.md @@ -4,29 +4,29 @@ 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 — -it answers the question vanilla never does: **who belongs where.** It reads the one shared -`CriminalRecord` every module writes to and turns it into a single verdict, so the crime system, the -search, the escape and the classifier all agree on how dangerous a pawn is, because they read the same -number. +it answers the question vanilla never does: **who belongs where.** It reads the one shared record every +part of the suite writes to and turns it into a single verdict, so the crime system, the search, the +escape and the classifier all agree on how dangerous a pawn is, because they read the same number. Grade drives everything downstream: search frequency, privileges, escape risk, and — critically — 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. --- ## 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 | |---|---|---| -| **Minimum** | `< 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. | -| **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. | +| **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. | +| **Maximum** | 0.80 – 1.49 | Repeat offender or proven flight risk. Steel, solitary door, frequent search. | +| **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. 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 -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: -``` -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 | +| What it counts | Adds to risk | 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. | -| `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. | -| `crimesCommitted` | `× 0.20` | The staple. Steady, cumulative. | -| `timesCaught` | `× 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. | -| `reform` (if > 0) | `− 0.4` | The only term that can *lower* the grade. At most −0.4 (reform capped at 1.0). | +| **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. | +| **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. | +| **Each crime committed** | +0.20 | The staple. Steady, cumulative. | +| **Each time caught** | +0.15 | Caught contraband is worse than merely suspected. | +| **Each contraband item made** | +0.10 | The lightest term — making a shiv is common; escaping with one is not. | +| **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: -- **`timesSearched` is not in the formula.** Searching a pawn does not make them more dangerous — - only *finding* something (`timesCaught`) does. Search freely; it costs the prisoner no grade. -- **Reform can shift at most 0.4 of risk.** At `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 - how thoroughly they reform — and therefore can **never be paroled**. A prolific offender is a +- **Searching a prisoner does not raise their risk.** Only *finding* something does — a search that + turns up nothing costs the prisoner no grade. Search freely. +- **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 or more can never fall to Medium (below 0.8) no + 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 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 -**Bram**, an Abrasive raider. Nature = `0.05 + 0.15 = 0.20`. He has three crimes, one escape attempt, -was caught twice, made two shivs, and has not been reformed (`reform = 0`). +**Bram**, an Abrasive raider, has a nature of 0.20. He has three crimes, one escape attempt, was caught +twice, made two shivs, and has not been reformed. Add it up: -``` -risk = 0.20 * 0.5 = 0.100 (nature) - + 1 * 0.35 = 0.350 (escape — the headline) - + 3 * 0.20 = 0.600 (crimes) - + 2 * 0.15 = 0.300 (caught) - + 2 * 0.10 = 0.200 (contraband) - - 0 * 0.4 = 0.000 (no reform) - ───────── - total = 1.550 → ≥ 1.5 → SUPERMAX -``` +| Source | Contribution | +|---|---| +| nature 0.20 × 0.5 | 0.100 | +| 1 escape × 0.35 | 0.350 (the headline) | +| 3 crimes × 0.20 | 0.600 | +| caught twice × 0.15 | 0.300 | +| 2 shivs × 0.10 | 0.200 | +| no reform | 0.000 | +| **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. -``` -risk = 1.550 - (0.40 * 0.4) = 1.550 - 0.160 = 1.390 → MAXIMUM -``` +But notice: his reform-*less* risk is 1.55, well above 1.2. Even at perfect reform (1.0) he lands at +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 -`reform = 1.0` he lands at `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. +Contrast **Cass**, a Kind pickpocket whose gentle nature floors her nature term at 0. She has one +crime, was caught once, no escapes, no contraband, no reform yet. Her risk is +(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 -escape, no contraband, `reform = 0`. - -``` -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. +Give Cass the standard treatment up to reform 0.30. That subtracts 0.30 × 0.4 = 0.12, dropping her to +**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. --- @@ -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 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 says they are. - **A clean nature is not a clean record.** A Kind pawn who has escaped twice still grades Maximum; disposition is only half the score. - **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 - pawn you let rack up escapes and catches has sentenced themselves. + 1.2 — mostly that means limiting how deep their record gets *before* you start treating them. A pawn + 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) 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.* diff --git a/Wiki/corrections/Discipline-and-Reform.md b/Wiki/corrections/Discipline-and-Reform.md index 082f73c..1ab8d4c 100644 --- a/Wiki/corrections/Discipline-and-Reform.md +++ b/Wiki/corrections/Discipline-and-Reform.md @@ -2,62 +2,57 @@ *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). -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 — - `RecordPunishment`, `+0.12`. See [Deterrence](Deterrence.md). -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 +1. **It deters the colony.** Order seen to be done raises the climate of order for everyone: any act of + 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 + disposition climbs for good. A corrective hand *rehabilitates* — reform rises, their disposition cools for good. -Both effects are Justice's own, so discipline is never inert. Prisoner Realism owns the passive *toll* -of isolation (a good sim of how solitary *feels*); Justice owns the *order* it produces and the -persistent *shift* it leaves on the pawn. When both mods are present, PR's solitary deterioration +Both effects belong to Corrections, so discipline is never inert. Prisoner Realism owns the passive +*toll* of isolation (a good sim of how solitary *feels*); Corrections owns the *order* it produces and +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. --- ## The two hands -``` -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 -``` +Every disciplinary act moves the prisoner's **reform** score, in one of two directions: -| Call | Reform delta | Meaning | +| The hand | Reform change | Meaning | |---|---|---| -| `Punish(pawn, harsh: true)` | **−0.15** | Beatings, deprivation, the hard hand. Hardens — "prisonization." | -| `Punish(pawn, harsh: false)` | **+0.10** | Correction, structure, rewarded good conduct. Rehabilitates. | +| **Harsh** | **−0.15** | Beatings, deprivation, the hard hand. Hardens — "prisonization." | +| **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 | -| `0.0` | Untouched — as they came in | -| `−1.0` | Fully hardened, institutionalized | +| **+1.0** | Fully rehabilitated | +| **0.0** | Untouched — as they came in | +| **−1.0** | Fully hardened, institutionalized | -Note the asymmetry the other way from Deterrence: **harsh (−0.15) moves faster than gentle (+0.10).** -It takes three corrective acts to build `+0.30` of reform; two harsh acts to tear down `−0.30`. Damage -is quicker than repair — as it should be. +Note the asymmetry: **harsh (−0.15) moves faster than gentle (+0.10).** It takes three corrective acts +to build +0.30 of reform; two harsh acts to tear down −0.30. Damage is quicker than repair — as it +should be. --- ## How reform becomes disposition -Reform is not a status effect or a mood buff. It is read straight back into `Propensity.Nurture` (in -Core), where it multiplies a pawn's whole disposition: +Reform is not a status effect or a mood buff. It feeds straight back into a pawn's **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: -``` -if reform != 0: - Nurture *= Clamp(1 - reform * 0.5, 0.4, 2) -``` - -| `reform` | Nurture factor | Effect | +| Reform | Disposition factor | Effect | |---|---|---| | −1.00 (hardened) | **1.500** | +50% more disposed — permanently | | −0.30 | 1.150 | +15% | @@ -67,78 +62,50 @@ if reform != 0: | +0.30 (parole threshold) | 0.850 | −15% | | +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 -safety rail that reform alone never reaches. **This is the whole trick.** Institutionalization and -recidivism are not a parallel mechanic bolted on the side — they are *one number the propensity engine -already reads*. A hardened pawn is not flagged "recidivist"; they simply carry a `−reform` that makes -every "would they?" roll for the rest of their life come up hot. Discipline and Parole are what *move* -reform; Nurture only *reads* it — which is how institutionalization becomes Justice's without a second -system. +Because reform is clamped between −1 and +1, the factor spans 0.5× to 1.5×. **This is the whole +trick.** Institutionalization and recidivism are not a parallel mechanic bolted on the side — they are +*one number the disposition already reads*. A hardened pawn is not flagged "recidivist"; they simply +carry a negative reform that makes every "would they?" roll for the rest of their life come up hot. +Discipline and Parole are what *move* reform; disposition only *reads* it — which is how +institutionalization emerges without a second system. --- ## Worked example — two sentences -Take a fresh prisoner, `reform = 0`, badly kept: mood `0.30`, held. From Core, before reform, their -Nurture is already `1 × 1.6 (mood) × 1.4 (held & unhappy) = 2.24`, and say the colony sits at baseline -so `DeterrenceFactor = 1.05`, giving Nurture `≈ 2.35`. +Take a fresh prisoner with reform 0, badly kept: mood 0.30, held. Before any reform, their disposition +is already running hot — a bad mood (×1.6) and a held-and-unhappy cell (×1.4) combine, and with the +colony at baseline the pawn sits around **2.35× disposition**. -**The hard hand.** You beat them into line — three harsh punishments: - -``` -reform: 0 → -0.15 → -0.30 → -0.45 -Nurture factor at reform -0.45 = 1 - (-0.45 * 0.5) = 1.225 -``` - -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 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. +This is prisonization: the sentence itself became the cause. (You did buy +0.36 of colony order 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 -their conditions (recreation, decent cell) so mood recovers to `0.55`: - -``` -reform: 0 → +0.10 → +0.20 → +0.30 -mood 0.55 → no mood multiplier, not held-&-unhappy → those factors drop out -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. +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 +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 -- **Harsh is a lever, not a punishment button.** It buys colony order *now* (`+0.12` climate) at the - price of the pawn's disposition *forever* (`−0.15` reform → hotter Nurture). Use it when you need the - deterrence 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. - Three gentle acts is the floor. -- **Damage compounds; repair is slow.** `−0.15` vs `+0.10` means a pawn you brutalize early is - expensive to bring back — and a hardened pawn commits more crime, which sinks the climate, which - makes *everyone* worse. Don't harden pawns you might want later. +- **Harsh is a lever, not a punishment button.** It buys colony order *now* (+0.12) at the price of the + pawn's disposition *forever* (−0.15 reform → hotter disposition). Use it when you need the deterrence + 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 or higher; only the corrective hand + 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 expensive to + bring back — and a hardened pawn commits more crime, which sinks the climate, which makes *everyone* + worse. Don't harden pawns you might want later. - **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 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.* diff --git a/Wiki/corrections/Home.md b/Wiki/corrections/Home.md index f867637..4e01666 100644 --- a/Wiki/corrections/Home.md +++ b/Wiki/corrections/Home.md @@ -5,9 +5,9 @@ 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 shared record 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 -prisoners the recreation need vanilla flatly denies them. +the prison into a regime you run** — it grades every held pawn from what they have actually done, +tracks a *reform* score that decides whether a sentence hardens or heals a prisoner, gates release on +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 | |---|---| -| **[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. | -| **[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. | -| **[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. | +| **[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, your execution stance sets the bar. | | **[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` -that **Institution: Core** holds — the same record Contraband, Policing, and the gang systems read. -Corrections authors the `reform` / `pardoned` columns via Discipline and Parole, and grades and gates -over all the counters its siblings feed. Reform and rehabilitation run on Core's shared **treatment -engine**, the same one Ward's psychiatric care uses. +Corrections keeps no crime history of its own. Every held pawn carries a single record that +**Institution: Core** maintains — the same record Contraband, Policing, and the gang systems all read +and write. Discipline and Parole write the *reform* and *pardon* onto it; classification grades and the +release gate read back over every counter its siblings feed in. Reform and rehabilitation run on Core's +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 - **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 inspect pane reads it) -- **Harmony** — required (Regime's prisoner-recreation patch) +- **Harmony** — required (Regime's prisoner-recreation feature) - Load **after** Core, Policing, and Harmony. --- diff --git a/Wiki/corrections/Parole.md b/Wiki/corrections/Parole.md index 002e5b4..8a0610a 100644 --- a/Wiki/corrections/Parole.md +++ b/Wiki/corrections/Parole.md @@ -2,78 +2,62 @@ *Release, driven by what the pawn has become.* -> This page is the release **decision** (`CanRelease`). The warden work that acts on it — the -> "Ready for parole" alert and the release job that frees a parolee back into the colony — is -> [Rehabilitation](Rehabilitation.md). +> This page is the release **decision**. The warden work that acts on it — the "Ready for parole" alert +> and the release that frees a parolee back into the colony — is [Rehabilitation](Rehabilitation.md). 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 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 -> sim Justice does not rebuild. Justice's mirror is the parolee who **stays** and may reoffend, -> carrying their record into the colony (a Justice follow-up). +> sim Corrections does not rebuild. Corrections' mirror is the parolee who **stays** and may reoffend, +> carrying their record into the colony. --- ## The gate -``` -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: +A prisoner can be paroled only when **all three** of these hold: | Condition | Threshold | Why | |---|---|---| -| Not already pardoned | `pardoned == false` | Release is a one-way flag; you can't re-parole. | -| Genuinely reformed | `reform ≥ 0.30` | 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. | +| Not already pardoned | never released before | Release is one-way; you can't re-parole. | +| Genuinely reformed | reform 0.30 or higher | Proof the sentence *healed* rather than hardened. Three corrective acts minimum. | +| 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 -else has to earn all three. +A pawn with **no record at all** is releasable trivially (there is nothing to hold them). Everyone else +has to earn all three. 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 -pawns whose record stayed shallow enough that treatment can pull them back under the ceiling. Let a -pawn rack up escapes and catches and you have quietly converted them into a lifer, whatever their -attitude. +pawns whose record stayed shallow enough that treatment can pull them back under the ceiling. Let a pawn +rack up escapes and catches and you have quietly converted them into a lifer, whatever their attitude. --- ## Release -``` -Release(pawn): - record.pardoned = true -``` - -`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. +Releasing a parolee marks a **pardon** on their record. Vanilla does the actual freeing; the pardon is +Corrections' memory that this pawn was let out after genuine reform, so reintegration can grant a clean +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. --- ## 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` | -| 2. Caught & held | [Classification](Classification.md) | A grade is assigned from the record | reads all counters | -| 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) | -| 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` | +| 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 every counter | +| 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 disposition) | +| 5. Regrade | [Classification](Classification.md) | Reform subtracts risk; grade falls | reads reform | +| 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* 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 -**Cass**, a Kind pickpocket. Nature `= clamp01(0.05 − 0.30) = 0`. She arrives with one crime, caught -once, no escapes, no contraband, `reform = 0`. +**Cass**, a Kind pickpocket whose gentle nature floors her nature term at 0. She arrives with one crime, +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. -``` -Grade at intake: -risk = 0 + 0 + (1*0.20) + (1*0.15) + 0 - 0 = 0.35 → MEDIUM -CanRelease? reform 0 < 0.30 → NO -``` +You give her the corrective hand three times (structure, rewarded conduct) and fix her cell so she +isn't stewing. Reform rises to 0.30, which subtracts 0.30 × 0.4 = 0.12 from her risk, dropping her to +**0.23 → Minimum.** Now all three gates are met — reform at least 0.30, Minimum is below Medium, and +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 -corrective hand three times (structure, rewarded conduct), and fix her cell so she isn't stewing: - -``` -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. +Contrast **Bram** from the Classification page, whose reform-less risk of 1.55 keeps him at Maximum even +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. --- ## A note on record cleanup -A pawn whose record is `IsBlank` — no crimes, no escapes, no contraband, no catches, `reform == 0`, -not pardoned — is dropped 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 -parolee who stays and reoffends still carries their history — Justice does not forget that they were +A pawn with a completely empty record — no crimes, no escapes, no contraband, no catches, no reform, no +pardon — is forgotten on save/load (Core prunes blank and dead-pawn records). A *pardoned* pawn is +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 — Corrections does not forget that they were once let out. --- ## Ideology-flavoured thresholds -Precepts layer on top of this baseline, not under it: an execution-favouring ideoligion paroles -rarely, a lenient one readily. `CanRelease` is the substrate-native baseline those precepts modulate — -the floor beneath the flavour, so the decision is coherent whether or not Ideology is installed. +Precepts layer on top of this baseline, not under it: an execution-favouring ideoligion paroles rarely, +a lenient one readily. The gate above is the baseline those precepts modulate — the floor beneath the +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 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. -- **Keep the record shallow if you want the option.** Every escape (`+0.35`) and catch (`+0.15`) you - let 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 +- **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 let + accumulate pushes a pawn toward the unparoleable side of the 1.2 line. +- **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. --- -## 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.* diff --git a/Wiki/corrections/Regime.md b/Wiki/corrections/Regime.md index 88a2a9e..ffacd3d 100644 --- a/Wiki/corrections/Regime.md +++ b/Wiki/corrections/Regime.md @@ -3,93 +3,62 @@ *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 -mood-driven disposition already flows through `Propensity.Nurture` — a neglected prisoner runs hotter -with no new machinery. So Regime does not rebuild any of that. It adds the single thing vanilla flatly -refuses to model: **recreation.** +mood-driven disposition already flows through the colony's underlying systems — a neglected prisoner +runs hotter with no new machinery. So Regime does not rebuild any of that. It adds the single thing +vanilla flatly refuses to model: **recreation.** --- ## What vanilla does, and what Regime flips -Vanilla denies prisoners the **Joy** (recreation) need outright — the need is flagged `colonistsOnly` -and `neverOnPrisoner`, so it simply never appears on a prisoner. A caged pawn has no recreation bar, -cannot take joy from anything, and never suffers for its absence. +Vanilla denies prisoners the **Joy** (recreation) need outright — the need is flagged colonists-only, so +it simply never appears on a prisoner. A caged pawn has no recreation bar, cannot take joy from +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 -**Prisoner Recreation** mod takes: - -```csharp -[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. +Regime flips exactly that one bit on — the same approach the good, popular **Prisoner Recreation** mod +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 +*adds* the need where vanilla withheld it — it never removes a need another mod granted. 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 -not the plumbing; it is what an empty bar now costs. +the rest (joy sources, the recreation bar, the mood effects of a full or empty one). The *value* is not +the plumbing; it is what an empty bar now costs. --- ## 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 -is the one that matters to the suite. Once a prisoner *has* the Joy need, they can be **starved** of -it, and vanilla's own machinery then does the work: +Giving prisoners recreation is not a mercy toggle — it is a **lever with two ends**, and the down end is +the one that matters to the suite. Once a prisoner *has* the Joy need, they can be **starved** of it, and +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. -``` -no recreation → Joy falls → mood falls → Propensity.Nurture rises → higher propensity -``` +Every one of those steps already exists. Regime only opens the first door; mood is wired to disposition +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 -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, a prisoner's mood pushes their disposition into these brackets: -Concretely, in Core's `Nurture`: - -| Prisoner condition | Nurture contribution | +| Prisoner condition | Disposition contribution | |---|---| -| Mood `< 0.20` (recreation-starved, at the floor of despair) | `× 2.5` | -| Mood `< 0.35` (badly kept) | `× 1.6` | -| Held **and** mood `< 0.40` (a badly run cell) | additional `× 1.4` | +| Mood below 0.20 (recreation-starved, at the floor of despair) | ×2.5 | +| Mood below 0.35 (badly kept) | ×1.6 | +| 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 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 -`ShouldHaveNeed` to return `true` for prisoner Joy — and because Regime's postfix only acts when -`__result` is still `false`, the two are simply *two postfixes that force the same `true`*. Whichever -runs first grants the need; the second sees it already granted and returns immediately. Running both is -harmless. Running either alone is sufficient. There is no double-need, no conflict, no load-order -sensitivity between them. +If you also run the **Prisoner Recreation** mod, nothing breaks. Both mods do the same thing — grant the +recreation need to prisoners — and because Regime only acts when the need isn't already granted, +whichever runs first grants it and the other sees it already done. Running both is 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: - -``` -[Institution: Justice] Regime: prisoner recreation enabled. -``` +Regime prints a short startup message so you can confirm it loaded and prisoner recreation is 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 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 -richer; the mechanical spine is already here in this one postfix. +they build on the same spine that this one recreation change put in place. When they land, they make a +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 that don't happen. - **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. - **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 @@ -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.* diff --git a/Wiki/corrections/Rehabilitation.md b/Wiki/corrections/Rehabilitation.md index cd1a0b8..1438cee 100644 --- a/Wiki/corrections/Rehabilitation.md +++ b/Wiki/corrections/Rehabilitation.md @@ -1,112 +1,108 @@ -# Rehabilitation — the post-arrest loop, wired +# Rehabilitation -*The warden work that finally drives discipline, reform, and parole. Source in -`Source/Justice/Corrections/`.* +*The warden work that drives discipline, reform, and parole.* -Classification, discipline, deterrence, and parole always had their **maths** — a grade over the -record, a reform score punishment moves, an order climate, a release decision. What they lacked was a -**trigger**: nothing in play called them. Reform never moved, so classification never re-graded and -parole never fired; the only thing that raised the colony's order was a punishment nothing invoked. -The post-arrest half of the loop was real code with no way to run it. +Classification, discipline, deterrence, and parole set the rules — a grade over the record, a reform +score punishment moves, an order climate, a release decision. Rehabilitation is what a warden actually +*does* day to day to move a prisoner through them. Without it, reform sits still, classification never +re-grades, and parole never fires; with it, the whole post-arrest arc comes alive. -Rehabilitation is that trigger. It gives corrections the same treatment Ward gave psychiatric care — -a **prisoner interaction mode**, **warden jobs**, an **alert**, and an inspect readout — so a warden -actually reforms a prisoner over time and paroles them when they have turned a corner. +It gives corrections the same shape Ward gives psychiatric care — a **prisoner interaction mode**, +**warden work**, an **alert**, and an **inspect readout** — so a warden reforms a prisoner over time and +paroles them when they have turned a corner. --- ## 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. | 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 | 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* -eligible is simply not acted on — the warden waits. +for a hands-off "reform, then release" pipeline. A prisoner flagged for parole who is *not yet* eligible +is simply not acted on — the warden waits. --- ## A rehabilitation session -A warden walks to the prisoner and counsels them for ~1250 ticks (about 20 in-game minutes), Social -being the active skill. On completion (never on interruption — the credit is a follow-on toil), one -session does three things, in `CorrectionsUtility.RunReformSession`: +A warden walks to the prisoner and counsels them for about 20 in-game minutes, with Social as the +active skill. On completion (never on interruption — you only earn credit for a finished session), one +session does three things: -1. **Corrective discipline** — `Discipline.Punish(prisoner, harsh: false)`: reform **+0.10**, and the - punishment is recorded, which raises the colony's **order** by `+0.12`. This is the deterrence-up - half that punishment alone never triggered — visible corrective justice deters everyone else. -2. **The reform track** — `TreatmentProgram.AdvanceRecovery` advances `Institution_Reforming` on - 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*. +1. **Corrective discipline** — a gentle disciplinary act: reform **+0.10**, and it raises the colony's + **order** by **+0.12**. This is the deterrence-up half that punishment alone never triggered — + visible corrective justice deters everyone else. +2. **The reform bar** — the visible progress bar fills, by **0.34 × cell quality × skill** per session, + and it **decays −0.08/day**, so rehabilitation has to be *sustained*. 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 | -| cell quality | `0.5 … 1.5` | `TreatmentProgram.FacilityQuality` (room impressiveness) | -| reform / session | `+0.10` | `Discipline.Punish` (fixed) | -| order / session | `+0.12` | `Justice.RecordPunishment` | +| skill factor | 0.5 … 1.0 | the warden's Social level | +| cell quality | 0.5 … 1.5 | the cell's impressiveness | +| reform per session | +0.10 | fixed | +| order per session | +0.12 | fixed | -Reform reaches the parole bar (`≥ 0.30`) in roughly **three completed sessions**, faster than a bad -cell or a poor counsellor fills the visible track — so the "Ready for parole" alert keys off the real -gate (`Parole.CanRelease`), not the bar. +Reform reaches the parole bar (0.30) in roughly **three completed sessions** — faster than a bad cell or +a poor counsellor fills the visible bar. So the "Ready for parole" alert keys off the real gate, not the +bar you see filling. --- ## Neglect — the teeth 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 -days** of neglect, reform falls `−0.15/day` and they resent it (a mood hit). The hardening lowers -reform *directly*, not through `Discipline.Punish` — neglect is passive prisonization, not an act of -visible justice, so unlike a session it must **not** raise the colony's order. +staff does. Once more than **two days** pass since the last real session, reform falls **−0.15/day** and +they resent it (a mood hit). This hardening lowers reform *directly*, not as an act of visible justice — +neglect is passive prisonization, so unlike a session it does **not** raise the colony's order. -Attend the prisoner and the clock resets; flag them and walk away and they slide back toward the -record that put them in. "A reform programme you don't staff is worse than none." +Attend the prisoner and the clock resets; flag them and walk away and they slide back toward the record +that put them in. "A reform programme you don't staff is worse than none." --- ## Parole — the release that stays -When a rehabilitated prisoner clears `Parole.CanRelease` — reform `≥ 0.30`, security grade -`≤ Medium`, not already pardoned — the **Ready for parole** alert names them. A warden set to Parole -then processes the release (`CorrectionsUtility.ReleaseOnParole`): +When a rehabilitated prisoner clears the parole gate — reform 0.30 or higher, security grade Medium or +below, not already pardoned — the **Ready for parole** alert names them. A warden set to Parole then +processes the release: - marks the **pardon** on the record; -- clears the prisoner hold so a former colonist is a **free colonist again** — a parolee who *stays* - in the colony carrying their record, not vanilla's release that ejects them from the map; +- clears the prisoner hold so a former prisoner becomes a **free colonist again** — a parolee who + *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). -The parolee keeps their `CriminalRecord`, so if they reoffend the [policing](Policing.md) loop catches -them again — recidivism, closing back onto the start. +The parolee keeps their record, so if they reoffend the [policing](Policing.md) loop catches them again +— recidivism, closing back onto the start. --- ## Discipline — the other lever 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* -them — reform falls — yet raises the colony's order (visible harsh justice deters the rest), and it -stings: a mood hit and an on-edge *in solitary* status. It fires at most once per term (the status -gates re-discipline for about two days), so it holds order without cratering reform in a single day. -The tension is the point: lean on discipline to keep order and you make the punished harder to reform. +**Discipline** and a warden puts them in **solitary**: harsh discipline *hardens* them — reform falls — +yet raises the colony's order (visible harsh justice deters the rest), and it stings: a mood hit and an +on-edge *in solitary* status. It fires at most once per term (the status blocks re-discipline for about +two days), so it holds order without cratering reform in a single day. The tension is the point: lean on +discipline to keep order and you make the punished harder to reform. ## 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 leaving you to guess. - **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 - grade of the nearest marker in range, so you tag a corridor once rather than cluttering every cell. A + (Minimum..Supermax, set with a gizmo). One marker covers a *whole block* — a prisoner takes the grade + 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 - 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. - **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, @@ -117,9 +113,9 @@ Classification always computed a **security grade** (`Minimum`..`Supermax`); now 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 - (with the prisoner Joy need Regime enables, they head out to a rec area), **Lockup** confines them to - the cell and stews their mood. The timetable itself is **Prison Labor's** — this reads it when PL is - present and is inert without it, so PL stays compatible: read, never fought. + (with the prisoner recreation need Regime enables, they head out to a rec area), **Lockup** confines + them to the cell and stews their mood. The timetable itself is **Prison Labor's** — Corrections reads + 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 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. @@ -128,28 +124,27 @@ Beyond punishment and reform, the held have a **daily life**: ## Seeing it -Every colony prisoner's inspect pane now carries the corrections readout (a Harmony postfix on -`Pawn.GetInspectString`, via Justice's existing Regime patch — not a contested method): +Every colony prisoner's inspect pane now carries the corrections readout: > *Security grade: Maximum* · *In solitary* · *Rehabilitation: reforming (40%) — neglected (3d)* · > *reformed — ready for parole* -The **grade** shows for every held pawn (classification's information pillar); *in solitary* while a -discipline term runs; the **rehabilitation** line for a pawn set to reform. And because the colony's -**order climate** otherwise has no readout at all, the same pane surfaces it when it is far enough from -ordinary to matter — *Colony order: low (28%) — crime pays here* / *high (71%) — order holds*. +The **grade** shows for every held pawn; *in solitary* while a discipline term runs; the +**rehabilitation** line for a pawn set to reform. And because the colony's **order climate** otherwise +has no readout at all, the same pane surfaces it when it is far enough from ordinary to matter — +*Colony order: low (28%) — crime pays here* / *high (71%) — order holds*. --- ## What this closes -With rehabilitation wired, the whole post-arrest arc runs in play: **discipline** moves reform, -**classification** re-grades as reform rises, **deterrence** climbs on visible justice (not just -falls on crime), **parole** releases the reformed, and Core's **treatment engine** carries the reform -track the same way it carries Ward's recovery. The loop closes back onto disposition — catching, -reforming, and releasing one pawn moves the climate every other pawn is judged against. +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 falls +on crime), **parole** releases the reformed, and Core's **treatment** carries the reform bar the same +way it carries Ward's recovery. The loop closes back onto disposition — catching, reforming, and +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. --- diff --git a/Wiki/gangs/Affiliations-and-Segregation.md b/Wiki/gangs/Affiliations-and-Segregation.md index 89c95e7..eee2882 100644 --- a/Wiki/gangs/Affiliations-and-Segregation.md +++ b/Wiki/gangs/Affiliations-and-Segregation.md @@ -3,48 +3,33 @@ *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 -contains: shared faction, shared faith, or real friendship. This page covers the formation rule — what -`SharesAffiliation` checks and why the *same room* requirement matters — and then the segregation -counter-play that turns those same bonds against the network. +contains: shared faction, shared faith, or real friendship. This page covers the formation rule — which +bonds count and why the *same room* requirement matters — and then the segregation counter-play that +turns those same bonds against the network. ## The formation rule -On each 5000-tick check, the gang component walks the eligible `crew` (every spawned humanlike that -currently meets the `Nature × Nurture ≥ 0.9` join bar) and tries to pair each unaffiliated member with -a mate: - -``` -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) -``` +On each 5000-tick pass, the game walks everyone currently over the join bar (Nature × Nurture ≥ 0.9) +and tries to pair each unaffiliated pawn with a mate. A pawn pairs up with someone who is, at that +moment, **in the same room** *and* someone they **already share a bond with**. 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 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 -fresh gang id is minted. So gangs accrete — a new member pairing with an existing member is absorbed +Pairing binds them together: if either already runs with a gang, the other joins that gang; otherwise a +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. ### 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 | |---|---|---| -| **Same faction** | `a.Faction == b.Faction` (non-null) | 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` | -| **Friendship** | `OpinionOf(b) >= 20` | a real positive relationship, not mere acquaintance | +| **Same faction** | both belong to the same (non-empty) faction | your colonists share one; captured raiders share theirs | +| **Same ideoligion** | both follow the same ideoligion, only if the Ideology DLC is active | needs Ideology enabled | +| **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 > 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 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 -[Rivalry and Fights](Rivalry-and-Fights.md) page, members of two different gangs are automatically -**rivals**, and rival-gang violence between them is booked as a crime. +along its own faction line — and now you have **two gangs**. As the +[Rivalry and Fights](Rivalry-and-Fights.md) page explains, members of two different gangs are +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 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 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 -(`CanReach(..., Touch, Danger.Deadly)`). Break the path and you break the network: +Passing contraband requires two things: the pawns must be in the same gang, **and** two pawns both on +the map must have a walkable path between them. Break the path and you break the network. -``` -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 design 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 > 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 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 -are still the same crew, still rivals of the other crew — but it can no longer pass a shiv from a -holder to a have-not. **A network that cannot reach itself is a network that cannot supply itself.** +them, fail the reach test on every network pass. The gang still *exists* — they are still the same crew, +still rivals of the other crew — but it can no longer pass a shiv from a holder to a have-not. **A +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 -you *who to keep apart*, and the reach requirement (`CanReach`) makes keeping them apart actually -starve the supply chain. You do not disband a gang; you **partition the graph** until its edges carry -nothing. +This is the elegant part of the design: the very bonds that formed the gang tell you *who to keep +apart*, and the reach requirement makes keeping them apart actually starve the supply chain. You do not +disband a gang; you **cut it off from itself** until nothing can move along it. ## How to play it - **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. -- **Segregate by Classification, not by hope.** Putting rivals in "different areas" is not enough — the - reach test cares about a *walkable path*. A shared corridor is a supply line. Use genuinely separate, - walled wings. +- **Segregate by Classification, not by hope.** Putting rivals in "different areas" is not enough — + resupply only needs a *walkable path* between two gangmates. A shared corridor is a supply line. Use + genuinely separate, walled wings. - **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 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.* -** diff --git a/Wiki/gangs/Home.md b/Wiki/gangs/Home.md index e6ed524..8ac3867 100644 --- a/Wiki/gangs/Home.md +++ b/Wiki/gangs/Home.md @@ -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 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 gang. The two systems are complementary — run both. Ringleader tells you *who stirs the pot*; Gangs tells you *how the shivs get around*. @@ -36,7 +36,7 @@ dependencies: | 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 | | *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 -This is the thesis, and it is enforced in the numbers, not just the flavour. Membership is gated at -`Nature × Nurture ≥ 0.9` — a high bar on purpose. A disposed pawn who is **well-kept and -well-policed** does not band up: their nurture multiplier stays low, and deterrence pulls it lower -still. It takes a foul streak (nature) *and* a badly-run situation (nurture) at the same time. +This is the thesis, and it is enforced in the numbers, not just the flavour. Membership is gated at a +Nature × Nurture score of **0.9 or higher** — a high bar on purpose. A disposed pawn who is +**well-kept and well-policed** does not band up: their nurture multiplier stays low, and deterrence +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, 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 -- **[Joining](Joining.md)** — `WouldJoin = Nature × Nurture ≥ 0.9`: why the bar is high, why good - treatment and deterrence keep pawns *out*, and how the 5000-tick check works for every kind of pawn. -- **[Networks and Smuggling](Networks-and-Smuggling.md)** — `MoveWithin`: how a holder resupplies a - needy gangmate, the *same-gang + can-reach* rule, why this defeats a single search, and how a bent - warden refills a starved network from outside. -- **[Rivalry and Fights](Rivalry-and-Fights.md)** — `AreRivals` and `RecordFight`: how a gang fight is - booked as a crime through Justice, why inside-the-wire and out-on-the-street are the same offence, - and how it feeds deterrence. -- **[Affiliations and Segregation](Affiliations-and-Segregation.md)** — `SharesAffiliation` plus - *same room* as the formation rule, why rival factions become rival gangs, and how Classification's - segregation is the counter-play that starves a network which cannot reach itself. +- **[Joining](Joining.md)** — why the join bar (Nature × Nurture ≥ 0.9) is high, why good treatment and + deterrence keep pawns *out*, and how membership is re-checked for every kind of pawn. +- **[Networks and Smuggling](Networks-and-Smuggling.md)** — how a holder resupplies a needy gangmate, + the *same-gang-and-can-reach* rule, why this defeats a single search, and how a bent warden refills a + starved network from outside. +- **[Rivalry and Fights](Rivalry-and-Fights.md)** — how a gang fight is booked as a crime through + Justice, why inside-the-wire and out-on-the-street are the same offence, and how it feeds deterrence. +- **[Affiliations and Segregation](Affiliations-and-Segregation.md)** — shared faction, faith, or + friendship plus *same room* as the formation rule, why rival factions become rival gangs, and how + Classification's segregation is the counter-play that starves a network which cannot reach itself. ## The suite 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: Justice** — Classification, Deterrence, Discipline, Parole, Regime. - **Institution: Gangs** — this mod: joining, networks/smuggling, rivalry/fights. -Sibling projects: **Foul Play** (the vessel/substance framework and the "Piss Nuke") and **Ward** (the -test harness and a ward/treatment prison mode). +Sibling projects: **Foul Play** (the vessel/substance framework and the "Piss Nuke") and **Ward** (a +ward/treatment prison mode). 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.* -** diff --git a/Wiki/gangs/Joining.md b/Wiki/gangs/Joining.md index 197f306..a70b16e 100644 --- a/Wiki/gangs/Joining.md +++ b/Wiki/gangs/Joining.md @@ -3,48 +3,39 @@ *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 -and their circumstances line up at once** — the same `Nature × Nurture` engine that drives every other -behaviour in the Institution suite. This page covers the exact test, why the bar is set where it is, -and how you keep pawns on the right side of it. +and their circumstances line up at once** — the same Nature × Nurture propensity that drives every +other behaviour in the Institution suite. This page covers the exact test, why the bar is set where it +is, and how you keep pawns on the right side of it. ## The rule -``` -WouldJoin(pawn) == Propensity.Nature(pawn) * Propensity.Nurture(pawn) >= JoinThreshold -JoinThreshold = 0.9 -``` +A pawn joins when their **Nature multiplied by their Nurture reaches 0.9 or higher**. Only humanlike +pawns are ever eligible — animals and mechs never join. -Two gates before the maths even runs: - -| Guard | Effect | -|---|---| -| `pawn == null` | never joins | -| 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.) +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 +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 +with no randomness.) ### The two halves 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: -- **Nature** (`0..1`) is *who the pawn is*: a base of `0.05`, plus trait weights, clamped to `[0, 1]`. - Psychopath `+0.45`, Bloodlust `+0.35`, Kleptomaniac `+0.40`, Greedy `+0.25`, Abrasive `+0.15`; - and it goes **down** for Kind `−0.30` or Ascetic `−0.15`. This is fixed at pawn creation and barely - moves. -- **Nurture** (a multiplier, `~≥ 0.4`, base `1.0`) is *the situation you put them in*: a floored mood - multiplies it up hard (roughly `×2.5` under 0.20 mood, `×1.6` under 0.35), being held while - miserable adds more (`×1.4`), a hardened record (negative reform) raises it, and — critically — - **deterrence pulls it down** (`×Lerp(1.3, 0.7, deterrence)`, neutral `1.0` at baseline order, so a - high-deterrence colony scales nurture toward `0.7`). This is the half you control. +- **Nature** (`0` to `1`) is *who the pawn is*: a base of `0.05`, plus trait weights, floored at `0` + and capped at `1`. Psychopath `+0.45`, Bloodlust `+0.35`, Kleptomaniac `+0.40`, Greedy `+0.25`, + Abrasive `+0.15`; and it goes **down** for Kind `−0.30` or Ascetic `−0.15`. This is fixed at pawn + creation and barely moves. +- **Nurture** (a multiplier, roughly `0.4` at the floor, `1.0` at baseline) is *the situation you put + them in*: a floored mood multiplies it up hard (roughly `×2.5` under 0.20 mood, `×1.6` under 0.35), + being held while miserable adds more (`×1.4`), a hardened record (negative reform) raises it, and — + critically — **deterrence pulls it down** (toward about `0.7` in a high-deterrence colony; a neutral, + baseline order sits at `1.0`). This is the half you control. ## 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 > — 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? | |---|---|---|---|---| -| **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**, 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 | @@ -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 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 -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. - -`WouldJoin` reads nothing about a pawn's secured status. A disposed **free colonist** can run with a +Membership pays no attention to 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 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 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: -``` -CheckInterval = 5000 ticks -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 +1. Gathers everyone on the map who currently meets the Nature × Nurture ≥ 0.9 join bar. +2. Pairs up unaffiliated members who **share a room and a bond** into gangs (see [Affiliations and Segregation](Affiliations-and-Segregation.md)). 3. Runs one round of network resupply inside each gang (see [Networks and Smuggling](Networks-and-Smuggling.md)). -Because the check re-reads `WouldJoin` every time, membership is *live*: a pawn whose situation improves -past the point where the product drops back under `0.9` simply stops being eligible to form new bonds. -The bar is not a one-time gate at recruitment — it is a standing condition the colony is continuously -graded against. +Because the join bar is re-checked from scratch every pass, membership is *live*: a pawn whose situation +improves until the product drops back under `0.9` simply stops being eligible to form new bonds. The +bar is not a one-time gate at recruitment — it is a standing condition the colony is continuously graded +against. ## 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.* -** diff --git a/Wiki/gangs/Networks-and-Smuggling.md b/Wiki/gangs/Networks-and-Smuggling.md index 6de95ae..497f97e 100644 --- a/Wiki/gangs/Networks-and-Smuggling.md +++ b/Wiki/gangs/Networks-and-Smuggling.md @@ -3,40 +3,33 @@ *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 -serve or to break the network described here. A gang is not a mood aura; it is a **logistics graph**, -and its edges carry contraband. +serve or to break the network described here. A gang is not a mood aura; it is a **supply chain**, and +contraband flows along it. ## The move -The heart of it is one method, `MoveWithin(from, to)`: a gangmate who is holding a stash passes a piece -of it to a gangmate who has none. It returns `true` if something actually moved. +The heart of it is a single transfer: a gangmate who is holding a stash passes one piece of it to a +gangmate who has none. Here is everything that has to be true for a piece to move: -``` -MoveWithin(from, to): - 1. both non-null, and SameGang(from, to) -- else false - 2. if both are Spawned: from must CanReach(to, -- the reach gate - PathEndMode.Touch, Danger.Deadly) -- else false - 3. a Contraband tracker must exist on the map -- else false - 4. 'from' must have at least one concealed item -- else false - 5. take stash[0]: - tracker.Confiscate(from, item.def) -- leaves the supplier's hands - tracker.Conceal(to, item.def) -- arrives, hidden, in the customer's - return true -``` +- both pawns are in the **same gang**; +- if both are on the map, the supplier has a **walkable path** to the customer (touch range, willing to + cross deadly danger to get there); +- there is contraband in play on the map at all; +- the supplier is actually holding at least one concealed item. + +When all of that holds, the supplier's first concealed item leaves their hands and arrives — still +hidden — on the customer. A few things worth reading carefully: -- **One item per move.** It takes `stash[0]` — the first concealed item on the supplier — and moves - exactly that. It is a redistribution, not a duplication: the item leaves `from` (`Confiscate`) and - arrives concealed on `to` (`Conceal`). The gang's total stash is unchanged; only *who holds it* - changes. -- **Same gang, always.** `SameGang` requires both pawns to hold the same non-zero gang id. There is no - smuggling to a stranger — the network only moves along membership. -- **The reach gate only applies to co-located pawns.** The `CanReach` check is guarded by - `from.Spawned && to.Spawned`. Two pawns both physically on the map must have a walkable path - (`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. +- **One item per move.** It moves the first concealed item on the supplier, and exactly that. It is a + redistribution, not a duplication: the item leaves the supplier and arrives concealed on the customer. + The gang's total stash is unchanged; only *who holds it* changes. +- **Same gang, always.** Both pawns must belong to the same gang. There is no 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 + have a walkable path between them. If a member is off the active map, the reach check is skipped — + which is what allows a gang to reach across the wall to a member who is not on the map right now. ## 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 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 > 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 -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 -You do not call `MoveWithin` by hand; the gang component does it on the 5000-tick check. After forming -gangs for the pass, it runs one resupply round **per gang**: - -``` -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) -``` +You never trigger a transfer by hand; the game runs it automatically on the 5000-tick pass. After +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. 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 -a scattershot search miss it. (Only members who currently meet the join bar participate in this -automatic pass; the resupply loop draws from the same `crew` used for formation.) +a scattershot search miss it. (Only members who currently meet the join bar take part in this automatic +pass.) ## 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 -*into* the prison — the outside man passes to the inside man. The mod's integration test builds exactly -this: a free psychopath colonist and a prisoner in one gang, the stash on the free member, and -`MoveWithin(free, prisoner)` succeeds — after which the prisoner is hiding contraband and the free -colonist is not. Your prison's contraband problem is not sealed inside your prison. +*into* the prison — the outside man passes to the inside man. A free colonist and a prisoner can be in +one gang with the stash on the free member; the transfer succeeds, and afterwards the prisoner is hiding +contraband and the colonist is not. Your prison's contraband problem is not sealed inside your prison. ## Resupply from outside: the bent warden -`MoveWithin` 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 +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 — there is nothing left to pass around? The 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 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 -directly. That single seeded item is then all the network needs: one holder, and the 5000-tick pass -spreads it back out across everyone who can reach. +contraband in to a prisoner*, planting a concealed item directly. That single planted item is then all +the network needs: one holder, and the next 5000-tick pass spreads it back out across everyone who can +reach. So the two halves of the supply picture are: | Mechanism | Owner | What it does | |---|---|---| -| `MoveWithin` | **Gangs** | moves existing contraband *between* gangmates who can reach each other | -| `Corruption.Smuggle` | **Contraband** | injects *new* contraband from outside via a corruptible warden | +| Passing contraband between gangmates | **Gangs** | moves existing contraband *between* gangmates who can reach each other | +| 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 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 routes around. - **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. - This is the primary counter-play; see + reach requirement is the lever — a network whose members cannot walk to each other cannot pass + anything. This is the primary counter-play; see [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 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.* -** diff --git a/Wiki/gangs/Rivalry-and-Fights.md b/Wiki/gangs/Rivalry-and-Fights.md index b7edd8b..5b8dae3 100644 --- a/Wiki/gangs/Rivalry-and-Fights.md +++ b/Wiki/gangs/Rivalry-and-Fights.md @@ -9,27 +9,22 @@ suite can *see* and *police*. ## Who is a rival -``` -AreRivals(a, b): - 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: +The rule is exactly as blunt as it sounds: **two pawns in two different gangs are rivals.** Both must +actually be in a gang, and it must be a *different* gang for each. As the design puts it: > 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 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: -- **Non-members are nobody's rival.** A pawn with gang id `0` — everyone in a healthy colony — is never - a rival to anyone, because `AreRivals` requires both ids non-zero. No gangs, no rivalry. -- **Same gang, never rivals.** Members of one crew fail the `ga != gb` test. Within a gang there is no - rivalry to book; there is the network (see [Networks and Smuggling](Networks-and-Smuggling.md)). +- **Non-members are nobody's rival.** A pawn in no gang — everyone in a healthy colony — is never a + 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 are in the *same* gang, so they are not rivals. + 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 — 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 -The payoff of tracking rivalry is `RecordFight`. When a rival-gang attack happens, it is not treated as -generic brawling — it is booked as a crime through the same Justice pipeline as any other offence. +The payoff of tracking rivalry is what happens when rivals fight. When a rival-gang attack happens, it +is not treated as generic brawling — it is booked as a crime through the same Justice pipeline as any +other offence. -``` -RecordFight(attacker, victim): - if attacker == null || victim == null: return - if !AreRivals(attacker, victim): return -- only rival-gang violence counts - Justice.RecordCrime(attacker) -``` +Only rival-gang violence counts: the two pawns must be in different gangs for anything to be recorded. +A scuffle between gangmates, or between two non-members, is not a *gang* fight and is not booked. When +it *is* rival-gang violence, the whole event is recorded as a crime against the **attacker**, and that +does three things at once (they live in Institution: Justice, but this is what Gangs is leaning on): -So the guard is precise: the two pawns must be **rivals** (different gangs) for anything to be recorded. -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 | +| Effect of booking a gang fight | Where it lands | |---|---| -| `crimesCommitted` on the attacker's record goes up | Core's `CriminalRecord` | -| `lastCrimeTick` is stamped to now | Core's `CriminalRecord` | -| the colony's **deterrence** nudges **down** (a crime happened; order slipped) | Justice's `MapComponent_Deterrence` | - -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. +| the attacker's crime count goes up | their criminal record (Core) | +| 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 deterrence system | ## 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 > 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. -There is no separate code path for a prison-yard shanking versus a colonists' brawl in the dining -room. `RecordFight` reads only `AreRivals` and calls `Justice.RecordCrime`. A free colonist who is a -gang member attacking a rival is booked identically to a prisoner doing the same in a cell block. The -gang system does not care which side of the wall the violence is on — only that it was between rivals. +There is no separate rule for a prison-yard shanking versus a colonists' brawl in the dining room. A +fight is booked purely on whether the two pawns are rivals. A free colonist who is a gang member +attacking a rival is booked identically to a prisoner doing the same in a cell block. The gang system +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 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: -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. -3. Lower deterrence *raises* the nurture multiplier for **every** disposed pawn on the map - (deterrence scales nurture via `×Lerp(1.3, 0.7, deterrence)` — less deterrence, bigger multiplier). -4. Higher nurture pushes more pawns over the `Nature × Nurture ≥ 0.9` join bar (see +3. Lower deterrence *raises* the nurture multiplier for **every** disposed pawn on the map (less + deterrence means a bigger multiplier). +4. Higher nurture pushes more pawns over the Nature × Nurture ≥ 0.9 join bar (see [Joining](Joining.md)). 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 -brake is the other half of Justice — **punishment raises deterrence back up** (`RecordPunishment` -nudges it up), which lowers nurture, which drops borderline pawns back under the bar. The integration -test checks exactly this seesaw: a punishment raises the deterrence level, and a subsequent crime -lowers it again. +brake is the other half of Justice — **punishment raises deterrence back up**, which lowers nurture, +which drops borderline pawns back under the bar. The seesaw works both ways: punishing a pawn raises the +deterrence level, and a fresh crime lowers it again. ## 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.* -** diff --git a/Wiki/policing/Deterrence.md b/Wiki/policing/Deterrence.md index 19b70b5..2fbda87 100644 --- a/Wiki/policing/Deterrence.md +++ b/Wiki/policing/Deterrence.md @@ -2,22 +2,22 @@ *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 -Justice reacts to one pawn: this reactor's *state* is the whole colony, and it feeds **back** into -every pawn's disposition. Crime that goes unanswered emboldens everyone a little; visible justice -deters everyone a little. The reason to punish is not just this prisoner — it is the message it sends -the rest, and here that message is a number they all read. +This is the part that makes policing **govern** rather than merely clean up. Everything else in Justice +reacts to one pawn: this reacts to the *whole colony*, and it feeds **back** into every pawn's +disposition. Crime that goes unanswered emboldens everyone a little; visible justice deters everyone a +little. The reason to punish is not just this prisoner — it is the message it sends the rest, and here +that message is a number they all read. --- ## 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) | -| **Baseline** | `0.5` — an ordinary colony | +| **Range** | 0.0 (lawless) … 1.0 (iron) | +| **Baseline** | 0.5 — an ordinary colony | | **Below baseline** | crime pays; dispositions run **hot** | | **Above baseline** | order holds; dispositions run **cool** | @@ -25,50 +25,40 @@ It is nudged by two events and, left alone, forgets. ### The nudges -| Event | Source | Nudge | Written by | -|---|---|---|---| -| A crime, unanswered | `Justice.RecordCrime(perp)` | **−0.08** | Justice, Gangs (fights) | -| Visible justice done | `Justice.RecordPunishment(map)` | **+0.12** | Justice (`Discipline.Punish`) | +| Event | Nudge | +|---|---| +| A crime, unanswered | **−0.08** | +| 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* 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. -`RecordCrime` also writes the record — it increments `crimesCommitted` and stamps `lastCrimeTick` on -the perpetrator — so the same event that moves the climate is the event classification and parole -later see. `RecordPunishment` moves only the climate; the *record* side of punishment (the reform -shift) is [Discipline](Discipline-and-Reform.md)'s job. +The same crime that moves the climate also marks the culprit's record — the offence that shifts the +colony's mood is the one classification and parole will remember later. A punishment moves only the +climate; the reform side of a punishment — how the offender themselves changes — is +[Discipline](Discipline-and-Reform.md)'s job. ### The drift -``` -every 2500 ticks: level = MoveTowards(level, 0.5, 0.0015) -``` +Memory fades. With nothing happening, the climate creeps back toward ordinary at about **0.0015 every +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, -one step every 2500 ticks** — which works out to: +**drift per in-game day ≈ 0.036** -``` -drift per in-game day = (60000 / 2500) * 0.0015 = 24 * 0.0015 = 0.036 / day -``` - -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. +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 -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: -``` -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 | +| Order level | Factor | Effect on every pawn's disposition | |---|---|---| | 0.00 (lawless) | **1.300** | +30% hotter — crime pays, everyone feels it | | 0.30 | 1.120 | +12% | @@ -79,55 +69,42 @@ Nurture(pawn) = ... * DeterrenceFactor(pawn) Two things to notice: -- **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 - ran hot (as an earlier tuning did), ordinary order would nudge propensity up, breed a little more - crime, drop order, and feed a slow runaway. Neutral-at-baseline means only a colony that lets order - *slide below* ordinary earns 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* - nurture at once. This is the multiplier that makes catching one pawn matter to the disposition 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 + ran hot, ordinary order would nudge propensity up, breed a little more crime, drop order, and feed a + slow runaway. Neutral-at-baseline means only a colony that lets order *slide below* ordinary earns + 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* + disposition at once. This is the multiplier that makes catching one pawn matter to the disposition of the rest. -### PolicingBootstrap — reconnecting the loop across the mod boundary +### When it applies -Core keeps `Propensity.DeterrenceFactor` as a neutral seam — a `Func` that returns `1f` -until something fills it. Core alone never has to know Justice exists; that one seam is what lets Core -stay a dependency-free leaf while the deterrence loop runs *through* it. - -At startup, `PolicingBootstrap` fills the seam: - -```csharp -Propensity.DeterrenceFactor = pawn => { - var d = pawn?.Map?.GetComponent(); - 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`.) +This bending of behaviour only happens while **Policing is installed**. Core on its own reads a flat, +neutral 1.0 — colonists are indifferent to order — and only Policing wires the climate into their +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. --- ## 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 | | 1 | Prisoner A shanks a guard (crime, unanswered) | 0.42 | 1.106 | | 2 | Prisoner B foments trouble (crime, unanswered) | 0.34 | 1.162 | -| 3 | Warden punishes A (`RecordPunishment`) | 0.46 | 1.078 | -| 4 | Warden punishes B (`RecordPunishment`) | 0.58 | 0.994 | +| 3 | Warden punishes A | 0.46 | 1.078 | +| 4 | Warden punishes B | 0.58 | 0.994 | | 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` — -**above** where it started — because punishment out-answers crime. Every pawn's disposition dipped -hotter as the crimes landed (`1.05 → 1.16`) and then cooled below neutral once order was reasserted -(`0.994`). By morning the drift has begun erasing the gain, and if nothing keeps the pressure on, the -colony sinks back to its slightly-hot baseline over the next couple of weeks. +Two crimes cost −0.16; two punishments returned +0.24. The colony ends the day at 0.58 — **above** +where it started — because punishment out-answers crime. Every pawn's disposition dipped hotter as the +crimes landed (1.05 → 1.16) and then cooled below neutral once order was reasserted (0.994). By morning +the drift has begun erasing the gain, and if nothing keeps the pressure on, the colony sinks back to its +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 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 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. -- **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. -- **Push past baseline if you want calm.** Neutral disposition needs `level ≈ 0.571`; ordinary - (`0.5`) still runs 5% hot. A steady rhythm of caught-and-punished offences is what holds you there. -- **A lawless spell is self-feeding.** At `level 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().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). +- **Push past baseline if you want calm.** Neutral disposition needs an order level of about 0.571; + ordinary (0.5) still runs 5% hot. A steady rhythm of caught-and-punished offences is what holds you + there. +- **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. --- diff --git a/Wiki/policing/Home.md b/Wiki/policing/Home.md index f861683..a30138a 100644 --- a/Wiki/policing/Home.md +++ b/Wiki/policing/Home.md @@ -4,8 +4,8 @@ RimWorld 1.6 mods.* 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 -seeded propensity spectrum, the law catches them, and catching one changes the disposition of the rest. +gives the colony a **climate of law and order**: colonists offend on a spectrum of criminal +propensity, the law catches them, and catching one changes how the rest behave. > **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 | |---|---| -| **[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. | -| **[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. | +| **[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 back to baseline, and fed back into every colonist's disposition. | -At load, **`PolicingBootstrap`** fills the neutral deterrence seam Core leaves open — reconnecting the -feedback loop across the mod boundary without Core ever depending on the policing layer. +The climate of order only steers your colonists while Policing is installed. Core on its own keeps the +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 -Policing keeps no per-pawn crime state of its own. It reads and writes the single `CriminalRecord` that -**Institution: Core** holds for every pawn — the same record Contraband, Corrections, and the gang -systems read. `RecordCrime` writes the `crimesCommitted` / `lastCrimeTick` columns; Corrections grades -and reforms over the same numbers. +Policing keeps no crime record of its own. It reads and writes the single criminal record that +**Institution: Core** holds for every colonist — the same rap sheet Contraband, Corrections, and the +gang systems read. A crime here adds to that record; Corrections grades and reforms over the same +history. --- ## Requirements & load order - **RimWorld 1.6** -- **Institution: Core** — required (the propensity/record engine) +- **Institution: Core** — required (the propensity and record system) - **Harmony** — required - Load **after** Core and Harmony. 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 -(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. --- diff --git a/Wiki/policing/Policing.md b/Wiki/policing/Policing.md index 19cb35c..a52c004 100644 --- a/Wiki/policing/Policing.md +++ b/Wiki/policing/Policing.md @@ -12,18 +12,17 @@ deterrence, discipline, parole, regime — without colony crime. ## 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 -policing is not a bolt-on — it feeds the same record classification grades on and discipline reforms. +Every stage draws on the same shared systems — each colonist's propensity, the one shared criminal +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 -Every ~40 seconds, each free colonist gets a seeded roll to offend. The base rate is low (`0.06`) and -multiplied hard by nature × nurture — almost nobody with an ordinary disposition ever does anything. -But it is *also* multiplied by **crime cover**: +Every ~40 seconds, each free colonist gets a hidden roll to offend. The base chance is low (about +6% per roll) and it is multiplied hard by nature × nurture — almost nobody with an ordinary +disposition ever does anything. But it is *also* multiplied by **crime cover**: | Free colonists | Crime cover | |---|---| @@ -41,8 +40,7 @@ kleptomaniac steals, a bloodlusty pawn assaults, a pyromaniac vandalizes). ## Consequences — the stakes -A crime leaves a mark the colony can *see* (all wrapped defensively — a consequence that finds no -target never breaks anything): +A crime leaves a mark the colony can *see*: - **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 @@ -58,30 +56,28 @@ target never breaks anything): ## Witnesses and investigation 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. -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 - effort — a few investigated windows). -- **The police work type.** Assign a colonist to *policing* and they walk to open **crime scenes** and - work them, adding a chunk of evidence scaled by their Intellectual + Social skill. A sharp constable - cracks cases fast; a colony that spares no one lets them go cold. +- **Ambient** progress, scaled by how many colonists the colony can spare — a background trickle from + everyone keeping an eye out, faster when the colony has people to spare. +- **The policing work type.** Assign a colonist to *policing* and they walk to open **crime scenes** + and work them, adding a chunk of evidence scaled by their Intellectual + Social skill. A sharp + constable cracks cases fast; a colony that spares no one lets them go cold. ## The weighed arrest 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`): - -``` -arrest ⇔ colony can afford a prison AND severity ≥ 1.5 + value × 3 -``` +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. - **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). -- **Value** — 0..1 from the culprit's best skills. A star colonist raises the bar to ~4.5; an - expendable one leaves it at ~1.5. +- **Value** — 0..1 from the culprit's best skills. The bar sits at about 1.5 for an expendable pawn + 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 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 -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 survives guest-management mods like Hospitality). - **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. -- **Subdued** → a resister the colony beats down is then **imprisoned** (auto-capture) — the resist - path resolves to a cell once the colony wins the confrontation. One who flees the map gets away. +- **Subdued** → a resister the colony beats down is then **imprisoned** — the resist path resolves to a + cell once the colony wins the confrontation. One who flees the map gets away. ## Prison riots 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 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 -population into one emergent event. +badly-run institution *earns*. ## Recidivism, made visible diff --git a/Wiki/ward/Commitment.md b/Wiki/ward/Commitment.md index 5d081ab..b6e61e4 100644 --- a/Wiki/ward/Commitment.md +++ b/Wiki/ward/Commitment.md @@ -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 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 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 ward**. A warden walks over, sits, and **counsels** them; each session adds a little -`Ward_UnderTreatment` severity, which **lowers their mental-break threshold**. That severity -**decays** at 0.15/day, so the effect fades unless wardens keep attending. Counselling also gives a -small mood lift (`Ward_Counselled`). Neglect a committed patient — leave them with no active treatment -hediff — and once a day they gain `Ward_Neglected`, a mood *penalty* that drags them back toward the +**psychiatric ward**. A warden walks over, sits, and **counsels** them; each session builds up their +**treatment**, which **lowers their mental-break threshold** so they break less often. That progress +**fades** over about a week if nobody keeps attending, so it has to be sustained. Counselling also +gives the patient a small mood lift. Neglect a committed patient — leave them with no ongoing +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 feedback loop is the entire mod. @@ -25,34 +22,25 @@ feedback loop is the entire mod. ## Step 1 — Becoming a ward patient -Commitment reuses vanilla's arrest → prisoner transition. You don't invent a new pawn state; you take -a prisoner (arrest your own colonist, or a raider) and then flip on a **non-exclusive interaction -mode**. +Commitment reuses vanilla's arrest → prisoner transition. You don't get a new pawn state; you take a +prisoner (arrest your own colonist, or a captured raider) and then switch on a new interaction mode. -### `Ward_PsychiatricCare` — the interaction mode +### Psychiatric care — the interaction mode -| Field | Value | Why | -|---|---|---| -| 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 | +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: -**Non-exclusive is load-bearing.** Vanilla's recruit/convert/enslave/release/execute modes are -mutually exclusive — you pick one from a radio group. Psychiatric care is modelled on vanilla's own -*Bloodfeed* and *Study* modes, which are toggles layered *on top* of the exclusive choice. So a -patient can be set to **recruit AND psychiatric care at once**: you counsel the sad colonist toward -stability while also chipping at their resistance. It also means Ward doesn't have to ship a -cross-product of `workAndPsychiatricCare` variants to coexist with *Prison Labor*'s work modes — both -just toggle on. +- **It stacks.** Unlike recruit / convert / enslave / release / execute — which are one-at-a-time + choices — psychiatric care is a toggle that layers *on top* of whatever else you've picked, the + same way vanilla's *Bloodfeed* and *Study* toggles do. So a patient can be set to **recruit AND + psychiatric care at once**: you counsel a sad captured colonist toward stability while also chipping + at their resistance. +- **You can flag a downed or sleeping patient.** Setting the mode doesn't require them to be awake — + 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, -true)` and then `SetExclusiveInteraction(AttemptRecruit)`, **both** modes report enabled -(`live.stacksWithRecruit = True`). +Because the mode stacks, it also lets Ward coexist cleanly with work-your-prisoners mods like *Prison +Labor* — see **Compatibility**. --- @@ -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 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 > psychiatric care will use it to counsel patients held in this room."* -| Field | Value | Why | -|---|---|---| -| `defName` | `Ward_TreatmentStation` | | -| Parent | `BuildingBase` | Ordinary passable furniture | -| Cost | **40 Steel + 2 Industrial medicine** | Deliberately cheap | -| `WorkToBuild` | `1600` | A quick build | -| `MaxHitPoints` | `120` | | -| `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 | +| What | Value | +|---|---| +| Cost | **40 Steel + 2 medicine** — deliberately cheap | +| Build time | Quick | +| Research needed | **Medicine Production** — the one gate | +| Size | A two-tile desk; pawns squeeze past it, so it doesn't wall off a cell | +| Notes | 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 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 -Once a treatment station stands in a room that already holds prisoner beds, the room's *role* changes -from prison cell to psychiatric ward. +Once a treatment station stands in a room that already holds prisoner beds, the room's role changes +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 - -| Field | Value | -|---|---| -| Def type | `RoomRoleDef` (`workerClass` is a public field — no patch needed) | -| `defName` | `Ward_PsychWard` | -| `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`. +- **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 + its normal Prison Cell role, untouched. Build the station and it's a ward; don't and nothing + changes. +- **It's still a prison cell.** Re-labelling the role does not release anyone. A ward is a prison cell + *and* a ward at the same time, so containment, food delivery, and prison breaks all keep working. --- ## 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 | -|---|---|---| -| `defName` | `Ward_WardenPsychiatricCare` | | -| `giverClass` | `Ward.WorkGiver_Warden_PsychiatricCare` | A **new** class extending `WorkGiver_Warden` | -| `workType` | `Warden` | Assigned like any other warden work | -| `verb` / `gerund` | "treat" / "treating" | | -| `priorityInType` | `70` | Above chat (60), below feeding — an untreated patient deteriorates, a bored one doesn't | -| `requiredCapacities` | Talking, Hearing | A mute or deaf warden can't counsel | +- 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 + unconscious or already spiralling. +- There must be a **treatment station in the patient's own room** (not out in the open). You can't + treat someone through a wall from a station two cells over. +- Its priority sits **above chatting** but **below feeding** — an untreated patient deteriorates, a + bored one doesn't — so wardens won't skip meals to run a session. -The class extends `WorkGiver_Warden` and its `JobOnThing` refuses the job unless **all** of these -hold: +### A session -1. The pawn is a prisoner of the colony being taken care of (`ShouldTakeCareOfPrisoner`). -2. `IsInteractionEnabled(Ward_PsychiatricCare)` — asked, not "is this *the* mode," because the mode is - non-exclusive and the patient may also be set to recruit or forced labour. -3. The patient is **awake, not in a mental state, and not downed** — counselling someone mid-break or - unconscious isn't counselling. -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. +- **Length:** about **20 in-game minutes**, with a progress bar while it runs. +- It **fails out** if the patient despawns, is forbidden, falls asleep, or breaks partway through. +- On completion it applies the treatment, gives the patient a "counselled" mood memory, runs a + deep-talk conversation (so the relationship builds and the *next* session lands harder), and awards + the warden **60 Social XP**. -**Being a new class is the entire compatibility story** (see The Compat Harness for the proof): - -- *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. +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**. --- -## 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**: -``` -gain = 0.20 + (wardenSocialLevel / 20) × 0.30 → range 0.20 .. 0.50 per session -``` - -| Warden Social skill | Severity gained per session | +| Warden Social skill | Treatment gained per session | |---|---| | 0 | 0.20 | | 5 | 0.275 | @@ -205,100 +127,88 @@ gain = 0.20 + (wardenSocialLevel / 20) × 0.30 → range 0.20 .. 0.50 per | 15 | 0.425 | | 20 | 0.50 | -The floor is deliberate: even an unskilled warden buys **0.20** per session. A bad ward is *neglect*, -not *zero* — a clumsy counsellor is still worth something. Severity is capped at `maxSeverity = 1.0`. +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. 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 | -|---|---|---| -| `defName` | `Ward_UnderTreatment` | | -| `hediffClass` | `HediffWithComps` | | -| `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.** 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 +right back. Sustained counselling keeps it topped up and the break threshold suppressed; stopping lets +it fade. Treatment is a *programme*, not a one-time cure. -**Decay is the mechanic.** Because severity bleeds off at 0.15/day, a patient treated once and then -ignored slides right back. Sustained treatment keeps the hediff topped up and the break threshold -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.) +> The −0.10 break-threshold figure is a first pass and not yet heavily playtested — expect it to be +> tuned. --- ## 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 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 -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 two moods -The absence of the treatment hediff is the signal *by design*: because the hediff decays on its own, -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 | +| Mood | Value | Duration | Stacks to | Meaning | |---|---|---|---|---| -| `Ward_Counselled` | **+6** | 2 days | up to 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." | +| **Counselled** | **+6** | 2 days | 3 | "Someone sat with me and listened. It helped, a little." | +| **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 -(+18). That's intentional: a neglected patient's mood falls, which raises their break risk, which is -exactly the crisis you committed them to avoid. The ward can *manufacture* the break it was built to -prevent. Staff it, or don't build it. +Neglect is **asymmetric** — a −8 penalty stacking four deep (−32) badly outweighs three counselling +lifts (+18). That's intentional: a neglected patient's mood falls, which raises their break risk, +which is exactly the crisis you committed them to avoid. A ward can *manufacture* the break it was +built to prevent. Staff it, or don't build it. -> **Known rough edge (from the README's "Open" list):** neglect currently keys off "no -> `Ward_UnderTreatment` hediff." A patient treated once a week technically dodges the neglect thought -> on the single day the hediff expires. A real last-treated timestamp would be cleaner. Documented, -> not yet fixed. +> **Known rough edge:** neglect keys off "not currently under treatment," so a patient treated once a +> week can technically dodge the penalty on the single day their treatment runs out. It's a documented +> quirk, not yet smoothed. --- ## 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 | |---|---|---| | 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 | | 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 -forbid working a patient, it just makes the trade-off visible. +The last row is the darkest option the mod exposes, and it exposes it on purpose: Ward doesn't forbid +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 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; -the patient satisfies "held pawn" and the rest follows. See **Home** for the suite map and **The -Compat Harness** for how all of it is verified in one running game. +searched, and can conceal or improvise contraband. See **Home** for the suite map and **Compatibility** +for how Ward stays out of other mods' way. --- -## Quick reference — every Ward def +## Quick reference — the numbers -| defName | Type | Class (if any) | Key numbers | -|---|---|---|---| -| `Ward_PsychiatricCare` | PrisonerInteractionModeDef | — | non-exclusive; listOrder 150 | -| `Ward_TreatmentStation` | ThingDef | `Building` | 40 steel + 2 medicine; WorkToBuild 1600; MedicineProduction research | -| `Ward_PsychWard` | RoomRoleDef | `RoomRoleWorker_PsychWard` | score = 1e6 × prisonerBeds (station-gated) | -| `Ward_WardenPsychiatricCare` | WorkGiverDef | `WorkGiver_Warden_PsychiatricCare` | priority 70; Talking + Hearing | -| `Ward_ProvidePsychiatricCare` | JobDef | `JobDriver_PsychiatricCare` | 1200-tick session; +60 Social XP | -| `Ward_UnderTreatment` | HediffDef | `HediffWithComps` | +0.20–0.50/session; −0.15/day decay; −0.10 break threshold | -| `Ward_Counselled` | ThoughtDef | `Thought_Memory` | +6 mood, 2 days, stack 3 | -| `Ward_Neglected` | ThoughtDef | `Thought_Memory` | −8 mood, 3 days, stack 4 | -| — | MapComponent | `MapComponent_WardNeglect` | daily scan (60000 ticks) | +| Thing | Number | +|---|---| +| Treatment station cost | 40 steel + 2 medicine; needs Medicine Production research | +| Counselling session | ~20 in-game minutes | +| Treatment per session | 0.20 (unskilled) → 0.50 (Social 20) | +| Treatment cap | 1.0 (a full course) | +| Treatment decay | −0.15/day (~a week from full to nothing) | +| Break-threshold effect | −0.10 while treated (breaks less often) | +| Counselled mood | +6, 2 days, stacks to 3 | +| Neglected mood | −8, 3 days, stacks to 4 | +| 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 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. - diff --git a/Wiki/ward/Home.md b/Wiki/ward/Home.md index 20b9798..9372097 100644 --- a/Wiki/ward/Home.md +++ b/Wiki/ward/Home.md @@ -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 **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 -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. --- -## 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` -is a three-value enum: +The reason "commit a colonist" isn't a stock button is simple: vanilla only knows two kinds of held +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. -``` -Guest = 0 -Prisoner = 1 -Slave = 2 -``` +**Ward takes the cheap road.** A committed pawn *is* a prisoner — arresting your own colonist already +does that. Ward just adds a new **way to handle** that prisoner: a "psychiatric care" mode you toggle +on, sitting alongside vanilla's recruit / convert / release options. Nothing exotic — it's the same +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 -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. +So committing someone is entirely ordinary from the game's point of view: -**Ward doesn't pay that tax.** A committed pawn *is* a prisoner, in vanilla's own sense of the word — -arresting your own colonist already performs the colonist→prisoner transition. Ward only adds a new -**mode** to hold them under, and `PrisonerInteractionModeDef` is a **Def, not an enum**. Adding one -is XML plus, at most, a worker class. This is not a trick; it is the established pattern. *Prison -Labor* already ships seven custom interaction modes of its own. +- **psychiatric care** — a new prisoner interaction mode you switch on +- **the psychiatric ward** — a new room role a cell earns once it holds a treatment station +- **counselling** — a new warden job, handled like any other warden work +- the **treatment station**, the **recovery**, the **neglect** — new content layered on top -So the *state itself* costs **zero patches** — it is all defs: - -| Extension point | What Ward adds | Patches needed | -|---|---|---| -| `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. +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 +**Compatibility** page. --- ## Riding the prisoner rail (why the suite cares) -The design choice that makes Ward *cheap* also makes it *interesting* inside the wider Institution -suite. A committed patient is a genuine vanilla prisoner. That means they are a **secure context** in -exactly the sense the rest of the suite understands: a pawn who is held, searchable, and capable of -concealing and improvising contraband. A patient in a psych ward can hoard a shiv or brew something -foul in a smuggled vessel for **free**, because the contraband, search, corruption and gang systems -were written against "held pawn," and a ward patient satisfies that predicate without Ward writing a -line of integration code. +The choice that makes Ward *cheap* also makes it *interesting* inside the wider Institution suite. A +committed patient is a genuine vanilla prisoner. That means they're a **held pawn** in exactly the +sense the rest of the suite understands: someone who can be searched, and who can conceal and +improvise contraband. A patient in a psych ward can hoard a shiv or brew something foul in a smuggled +vessel, because the contraband, search, corruption and gang systems already work on any held +prisoner — and a ward patient is one, for free. -The ward is a prison cell that happens to also be a ward. `Room.isPrisonCell` is a cached field -written only when the room's shape changes — it is **not** derived from the room's role — so -re-labelling a cell as a ward cannot break containment, food delivery, or prison breaks. The compat -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. +**And re-labelling a cell as a ward never lets anyone out.** A ward is a prison cell that also happens +to be a ward — the two roles coexist. Containment, food delivery, and prison breaks all keep working +exactly as they did before you built the station. --- @@ -84,8 +66,8 @@ harness asserts exactly this: a built ward is both `roleIsWard = True` **and** ` | Page | What's on it | |---|---| | **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 | -| **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 | +| **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 | +| **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 | |---|---| -| **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: 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: 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. | Reference sibling mods by name — the mods are separate repositories and cross-repo links break. --- - diff --git a/Wiki/ward/The-Compat-Harness.md b/Wiki/ward/The-Compat-Harness.md index ad8be90..0ef877f 100644 --- a/Wiki/ward/The-Compat-Harness.md +++ b/Wiki/ward/The-Compat-Harness.md @@ -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, -headless RimWorld* with the entire Institution suite and the most-subscribed relevant Workshop mods -loaded **at once**, plays the game at maximum speed for a few thousand ticks, and greps the log for -assertions that the mods it built actually work — and don't silently eat each other. +Ward is built to sit alongside your existing mod list rather than elbow it aside. The trick is that +Ward mostly **adds** things — a new "psychiatric care" prisoner mode, a new building, a new room role, +new jobs — instead of rewriting how vanilla prisoners already behave. And a committed patient isn't a +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: -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. +Here's how Ward gets along with the popular psych and prison mods: ---- - -## 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 | +| Mod | How it coexists with Ward | |---|---| -| Base + DLC | RimWorld Core, Royalty, Ideology, Biotech | -| Libraries | Harmony, HugsLib | -| Guest/prisoner rail | Hospitality, Prison Labor, Locks, Prison Commons, Custom Prisoner Interactions, Prisoner Realism, Prisoner Recreation | -| Mental health | Psychology (unofficial), Rim Disorders, More Mental Breaks, Restraints | -| The big neighbour | Dubs Bad Hygiene (632k subscribers — the one Foul Play deliberately does *not* duplicate) | -| Institution suite | Core, Contraband, Justice, Gangs, Foul Play, **Ward** | -| Optional fixture | FakeDLC (stands in for Anomaly/Odyssey; only with `FAKE_DLC=1`) | +| **Hospitality** | Hospitality manages *guests*; Ward manages *prisoners*. They ride two completely separate rails and never touch. | +| **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. | +| **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. | +| **Prison Commons** | Ward's warden work automatically respects prison-commons areas — you get that for free, without either mod knowing about the other. | +| **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. | +| **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. | +| **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 -`/home/dev/rimworld-ref/mods-cache`, keyed by Workshop file id). The cache lives on the home -partition, not `/tmp`, because a scratchpad reap once took the whole stack down with it. Rebuild it -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-` 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 ` — 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`. +The general rule: because Ward *adds* rather than *overrides*, it should also coexist with prison and +psych mods that aren't on this list. If you ever find one it genuinely fights, that's a bug worth +reporting — Ward's whole design is built around not doing that. --- ## Part of the Institution suite -The harness in this repo builds and tests the whole suite — **Institution: Core**, **Institution: -Contraband**, **Institution: Justice**, **Institution: Gangs**, **Foul Play**, and **Institution: -Ward** — in dependency order, alongside the most-subscribed psych/prison Workshop mods, in one running -game. Ward carries it because Ward's design *is* a compatibility claim, and a claim like that is worth -nothing until it's run. - +The Institution suite — **Institution: Core**, **Institution: Contraband**, **Institution: Justice**, +**Institution: Gangs**, **Foul Play**, and **Institution: Ward** — is a set of RimWorld 1.6 mods about +what an institution does to the people inside it. Each stands alone; together they interlock. Ward's +psychiatric ward is a secure context where a committed patient can conceal and improvise contraband +exactly as a prisoner can, because they ride the same prisoner rail the rest of the suite polices.