Aggregate the whole suite wiki onto the Institution page

Wiki/Home.md is the suite's front door; every layer's wiki now mirrors here
under Wiki/<layer>/ (core, contraband, justice, gangs, ward), so the whole
suite reads in one place with the index linking to the local pages. The
layer repos stay canonical -- Tools/sync-wiki.sh refreshes the aggregated
copy on demand. The README's main page points to it.
This commit is contained in:
flan
2026-07-15 20:30:38 +00:00
parent 25eb3a77db
commit 3530100ed5
31 changed files with 4574 additions and 6 deletions
+198
View File
@@ -0,0 +1,198 @@
# Warden Search
*The sixteenth warden job vanilla never wrote. A guard turns the cell over, and might find nothing —
which is exactly why it has to cost.*
The warden can chat, convert, feed, execute, enslave, release, suppress, and eleven other things.
Searching a prisoner is not one of them, and no such verb exists anywhere in the base game.
Contraband adds it as a **new** `WorkGiver`, not a patch on a vanilla one, and builds it around a
single hard rule: **the search must be able to come up empty.**
## Authority vs. act
The design splits one job into two classes:
| Class | Question it answers | Notes |
|---|---|---|
| `WorkGiver_Warden_Search` | **Who** may be searched? | subclasses `WorkGiver_Warden` — a warden already has the run of the prison, so no new authority is invented |
| `JobDriver_SearchPawn` | **What** is a search? | takes any pawn, searches any pawn |
> The WorkGiver is the authority. This driver is just the act.
Why bother splitting them? Because the enforcement layer is meant to grow. A future **Institution:
Police** module adds a *second* authority — a cop who may stop and frisk a **free colonist** — as a
new WorkGiver that reuses the same driver with a **narrower `Reach`** (a street frisk gets what is
`OnBody`, not what is under the floorboards). That module is an *addition*, not a rewrite, precisely
because "who may search" and "what a search reaches" were never welded together.
## The WorkGiver: who gets searched
`Contraband_WardenSearch` is a `Warden` work-type giver:
| Field | Value | Why |
|---|---|---|
| `workType` | Warden | it is warden work |
| `priorityInType` | 40 | **below feeding** — a starving prisoner matters more than a hidden shiv |
| `requiredCapacities` | Manipulation, Sight | you search with your hands and eyes |
| `Prioritized` | **true** (in C#) | rank candidates by suspicion, not by distance |
`Prioritized` has to be set in code: in 1.6 it is a `WorkGiver_Scanner` property, **not** a
`WorkGiverDef` field, and an XML `<prioritized>` silently errors on load. That flag is what lets a
gnawed bed and a thick record send the warden to the *right* prisoner first instead of the nearest.
`JobOnThing` will offer a search only if all of these hold: the warden should take care of this
prisoner, the target is a `Pawn`, the per-prisoner cooldown is up (`CanSearchNow`), the prisoner is
**awake, not downed, and not in a mental state**, and the warden can reserve them.
Note what is **not** checked — and the source is emphatic about it:
> Whether the prisoner is actually hiding anything. **The warden does not know. Nobody knows until
> the cell is turned over.** If the job were only offered when there was something to find, the mere
> appearance of the job would BE the discovery, and searching would stop being a gamble the player
> pays for in warden-hours. An empty search has to be possible, and has to cost.
### The cooldown
`MapComponent_Contraband` won't let the same prisoner be turned over more than **once per day**:
```
SearchCooldownTicks = 60000 // one in-game day
```
You cannot brute-force a well-hidden shiv by re-searching the same prisoner ten times in an hour.
Every search is one shot, and then that prisoner is off the list for a day.
## Priority: the search reads the tells
`GetPriority` is where suspicion becomes routing. Everyone is worth a routine toss; the tells push a
prisoner up the queue:
```
priority = 4 // baseline — everyone gets frisked
+ 12 × bedDamageFraction // chewed furniture = probable cause
+ min(8, timesCaught×2 + contrabandMade×0.5 + escapeAttempts×3) // the record
```
| Term | Weight | Meaning |
|---|---|---|
| Base | 4 | a routine toss for every prisoner |
| Gnawed bed | up to +12 | `1 − HP/maxHP` of their owned bed — a badly damaged bed shoots to the top |
| `timesCaught` | ×2 | already caught with contraband before |
| `contrabandMade` | ×0.5 | has finished contraband in the past |
| `escapeAttempts` | ×3 | **the heaviest term** — a known tunneller is checked first, every time |
| (record cap) | +8 max | the record cannot completely swamp fresh probable cause |
The record is read with `PeekFor` (a read-only lookup that never creates a record). The base of 4 for
everyone is not an accident — **innocent prisoners still get frisked**, because the search must be
able to come up empty or its mere offer would be the discovery.
**Worked example.** A prisoner whose bed sits at 40% HP (`missing = 0.6`), with 2 prior catches and
1 foiled tunnel:
```
priority = 4 + 12×0.6 + min(8, 2×2 + 0 + 1×3)
= 4 + 7.2 + min(8, 7)
= 4 + 7.2 + 7 = 18.2
```
versus **4** for a clean prisoner with an intact bed. The warden walks past the quiet one and goes
straight for the one who has been busy. This closes the loop [Improvised Weapons](Improvised-Weapons.md)
opens: whittling a shiv chews the bed, the damage is probable cause, and the warden's *suspicion* —
not the player's eye — sends them to search.
## The act: what a search does
`JobDriver_SearchPawn` walks the searcher to the subject, runs a **900-tick** wait toil with a
progress bar, then resolves. Resolution always does two things, hit or miss:
1. `MarkSearched(subject)` — starts the daily cooldown.
2. `timesSearched++` on the record — *"classification and the guard's future suspicion read how often
this pawn has been turned over."*
Then it gathers everything the subject is hiding that this search can **reach** (`Reach` defaults to
`OnBody | InCell`; a subclass can narrow it). If nothing is reachable, the job ends here — the time
was still spent, and the player is told nothing.
### Find math, per hidden item
For each reachable secret:
```
conceal = item.concealability // 0 = always found, 1 = never
chance = clamp( (0.35 + 0.03 × socialSkill) × (1 − conceal) × 2 , 0.02 , 0.95 )
if item is a tunnel in progress:
chance = clamp( chance + tunnelExtra , 0.02 , 0.98 ) // tunnelExtra up to +0.40
chance ×= WardenDisposition.Diligence(searcher) // the guard's thoroughness
if Rand.Chance( WardenDisposition.Corruption(searcher) × 0.6 ):
continue // the warden saw it and said nothing — looked away
found = Rand.Chance(chance)
```
Reading it in plain terms:
- **Skill raises the ceiling; concealability lowers it.** `BaseFindChance` is 0.35; each level of the
warden's **Social** skill adds 0.03. The `(1 − conceal) × 2` factor pivots at concealability 0.5
(neutral, ×1.0): a drug at 0.5 is average, a shiv at 0.75 is halved (×0.5), a heavy weapon at 0.35
is boosted (×1.3).
- **A good warden still misses a well-hidden shiv more often than not.** Worked: a level-8 Social
warden searching for a 0.75-concealability shiv gets `(0.35 + 0.24) × 0.5 = 0.295` *before*
Diligence scales it down further. Well under a coin-flip.
- **A tunnel is a different story.** The pick hides at 0.5, but a nearly-finished shaft adds up to
+0.40 and the cap rises to 0.98 — the deeper the dig, the harder it is to miss. A routine cell toss
catches a dig in progress.
- **The warden's own thoroughness and price** enter last: `Diligence` multiplies the whole chance
down for a lax, kind, or miserable guard, and `Corruption` gives a bent guard a flat chance to look
the other way even on an item they would otherwise have found. Both are on the
[Corruption](Corruption.md) page.
### On a find
```
Confiscate(subject, item.def) // it never becomes a Thing — nothing drops on the floor
timesCaught++ // raises future suspicion, feeds classification
if it was a tunnel: escapeAttempts++ // a foiled dig is a logged attempt, same as one that ran
```
and the player gets a message — *"{warden} searched {prisoner} and found hidden {item}"*, or the
tunnel variant. If nothing is found the player is told **nothing**: they do not learn there *was*
something, only that the warden's time was spent.
## Writing back to the record
Every outcome updates Core's `CriminalRecord`, which is what makes the search a *loop* rather than a
one-off dice roll:
| Field | Written when | Read by |
|---|---|---|
| `timesSearched` | every search, hit or miss | priority, suspicion, classification |
| `timesCaught` | a find | priority (×2), classification |
| `escapeAttempts` | a foiled tunnel | priority (×3, heaviest), classification |
If you run **Institution: Justice**, those same fields drive a prisoner's security classification and
feed the deterrence signal — a prison that catches contraband becomes a prison that classifies its
troublemakers correctly and deters the next attempt. If you run Contraband alone, the fields still
route the warden. See [Compatibility](Compatibility.md).
## Quick reference
| Constant | Value | Meaning |
|---|---|---|
| Base find chance | 0.35 | before skill and concealability |
| Per Social level | +0.03 | skill raises the ceiling |
| Find chance clamp | 0.02 .. 0.95 | (0.98 for a tunnel) |
| Search duration | 900 ticks | one wait toil, hit or miss |
| Search cooldown | 60000 ticks (1 day) | per prisoner |
| Priority base | 4 | every prisoner is worth a routine toss |
| Gnawed-bed weight | up to +12 | `1 − HP/maxHP` |
| Record weight | up to +8 | `timesCaught×2 + contrabandMade×0.5 + escapeAttempts×3` |
| Default `Reach` | `OnBody \| InCell` | narrowed by a future street-frisk subclass |
---
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
Ward. Each stands alone; together they interlock.*
*Developed with substantial assistance from Claude (Anthropic).*