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

This commit is contained in:
flan
2026-07-16 02:18:25 +00:00
parent ff703271ba
commit 8250dcb369
32 changed files with 1654 additions and 2510 deletions
+68 -80
View File
@@ -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.*
**
+124 -153
View File
@@ -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<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
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<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
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.*
**
+32 -62
View File
@@ -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.*
**
+45 -49
View File
@@ -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.*
**
+80 -107
View File
@@ -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.*
**
+60 -80
View File
@@ -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.*
**
+97 -114
View File
@@ -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 `<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.
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.*
**