Wiki: player-facing rewrite of every layer + add a scenario FAQ, linked from Home
This commit is contained in:
+45
-110
@@ -1,29 +1,18 @@
|
||||
# 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.
|
||||
It keys off the *kind of hold* a pawn is under. That one step of 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 feature hard-coding a single 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`.
|
||||
A pawn's secured context boils down to one of three kinds and the question "who, if anyone, may search
|
||||
them." Everything downstream reads that, so a new feature works across every kind of hold the day it's
|
||||
written, and a new *kind of hold* works across every existing feature the day it's added.
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
@@ -32,121 +21,67 @@ public enum SecuredKind
|
||||
|
||||
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.
|
||||
- **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 isn't innocence — it's only
|
||||
the absence of an authority allowed to check.
|
||||
- **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 warden for one and the overseer for the other. Features that only care "is someone entitled to
|
||||
search this pawn" read whether the pawn is *held* and never branch on which.
|
||||
|
||||
## The struct
|
||||
## Two questions, two axes
|
||||
|
||||
`SecuredContext` is a small `readonly struct` — the kind, the pawn, and two computed predicates.
|
||||
The context answers two different questions, and picking the right one is the whole skill of using it:
|
||||
|
||||
```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… |
|
||||
| Question | 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 |
|
||||
| **Is this pawn held?** | Prisoner, Slave | **custody** — who is under the colony's control, who can be disciplined, whose cell can be searched |
|
||||
| **Could this pawn be hiding something?** | 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.
|
||||
For example, [Propensity](Propensity.md)'s Nurture uses *held* in its "held & unhappy compounds" lever
|
||||
— 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, cares who *could* be hiding something to
|
||||
decide who's even worth checking, and then checks standing to decide whether it's *allowed* to.
|
||||
|
||||
---
|
||||
|
||||
## `Of` — one pawn to a context
|
||||
## How a pawn gets classified
|
||||
|
||||
```csharp
|
||||
public static SecuredContext? Of(Pawn p)
|
||||
```
|
||||
Any pawn resolves to one context, or to **nothing at all** if the suite doesn't model them:
|
||||
|
||||
`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:
|
||||
- Animals, mechs, and the dead are simply not the suite's business — they resolve to nothing, and
|
||||
every sweep and check skips them without a special case.
|
||||
- A pawn is tested as **prisoner** first, then **slave**, then **free colonist**. So a pawn who is
|
||||
somehow both a colonist and imprisoned is classified by their *hold* first.
|
||||
|
||||
```
|
||||
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.
|
||||
Keeping "nothing at all" separate from "a free pawn we track" is deliberate: there's a real difference
|
||||
between "a free pawn we watch" and "a pawn we don't model at all," and collapsing them would let
|
||||
non-colony pawns leak into colony machinery. A single sweep can walk every prisoner, slave, and
|
||||
colonist on a map in one pass, skipping everything that resolves to nothing.
|
||||
|
||||
---
|
||||
|
||||
## 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:
|
||||
This is the design decision the whole page exists to justify. If every feature checked "is this pawn a
|
||||
prisoner" 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.
|
||||
- adding **slaves** to a feature would mean revisiting every feature;
|
||||
- adding a **ward patient** mode would mean revisiting every feature;
|
||||
- adding a future **secure-zone** concept would mean revisiting every feature.
|
||||
|
||||
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`.
|
||||
By routing all of them through one question — *what kind of hold is this?* — a new kind of hold is
|
||||
added *once*, and every existing feature inherits it. The concealment, search, and needs machinery
|
||||
never has to learn what a slave is; it only ever asked whether the pawn is *held* and whether they
|
||||
*could conceal*.
|
||||
|
||||
### 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.
|
||||
There's no separate "ward patient" kind, and its absence is intentional. A committed ward patient
|
||||
**is** a prisoner in the game's own sense — Institution: Ward builds its committed-patient mode on top
|
||||
of vanilla's prisoner system — so such a pawn already counts as **Prisoner** here, with no special case
|
||||
and no dependency from Core (or Contraband) onto Ward. Every feature treats a ward patient exactly as
|
||||
it treats any prisoner. The right amount of new work to support Ward's hold semantics was zero, and
|
||||
that's the payoff of keying off the kind of hold instead of the label.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user