# Modder API — building on Core Core is a library. It has no XML, no defs, no UI — its entire purpose is to be *called*, by the other Institution mods and by yours. This page is the public surface, with the one seam that matters most (`Propensity.DeterrenceFactor`) explained in full. > **This page is for modders.** Every other page in this wiki is written for players; this one is the > technical surface for building on Core. If you just want to know how the mod plays, start at the > [Home](Home.md) page and the [Propensity](Propensity.md) / [Criminal Record](Criminal-Record.md) / > [Secured Context](Secured-Context.md) / [Treatment Engine](Treatment-Engine.md) pages instead. Everything here is verified against the three source files in `Source/Core/`. --- ## The namespace is `Contraband` Before anything else: **all of Core's types live in `namespace Contraband`**, not `Core` and not `Institution`. This is historical — the engine was extracted from Institution: Contraband — and it is not going to change under you, but it does mean your `using` looks slightly surprising: ```csharp using Contraband; // Propensity, CriminalRecord, GameComponent_CriminalRecords, // SecuredKind, SecuredContext, SecuredContexts, // TreatmentProgram, TreatableConditionExtension ``` Reference the assembly `InstitutionCore.dll` (packageId `flan.institution.core`) and load your mod after Core. ## The public surface at a glance | Call | Returns | Creates state? | Use for | |---|---|---|---| | `Propensity.Nature(pawn)` | `float 0..1` | no | fixed trait-based disposition | | `Propensity.Nurture(pawn)` | `float` (multiplier) | no | live circumstance multiplier | | `Propensity.Would(pawn, baseChance, salt, cap=0.85)` | `bool` | no | the seeded yes/no for a behaviour | | `Propensity.DeterrenceFactor` | `Func` field | — | the seam a justice layer fills | | `GameComponent_CriminalRecords.For(pawn)` | `CriminalRecord` | **yes** (blank on first ask) | write paths | | `GameComponent_CriminalRecords.PeekFor(pawn)` | `CriminalRecord?` | no | read paths | | `SecuredContexts.Of(pawn)` | `SecuredContext?` | no | one pawn's hold kind | | `SecuredContexts.OnMap(map)` | `IEnumerable` | no | every held/free pawn on a map | | `TreatmentProgram.HasTreatableCondition(pawn)` | `bool` | no | is there a marked condition to work through? | | `TreatmentProgram.ReduceConditions(pawn, strength)` | `void` | mutates hediffs | run one session's worth of treatment | | `TreatmentProgram.FacilityQuality(pawn)` | `float 0.5..1.5` | no | scale a session by the room's quality | | `TreatmentProgram.AdvanceRecovery(pawn, def, amount)` | `float` | **yes** (recovery hediff) | build a discharge / parole track | All of these are `static` and null-safe. None throws on a null pawn; they return `0.05`/`1f`/`null`/ `false` as appropriate. See the [Propensity](Propensity.md), [Criminal Record](Criminal-Record.md), and [Secured Context](Secured-Context.md) pages for the mechanics behind each. --- ## Asking "would this pawn?" The idiomatic pattern for a new behaviour is: pick a **base rate**, pick a **stable salt** unique to your question, and call `Would`. Do not re-implement the nature/nurture math — that is the whole point of the shared engine. ```csharp using Contraband; // A unique, STABLE salt for this specific question. Any constant int works; keep it // distinct from other questions so the rolls stay independent per behaviour. private const int Salt_WouldStealMeds = 0x5EED01; if (Propensity.Would(pawn, baseChance: 0.08f, salt: Salt_WouldStealMeds)) { // this pawn, under these conditions, is the kind who does this } ``` Because the roll is seeded on `pawn.thingIDNumber ^ salt`, the answer is a stable fact per pawn per question and survives save/reload — so you can ask it repeatedly and get a consistent character, and different salts give you independent facts about the same pawn. Pass a custom `cap` if `0.85` is the wrong ceiling for your behaviour (e.g. a lower cap for something that should always keep a large chance of not happening). If you only need the ingredients — say, to display a disposition readout or gate a threshold — read `Nature` and `Nurture` directly. Institution: Gangs, for instance, joins a pawn to a gang when `Nature(pawn) * Nurture(pawn)` clears its own threshold, computed from these two calls rather than from `Would`. ## Reading and writing records Follow the one rule from the [Criminal Record](Criminal-Record.md) page — **write with `For`, read with `PeekFor`** — so read paths never litter the save with blank records. ```csharp using Contraband; // WRITE: an event happened, so we need a record to write to. CriminalRecord rec = GameComponent_CriminalRecords.For(pawn); rec.contrabandMade++; // READ: peek, and tolerate null (the pawn may have no record yet). CriminalRecord existing = GameComponent_CriminalRecords.PeekFor(pawn); if (existing != null && existing.reform < 0f) { // this pawn has been hardened } ``` Core owns the storage and persistence; you own the meaning of your own events. Do not keep your own per-pawn crime dictionary — that is exactly the fragmentation Core exists to prevent. ## Working across hold kinds Reduce a pawn to its context and branch on the *predicate*, not the label: ```csharp using Contraband; SecuredContext? maybe = SecuredContexts.Of(pawn); if (maybe is SecuredContext ctx && ctx.IsHeld) { // prisoner OR slave OR ward patient -- all covered, no special cases } // Sweep everyone the suite cares about on a map: foreach (SecuredContext c in SecuredContexts.OnMap(map)) { if (c.CanConceal) { /* consider hoarders too, not just the held */ } } ``` `Of` returns `null` for anything the suite does not model (animals, mechs, the dead), so a `null` check is your "not our business" branch. ## Running a treatment programme Core carries the maths of "sustained attention in a secured facility reduces a marked condition and builds a recovery track toward discharge" — the shared engine behind Ward's psychiatric care and Justice's rehabilitation. Mark a hediff treatable with `TreatableConditionExtension`, then drive it from your own job or interaction: ```csharp using Contraband; float strength = skill * TreatmentProgram.FacilityQuality(pawn) * share; // your own blend TreatmentProgram.ReduceConditions(pawn, strength); // lower the condition if (!TreatmentProgram.HasTreatableCondition(pawn)) { TreatmentProgram.AdvanceRecovery(pawn, MyDefOf.Recovery, 0.15f * strength); // build discharge } ``` You own the interaction mode, the job, the recovery hediff (give it a negative `severityPerDay` so it decays without attention), and the discharge alert; Core owns only the numbers. Full formulas and the consumer contract are on the **[Treatment Engine](Treatment-Engine.md)** page. --- ## The `DeterrenceFactor` seam This is the most important extension point in Core, and the reason Core can remain a **dependency-free leaf** while a colony-wide deterrence feedback loop runs *through* it. ### What it is ```csharp // In Propensity: public static System.Func DeterrenceFactor = _ => 1f; ``` A single static delegate. `Nurture` multiplies its result by `DeterrenceFactor(pawn)` as its last step. Out of the box the delegate returns `1f` for every pawn — perfectly neutral — so **Core alone behaves as if deterrence did not exist.** ### What it is *for* Deterrence is a property of the whole colony's climate of order: a well-policed colony should deter everyone a little; a lawless one should embolden them. That is a feedback loop — catching and punishing *one* pawn changes the disposition of *the rest*. But deterrence itself is a justice-layer concept, and Core must not know the justice layer exists. The seam resolves the tension. Core defines *where* the colony's climate multiplies into disposition (the last line of `Nurture`) without defining *what* that climate is. From the source: > *"Deterrence lives in the Justice layer, so Core reads it through the seam above — neutral until > Justice fills it. This one seam is what keeps Core a dependency-free leaf despite the deterrence > feedback loop running through it."* ### How Institution: Justice fills it When Institution: Justice loads, it assigns the delegate, pointing it at its own live deterrence level. The shape it installs maps a `0..1` deterrence reading to a multiplier that straddles `1.0`: ```csharp // Illustrative — this lives in Institution: Justice, not Core. Propensity.DeterrenceFactor = pawn => { float level = DeterrenceLevelFor(pawn.Map); // 0 = lawless, 1 = tightly policed return Mathf.Lerp(1.3f, 0.7f, level); // lawless emboldens, order deters; neutral at 0.5 }; ``` | Deterrence level | Factor | Effect on disposition | |---|---:|---| | `0.0` (lawless) | `1.3` | everyone ~30% more inclined | | `0.5` (baseline) | `1.0` | neutral — a default colony sits exactly here | | `1.0` (tightly policed) | `0.7` | everyone ~30% less inclined | *(The `1.3 → 0.7` range, neutral at the `0.5` baseline, are Justice's numbers, documented here for context; Core only guarantees the neutral `1f` default. Confirm against Institution: Justice's own source for its current values.)* ### How to fill it yourself If you are writing your own policing or morale layer and want it to feed disposition the same way, just assign the delegate — ideally at game load, and ideally composing with whatever is already there rather than clobbering it: ```csharp using Contraband; using UnityEngine; // Compose: fold your factor into any existing one (e.g. Justice's) instead of overwriting. var previous = Propensity.DeterrenceFactor; Propensity.DeterrenceFactor = pawn => previous(pawn) * MyClimateFactorFor(pawn); ``` Two cautions. First, it is a single static field — if two mods both *assign* it (rather than compose), the last one wins, so compose when you can. Second, keep your factor bounded and centred near `1.0`: `Nurture` has no clamp on this term, so an extreme factor multiplies straight through into every pawn's `Would` roll. Return `1f` when you have nothing to say, exactly as the default does. ### Why a delegate and not a dependency The alternative — Core calling into Justice — would make Core depend on Justice, inverting the whole topology and breaking Core's promise that it references *only the base game*. A single assignable `Func`, neutral by default, lets the feedback loop run through Core while the arrow of dependency still points only *at* Core. It is the smallest possible seam that buys the largest possible decoupling, and it is why Core can sit at the bottom of the suite with zero dependencies of its own. --- *Part of the **Institution** suite.*