Files
institution/Wiki/contraband/Warden-Search.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

9.5 KiB
Raw Blame History

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 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 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.

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).