Aggregate the whole suite wiki onto the Institution page

Wiki/Home.md is the suite's front door; every layer's wiki now mirrors here
under Wiki/<layer>/ (core, contraband, justice, gangs, ward), so the whole
suite reads in one place with the index linking to the local pages. The
layer repos stay canonical -- Tools/sync-wiki.sh refreshes the aggregated
copy on demand. The README's main page points to it.
This commit is contained in:
flan
2026-07-15 20:30:38 +00:00
parent 25eb3a77db
commit 3530100ed5
31 changed files with 4574 additions and 6 deletions
+128
View File
@@ -0,0 +1,128 @@
# 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.*
## 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:
| 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 |
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
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:
- **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.
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:
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
[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.
## Institution: Justice — the record is the shared bus
Contraband and **Justice** never call each other. They meet on Core's `CriminalRecord`:
```
Contraband writes ──▶ CriminalRecord ──▶ Justice reads
timesSearched (in Core) Classification (security grade)
timesCaught Deterrence (colony-wide signal)
escapeAttempts Discipline / Parole (reform)
contrabandMade
```
- **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.
## 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.
## 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 |
## 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)
```
## 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.
---
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
Ward. Each stands alone; together they interlock.*
*Developed with substantial assistance from Claude (Anthropic).*
+230
View File
@@ -0,0 +1,230 @@
# Concealment and Intake
*How a hidden item exists without existing, how it gets onto a prisoner in the first place, and how
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.
## While it is concealed, there is no Thing
The single most important rule, and the source states it plainly:
> `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
> 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:
| 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` |
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`
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 }
```
| 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** |
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).
## Three sources 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:
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**.
The inference (`ContrabandUtility.Infer`) fills in concealability and hiding place so a modder never
has to:
| Kind | Test | `concealability` | `hideIn` |
|---|---|---|---|
| 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) |
`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.
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.
## The poll: no Harmony
`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.
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.
### 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**:
> 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.
Whether anyone may *search* a given concealer is a separate question with a separate answer (the
warden's authority reaches prisoners; a future cop's reaches colonists). Tracking is pawn-agnostic;
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.
`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
carried gear is never intaken.
2. **Once.** The pawn's `thingIDNumber` is added to a `HashSet<int>`. 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
(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.
5. **Per-item disposition.** For each concealable thing the captive carries, the mod rolls whether
*this* pawn would hide *this* item:
```
Propensity.Would(pawn, 0.6, SaltIntake ^ item.thingIDNumber)
```
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.
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`.
## The content tag: preserving contents without understanding them
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<Thing, string> — read on conceal
ContrabandUtility.ApplyContent; // Action<Thing, string> — applied on redeem
```
On concealment, `ContentTagOf(thing)` captures the tag (e.g. "a jar of a piss+hooch cocktail"). On
redemption, `ApplyContent(newThing, tag)` pours that content back into the freshly-made thing. This
is the exact seam **Foul Play** uses to keep a smuggled carboy's contents intact. Without a bridge,
both delegates are null, no content is captured, and nothing is lost — a shiv is just a shiv.
Fermentation itself lives in Foul Play, not here. A concealed vessel materialises **fresh** and
ferments afterward like any other, which suits the theme: fresh-brewed piss is weak, and time is
what makes it a weapon. (`concealedTick` records how long it was hidden, for any bridge that wants to
restore pre-aging — Contraband keeps the number but never acts on it.) See
[Compatibility](Compatibility.md).
## Redemption: the secret becomes a Thing
`TryRedeem(pawn, def)` is where a secret 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.
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#):
| Duty | Injected | Where |
|---|---|---|
| `PrisonerEscape` | `JobGiver_ArmSelf`, then `JobGiver_UseContraband` | **after** `JobGiver_TakeCombatEnhancingDrug` |
| `PrisonerAssaultColony` | `JobGiver_ArmSelf`, then `JobGiver_UseContraband` | **before** `JobGiver_AIFightEnemies` |
`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:
- **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.
## Confiscation and the door every route comes through
`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.)
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.
## Quick reference
| Constant | Value | Meaning |
|---|---|---|
| `CheckIntervalTicks` | 250 | 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 |
| 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 |
---
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
Ward. Each stands alone; together they interlock.*
*Developed with substantial assistance from Claude (Anthropic).*
+141
View File
@@ -0,0 +1,141 @@
# Corruption
*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**:
| 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.
## 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)
```
| Trait / factor | Δ Diligence | Reasoning |
|---|---|---|
| Industrious | +0.1 per degree | a hard worker searches hard |
| Lazy | −0.1 per degree | a lazy one frisks gently |
| Abrasive | +0.15 | happy to toss a cell |
| Psychopath | +0.10 | thoroughness without squeamishness |
| Kind | −0.20 | reluctant to be rough |
| Mood | ×0.6 (miserable) .. ×1.1 (content) | morale is thoroughness |
## 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)
```
| Trait / factor | Δ Corruption | Reasoning |
|---|---|---|
| Greedy | +0.40 | the classic bent guard — everyone has a number |
| Kind | +0.15 | looks the other way out of sympathy, not money |
| Ascetic | −0.10 | wants nothing, so cannot be bought |
| Abrasive | −0.05 | too surly to do anyone a favour |
| Mood | ×1.6 (miserable) .. ×0.7 (content) | a happy guard is harder to tempt |
## 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.
## 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:
| Warden | Diligence | Corruption | Look-away/item | Net on a 0.30 shiv |
|---|---|---|---|---|
| Neutral, content | ~0.61 | ~0.03 | ~2% | thorough-ish: ~0.18 find |
| Abrasive + Psychopath, content | ~0.88 | ~0.02 | ~1% | a nightmare for prisoners: ~0.26 find |
| Greedy, content | ~0.61 | ~0.31 | ~18% | looks capable, pockets bribes |
| Greedy, **miserable** | ~0.37 | ~0.62 | ~37% | ignores contraband over a third of the time |
| Kind + **miserable** | ~0.25 | ~0.27 | ~16% | a sieve at both ends: ~0.07 find |
(Numbers rounded; mood taken as content ≈ 100% or miserable ≈ 20%.) The takeaway is that **who you
assign to search matters as much as whether you search at all.** A greedy, miserable warden is not a
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:
```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).
## How to play with it
- **Vet your wardens.** A Greedy or Kind pawn on prison duty is a liability the trait tooltip won't
warn you about. Ascetic and Industrious make the best searchers; Psychopath and Abrasive are
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.
## Quick reference
| Constant | Value | Meaning |
|---|---|---|
| Diligence baseline | 0.55 | before traits and mood |
| Corruption baseline | 0.04 | most guards are mostly honest |
| 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 |
---
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
Ward. Each stands alone; together they interlock.*
*Developed with substantial assistance from Claude (Anthropic).*
+130
View File
@@ -0,0 +1,130 @@
# Institution: Contraband
*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.
> **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.
## The one idea
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.
## 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`).
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.
What does **not** ship 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.
## Concealment is a secret, not an item
This is the load-bearing design decision, inherited from Foul Play (the Piss Nuke) and kept:
> **While it is concealed, there is no `Thing`.**
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:
- 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).
Why go to the trouble? Because *an item you can see in a 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**.
## The four supply routes and the one counter
| Route | What it is | Page |
|---|---|---|
| **Intake** | A captive hides the small contraband they walked in with | [Concealment and Intake](Concealment-and-Intake.md) |
| **Improvise** | Whittle a shiv from the cell's own furniture | [Improvised Weapons](Improvised-Weapons.md) |
| **Tunnel** | Dig out under the perimeter with a filed pick | [Tunnels](Tunnels.md) |
| **A bent warden** | A greedy or over-kind guard smuggles *for* a prisoner | [Corruption](Corruption.md) |
| **The counter** | A prioritized cell toss that reads the tells | [Warden Search](Warden-Search.md) |
And because every guard is a person with their own diligence and their own price, the search is only
as good as who you send — see **Corruption**.
## How everything is contraband without a list
Contraband names no items. It recognises three sources at load and logs the count:
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.
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).
## 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.
- **[Warden Search](Warden-Search.md)** — the prioritized search, how tells and the record drive
priority, what a search does, and how it writes 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
interplay, and the prison mods the suite is tested against.
## The suite
Each mod stands alone as an install; together they form one system.
| Mod | Owns | Depends on |
|---|---|---|
| **Institution: Core** | propensity, criminal record, secured context | base game |
| **Institution: Contraband** (this) | concealment, intake, shivs, tunnels, search, corruption | Core |
| **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 | — |
---
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
Ward. Each stands alone; together they interlock.*
*Developed with substantial assistance from Claude (Anthropic).*
+203
View File
@@ -0,0 +1,203 @@
# Improvised Weapons
*The acquisition 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.**
## 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."*
| Stat | Value |
|---|---|
| Tech level | Neolithic |
| Mass | 0.4 kg |
| `concealability` | **0.75** (hides very well — it rides on the body) |
| `hideIn` | `OnBody \| InCell` |
| 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 |
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.
## Who whittles: the disposition gate
`AcquireWorker_Improvise.CanAcquire` is not "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
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 source: what counts as raw material
`FindSource` looks for the nearest reachable, damageable, usefully-stuffed 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:
| 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 |
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
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:
```
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.
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.
## 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:
```
progressPerPoll = 250 / (1.5 × 60000) × Rand.Range(0.75, 1.25)
```
When progress reaches 1, the shiv is finished, **silently** — the player is told nothing. Two things
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
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.
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.
### `WouldArmSelf`
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:
```
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
```
| Factor | Value |
|---|---|
| Bloodlust | ×2.5 (highest applicable trait only — not a stack) |
| Psychopath | ×2.0 |
| Brawler | ×1.8 |
| Wimp | ×0.3 |
| Kind | ×0.4 |
| skill | `0.5 + max(Shooting, Melee)/20 × 1.5` → 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:
| Condition | Effect | Reasoning |
|---|---|---|
| Health < 50% | ×0.4 | badly hurt people run or crawl; they don't seek a fight |
| ≥ 2 other prisoners escaping within 12 tiles | ×2.0 | a crowd emboldens; one man slips out, six men riot |
| An armed colonist within 20 tiles | ×1.8 | now a weapon is not bravado, it's the plan |
### Which weapon they reach for
`JobGiver_ArmSelf` 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.
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.
## 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).
## 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 |
| 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 |
| 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 |
---
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
Ward. Each stands alone; together they interlock.*
*Developed with substantial assistance from Claude (Anthropic).*
+178
View File
@@ -0,0 +1,178 @@
# Tunnels
*The Prison-Architect dig: a disposed pawn goes down, not through — under the ground and everything
built on it, out past the last wall you were relying on.*
A locked door stops a prisoner who wants to walk out. It does nothing at all about a prisoner who
digs. This is the escape that ignores your perimeter, because it does not cross it — it goes beneath
it. It is slow, it is quiet, and it works for pawns who would never join a mood-driven riot.
## 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.
| 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 |
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.
## 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*.
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`).
## Who digs
`EscapeTunnelWorker.WouldDig` 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.
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.
## 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.
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.
### 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)
```
| | Days |
|---|---|
| Minimum (shallow cell near the edge) | **3** |
| Maximum (deep bunker) | **15** |
| Per day of digging | ~6 cells of depth |
The floor of 3 days keeps even the shortest tunnel a *real threat window* you have time to catch; the
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 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.
## Finding a tunnel in a search
A tunnel is *"a second, far less hideable secret."* The pick has `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
```
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
[Warden Search](Warden-Search.md) page.
## Breakout
When `escapeProgress` reaches 1, `OnBreakout` fires:
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.
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.
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.
## How to defend against it
| Defence | Effect |
|---|---|
| **Floor the cells** | spoil dirt becomes visible; you can *see* a dig |
| **Search known offenders** | a logged escape attempt is the heaviest single term in search priority |
| **Deny the pick's raw material** | no workable furniture, no pick — same starve as the shiv |
| **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.
## Quick reference
| Constant | Value | Meaning |
|---|---|---|
| Dig base chance | 0.08 | before nature × nurture; seeded 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 |
---
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
Ward. Each stands alone; together they interlock.*
*Developed with substantial assistance from Claude (Anthropic).*
+198
View File
@@ -0,0 +1,198 @@
# Warden Search
*The sixteenth warden job vanilla never wrote. A guard turns the cell over, and might find nothing —
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.**
## Authority vs. act
The design splits one job into two classes:
| Class | 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 WorkGiver is the authority. This driver 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.
## The WorkGiver: who gets searched
`Contraband_WardenSearch` is a `Warden` work-type giver:
| Field | 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 |
`Prioritized` has to be set in code: in 1.6 it is a `WorkGiver_Scanner` property, **not** a
`WorkGiverDef` field, and an XML `<prioritized>` 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.
`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.
Note what is **not** checked — and the source is emphatic about it:
> 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.
## 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:
```
priority = 4 // baseline — everyone gets frisked
+ 12 × bedDamageFraction // chewed furniture = probable cause
+ min(8, timesCaught×2 + contrabandMade×0.5 + escapeAttempts×3) // the record
```
| 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 |
| (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.
**Worked example.** A prisoner whose bed sits at 40% HP (`missing = 0.6`), 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
```
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.
## 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:
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."*
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.
### Find math, per hidden item
For each reachable secret:
```
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)
```
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.
- **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
[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
```
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.
## Writing back to the record
Every outcome updates Core's `CriminalRecord`, which is what makes the search a *loop* rather than a
one-off dice roll:
| Field | Written 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 |
If you run **Institution: Justice**, those same fields 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
route the warden. See [Compatibility](Compatibility.md).
## Quick reference
| Constant | Value | Meaning |
|---|---|---|
| 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 |
| 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 |
---
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
Ward. Each stands alone; together they interlock.*
*Developed with substantial assistance from Claude (Anthropic).*