The Affliction data interface.
Binds this logic to its parent data model and builds the actions map from the parent's intrinsic and scripted action definitions, selecting a default action.
Reserved base data (unused by the base class).
Must provide options.parent, the data model this logic
is embedded in; the parent's actionDefs are used to build
actions.
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.
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.
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.
Effective seconds of incubation (contract → onset), as a sohl.entity.modifier.ValueModifier, seeded from AfflictionData.onsetDurationBase.
Effective seconds from onset to resolution, as a sohl.entity.modifier.ValueModifier, seeded from AfflictionData.resolutionDurationBase.
Mode by which this affliction spreads, copied from AfflictionData.transmission; defaults to NONE.
The owning SohlActor — the document itself when it is an actor,
otherwise its owning actor (for an item, combatant, or effect), or null.
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.
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.
Whether an actor can currently contract this affliction.
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.
Whether this affliction can currently be transmitted to another actor.
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.
Localized qualitative label for the current sub-category — the raw
category string (empty when unset).
The categorized subtypes (fatigue) are now traumas; on afflictions the category carries no named sub-category.
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.
Convenience accessor for parent.
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.
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.
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.
The owning document's id.
Whether medical treatment has been applied. Derived: true when a treatmentDate is set.
The owning SohlItem.
The owning document's kind (its actor or item type id).
Localized display label combining the typeLabel and the document's name.
Localized qualitative label for the current effective level — the numeric level as a string.
The named-severity subtypes (fear, morale) are now traumas; on afflictions (disease, poison/toxin, other) the level has no named severity.
The owning document's name.
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.
A SohlSpeaker for the owning actor/item (a blank speaker if neither resolves).
Localized type (and sub-type, when present) label for the owning document.
The owning document's UUID — the opaque identity token from the data port.
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.
The action context (unused; the check takes no input).
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.
The action context; skipDialog accepts the reaction
without confirming.
The resulting Healing Rate and whether the reaction was applied,
or undefined when the test was cancelled.
Delete the owning document, after confirming with the user.
The action context; unused.
Open the sheet for the owning document.
The action context; unused.
Compute derived values that depend on sibling items being initialized.
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.
Execute an action by shortcode, using the provided context or creating a new one.
The shortcode of the action to execute.
Optionalcontext: sohl.entity.action.SohlActionContextThe action context to use, if any.
The result of the action execution, or undefined if the action was not found or could not be executed.
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).
The context-menu options — the actions currently available — for this logic's document.
The available context-menu entries.
Intrinsic-action executor for the recurring healingCheck — the *Check
half of the affliction's natural-recovery cycle, and the exact counterpart
of the wound's (see sohl.document.item.logic.TraumaLogic).
A *Check offers, and does nothing else: it posts a card whose button
invites the affliction's controller to perform one healingTest. No
roll is made and nothing is written, so it imposes nothing and needs no
ownership gate — anyone may initiate one.
The action context (unused; the check takes no input).
A promise that resolves once the check card is posted.
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.
The action context; skipDialog accepts the seeded values.
The resulting Level, or undefined when the test was cancelled.
Set up base state from persisted data: create ValueModifiers, set base values.
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.
The action context (its scope is the trigger context).
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 this item's description to the chat log — a human-triggered, informational card (no follow-up buttons) built by buildItemDescCardData. Assist, never act: this only shows the item's own text; it takes no action on any character.
The action context; unused.
Intrinsic action performed after finalize lifecycle stage. This is intended for modules to hook into (or ActionItems to override) to perform additional logic after the main lifecycle stages have completed.
The action context for the post-finalize hook.
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.
The action context (unused; the request takes no input).
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.
The action context (its scope is the trigger context).
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.
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.
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).
The action context; skipDialog sets the onset without
confirming, and scope.schedule pre-answers the two schedule offers.
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.
The action context; scope.outcome pre-selects the outcome
and skipDialog accepts it without asking.
The recorded outcome and date, or undefined when the dialog was
dismissed.
Serialize this logic to a plain reference.
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.
The action context; scope.successStars seeds the Course
Bonus and skipDialog accepts the seeded values without confirmation.
The recorded treatment date and Course Bonus, or undefined when
the dialog was dismissed.
StaticdefineDefine and return all intrinsic actions for this logic type.
A map of action shortcodes to their definitions
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:
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.