Files
institution/Wiki/contraband/Concealment-and-Intake.md
T
flan 3530100ed5 Aggregate the whole suite wiki onto the Institution page
Wiki/Home.md is the suite's front door; every layer's wiki now mirrors here
under Wiki/<layer>/ (core, contraband, justice, gangs, ward), so the whole
suite reads in one place with the index linking to the local pages. The
layer repos stay canonical -- Tools/sync-wiki.sh refreshes the aggregated
copy on demand. The README's main page points to it.
2026-07-15 20:30:38 +00:00

231 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Concealment and Intake
*How a hidden item exists without existing, how it gets onto a prisoner in the first place, and how
it becomes real again.*
This is the spine of the whole mod. Everything else — shivs, tunnels, searches, corruption — is a
different way to add to, or remove from, the private list this page describes.
## While it is concealed, there is no Thing
The single most important rule, and the source states it plainly:
> `MapComponent_Contraband` tracks what each pawn is hiding. **Concealed contraband is not a Thing.
> It is a secret.** It is not in the gear tab, there is no icon, and the player is told nothing —
> because an item you can see in a UI is not contraband, it is inventory, and there would be nothing
> for a warden to discover and no reason to ever order a search.
Concretely, a concealed item lives as a `ConcealedItem` row in a `List` on the map component. It
carries the def, a `progress` value, the material it ended up made of, an optional source it is
being harvested from, tunnel state if it is a pick, and an opaque `contentTag`. It holds **no
spawned object** anywhere in the world until one of two things happens:
| Moment | What happens | Method |
|---|---|---|
| The prisoner **uses** it | It materialises into their inventory as a real `Thing` | `TryRedeem` |
| A warden **finds** it | It is deleted from the list; it never becomes a `Thing` at all | `Confiscate` |
A corollary the search page leans on hard: **an empty search must still cost time.** If the
`Search` job only ever appeared when there was genuinely something to find, the mere *offer* of the
job would be the discovery. You have to be able to toss a clean cell and come up with nothing.
## Where a thing can hide: `ConcealSite`
Every contraband def declares where it can be hidden. This is not decoration — it decides **who can
ever find it**.
```csharp
[Flags] enum ConcealSite { None = 0, OnBody = 1, InCell = 2 }
```
| Site | Holds | Found by |
|---|---|---|
| `OnBody` | small: a shiv, pills, a phone | searching the **pawn** |
| `InCell` | bulky: a rifle, a carboy, a crude pick | tossing the **room** |
A warden turning over a prisoner and their cell reaches both (`OnBody | InCell`). The reason the
enum is split at all: a **constable** in Justice's now-shipping policing layer, frisking a colonist
named in a street crime, reaches only `OnBody`, so whatever is under their floorboards stays there
until somebody has grounds to search the *room*. The split between "who may search" and "what a
search reaches" is deliberate — see [Warden Search](Warden-Search.md).
## Three sources of contraband, and no list of items
At startup, `ContrabandUtility` scans every `ThingDef` and decides whether it is contraband. There
is no hardcoded list of item names — there are three recognisers:
1. **Explicit.** Any def carrying `CompProperties_Contraband`. This is the opt-in, and it is how a
mod plugs in its own `acquireWorker` / `useWorker` / `escapeWorker` behaviour.
2. **Every drug** (`ThingDef.IsDrug`) — inferred automatically.
3. **Every weapon** (`ThingDef.IsWeapon`) that is not `destroyOnDrop` — inferred from **mass**.
The inference (`ContrabandUtility.Infer`) fills in concealability and hiding place so a modder never
has to:
| Kind | Test | `concealability` | `hideIn` |
|---|---|---|---|
| Hard drug | `drugCategory == Hard` | **0.75** | `OnBody \| InCell` |
| Other drug | any other `IsDrug` | **0.5** | `OnBody \| InCell` |
| Light weapon | `Mass ≤ 2.0` | **0.7** | `OnBody \| InCell` |
| Heavy weapon | `2.0 < Mass ≤ 10.0` | **0.35** | `InCell` only |
| Very heavy weapon | `Mass > 10.0` | — | **not contraband** (a minigun hides nowhere) |
`concealability` runs 0..1 where **0 = a warden finds it every time and 1 = never**; higher is
harder to find. Drugs and inferred weapons get **no `acquireWorker`** (you cannot synthesise yayo in
a bare cell, and you cannot conjure a rifle) and **no `useWorker`** — because vanilla already knows
what to do with them. `JobGiver_SatisfyChemicalNeed` takes a drug; an armed prisoner *is* the
payload. Getting the item into the cell is the entire problem, and that is all these routes solve.
Nothing here mutates another mod's defs. It is a lookup keyed on the def, computed once, held in a
dictionary. Modded drugs and weapons are covered without ever being named.
## The poll: no Harmony
`MapComponent_Contraband` runs on a heartbeat. Every **250 ticks** (`CheckIntervalTicks`) it visits
every pawn who could be concealing something and does all the ongoing work: intake, tunnel progress,
and improvisation. If nothing on the map is self-acquirable and there are no escape tools registered,
it returns immediately and costs nothing.
Polling — rather than patching — is a deliberate architecture choice. **The tick already visits
every held pawn**, so intake needs no Harmony hook on the capture event: a pawn who is in custody but
not yet in the `intakeProcessed` set is, by definition, one who was newly taken. The whole mod ships
zero Harmony patches, and this is a large part of why.
### Who counts as a concealer
`ContrabandUtility.Concealers(map)` is **not** just prisoners. It delegates to Core's
secured-context predicate and yields every pawn whose belongings are anyone's business — prisoners,
slaves, **and free colonists**:
> A free colonist forbidden their drug by a drug policy or an ideoligion is precisely the pawn who
> keeps a private stash — and vanilla already denies them, with no valve.
Whether anyone may *search* a given concealer is a separate question with a separate answer (the
warden's authority reaches prisoners; a future cop's reaches colonists). Tracking is pawn-agnostic;
authority is not.
## Intake: the moment of capture
Vanilla never strips a captive *on capture* — a downed pawn keeps whatever they walked in with, and
the warden path just hauls them to a cell. Intake is Contraband's answer to that gap.
`RunIntake(pawn)` runs from the poll, on the first pass that sees a newly-held pawn:
1. **Held only.** The pawn must be humanlike and `IsHeld` (prisoner, slave, ward patient). A free
colonist is a concealer, but nobody frisks and processes *them* on the way into a cell, so their
carried gear is never intaken.
2. **Once.** The pawn's `thingIDNumber` is added to a `HashSet<int>`. The set stores an `int`, not a
pawn reference, so it holds no ghost of a captive who has since died. A second pass finds them
already processed and does nothing.
3. **Even while downed.** Intake runs *before* the "must be awake" gate. The stash a captive walked
in with is hidden the moment they are in custody, not when they wake — everything *after* intake
(whittling, digging) needs them awake, but hiding what is already on you does not.
4. **Only what can ride on a body.** `IsIntakeConcealable` accepts a thing only if its contraband
props include the `OnBody` flag. A shiv up a sleeve is intaken; a rifle or a suit of armour is
too bulky to palm and stays as ordinary strippable gear the player confiscates for free.
5. **Per-item disposition.** For each concealable thing the captive carries, the mod rolls whether
*this* pawn would hide *this* item:
```
Propensity.Would(pawn, 0.6, SaltIntake ^ item.thingIDNumber)
```
Base chance 0.6, then scaled by the pawn's nature × nurture (Core's `Propensity`). The roll is
**seeded per (pawn, item)** — a fixed fact, not a fresh coin each tick. The frightened cook
surrenders the knife; the disposed lifer palms it, and always would have.
When a pawn does hide an item, the real `Thing` is **destroyed** (equipment via
`DestroyEquipment`, inventory via `Destroy(Vanish)`) and a concealed secret is created in its place,
preserving what the item was made of (`stuff`) and — critically — its `contentTag`.
## The content tag: preserving contents without understanding them
A concealed vessel is not just "a jar" — it is a jar *of* something (hooch, piss, a fermented
blend), and that something has to survive being hidden and come back on redemption. Contraband
stores a single opaque string, `contentTag`, and **has no idea what it means**:
```csharp
// A bridge mod (Foul Play) fills these in. Null (no bridge) = items have no contents.
ContrabandUtility.ContentTagOf; // Func<Thing, string> — read on conceal
ContrabandUtility.ApplyContent; // Action<Thing, string> — applied on redeem
```
On concealment, `ContentTagOf(thing)` captures the tag (e.g. "a jar of a piss+hooch cocktail"). On
redemption, `ApplyContent(newThing, tag)` pours that content back into the freshly-made thing. This
is the exact seam **Foul Play** uses to keep a smuggled carboy's contents intact. Without a bridge,
both delegates are null, no content is captured, and nothing is lost — a shiv is just a shiv.
Fermentation itself lives in Foul Play, not here. A concealed vessel materialises **fresh** and
ferments afterward like any other, which suits the theme: fresh-brewed piss is weak, and time is
what makes it a weapon. (`concealedTick` records how long it was hidden, for any bridge that wants to
restore pre-aging — Contraband keeps the number but never acts on it.) See
[Compatibility](Compatibility.md).
## Redemption: the secret becomes a Thing
`TryRedeem(pawn, def)` is where a secret turns real:
1. Choose the material — the harvested `stuff`, or the def's default if it was made from stuff.
2. `ThingMaker.MakeThing(def, stuff)`.
3. If there was a `contentTag`, call `ApplyContent` to restore the contents.
4. `TryAdd` it to the pawn's inventory. If that fails, the thing is vanished and nothing is lost.
5. Remove the secret from the list.
The trigger for redemption is the breakout duty tree. `ThinkTreeInjection` runs once at startup and
mutates two duty defs directly (an XML patch on a `DutyDef` think tree silently no-ops in 1.6, so it
is done in C#):
| Duty | Injected | Where |
|---|---|---|
| `PrisonerEscape` | `JobGiver_ArmSelf`, then `JobGiver_UseContraband` | **after** `JobGiver_TakeCombatEnhancingDrug` |
| `PrisonerAssaultColony` | `JobGiver_ArmSelf`, then `JobGiver_UseContraband` | **before** `JobGiver_AIFightEnemies` |
`JobGiver_UseContraband` sits right beside `JobGiver_TakeCombatEnhancingDrug` — the one contraband
hook Ludeon wrote and then left reading an always-empty inventory. For each thing a pawn is hiding:
- **Inert contraband** (a drug, a stashed rifle — no `useWorker`): just materialise it and get out
of the way. Vanilla takes the drug via `JobGiver_SatisfyChemicalNeed`; a weapon in inventory is a
weapon. But a stashed **weapon** only comes out for someone who would actually use it
(`Disposition.WouldArmSelf`) — the same person who would not stop to pick a rifle off the floor
does not become a fighter because the knife was in their sock, and a pawn incapable of violence
never does at all. If they qualify, the weapon is moved from inventory into their equipment slot;
otherwise it stays hidden and they run. See [Improvised Weapons](Improvised-Weapons.md).
- **Active contraband** (a `useWorker` — e.g. Foul Play's throw-the-carboy): materialise it, then
hand off to the worker's `TryGiveJob`, which produces the actual use job.
## Confiscation and the door every route comes through
`Confiscate(pawn, def)` simply removes the secret. It **never becomes a Thing** — the warden found
it, and there is nothing to drop on the floor. (This is why searching is not a way for the colony to
*acquire* a prisoner's drugs; it is a way to *deny* them.)
At the other end, `Conceal(prisoner, def, ...)` is the public door **every** supply route comes
through — intake calls it, a bent warden calls it ([Corruption](Corruption.md)), and a smuggling
gangmate ([Institution: Gangs](Compatibility.md)) calls it. It hands a prisoner something already
finished and hidden, optionally with a `contentTag`. One door, many keys.
## Persistence
`MapComponent_Contraband.ExposeData` deep-saves the secret list, the per-pawn search cooldowns, and
the `intakeProcessed` set. On load it prunes any row whose pawn or def failed to resolve — prisoners
who died or left take their secrets with them, and a null key would throw on the next tick.
## Quick reference
| Constant | Value | Meaning |
|---|---|---|
| `CheckIntervalTicks` | 250 | poll cadence for intake / tunnels / improvisation |
| Intake base chance | 0.60 | per-item, before nature × nurture |
| Intake gate | `IsHeld` + `OnBody` | only held pawns, only body-hideable items |
| Hard-drug concealability | 0.75 | inferred |
| Other-drug concealability | 0.50 | inferred |
| Light-weapon concealability | 0.70 | `Mass ≤ 2.0`, hides on body |
| Heavy-weapon concealability | 0.35 | `2.0 < Mass ≤ 10.0`, `InCell` only |
| Unhideable mass | > 10.0 | not contraband |
---
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
Ward. Each stands alone; together they interlock.*
*Developed with substantial assistance from Claude (Anthropic).*