Wiki: player-facing rewrite of every layer + add a scenario FAQ, linked from Home

This commit is contained in:
flan
2026-07-16 02:18:25 +00:00
parent ff703271ba
commit 8250dcb369
32 changed files with 1654 additions and 2510 deletions
+111 -202
View File
@@ -4,20 +4,17 @@ How involuntary psychiatric commitment actually works in play: how a colonist be
what the treatment station does, how a room turns into a ward, who does the counselling, what the
treatment buys, and what happens if you commit someone and then forget about them.
Every def and class name below is the real one from the mod source. The `Ward_` prefix marks a
def; the C# class names are the workers behind them.
---
## The loop in one paragraph
You **arrest** a colonist (or otherwise take a prisoner) and set their interaction mode to
**psychiatric care**. You build a **treatment station** in their cell, which turns the room into a
**psychiatric ward**. A warden walks over, sits, and **counsels** them; each session adds a little
`Ward_UnderTreatment` severity, which **lowers their mental-break threshold**. That severity
**decays** at 0.15/day, so the effect fades unless wardens keep attending. Counselling also gives a
small mood lift (`Ward_Counselled`). Neglect a committed patient — leave them with no active treatment
hediff — and once a day they gain `Ward_Neglected`, a mood *penalty* that drags them back toward the
**psychiatric ward**. A warden walks over, sits, and **counsels** them; each session builds up their
**treatment**, which **lowers their mental-break threshold** so they break less often. That progress
**fades** over about a week if nobody keeps attending, so it has to be sustained. Counselling also
gives the patient a small mood lift. Neglect a committed patient — leave them with no ongoing
treatment — and once a day they pick up a **neglected** mood penalty that drags them back toward the
break that got them committed. Treat them and they stabilise; forget them and they don't. That
feedback loop is the entire mod.
@@ -25,34 +22,25 @@ feedback loop is the entire mod.
## Step 1 — Becoming a ward patient
Commitment reuses vanilla's arrest → prisoner transition. You don't invent a new pawn state; you take
a prisoner (arrest your own colonist, or a raider) and then flip on a **non-exclusive interaction
mode**.
Commitment reuses vanilla's arrest → prisoner transition. You don't get a new pawn state; you take a
prisoner (arrest your own colonist, or a captured raider) and then switch on a new interaction mode.
### `Ward_PsychiatricCare` — the interaction mode
### Psychiatric care — the interaction mode
| Field | Value | Why |
|---|---|---|
| Def type | `PrisonerInteractionModeDef` | A Def, not the compiled `GuestStatus` enum — that's the whole point |
| `defName` | `Ward_PsychiatricCare` | |
| `label` | "psychiatric care" | Shown in the prisoner's interaction dropdown |
| `listOrder` | `150` | Sits between vanilla's `ReduceResistance` (100) and `Release` (200) |
| `isNonExclusiveInteraction` | `true` | **Stacks** with recruit/convert/work instead of stealing the radio button |
| `mustBeAwake` | `false` | You can flag a downed or sleeping patient for care; the *work giver* decides when to actually treat |
| `allowOnWildMan` | `false` | A wild man isn't a commitment case |
| `allowInClassicIdeoMode` | `true` | Available without Ideology's ideoligion system |
In the prisoner's interaction dropdown you'll find a new option, **psychiatric care**, sitting between
*Reduce resistance* and *Release*. A few things make it behave the way it does:
**Non-exclusive is load-bearing.** Vanilla's recruit/convert/enslave/release/execute modes are
mutually exclusive — you pick one from a radio group. Psychiatric care is modelled on vanilla's own
*Bloodfeed* and *Study* modes, which are toggles layered *on top* of the exclusive choice. So a
patient can be set to **recruit AND psychiatric care at once**: you counsel the sad colonist toward
stability while also chipping at their resistance. It also means Ward doesn't have to ship a
cross-product of `workAndPsychiatricCare` variants to coexist with *Prison Labor*'s work modes — both
just toggle on.
- **It stacks.** Unlike recruit / convert / enslave / release / execute — which are one-at-a-time
choices — psychiatric care is a toggle that layers *on top* of whatever else you've picked, the
same way vanilla's *Bloodfeed* and *Study* toggles do. So a patient can be set to **recruit AND
psychiatric care at once**: you counsel a sad captured colonist toward stability while also chipping
at their resistance.
- **You can flag a downed or sleeping patient.** Setting the mode doesn't require them to be awake —
the warden decides when to actually treat.
- **Not for wild men**, and it works fine **without Ideology's ideoligion system**.
The harness proves this on a live pawn: after `ToggleNonExclusiveInteraction(Ward_PsychiatricCare,
true)` and then `SetExclusiveInteraction(AttemptRecruit)`, **both** modes report enabled
(`live.stacksWithRecruit = True`).
Because the mode stacks, it also lets Ward coexist cleanly with work-your-prisoners mods like *Prison
Labor* — see **Compatibility**.
---
@@ -60,144 +48,78 @@ true)` and then `SetExclusiveInteraction(AttemptRecruit)`, **both** modes report
Flagging a patient for care does nothing on its own. Treatment happens *at a station, in the
patient's own room*. Without one, a patient flagged for care in an ordinary cell simply goes
untreated — which is the point of the neglect thought (Step 6).
untreated — which is the point of the neglect penalty (Step 6).
### `Ward_TreatmentStation` — the building
### The treatment station — the building
> *"A desk, a chair bolted to the floor, and a locked cabinet of sedatives. A warden set to
> psychiatric care will use it to counsel patients held in this room."*
| Field | Value | Why |
|---|---|---|
| `defName` | `Ward_TreatmentStation` | |
| Parent | `BuildingBase` | Ordinary passable furniture |
| Cost | **40 Steel + 2 Industrial medicine** | Deliberately cheap |
| `WorkToBuild` | `1600` | A quick build |
| `MaxHitPoints` | `120` | |
| `researchPrerequisites` | `MedicineProduction` | The one research gate |
| `size` | `(2,1)` | A two-tile desk |
| `graphicClass` | `Graphic_Single` | One texture, not a rotation set — nothing to keep in sync |
| `designationCategory` | `Misc` | |
| `passability` | `PassThroughOnly`, `pathCost 60` | Pawns squeeze past it; it doesn't wall a cell |
| `Flammability` | `1.0` | It burns |
| What | Value |
|---|---|
| Cost | **40 Steel + 2 medicine** — deliberately cheap |
| Build time | Quick |
| Research needed | **Medicine Production** — the one gate |
| Size | A two-tile desk; pawns squeeze past it, so it doesn't wall off a cell |
| Notes | It burns |
**It's cheap on purpose.** The real cost of running a ward is not the 40 steel. It is the **warden
**It's cheap on purpose.** The real cost of running a ward is not the 40 steel. It's the **warden
hours** you spend counselling and the **labour you give up** by not putting the patient to work. The
building is just the gate that says "this room is set up to treat people."
And the locked cabinet isn't only flavour: a patient sliding toward a break faster than counselling
can steady them is the moment a **sedative** earns its keep — something to calm a patient who's close
to going over the edge.
---
## Step 3 — The room becomes a ward
Once a treatment station stands in a room that already holds prisoner beds, the room's *role* changes
from prison cell to psychiatric ward.
Once a treatment station stands in a room that already holds prisoner beds, the room's role changes
from prison cell to **psychiatric ward**. Vanilla already has Prison Cell, Prison Barracks and
Hospital; the ward is the fourth sibling. Two things are worth knowing:
### `Ward_PsychWard` — the room role
| Field | Value |
|---|---|
| Def type | `RoomRoleDef` (`workerClass` is a public field — no patch needed) |
| `defName` | `Ward_PsychWard` |
| `label` | "psychiatric ward" |
| `workerClass` | `Ward.RoomRoleWorker_PsychWard` |
| `relatedStats` | Impressiveness, Cleanliness, Space |
Vanilla already ships `PrisonCell`, `PrisonBarracks` and `Hospital` on exactly this rail; the ward is
the fourth sibling. The scoring worker, `RoomRoleWorker_PsychWard.GetScore`, is where the care goes:
```
score = 0 if stations == 0 OR prisonerBeds == 0
score = 1_000_000 × prisonerBeds otherwise
```
Two design decisions are baked into that formula:
- **Gated on a station.** The score is **zero** unless the room actually contains a
`Ward_TreatmentStation` *and* at least one prisoner bed. This is deliberate: without the gate, any
ordinary prison cell full of prisoner beds would start scoring as a ward and quietly steal the
`PrisonCell` role from vanilla — a compatibility bug dressed up as a feature. Build the station and
it's a ward; don't and your cells keep their vanilla role, untouched.
- **1e6, not a hard-coded constant.** Vanilla's room roles score in the `~1e5` range. The ward must
*outscore* `PrisonCell` for the same room or it would read as a cell block. Multiplying by
1,000,000 clears that range without pinning to a magic number a future RimWorld patch might move.
(The harness's `FakeDLC` fixture deliberately adds a greedy competing role at `1e5 × beds`, and the
ward still wins the room.)
**Crucially, it is still a prison cell.** Re-labelling the role does not release anyone. `Room.isPrisonCell`
is a cached field set only when the room's shape changes — never derived from the role — so a ward is
a prison cell *and* a ward simultaneously. Containment, food delivery, and prison breaks all keep
working. This is the pair of claims the harness holds together: `room.roleIsWard = True` **and**
`room.stillPrisonCellAsWard = True`.
- **It's gated on the station.** A room only reads as a ward if it actually contains a treatment
station *and* at least one prisoner bed. Without that, an ordinary prison cell full of beds keeps
its normal Prison Cell role, untouched. Build the station and it's a ward; don't and nothing
changes.
- **It's still a prison cell.** Re-labelling the role does not release anyone. A ward is a prison cell
*and* a ward at the same time, so containment, food delivery, and prison breaks all keep working.
---
## Step 4 — The warden does the work
### `Ward_WardenPsychiatricCare` — the work giver
Counselling is **warden work**. Assign it the way you assign any warden task, and a warden will walk
over and treat the patient. A few rules govern when a session can start:
| Field | Value | Why |
|---|---|---|
| `defName` | `Ward_WardenPsychiatricCare` | |
| `giverClass` | `Ward.WorkGiver_Warden_PsychiatricCare` | A **new** class extending `WorkGiver_Warden` |
| `workType` | `Warden` | Assigned like any other warden work |
| `verb` / `gerund` | "treat" / "treating" | |
| `priorityInType` | `70` | Above chat (60), below feeding — an untreated patient deteriorates, a bored one doesn't |
| `requiredCapacities` | Talking, Hearing | A mute or deaf warden can't counsel |
- The warden needs to be able to **talk and hear** — a mute or deaf warden can't counsel.
- The patient must be **awake, not downed, and not mid-break** — you can't counsel someone who's
unconscious or already spiralling.
- There must be a **treatment station in the patient's own room** (not out in the open). You can't
treat someone through a wall from a station two cells over.
- Its priority sits **above chatting** but **below feeding** — an untreated patient deteriorates, a
bored one doesn't — so wardens won't skip meals to run a session.
The class extends `WorkGiver_Warden` and its `JobOnThing` refuses the job unless **all** of these
hold:
### A session
1. The pawn is a prisoner of the colony being taken care of (`ShouldTakeCareOfPrisoner`).
2. `IsInteractionEnabled(Ward_PsychiatricCare)` — asked, not "is this *the* mode," because the mode is
non-exclusive and the patient may also be set to recruit or forced labour.
3. The patient is **awake, not in a mental state, and not downed** — counselling someone mid-break or
unconscious isn't counselling.
4. The warden can reserve the patient.
5. A `Ward_TreatmentStation` exists **in the patient's own room** (not `PsychologicallyOutdoors`), is
reservable, and isn't forbidden. You can't treat someone through a wall from the station two
blocks over.
- **Length:** about **20 in-game minutes**, with a progress bar while it runs.
- It **fails out** if the patient despawns, is forbidden, falls asleep, or breaks partway through.
- On completion it applies the treatment, gives the patient a "counselled" mood memory, runs a
deep-talk conversation (so the relationship builds and the *next* session lands harder), and awards
the warden **60 Social XP**.
**Being a new class is the entire compatibility story** (see The Compat Harness for the proof):
- *Custom Prisoner Interactions* prefixes/postfixes the **named** vanilla warden givers (`_Chat`,
`_Convert`, `_Enslave`, `_ReleasePrisoner`). It literally cannot see `WorkGiver_Warden_PsychiatricCare`,
so it can't break it.
- *Prison Commons* postfixes `WorkGiver_Warden.ShouldSkip` on the **base** class. Ward's giver
inherits that method and doesn't override it, so Ward respects prison-commons areas **for free**,
without Ward knowing Prison Commons exists.
### `Ward_ProvidePsychiatricCare` — the job
| Field | Value |
|---|---|
| `defName` | `Ward_ProvidePsychiatricCare` |
| `driverClass` | `Ward.JobDriver_PsychiatricCare` |
| `casualInterruptible` | `false` |
> Note: the `JobDef` and the `PrisonerInteractionModeDef` deliberately have **different** defNames.
> `[DefOf]` binds fields to defs by name, so two defs sharing one name couldn't both be reached from
> the `WardDefOf` class.
The driver, `JobDriver_PsychiatricCare`, walks the warden to the patient and runs a counselling toil:
- **Session length:** `SessionTicks = 1200` — about **20 in-game minutes**.
- The wait toil uses `activeSkill = Social` and shows a progress bar; it fails out if the patient
despawns, is forbidden, falls asleep, or enters a mental state partway through.
- On completion it applies treatment, grants the counselled mood memory, fires a `DeepTalk` social
interaction (so the relationship builds and the *next* session lands harder), and awards **60 Social
XP** to the warden.
Because psychiatric care is its own separate warden job rather than a rewrite of vanilla's, it doesn't
collide with mods that modify the built-in warden interactions — again, **Compatibility**.
---
## Step 5 — What treatment buys: `Ward_UnderTreatment`
## Step 5 — What treatment buys
Each completed session adds severity to a single hediff. The severity is **skill-scaled**:
Each completed session adds to the patient's treatment, and how much depends on the warden's **Social
skill**:
```
gain = 0.20 + (wardenSocialLevel / 20) × 0.30 → range 0.20 .. 0.50 per session
```
| Warden Social skill | Severity gained per session |
| Warden Social skill | Treatment gained per session |
|---|---|
| 0 | 0.20 |
| 5 | 0.275 |
@@ -205,100 +127,88 @@ gain = 0.20 + (wardenSocialLevel / 20) × 0.30 → range 0.20 .. 0.50 per
| 15 | 0.425 |
| 20 | 0.50 |
The floor is deliberate: even an unskilled warden buys **0.20** per session. A bad ward is *neglect*,
not *zero* — a clumsy counsellor is still worth something. Severity is capped at `maxSeverity = 1.0`.
The floor is deliberate: even an unskilled warden buys **0.20** a session. A bad ward is *neglect*,
not *zero* — a clumsy counsellor is still worth something. Treatment tops out at **1.0**, a full
course.
### The hediff
**What it does:** while treatment is active it **lowers the patient's mental-break threshold by
0.10** — a lower threshold means they break at a worse mood, i.e. **break less often**.
| Field | Value | Why |
|---|---|---|
| `defName` | `Ward_UnderTreatment` | |
| `hediffClass` | `HediffWithComps` | |
| `label` | "under psychiatric care" | |
| `isBad` | `false` | It's a good hediff; won't be treated as an injury |
| `scenarioCanAdd` | `false` | Can't be granted by scenario editor |
| `maxSeverity` | `1.0` | |
| **Decay** | `severityPerDay = −0.15` | Roughly a week from a full course back to nothing, unattended |
| **Effect** | `MentalBreakThreshold −0.10` (stage 0) | A *negative* offset means the pawn breaks at a **lower** mood — i.e. **breaks less often** |
**Decay is the mechanic.** Treatment bleeds off at **0.15 per day**, so a full course fades back to
nothing in **about a week** if nobody keeps attending. A patient treated once and then ignored slides
right back. Sustained counselling keeps it topped up and the break threshold suppressed; stopping lets
it fade. Treatment is a *programme*, not a one-time cure.
**Decay is the mechanic.** Because severity bleeds off at 0.15/day, a patient treated once and then
ignored slides right back. Sustained treatment keeps the hediff topped up and the break threshold
suppressed; stopping lets it fade. Treatment is a *programme*, not a one-time cure. (The
`MentalBreakThreshold −0.10` value is a first-guess, not yet playtested — see the "unplaytested"
caveats in the README.)
> The −0.10 break-threshold figure is a first pass and not yet heavily playtested — expect it to be
> tuned.
---
## Step 6 — The other half: neglect
A ward you don't staff is not a neutral place to keep someone. It is a *worse* one. Without a cost,
A ward you don't staff is not a neutral place to keep someone. It's a *worse* one. Without a cost,
"arrest the sad colonist and park them" would be a free way to remove a problem pawn from play. It
isn't free.
### `MapComponent_WardNeglect`
**Once per in-game day**, Ward checks every prisoner flagged for psychiatric care. Any patient who
isn't currently under treatment — meaning no warden has been near them in days — picks up a
**neglected** mood penalty. (The check keys off active treatment, and since treatment decays on its
own, its absence is the tell that the patient has genuinely been left alone, not merely that nobody
happens to be counselling them this exact minute.)
Once per **in-game day** (`CheckIntervalTicks = 60000`), this component scans every spawned prisoner
of the colony. For each prisoner flagged for `Ward_PsychiatricCare` who does **not** currently have a
`Ward_UnderTreatment` hediff, it grants the `Ward_Neglected` thought.
### The two moods
The absence of the treatment hediff is the signal *by design*: because the hediff decays on its own,
its absence means **no warden has been near this patient in days** — not merely that they aren't being
counselled at this exact instant.
### The two thoughts
| Thought | Mood | Duration | Stack | Meaning |
| Mood | Value | Duration | Stacks to | Meaning |
|---|---|---|---|---|
| `Ward_Counselled` | **+6** | 2 days | up to 3 | "Someone sat with me and listened. It helped, a little." |
| `Ward_Neglected` | **−8** | 3 days | up to 4 | "They locked me in here for my own good and then forgot about me." |
| **Counselled** | **+6** | 2 days | 3 | "Someone sat with me and listened. It helped, a little." |
| **Neglected** | **−8** | 3 days | 4 | "They locked me in here for my own good and then forgot about me." |
Neglect is asymmetric — a −8 penalty stacking four deep (−32) badly outweighs three counselling lifts
(+18). That's intentional: a neglected patient's mood falls, which raises their break risk, which is
exactly the crisis you committed them to avoid. The ward can *manufacture* the break it was built to
prevent. Staff it, or don't build it.
Neglect is **asymmetric** — a −8 penalty stacking four deep (−32) badly outweighs three counselling
lifts (+18). That's intentional: a neglected patient's mood falls, which raises their break risk,
which is exactly the crisis you committed them to avoid. A ward can *manufacture* the break it was
built to prevent. Staff it, or don't build it.
> **Known rough edge (from the README's "Open" list):** neglect currently keys off "no
> `Ward_UnderTreatment` hediff." A patient treated once a week technically dodges the neglect thought
> on the single day the hediff expires. A real last-treated timestamp would be cleaner. Documented,
> not yet fixed.
> **Known rough edge:** neglect keys off "not currently under treatment," so a patient treated once a
> week can technically dodge the penalty on the single day their treatment runs out. It's a documented
> quirk, not yet smoothed.
---
## How it stacks with recruitment (and everything else)
Because `Ward_PsychiatricCare` is non-exclusive, it layers onto whatever else you've set:
Because psychiatric care is a non-exclusive toggle, it layers onto whatever else you've set:
| You want to… | Set | Result |
|---|---|---|
| Just stabilise a broken colonist | Psychiatric care | Wardens counsel; break threshold drops |
| Talk a captured raider down *and* recruit them | Psychiatric care **+** Attempt recruit | Both run — counselling lifts mood while resistance is chipped |
| Convert *and* treat | Psychiatric care **+** convert | Both toggle on |
| Work a patient *and* treat them | Psychiatric care **+** Prison Labor's work mode | Both apply. Working a fragile patient is bleak — and it's your call to make |
| Work a patient *and* treat them | Psychiatric care **+** Prison Labor's work mode | Both apply. Working a fragile patient is bleak — and it's your call |
The last row is the darkest option the mod exposes, and it exposes it on purpose: Ward doesn't
forbid working a patient, it just makes the trade-off visible.
The last row is the darkest option the mod exposes, and it exposes it on purpose: Ward doesn't forbid
working a patient, it just makes the trade-off visible.
Because a ward patient is a genuine vanilla prisoner, everything the wider Institution suite does to
prisoners applies to them unchanged — a committed patient can be classified, disciplined, paroled,
searched, and can conceal or improvise contraband. Ward writes no integration code for any of that;
the patient satisfies "held pawn" and the rest follows. See **Home** for the suite map and **The
Compat Harness** for how all of it is verified in one running game.
searched, and can conceal or improvise contraband. See **Home** for the suite map and **Compatibility**
for how Ward stays out of other mods' way.
---
## Quick reference — every Ward def
## Quick reference — the numbers
| defName | Type | Class (if any) | Key numbers |
|---|---|---|---|
| `Ward_PsychiatricCare` | PrisonerInteractionModeDef | — | non-exclusive; listOrder 150 |
| `Ward_TreatmentStation` | ThingDef | `Building` | 40 steel + 2 medicine; WorkToBuild 1600; MedicineProduction research |
| `Ward_PsychWard` | RoomRoleDef | `RoomRoleWorker_PsychWard` | score = 1e6 × prisonerBeds (station-gated) |
| `Ward_WardenPsychiatricCare` | WorkGiverDef | `WorkGiver_Warden_PsychiatricCare` | priority 70; Talking + Hearing |
| `Ward_ProvidePsychiatricCare` | JobDef | `JobDriver_PsychiatricCare` | 1200-tick session; +60 Social XP |
| `Ward_UnderTreatment` | HediffDef | `HediffWithComps` | +0.20–0.50/session; −0.15/day decay; −0.10 break threshold |
| `Ward_Counselled` | ThoughtDef | `Thought_Memory` | +6 mood, 2 days, stack 3 |
| `Ward_Neglected` | ThoughtDef | `Thought_Memory` | −8 mood, 3 days, stack 4 |
| — | MapComponent | `MapComponent_WardNeglect` | daily scan (60000 ticks) |
| Thing | Number |
|---|---|
| Treatment station cost | 40 steel + 2 medicine; needs Medicine Production research |
| Counselling session | ~20 in-game minutes |
| Treatment per session | 0.20 (unskilled) → 0.50 (Social 20) |
| Treatment cap | 1.0 (a full course) |
| Treatment decay | −0.15/day (~a week from full to nothing) |
| Break-threshold effect | −0.10 while treated (breaks less often) |
| Counselled mood | +6, 2 days, stacks to 3 |
| Neglected mood | −8, 3 days, stacks to 4 |
| Neglect check | once per in-game day |
| Warden reward | 60 Social XP per session |
---
@@ -309,4 +219,3 @@ Institution: Ward is one mod in the **Institution** suite of RimWorld 1.6 mods
and **Foul Play**. Each stands alone; together they interlock. A committed patient rides the same
prisoner rail the rest of the suite polices, so a psychiatric ward is also a secure context where a
patient can conceal and improvise contraband exactly as a prisoner can.
+31 -50
View File
@@ -16,66 +16,48 @@ as though nothing happened. Mental breaks are episodic and amnesiac — nothing
Ward adds the missing state, and the room to put it in. A colonist you arrest can be set to
**psychiatric care**. Wardens counsel them at a **treatment station**. A room with prisoner beds
and a treatment station is a **psychiatric ward**, not a prison cell. Treatment lowers the patient's
mental-break threshold while it lasts — and it decays, so it has to be *sustained*. A ward you don't
mental-break threshold while it lasts — and it fades, so it has to be *sustained*. A ward you don't
staff is worse than no ward at all.
---
## The third state, without a fourth enum slot
## The third state RimWorld doesn't have
The reason "commit a colonist" isn't a stock feature is a single compiled fact. `RimWorld.GuestStatus`
is a three-value enum:
The reason "commit a colonist" isn't a stock button is simple: vanilla only knows two kinds of held
person — a free colonist and a prisoner. There's no built-in slot for someone who is neither free nor
guilty, which is why other mods that keep a not-quite-colonist around (like *Hospitality* and its
guests) have to work so hard to maintain them.
```
Guest = 0
Prisoner = 1
Slave = 2
```
**Ward takes the cheap road.** A committed pawn *is* a prisoner — arresting your own colonist already
does that. Ward just adds a new **way to handle** that prisoner: a "psychiatric care" mode you toggle
on, sitting alongside vanilla's recruit / convert / release options. Nothing exotic — it's the same
kind of custom prisoner mode other prison mods already add.
A mod **cannot add a fourth value** to a compiled enum. That one constraint drives every design
decision in Ward — and in the rest of the ecosystem. It is why *Hospitality* carries dozens of
Harmony patches to maintain a pawn who is in your colony but is not your colonist: the enum has no
slot for one, so it runs a shadow guest system and defends it by patching bed validity, allowed
areas, the work JobGiver and the mental-state handler.
So committing someone is entirely ordinary from the game's point of view:
**Ward doesn't pay that tax.** A committed pawn *is* a prisoner, in vanilla's own sense of the word —
arresting your own colonist already performs the colonist→prisoner transition. Ward only adds a new
**mode** to hold them under, and `PrisonerInteractionModeDef` is a **Def, not an enum**. Adding one
is XML plus, at most, a worker class. This is not a trick; it is the established pattern. *Prison
Labor* already ships seven custom interaction modes of its own.
- **psychiatric care** — a new prisoner interaction mode you switch on
- **the psychiatric ward** — a new room role a cell earns once it holds a treatment station
- **counselling** — a new warden job, handled like any other warden work
- the **treatment station**, the **recovery**, the **neglect** — new content layered on top
So the *state itself* costs **zero patches** — it is all defs:
| Extension point | What Ward adds | Patches needed |
|---|---|---|
| `PrisonerInteractionModeDef` | `Ward_PsychiatricCare` (non-exclusive) | 0 |
| `RoomRoleDef` (`workerClass` is public) | `Ward_PsychWard` | 0 |
| `WorkGiverDef` (`giverClass`) | `Ward_WardenPsychiatricCare` | 0 |
| `JobDef`, `HediffDef`, `ThoughtDef`, `ThingDef` | treatment, recovery, neglect, the station | 0 |
Ward *does* use Harmony where a patch is the cleaner tool (a patient's inspect line, and more), so it
is not a zero-patch mod. Its compatibility is about **which** methods it avoids: it touches none of
the contested methods the popular psych/prison mods fight over, and never prefixes
`MentalStateHandler.TryStartMentalState`. The full argument — which mods it was verified against and
why it can't collide with them — is on **The Compat Harness** page.
Because Ward *adds* things rather than rewriting how vanilla prisoners behave, it sits quietly next to
the popular psych and prison mods instead of fighting them. Which mods, and why, is on the
**Compatibility** page.
---
## Riding the prisoner rail (why the suite cares)
The design choice that makes Ward *cheap* also makes it *interesting* inside the wider Institution
suite. A committed patient is a genuine vanilla prisoner. That means they are a **secure context** in
exactly the sense the rest of the suite understands: a pawn who is held, searchable, and capable of
concealing and improvising contraband. A patient in a psych ward can hoard a shiv or brew something
foul in a smuggled vessel for **free**, because the contraband, search, corruption and gang systems
were written against "held pawn," and a ward patient satisfies that predicate without Ward writing a
line of integration code.
The choice that makes Ward *cheap* also makes it *interesting* inside the wider Institution suite. A
committed patient is a genuine vanilla prisoner. That means they're a **held pawn** in exactly the
sense the rest of the suite understands: someone who can be searched, and who can conceal and
improvise contraband. A patient in a psych ward can hoard a shiv or brew something foul in a smuggled
vessel, because the contraband, search, corruption and gang systems already work on any held
prisoner — and a ward patient is one, for free.
The ward is a prison cell that happens to also be a ward. `Room.isPrisonCell` is a cached field
written only when the room's shape changes — it is **not** derived from the room's role — so
re-labelling a cell as a ward cannot break containment, food delivery, or prison breaks. The compat
harness asserts exactly this: a built ward is both `roleIsWard = True` **and** `stillPrisonCellAsWard
= True`. The two claims pull against each other, and both have to hold.
**And re-labelling a cell as a ward never lets anyone out.** A ward is a prison cell that also happens
to be a ward — the two roles coexist. Containment, food delivery, and prison breaks all keep working
exactly as they did before you built the station.
---
@@ -84,8 +66,8 @@ harness asserts exactly this: a built ward is both `roleIsWard = True` **and** `
| Page | What's on it |
|---|---|
| **Home** (this page) | What Ward is, the third-state problem, the suite |
| **Commitment** | How commitment works in play: the mode, station, room role, work giver, job, hediff, thoughts, neglect, and how it stacks with recruitment — with every real def name and number |
| **The Compat Harness** | The in-game test tool this repo carries: booting real headless RimWorld with the whole suite + the most-subscribed Workshop mods (~24 active), the dependency-ordered build, the `WARDTEST` / `PNTEST` / `CBTEST` / `FPBRIDGE` assertion families, and how to run it |
| **Commitment** | How commitment works in play: the care mode, the station, the ward room, the warden's job, treatment, recovery, neglect, and how it stacks with recruitment — with the real numbers |
| **Compatibility** | Which popular psych and prison mods Ward plays nicely with, and why it doesn't fight them |
---
@@ -99,14 +81,13 @@ conceal and improvise contraband exactly as a prisoner can, because they ride th
| Mod | What it is |
|---|---|
| **Institution: Ward** (this) | Involuntary psychiatric commitment — the third pawn state, neither free nor guilty. Also carries the suite's in-game compat harness. |
| **Institution: Ward** (this) | Involuntary psychiatric commitment — the third pawn state, neither free nor guilty. |
| **Institution: Core** | The shared engine — propensity (nature × nurture), the criminal record, secured context. |
| **Institution: Contraband** | The physical smuggling loop — conceal, improvise shivs, dig Prison-Architect tunnels, warden search, warden corruption. |
| **Institution: Justice** | Corrections & policing — classification, deterrence, discipline, parole, regime. |
| **Institution: Gangs** | Gangs as contraband economies — joining, smuggling networks, rivalry, fights-as-crime. Also the home of the suite's cross-module `CBTEST` integration test. |
| **Institution: Gangs** | Gangs as contraband economies — joining, smuggling networks, rivalry, fights-as-crime. |
| **Foul Play** | The vessel + substance + throw framework, and the "Piss Nuke" flagship; bridges to Core + Contraband when both are present. |
Reference sibling mods by name — the mods are separate repositories and cross-repo links break.
---
+26 -271
View File
@@ -1,281 +1,36 @@
# The Compat Harness
# Compatibility
`Tools/run-compat-test.sh` is not a unit test. It is a **documented tool** that boots a *real,
headless RimWorld* with the entire Institution suite and the most-subscribed relevant Workshop mods
loaded **at once**, plays the game at maximum speed for a few thousand ticks, and greps the log for
assertions that the mods it built actually work — and don't silently eat each other.
Ward is built to sit alongside your existing mod list rather than elbow it aside. The trick is that
Ward mostly **adds** things — a new "psychiatric care" prisoner mode, a new building, a new room role,
new jobs — instead of rewriting how vanilla prisoners already behave. And a committed patient isn't a
new kind of pawn: they're an ordinary vanilla prisoner with an extra "treat me" flag switched on. So
any mod that already works with prisoners keeps working with a ward patient, and Ward stays out of its
way.
Ward carries this harness for the whole suite because Ward is where the compatibility argument lives:
Ward's central claim is "I add defs, I patch nothing, so I can't collide with your mod list," and a
claim like that can only be *believed* from static analysis. It has to be *run* to be proven.
Here's how Ward gets along with the popular psych and prison mods:
---
## Why a running game, not static analysis
Static analysis got the design most of the way. Dumping the Harmony attributes out of every mod
assembly shows **where** two mods want to patch the same vanilla method. What it cannot show is
whether that collision actually **bites**:
- Two mods can both postfix a method and be completely fine.
- Or one mod's **prefix returns `false`**, which short-circuits the original method *and every other
prefix* — silently eating another mod's patch. **Nothing crashes.** A feature just quietly stops
working.
The canonical example the harness guards: *Hospitality* prefixes
`MentalStateHandler.TryStartMentalState`. If Ward ever added a second prefix there that returned
`false`, Hospitality's would silently never run, and there would be no error to notice. So the harness
doesn't read code — it interrogates the **live Harmony patch table inside a running RimWorld** and
asserts who owns each contested method.
The same failure mode, in a different shape, threatens Foul Play: the "Piss Nuke" psychotic-spree
feature is delivered by **XPath PatchOperations** into `MentalStateNonCritical` (and the escape/duty
defs). If another mod reshapes those defs, the XPath silently stops matching, the feature dies, and —
again — nothing errors. Only booting the whole stack and watching the spree actually happen catches
it.
---
## What it loads (~24 active mods)
The full-stack run activates the base game + three DLC, two libraries, the popular psych/prison
Workshop mods, and the whole Institution suite — **24 active mods** by default, **25** with the
`FakeDLC` fixture switched on.
| Group | Mods |
| Mod | How it coexists with Ward |
|---|---|
| Base + DLC | RimWorld Core, Royalty, Ideology, Biotech |
| Libraries | Harmony, HugsLib |
| Guest/prisoner rail | Hospitality, Prison Labor, Locks, Prison Commons, Custom Prisoner Interactions, Prisoner Realism, Prisoner Recreation |
| Mental health | Psychology (unofficial), Rim Disorders, More Mental Breaks, Restraints |
| The big neighbour | Dubs Bad Hygiene (632k subscribers — the one Foul Play deliberately does *not* duplicate) |
| Institution suite | Core, Contraband, Justice, Gangs, Foul Play, **Ward** |
| Optional fixture | FakeDLC (stands in for Anomaly/Odyssey; only with `FAKE_DLC=1`) |
| **Hospitality** | Hospitality manages *guests*; Ward manages *prisoners*. They ride two completely separate rails and never touch. |
| **Prison Labor** | Psychiatric care is a toggle, so you can put a patient **to work *and* in treatment at the same time**. No conflict, no either/or. |
| **Custom Prisoner Interactions** | That mod tweaks the built-in warden interactions (chat, convert, enslave, release). Ward's counselling is a *separate* job of its own, so the two don't overlap. |
| **Prison Commons** | Ward's warden work automatically respects prison-commons areas — you get that for free, without either mod knowing about the other. |
| **Psychology (unofficial) · Rim Disorders · More Mental Breaks** | These reshape the mental-break and mental-illness machinery. Ward doesn't touch that machinery — it just nudges the break *threshold* the way a mood effect would — so they all stack. |
| **Prisoner Realism** | It changes how the game decides what counts as a prison cell. Ward's ward role sits on top of that without breaking containment — a ward is still a prison cell. |
| **Locks · Restraints · Prisoner Recreation** | All fine. They touch prisoner behaviours Ward leaves alone. |
| **Dubs Bad Hygiene** | No overlap at all. |
| **Royalty · Ideology · Biotech** | Ward works with or without the DLC. Psychiatric care is available even if you don't run Ideology's ideoligions. |
The Workshop mods are read from a persistent, ID-keyed cache (default
`/home/dev/rimworld-ref/mods-cache`, keyed by Workshop file id). The cache lives on the home
partition, not `/tmp`, because a scratchpad reap once took the whole stack down with it. Rebuild it
from an installed run with `Tools/rebuild-mods-cache.sh`.
Two sharp compat targets in that list are worth calling out, because they are exactly the ones that
*could* break Ward:
- **Prisoner Realism** postfixes `Verse.Room.IsPrisonCell` — and Ward introduces a room role that can
outrank `PrisonCell`. Statically, `isPrisonCell` is a cached field written only by
`Notify_RoomShapeChanged` and is **not** derived from the role, so Ward can't break it. The harness
proves that at runtime, on a room it builds and re-roles live.
- **Hospitality** owns the whole "a pawn in the colony who is not a colonist" surface, including that
`TryStartMentalState` prefix. Ward rides the *prisoner* rail; Hospitality rides the *guest* rail;
the harness asserts they never touch.
---
## The dependency-ordered build
The Institution suite is split across separate assemblies with real cross-references, so they must
**build** and **load** in dependency order or the references won't resolve. Core is the dependency-free
leaf everything else reads.
### Build order (what the script compiles, and why)
| # | Built | Depends on (to compile) | SelfTest? | Notes |
|---|---|---|---|---|
| 1 | **Institution: Core** | base game only | no | The propensity / criminal-record / secured-context engine. Built even in Core-only, because everything below references its DLL. |
| 2 | **Institution: Justice** | Core | no | Classification, deterrence, discipline, parole, regime. Built even in Core-only (the bridge references it). |
| 3 | **Institution: Contraband** | Core | no | Concealment, search, tunnels. Builds **clean** now — see "Where CBTEST lives" below. |
| 4 | **Foul Play** (+ bridge) | Core, Contraband, its own DLL | **yes** (`PNTEST`) | The vessel/substance/throw framework and the Piss Nuke flagship. Its optional Contraband **bridge** is compiled against the Core/Contraband/FoulPlay DLLs. |
| 5 | **Institution: Gangs** | Core, Contraband, Justice | **yes** (`CBTEST`) | The social capstone — the only assembly that references *every* other Institution mod, which is why the cross-module integration harness lives here. |
| 6 | **Institution: Ward** | base game only (harness: + Harmony) | **separate assembly** (`WARDTEST`) | Ship `Ward.dll` carries no harness (Harmony ships as a normal dependency; the harness does not). The `WARDTEST` harness is its own project, `Source/Ward.CompatTest` → `Ward.CompatTest.dll`, built to a scratch dir and dropped beside the ship DLL at test time. |
### Load order (the `activeMods` list)
Harmony first (it says so itself), then the base game + DLC, then HugsLib, then the Workshop mods,
then the Institution suite **in dependency order**, and **Ward dead last**:
```
Core → Contraband → Justice → Foul Play → Gangs → Ward
```
Contraband and Justice each need only Core (their relative order doesn't matter). Foul Play's bridge
needs Contraband **active**, so Foul Play loads after it. Gangs is the capstone and needs Core +
Contraband + Justice. **Ward loads last on purpose** — its Harmony conflict report reads the live
patch table, so every other mod must have had its chance to patch *before* Ward looks.
The script self-checks the config, not just the mod: every Workshop mod copied into `Mods/` has its
real `packageId` cross-checked against `activeMods`, because copying a mod in without activating it
would silently shrink the stack and still pass. It also lints every def XML first (`Tools/lint-xml.py`) —
a malformed comment makes RimWorld drop a def *silently*, and that's not worth a 15-minute headless
run to discover.
---
## Where CBTEST lives now (Gangs, SelfTest)
The suite's cross-module integration test — the `CBTEST` family, which drives the Prison-Architect
tunnel dig→search→breach loop and the full Core/Justice/Gangs interaction — used to live in
Contraband. It **moved into Gangs**, because Gangs is the one assembly that already references every
Institution mod (Core, Contraband, Justice). Consequences:
- **Contraband now builds clean** — no `SelfTest`, no test code in the shipped `Contraband.dll`.
- **Gangs** is built with `-p:SelfTest=true`, which compiles its integration `GameComponent` in. That
build leaves test code in `Assemblies/InstitutionGangs.dll`, so the Gangs repo must be rebuilt
clean before its shipped DLL is committed.
- **Foul Play** is likewise built `-p:SelfTest=true` for its own `PNTEST` harness, so its DLL must
be rebuilt clean before it is shipped.
- **Ward** no longer works that way. Its harness is a **separate assembly**
(`Source/Ward.CompatTest` → `Ward.CompatTest.dll`), never compiled into `Ward.dll`. There is no
`SelfTest` flag on the ship project and nothing to "rebuild clean" — a build of `Ward.csproj` is
physically incapable of emitting the harness or a Harmony reference. `run-compat-test.sh` builds
the harness to a scratch dir and installs `Ward.CompatTest.dll` beside the clean ship DLL, where
RimWorld loads both. A CI guard additionally fails any push whose committed `Ward.dll` carries
`HarmonyLib`/`CompatTest` symbols. (Gangs and Foul Play still rely on the rebuild-clean
discipline; Ward removed that failure mode outright after a SelfTest `Ward.dll` shipped by
accident.)
---
## The assertion families
Everything is written to the log with a family tag, and the verdict section greps for each one. A run
with no `WARDTEST` lines at all is a hard failure ("NO ASSERTIONS RAN").
| Tag | Owner | What it proves |
|---|---|---|
| **`WARDTEST`** | Ward (`Source/Ward.CompatTest/CompatTest.cs`) | Ward loaded, patches no *contested* method, and works on a live prisoner |
| **`PNTEST`** | Foul Play self-test | The Piss Nuke / vessel / substance / ferment content works under the stack |
| **`FPBRIDGE`** | Foul Play ↔ Contraband bridge | The optional bridge loaded (and *only* when Contraband is present) and merged the two frameworks |
| **`CBTEST`** | Gangs self-test | The whole Institution loop — tunnel, search, classification, discipline, parole, corruption, gangs, regime — runs end-to-end on a live map |
| *(`PNCOMBAT`)* | Foul Play | Grepped alongside PNTEST for the combat/throw path |
### `WARDTEST` — Ward's own checks
Split between a `[StaticConstructorOnStartup]` (static def checks, "did Ward *load*") and a
`GameComponent` that runs at tick 60+ (the live checks, because the static-ctor order between mods is
undefined and Contraband injects its escape JobGivers from its *own* static ctor):
- **Def load:** `def.interactionMode` (and `.nonExclusive`), `def.roomRole` (and `.workerResolves`),
`def.workGiver` (and `.classResolves`), `def.job`, `def.hediff`, `def.station`,
`station.graphicLoaded` (a missing texture is a magenta box and a log line, not a crash — so it's
checked on purpose).
- **Vanilla intact:** `vanilla.prisonerModes.intact` — `MaintainOnly`, `AttemptRecruit`,
`ReduceResistance`, `Release`, `Execution` all still exist (a defName collision would let the game
boot while quietly removing your ability to execute or release a prisoner).
- **The central claim:** `ward.patchesNoContestedMethod` — the harness enumerates every vanilla method
where a psych/prison mod *could* collide (`SetGuestStatus`, `IsValidBedFor`, `InAllowedArea`,
`TryStartMentalState`, `MentalBreaker.TryDoRandomMoodCausedMentalBreak`, `WorkGiver_Warden.ShouldSkip`,
`WorkGiver_Warden_Chat.JobOnThing`, …), reads the live Harmony patch owners, and asserts **none of
them is owned by Ward.** The moment Ward grows a Harmony patch on a contested method, this fails.
- **The specific one:** `ward.noPrefixOn.TryStartMentalState` — Ward must never add a *prefix* there,
or it silently eats Hospitality's.
- **Live usability:** `live.isPrisoner`, `live.psychCareEnabled`, `live.stacksWithRecruit` — a real
prisoner is generated, flagged for care via vanilla's *own* public API, and confirmed to stack with
`AttemptRecruit`, with Prison Labor / Custom Prisoner Interactions / Hospitality all loaded and
patching the guest tracker.
- **The built ward:** the component builds an actual walled, roofed room with a prisoner bed and a
station on the live map, forces a region/room rebuild, and asserts `room.proper`,
`room.plainCellNotStolen` (a bare cell keeps its vanilla role), `room.roleIsWard`, and — the
load-bearing one — `room.stillPrisonCellAsWard`.
- **Escape-tree order (`escape.armBeforeExit`, `escape.useContrabandBeforeExit`):** Ward also verifies
that Contraband's escape JobGivers land *before* `JobGiver_GotoTravelDestination` in the
`PrisonerEscape` think tree. A think node that *landed* is not a think node that can *run* — the tree
takes the first node that returns a job, and the travel-destination node returns one essentially
always, so anything appended after it is dead code that would pass a mere presence test. The harness
asserts **position**, because pre-order index is evaluation order.
- Terminated by `WARDTEST DONE`.
### `PNTEST` / `FPBRIDGE` — Foul Play
The Foul Play self-test proves the flagship content survives the stack: the carboy weapon def loads
(`def.weapon`), the spree think-tree XPath still matches (`thinktree.patched`), and the generic-vessel
refactor holds — runtime substance, label-shows-contents, the eight-substance catalogue, drink
consequences, mix ratios, pour capacity, water-douses-fire, blend round-trip through save/load,
fermentation dilution and spoilage, "only 100% ripe piss reads as a nuke," any pawn filling a vessel,
furniture dip/pour, and the autonomous cocktail on fill. The `FPBRIDGE` family fires only when
Contraband is present, and proves the optional bridge wired the carboy's brew-from-body worker, merged
the two propensity engines, and wired the content-tag delegates so a concealed vessel keeps its
substance.
### `CBTEST` — the suite integration test (in Gangs)
When Contraband is active, the Gangs self-test drives the entire loop on a live map and every one of
these must report `True`: `escape.registered`, `escape.foundWillingDigger`, `escape.digRatePositive`,
`escape.tickAdvancesTunnel`, `escape.recordsAttempt`, `escape.breachDestroysWall`,
`escape.emergedOutside`, `escape.toolConsumed`, `intake.concealsOnBodyContraband`,
`content.tagSurvivesRedeem`, `corruption.dispositionVaries`, `classification.gradeReflectsRecord`,
`prisonization.reshapesNurture`, `justice.deterrenceFeedback`, `discipline.hardens`,
`parole.releaseDecision`, `corruption.bentWardenSmuggles`, `gangs.smugglesAcrossWall`,
`gangs.fightIsCrime`, `gangs.membershipByDisposition`, `regime.prisonerRecreation`. Any `CBTEST … =
False`, or a `CBTEST HARNESS THREW`, fails the run.
---
## How to run it
```bash
# Full stack: ~24 active mods, all DLC, the popular Workshop mods.
Tools/run-compat-test.sh /path/to/rimworld-install [/path/to/workshop-mods]
```
The second argument (the Workshop-mod cache) defaults to `/home/dev/rimworld-ref/mods-cache` or
`$RIMWORLD_MODS_CACHE`. The Institution sibling source dirs default to `$HOME/rimworld-<mod>` and can
be overridden with `INSTITUTIONCORE`, `INSTITUTIONJUSTICE`, `CONTRABAND`, `FOULPLAY`,
`INSTITUTIONGANGS`.
### The two modes — they fail in opposite directions
| Env var | What it does | Why it exists |
|---|---|---|
| `CORE_ONLY=1` | No DLC, no Workshop mods, no Contraband/Justice/Gangs — just **Foul Play + Ward on a bare Core install** | Coexisting with 24 mods proves nothing about the player who owns no DLC. A def that accidentally referenced a DLC thing would resolve happily in the full run and break for everyone else. This is the opposite test. |
| `FAKE_DLC=1` | Loads `Tools/fixtures/FakeDLC` — a *hostile* mod that does to Ward's surface everything an unknown DLC could: competing prisoner interaction modes, a competing room role (`~1e5 × beds`), and pokes the very think-tree/duty defs the Piss Nuke XPaths depend on | You own Royalty/Ideology/Biotech but **cannot** load Anomaly/Odyssey without owning them (a DLC's defs can't be downloaded otherwise). The fixture stands in for what can't be loaded, and it loads *before* the suite so its patches land first. |
`CORE_ONLY` is not achievable by editing `ModsConfig` (RimWorld re-activates any expansion whose
`Data/` folder is present) or by symlinks (Unity resolves the executable's real path back into the
original install). The script builds a **hard-link copy** of the game with the DLC `Data/` folders
deleted — the binary genuinely lives in a DLC-free tree, so its real path resolves there, at no disk
cost. In Core-only, `CB_ACTIVE=0` drops Contraband/Justice/Gangs from the load order too, so the pass
tests Foul Play + Ward genuinely standalone: the Contraband bridge must **not** load, yet the piss
content must still work. The verdict section asserts both directions — and a Core-only run that
quietly still had DLC loaded (which happened, twice, during development) is caught by grepping the
`loaded mods` line for `royalty|ideology|biotech`.
### Watching a run
```bash
# Stream a run to a stable log on the home partition (survives /tmp reaps):
FAKE_DLC=1 Tools/harness-stream.sh /path/to/rimworld-install
# Follow it live from anywhere on the box (-F survives the log being recreated each run):
Tools/watch-harness.sh
```
Internally the run boots via `timeout 900 xvfb-run … RimWorldLinux -logfile … -quicktest`, forces
`TimeSpeed.Ultrafast` from the `GameComponent` (same per-tick logic, just no idle real-time between
ticks), and calls `Root.Shutdown()` once every assertion is written (~tick 8000; a 12000-tick watchdog
is the safety cap). The 900-second `timeout` is a backstop, not the normal exit.
---
## Requirements — and why it's local, not CI
| Requirement | Why |
|---|---|
| A **licensed RimWorld install** with the `RimWorldLinux` executable | The harness boots the *real* game engine. There is no headless stub — the whole value is that vanilla's own room grid, think trees, and mental-state handler run for real. |
| **The Workshop mods**, in the ID-keyed cache | Fetched with `DepotDownloader -app 294100 -pubfile <id>` — which needs a Steam account that *owns* RimWorld. The mods can't be redistributed. |
| **xvfb** + Mesa software GL (`libgl1-mesa-dri`) | RimWorld is a Unity app that insists on a GL context even for `-quicktest`. `xvfb-run` gives it a headless framebuffer; `LIBGL_ALWAYS_SOFTWARE=1` / `llvmpipe` render on the CPU (slow, but no GPU needed). |
| **dotnet** | Builds all six Institution assemblies (net472 via Krafs reference assemblies — no game install needed to *compile*, only to *run*). |
Every one of those — a licensed game binary, non-redistributable Workshop content, a Steam login, a
GL-hungry Unity process — is a reason this cannot live in ordinary CI. It is a **local, on-demand
integration test**: you run it before a release, on a box that owns the game, and read the verdict.
The last line of a good run is `ALL COMPAT ASSERTIONS PASSED`.
The general rule: because Ward *adds* rather than *overrides*, it should also coexist with prison and
psych mods that aren't on this list. If you ever find one it genuinely fights, that's a bug worth
reporting — Ward's whole design is built around not doing that.
---
## Part of the Institution suite
The harness in this repo builds and tests the whole suite — **Institution: Core**, **Institution:
Contraband**, **Institution: Justice**, **Institution: Gangs**, **Foul Play**, and **Institution:
Ward** — in dependency order, alongside the most-subscribed psych/prison Workshop mods, in one running
game. Ward carries it because Ward's design *is* a compatibility claim, and a claim like that is worth
nothing until it's run.
The Institution suite — **Institution: Core**, **Institution: Contraband**, **Institution: Justice**,
**Institution: Gangs**, **Foul Play**, and **Institution: Ward** — is a set of RimWorld 1.6 mods about
what an institution does to the people inside it. Each stands alone; together they interlock. Ward's
psychiatric ward is a secure context where a committed patient can conceal and improvise contraband
exactly as a prisoner can, because they ride the same prisoner rail the rest of the suite polices.