An ongoing condition affecting a character.

Afflictions represent diseases, poisons, curses, madness, and other persistent conditions that impair a character over time. Each affliction tracks:

  • level — Severity of the affliction, as a sohl.entity.modifier.ValueModifier
  • healingRate — Rate of natural recovery (−1 indicates no natural healing)
  • contagionIndex — Risk of transmission to others
  • transmission — Mode of spread (contact, airborne, ingestion, etc.)
  • isDormant — Whether the affliction is currently inactive
  • isTreated — Whether medical treatment has been applied

Afflictions support a full medical workflow through intrinsic actions: diagnosis, treatment, healing, course progression (worsening/improving), fatigue effects, morale/fear impacts, and contagion transmission.

Afflictions are categorized by subType (Disease, Poison, Madness, etc.) and are typically attached to Beings or Cohorts.

Type Parameters

Hierarchy (View Summary)

Indexable

  • [key: symbol]: true

Constructors

Properties

actions: SohlMap<string, sohl.entity.action.SohlAction>

Executable actions for this document, keyed by shortcode — context-menu entries, chat-card buttons, and lifecycle hooks. A script action shadows (wholly overrides) the intrinsic action of the same shortcode (see the constructor).

Risk of transmitting this affliction to others, as a sohl.entity.modifier.ValueModifier, seeded from AfflictionData.contagionIndexBase.

The target value of the affliction's Course Test, as a sohl.entity.modifier.ValueModifier. Its base is Healing Rate × Healing Base; Active Effects keyed COURSE (mod:logic.course) modify it — a treatment Course Bonus is exactly such an effect.

The target value of the affliction's healing test, as a sohl.entity.modifier.ValueModifier. Its base is Healing Rate × Healing Base; Active Effects keyed HEALING (mod:logic.healing) modify it.

healingCheckDurationBase: sohl.entity.modifier.ValueModifier

Effective seconds between course/recovery checks, as a sohl.entity.modifier.ValueModifier, seeded from AfflictionData.healingCheckDurationBase.

Rate of natural recovery, as a sohl.entity.modifier.ValueModifier, seeded from AfflictionData.healingRateBase. An unset (null) base disables the modifier, indicating the affliction does not heal naturally.

isDormant: boolean

Whether the affliction is currently inactive (but possibly still contagious).

Effective severity of the affliction, as a sohl.entity.modifier.ValueModifier, seeded from AfflictionData.levelBase.

onsetDurationBase: sohl.entity.modifier.ValueModifier

Effective seconds of incubation (contract → onset), as a sohl.entity.modifier.ValueModifier, seeded from AfflictionData.onsetDurationBase.

resolutionDurationBase: sohl.entity.modifier.ValueModifier

Effective seconds from onset to resolution, as a sohl.entity.modifier.ValueModifier, seeded from AfflictionData.resolutionDurationBase.

transmission: KindValue

Mode by which this affliction spreads, copied from AfflictionData.transmission; defaults to NONE.

Accessors

  • get actor(): null | SohlActor
  • The owning SohlActor — the document itself when it is an actor, otherwise its owning actor (for an item, combatant, or effect), or null.

    Returns null | SohlActor

  • get actorLogic(): null | SohlActorLogic<any>
  • The logic of the owning actor — the Foundry-free way to reach the actor layer from any logic. For an actor's own logic this is itself; for an item's logic it is the owning actor's logic; otherwise null.

    Returns null | SohlActorLogic<any>

    Resolved through the SohlLogicData port, so logic code can navigate to the actor (and iterate items via allLogics / logicTypes / getItemLogic) without touching the Foundry document.

  • get canHeal(): boolean
  • Whether this affliction can currently be healed.

    True only when the affliction heals naturally (its healingRate is not disabled) and the bearer has a usable Endurance attribute — the gate the pre-port healing test enforced.

    Returns boolean

  • get canTreat(): boolean
  • Whether this affliction can currently be treated.

    True until treatment has been applied (i.e. while isTreated is false — derived from AfflictionData.treatmentDate) — the gate the pre-port treatment test enforced. Afflictions have no bleeding concept (that lives on Trauma), so treatment is not gated on any bleeding state.

    Returns boolean

  • get categoryLabel(): string
  • Localized qualitative label for the current sub-category — the raw category string (empty when unset).

    Returns string

    The categorized subtypes (fatigue) are now traumas; on afflictions the category carries no named sub-category.

  • get data(): TData
  • This logic's typed data — its *Data interface (e.g. SkillData), the same persisted object as document.system. Prefer document.logic.data when reading a document's fields from a macro, Script Action, or module: it is the typed, API-documented surface (autocomplete and reference links resolve), whereas document.system is typed as the internal DataModel.

    Returns TData

    Convenience accessor for parent.

  • get document(): TData["parent"]
  • The owning document — the actor or item this logic is embedded in.

    Returns TData["parent"]

  • get estOnsetDate(): undefined | number
  • Estimated world time (seconds) at which incubation completes and the affliction becomes symptomatic — contractDate + onsetDurationBase — for display only (never persisted). undefined when the affliction has no contract anchor. Once onset actually occurs the crystallized AfflictionData.onsetDate is the authoritative fact; this remains the projection from the contract anchor.

    Returns undefined | number

  • get estResolutionDate(): undefined | number
  • Estimated world time (seconds) at which the affliction resolves — (onsetDate ?? contractDate) + resolutionDurationBase — for display only (never persisted). Anchors on onset once symptomatic, else the contract anchor while incubating; undefined when neither anchor is set.

    Returns undefined | number

  • get hasCourse(): boolean
  • Whether this affliction has a progressive course (i.e. can worsen or improve over time via course tests).

    True only while the affliction is active (not dormant) and the bearer has a usable Endurance attribute — the gate the pre-port course test enforced.

    Returns boolean

  • get item(): SohlItem
  • The owning SohlItem.

    Returns SohlItem

    If this logic is not embedded in an item.

  • get kind(): string
  • The owning document's kind (its actor or item type id).

    Returns string

  • get levelLabel(): string
  • Localized qualitative label for the current effective level — the numeric level as a string.

    Returns string

    The named-severity subtypes (fear, morale) are now traumas; on afflictions (disease, poison/toxin, other) the level has no named severity.

  • get nextHealTest(): undefined | number
  • World time (seconds) of the affliction's next course/recovery check, for display only (never persisted). Queue-first: the live system.scheduledActions entry for the armed healingCheck (anchor + interval) when present — so an accepted reschedule is reflected — otherwise the arithmetic projection (onsetDate ?? contractDate) + healingCheckDurationBase. undefined when there is no armed check and no anchored interval to project from.

    Returns undefined | number

  • get typeLabel(): string
  • Localized type (and sub-type, when present) label for the owning document.

    Returns string

  • get uuid(): string
  • The owning document's UUID — the opaque identity token from the data port.

    Returns string

Methods

  • Intrinsic-action executor for the recurring courseCheck — the *Check half of the course cycle (#1183).

    A *Check offers, and does nothing else: it posts a card whose button invites the affliction's controller to perform one courseTest. No roll is made, no Healing Rate changes, and nothing is written. Because it imposes nothing it carries no ownership gate — anyone may initiate one.

    Parameters

    Returns Promise<void>

    A promise that resolves once the check card is posted.

  • Intrinsic-action executor for the Course Test (#1183) — the *Test half of the course cycle, and the action that actually advances an affliction.

    Rolls one standard success test against course (Healing Rate × Healing Base, plus whatever Active Effects have modified it — a treatment Course Bonus among them). The result moves the affliction's Healing Rate by CF −2 / MF −1 / MS +1 /, and the resulting rate determines the host's reaction via sohl.document.item.logic.courseOutcomeFor.

    The reaction is never applied silently: a confirmation dialog offers it first, and the outcome card reports both the result and whether it was applied to the character sheet. Exactly one test runs per invocation — there is no catch-up over missed intervals.

    Parameters

    Returns Promise<undefined | { applied: boolean; healingRate: number }>

    The resulting Healing Rate and whether the reaction was applied, or undefined when the test was cancelled.

  • Compute derived values that depend on sibling items being initialized.

    Returns void

    Called on every item after ALL items have completed initialize.

    Safe to access: sibling items' initialized state (e.g., reading trait attribute values for a skill base formula).

    Not safe to access: sibling items' evaluated state — another item's evaluate() may not have run yet. Dependencies on evaluated state belong in finalize.

    Example: a Skill reads trait attribute values to compute its skill base; a gear item resolves its containerId to find its parent container.

  • Re-arm the affliction's persisted schedules into the event queue on every preparation, on every client (issue #588 generic store; #579 consent). The phase machine's arming now lives in the executors: onsetCheck schedules the resolution and recurring healing-check events at onset and clears itself; resolutionCheck clears the rest at resolution; the recurring healingCheck offers its own reschedule. finalize() therefore only restores whatever system.scheduledActions currently holds — a reschedule update() replicates, every client re-preps, and this generic re-arm restores the queue (the active GM's included, which alone fires).

    Returns void

  • The context-menu options — the actions currently available — for this logic's document.

    Returns ContextMenuEntry[]

    The available context-menu entries.

    One entry per action whose visible predicate currently passes (an action's trigger / domain preconditions can hide it); SCRIPT actions are additionally permission-gated when executed. Use this to discover which actions can be performed on the document.

  • Intrinsic-action executor for the affliction's Healing Test — the *Test half of natural recovery, and the same test the wound makes.

    Rolls one standard success test against healing (Healing Rate × Healing Base, plus whatever Active Effects have modified it). A marginal success reduces the affliction's Level by 1 and a critical success by 2; a failure makes no progress. An affliction reduced to Level 0 has run its course, so its recurrence ends.

    Exactly one test runs per invocation — there is no catch-up over missed intervals — and the next check is offered, never auto-armed.

    Parameters

    Returns Promise<undefined | { level: number }>

    The resulting Level, or undefined when the test was cancelled.

  • Set up base state from persisted data: create ValueModifiers, set base values.

    Returns void

    Called on every item before any item's evaluate runs.

    Safe to access: own persisted data fields (this.data.*).

    Not safe to access: sibling items on the same actor — they may not have initialized yet. Cross-item reads belong in evaluate.

    Example: a Skill creates its MasteryLevelModifier from persisted fields; it does not yet read trait attribute values.

  • Intrinsic-action executor for the onsetCheck transition (incubation → symptomatic). Crystallizes onsetDate, rolls the resolution and healing-check intervals, and schedules the next-phase events — the one-shot resolutionCheck and the recurring healingCheck — then clears the spent onsetCheck schedule.

    Parameters

    Returns Promise<void>

    A promise that resolves once the phase transition is persisted.

    The onset effect marks the affliction symptomatic (crystallizes onsetDate) and starts its course/resolution cycle; the symptoms themselves are role-played, out of VTT scope (#488). Scheduling the next phase is the direct consequence of this human-performed transition (issue #579 gates the firing via the [Perform] reminder, not the phase progression itself). An optional author onset Macro then runs and may schedule further events.

  • Post a treatment request for this affliction (#1183) — the patient's half of the treatment exchange.

    Unlike an injury, treatment for an affliction is mostly ineffectual: the body either fights the affliction off or it does not. A request can still be posted, and it names the affliction so a physician knows what they are being asked to treat. The card carries an open button inviting anyone with the Physician skill to make a Treatment Success Value test; nothing is applied until its result is accepted through treatAffliction.

    Parameters

    Returns Promise<void>

    A promise that resolves once the request card is posted.

  • Intrinsic-action executor for the resolutionCheck transition (symptomatic → resolved). Crystallizes resolutionDate and clears the affliction's remaining schedules (the recurring healing check and this one-shot resolution) — resolution is terminal.

    Parameters

    Returns Promise<void>

    A promise that resolves once the resolution is persisted.

    Crystallizes resolutionDate and, when the affliction was not defeated (Healing Rate below 6), applies its authored outcome (#490): DEATH sets the being's shock state to Dead; CURED sets Healing Rate to 6. Either combines with an optional outcomeTrauma sohl.entity.expr.SafeExpression whose result — a trauma shortcode or array of them — is contracted as new trauma(s) (searched world-first, then compendiums).

  • GM result-edit for a posted test card (#856) — the higher-fidelity counterpart to Fate. Re-opens the standard test dialog pre-filled with the result's current situational and success-level modifiers; on submit it applies the new modifiers and re-evaluates on the SAME frozen roll (never a re-roll, no Fate cost), then reposts the card. The prior result rides in context.scope.priorTestResult (the reconstruction seam), so this works for any standard test card — skill, attribute, or combat strike mode.

    Changing the situational modifier changes the effective target, so the base success level re-derives from the frozen roll; the success-level modifier is a flat offset applied after. Clicking OK without a change is a no-op (nothing re-evaluated, nothing reposted).

    GM-only. The pencil is render-hidden from non-GMs (sohl.document.chat.gateEditActionPencil); this is the click-time half of that gate — a synthesized click from a non-GM is refused here.

    Parameters

    • context: sohl.entity.action.SohlActionContext<
          {
              priorTestResult?: sohl.entity.result.SuccessTestResult;
              rollMode?: string;
              situationalModifier?: number;
              successLevelMod?: number;
          },
      >

      The action context; context.scope.priorTestResult is the result being edited. When skipDialog is set, the new situationalModifier / successLevelMod / rollMode are taken from context.scope instead of the dialog.

    Returns Promise<undefined | sohl.entity.result.SuccessTestResult>

    The re-evaluated result, or undefined when refused (non-GM), cancelled (dialog dismissed), or unchanged (no-op).

  • Intrinsic-action executor for Set Onset — the action half of the onset phase, paired with onsetCheck.

    Asks whether to mark the affliction symptomatic as of now, and on yes crystallizes AfflictionData.onsetDate. The per-affliction interval formulas are rolled at the same time so the sheet's projected resolution and next-check dates read correctly, and the authored onset Macro (if any) runs once the onset is persisted.

    Onset is what sets the affliction running, so it finishes by offering the two events that carry it from here — the recurring courseCheck and the one-shot resolutionCheck. They are offered, never armed: pressing Set Onset consents to the affliction being symptomatic, not to a schedule (issue #579).

    Parameters

    • context: sohl.entity.action.SohlActionContext

      The action context; skipDialog sets the onset without confirming, and scope.schedule pre-answers the two schedule offers.

    Returns Promise<undefined | { onsetDate: number }>

    The onset date, or undefined when the dialog was declined.

  • Intrinsic-action executor for Set Resolution — the action half of the resolution phase, paired with resolutionCheck.

    Asks which outcome the affliction resolves to (defaulting to the authored one), and on OK records that outcome with AfflictionData.resolutionDate set to now. Resolution is terminal, so the affliction's remaining schedules are cleared, and the chosen outcome is applied — death, or a cure that takes the Healing Rate to 6 — along with any authored outcomeTrauma.

    An affliction already defeated (Healing Rate 6 or better) has beaten its course on its own; its resolution is recorded but no outcome is inflicted.

    Parameters

    Returns Promise<undefined | { outcome: KindValue; resolutionDate: number }>

    The recorded outcome and date, or undefined when the dialog was dismissed.

  • Serialize this logic to a plain reference.

    Returns PlainObject

    A uuid-keyed reference to this logic.

    A logic is a behavior wrapper over a live Foundry document; it is never revived from its own JSON (its constructor needs that document). Wherever a logic is persisted — a chat card, an action sohl.entity.action.SohlActionContext.scope — it is re-resolved from its uuid (e.g. via fvttLogicFromUuidSync), not rebuilt from a payload. So it serializes as a compact, resolvable reference (name/kind are carried for display and debugging); the owning document holds the actual persisted state.

  • Record treatment of this affliction (#1183) — the patient's half of the treatment exchange, and the counterpart to requestTreatment.

    Opens a dialog confirming the treatment date and a Course Bonus. The bonus defaults to the Success Stars of the physician's Treatment Success Value test when the action was reached from that card's Accept button (scope.successStars), and to 0 when run by hand. A Course Bonus above zero is persisted as an Active Effect on this affliction, keyed COURSE, so it raises the target of every subsequent courseTest.

    Parameters

    • context: sohl.entity.action.SohlActionContext

      The action context; scope.successStars seeds the Course Bonus and skipDialog accepts the seeded values without confirmation.

    Returns Promise<undefined | { courseBonus: number; treatmentDate: number }>

    The recorded treatment date and Course Bonus, or undefined when the dialog was dismissed.