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.
154 lines
6.6 KiB
Markdown
154 lines
6.6 KiB
Markdown
# Secured Context — the kind of hold a pawn is under
|
||
|
||
The suite has a rule that looks small and is load-bearing: **nothing keys off "is this a prisoner."**
|
||
It keys off `SecuredContext` — the *kind of hold* a pawn is under. That indirection is why a ward
|
||
patient, a slave, and (later) anyone inside a secure zone get the same concealment, search, schedule,
|
||
and needs machinery for free, without any module hard-coding a pawn status.
|
||
|
||
`SecuredContext` reduces a pawn to one of three kinds and the question "who, if anyone, may search
|
||
them." Everything downstream reads that, so a new module works across every context the day it is
|
||
written, and a new *context* works across every existing module the day it is added.
|
||
|
||
Verified against `Source/Core/SecuredContext.cs`.
|
||
|
||
---
|
||
|
||
## The three kinds
|
||
|
||
```csharp
|
||
public enum SecuredKind
|
||
{
|
||
Free, // a free colonist
|
||
Prisoner, // a prisoner of the colony
|
||
Slave, // a slave (Ideology)
|
||
}
|
||
```
|
||
|
||
| Kind | Who it is | May search them | Can conceal? | Held against their will? |
|
||
|---|---|---|---|---|
|
||
| **Free** | a free colonist | *no one* — until a secure-area or policing layer grants standing | **yes** — they can hoard | no |
|
||
| **Prisoner** | a prisoner of the colony | the **warden** | yes | **yes** |
|
||
| **Slave** | a slave (Ideology DLC) | the **overseer** | yes | **yes** |
|
||
|
||
Two subtleties fall out of this table:
|
||
|
||
- **Free colonists can still conceal.** A free colonist "can hoard contraband, but no one has
|
||
standing to search them until a secure-area or policing layer grants it." Freedom is not innocence —
|
||
it is only the absence of an authority permitted to check. That is why `CanConceal` (below) includes
|
||
the free.
|
||
- **Prisoner and Slave differ only in who holds the keys.** Both are "held"; the search authority is
|
||
the warden for one and the overseer for the other. Downstream modules that only care "is someone
|
||
entitled to search this pawn" read `IsHeld` and never branch on which.
|
||
|
||
## The struct
|
||
|
||
`SecuredContext` is a small `readonly struct` — the kind, the pawn, and two computed predicates.
|
||
|
||
```csharp
|
||
public readonly struct SecuredContext
|
||
{
|
||
public readonly SecuredKind Kind;
|
||
public readonly Pawn Pawn;
|
||
|
||
// Held against their will -- prisoner or slave. Excludes a free colonist.
|
||
public bool IsHeld => Kind == SecuredKind.Prisoner || Kind == SecuredKind.Slave;
|
||
|
||
// Anyone with something to hide -- includes free colonists (who may hoard).
|
||
public bool CanConceal => Pawn != null;
|
||
}
|
||
```
|
||
|
||
The two predicates split the world along two different axes, and picking the right one is the whole
|
||
skill of using this type:
|
||
|
||
| Predicate | True for | Ask it when you care about… |
|
||
|---|---|---|
|
||
| `IsHeld` | Prisoner, Slave | **custody** — who is under the colony's control, who can be disciplined, whose cell can be searched |
|
||
| `CanConceal` | Free, Prisoner, Slave (any real pawn) | **contraband** — who could be *hiding* something, regardless of status |
|
||
|
||
For example, [Propensity](Propensity.md)'s Nurture uses `IsHeld` in its "held & unhappy compounds"
|
||
clause — it only wants to pile the ×1.4 penalty on a pawn the colony actually holds and mistreats, not
|
||
on a grumpy free colonist. A warden-search feature, by contrast, would gate on `CanConceal` to decide
|
||
who is even worth checking, and then on standing to decide whether it is *allowed* to.
|
||
|
||
---
|
||
|
||
## `Of` — one pawn to a context
|
||
|
||
```csharp
|
||
public static SecuredContext? Of(Pawn p)
|
||
```
|
||
|
||
`Of` maps a pawn to a context, or **`null`** if the pawn has none. The order of the checks matters and
|
||
is worth reading literally:
|
||
|
||
```
|
||
if p is not humanlike, or p is dead -> null (not one of ours)
|
||
if p.IsPrisonerOfColony -> Prisoner
|
||
if p.IsSlaveOfColony -> Slave
|
||
if p.IsFreeColonist -> Free
|
||
otherwise -> null
|
||
```
|
||
|
||
- **The guard comes first.** Animals, mechs, and the dead are simply not the suite's business — they
|
||
return `null`, and every `OnMap` loop and downstream check skips them without a special case.
|
||
- **Prisoner and Slave are tested before Free.** A pawn who is somehow both a colonist and imprisoned
|
||
is classified by their *hold* first. The nullable return is the clean signal for "this pawn is
|
||
outside the model," so callers pattern-match `HasValue` rather than defaulting to some "Free-ish"
|
||
status.
|
||
|
||
Returning `SecuredContext?` (nullable) rather than a `Free` fallback is deliberate: there is a real
|
||
difference between "a free pawn we track" and "a pawn we do not model at all," and collapsing them
|
||
would let non-colony pawns leak into colony machinery.
|
||
|
||
## `OnMap` — every context on a map
|
||
|
||
```csharp
|
||
public static IEnumerable<SecuredContext> OnMap(Map map)
|
||
```
|
||
|
||
`OnMap` yields a context for every spawned pawn that has one — prisoners, slaves, and colonists,
|
||
skipping everything `Of` returns `null` for. It is a lazy `yield` iterator, so a caller can enumerate
|
||
"everyone the suite cares about on this map" in one loop:
|
||
|
||
```csharp
|
||
foreach (SecuredContext ctx in SecuredContexts.OnMap(map))
|
||
{
|
||
if (ctx.IsHeld) { /* consider only the held */ }
|
||
}
|
||
```
|
||
|
||
A `null` map yields nothing (an empty sequence), so callers do not need to null-check before looping.
|
||
|
||
---
|
||
|
||
## Why not just "prisoner"?
|
||
|
||
This is the design decision the whole page exists to justify, so it is worth stating plainly. If every
|
||
module checked `pawn.IsPrisonerOfColony` directly, then:
|
||
|
||
- adding **slaves** to a feature would mean editing every module;
|
||
- adding a **ward patient** mode would mean editing every module;
|
||
- adding a future **secure-zone** concept would mean editing every module.
|
||
|
||
By routing all of them through one predicate, a new hold-kind is added *once*, in Core, and every
|
||
existing module inherits it. The concealment, search, and needs machinery never has to learn what a
|
||
slave is — it only ever asked `IsHeld` and `CanConceal`.
|
||
|
||
### The deliberately-absent Ward kind
|
||
|
||
There is no `SecuredKind.WardPatient`, and its absence is intentional. From the source:
|
||
|
||
> *"a committed patient IS a prisoner in vanilla's sense (Ward rides the prisoner rail), so it is
|
||
> `SecuredKind.Prisoner` here and needs no special case. Contraband does not depend on Ward."*
|
||
|
||
Because Institution: Ward implements a committed patient *on top of* vanilla's prisoner rail, such a
|
||
pawn already returns `Prisoner` from `Of` — no new kind, no dependency from Core (or Contraband) onto
|
||
Ward, and every module treats a ward patient exactly as it treats any prisoner. The right amount of
|
||
new code to support Ward's hold semantics was zero, and that is the payoff of keying off the context
|
||
instead of the label.
|
||
|
||
---
|
||
|
||
*Part of the **Institution** suite. AI disclosure: developed with substantial assistance from Claude (Anthropic).*
|