A ValueModifier specialized for mastery level tests — the primary resolution mechanic in SoHL.

Extends ValueModifier with test-specific state and the ability to execute success tests, success value tests, and opposed tests.

  • minTarget / maxTarget — clamp the effective mastery level to a valid range. constrainedEffective returns the clamped value.
  • successLevelMod — flat offset to the success level after the roll (e.g., fate bonuses).
  • critFailureDigits / critSuccessDigits — last-digit lists that trigger critical results (e.g., [0, 5] means rolls ending in 0 or 5 are critical).
  • testDescTable — maps success levels to descriptive labels and star ratings for chat output.
  • svTable — maps success levels to success value results for value-producing tests.
  • successTest — standard d100 test against the constrained effective mastery level. Displays a dialog (unless suppressed) to collect situational modifiers, rolls, evaluates, and posts to chat.
  • successValueTest — like successTest but produces a success value (quality of result) rather than just pass/fail.
  • opposedTestStart — initiates an opposed test by performing the source actor's success test and creating an OpposedTestResult awaiting the target's response.
  • opposedTestResume — completes an opposed test by performing the target actor's success test and evaluating the opposed outcome.

Created during the owning logic's initialize phase with a base from the persisted mastery level. Deltas are added during evaluate/finalize (e.g., injury penalties, equipment bonuses, situational modifiers from the test dialog). Rebuilt each preparation cycle like all ValueModifiers.

Hierarchy (View Summary)

Constructors

Properties

baseValue?: number

The base value before deltas (undefined until set; treated as 0 by base).

critFailureDigits: number[]

Roll last-digits that make a failure critical (e.g. [0]).

critSuccessDigits: number[]

Roll last-digits that make a success critical (e.g. [5]).

customFunction?: Function

Handler invoked by a CUSTOM delta to compute a value, when one is used.

The list of ValueDelta modifiers applied on top of the base.

disabledReason: string

Reason the value is disabled as an i18n key; empty string means enabled. Localize for display via disabledLabel. See disabled.

maxTarget: number

Upper clamp on the effective mastery level — the roll-under target can't exceed this.

minTarget: number

Lower clamp on the effective mastery level — the roll-under target can't go below this.

successLevelMod: number

Flat offset applied to the success level after the roll (e.g. a fate bonus).

Success-value table mapping success levels to graded results for value-producing tests.

testDescTable: LimitedDescription[]

Description table mapping success levels to labels and star ratings for chat output.

title: string

Display title for the test.

type: string

Identifier for the kind of test (used in dialogs and chat).

Accessors

  • get chatHtml(): string
  • Render the deltas as an HTML breakdown (name + adjustment per row) for chat cards and tooltips; when disabled, renders the localized disabled reason instead.

    Returns string

  • get deltaLabel(): string
  • A compact, human-readable summary of how the value is derived: the base contribution followed by each applied delta (e.g. Base +30, SSMod +25), or the disabled marker (Dsbl) when disabled. An unmodified value still summarizes as Base +N, so the summary is never empty for an enabled value.

    Named deltaLabel — not shortcode — because a ValueModifier's derivation summary is unrelated to the document system.shortcode identity key used across the rest of the system.

    Returns string

  • get disabled(): string
  • The disabled reason as stored — an i18n key (or "" when enabled). A non-empty value forces effective to 0. This is the serialized form; use disabledLabel for localized display (#948).

    Returns string

  • set disabled(reason: string | boolean): void
  • Disable with a reason string, or toggle via a boolean (passing true applies a default reason; false clears it). A string reason must be an i18n key (see disabled); it is localized for display by disabledLabel.

    Parameters

    • reason: string | boolean

    Returns void

  • get disabledLabel(): string
  • The disabled reason localized for display, or "" when enabled.

    disabledReason always stores an i18n key (never localized prose), so callers that surface the reason to a human must localize it here rather than emitting the raw key (#948). Idempotent on already-plain text.

    Returns string

  • get effective(): number
  • The computed effective value — the base with all deltas applied (always 0 when disabled). Recomputed lazily on access.

    Returns number

  • get kind(): string
  • The serialization discriminator for this instance — the concrete class's static Kind. Written into the JSON by toJSON under the kind key and read back by sohl.utils.defaultFromJSON to select the constructor. Derived from the class, never stored per-instance.

    Returns string

  • get parent(): SohlLogic<any>
  • The Logic that owns this entity. Always present (the constructor rejects a missing parent) and transient — it is not serialized and is re-supplied when the entity is revived or cloned.

    Returns SohlLogic<any>

Methods

  • Fold another modifier into this one, preserving the full auditable derivation: every labeled delta from other (its name, abbrev, operator, and value) is replayed onto this modifier, so the merged result keeps each source justification in its tooltip and this modifier can then layer its own deltas on top.

    Deltas are additiveother's are appended to whatever this modifier already carries (each replayed through the internal _oper, so same-abbrev replacement and OVERRIDE semantics apply, and the clones are re-parented to this modifier). The base is not additive — a modifier has exactly one base — so other's base is adopted only when includeBase is set, and it replaces any existing base rather than adding to it. Omit includeBase to take other's modifiers while keeping this modifier's own base.

    Parameters

    • other: sohl.entity.modifier.ValueModifier

      The modifier to merge from.

    • options: { includeBase?: boolean } = {}

      Merge options.

      • OptionalincludeBase?: boolean

        When set, replace this modifier's base with other's base.

    Returns sohl.entity.modifier.ValueModifier

    this, for chaining.

  • Whether a delta with the given abbrev is present.

    Parameters

    • abbrev: string

      The delta abbrev to test for.

    Returns boolean

    true if a matching delta exists.

    TypeError if abbrev is not a string.

  • Perform an opposed test

    Parameters

    Returns Promise<null | sohl.entity.result.OpposedTestResult>

    The resulting opposed test, or null when no target is available.

    This method handles both starting a new opposed test and resuming an existing one. If context.priorTestResult is not provided, a new opposed test is started by creating a new OpposedTestResult with the current SuccessTestResult as the source test. If context.priorTestResult is provided, it is assumed to be an existing OpposedTestResult that needs to be completed by performing the target test.

  • Performs a Mastery Level success test.

    Parameters

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

    A Promise resolving to undefined if the test was cancelled, false if there was an error during the test (including an unowned speaker), or the result of the success test.

    If no priorTestResult is provided, this method creates a new test result from the mastery level of the current item, collects situational modifiers, and evaluates the outcome — which rolls the d100 (see sohl.entity.result.SuccessTestResult.evaluate).

    If a priorTestResult is provided, this method collects modifiers but applies them to the prior test's roll (without rolling again), generating a new test result from the modifiers and the prior roll. This is what lets modifiers be changed without disturbing the random roll — e.g. when fate is applied to an already-rolled test.

    Interactive vs. headless. By default the modifiers are collected from a pre-roll dialog. When context.skipDialog is set — the path timed and automated effects use, since they run on the active GM with no user present — the dialog is bypassed and the situational modifier is read from context.scope.situationalModifier instead. Set context.noChat to suppress the result chat card (e.g. to avoid spamming a card per checkpoint when a timed handler catches up many elapsed occurrences).

    The result is attributed to context.speaker (falling back to the owning document's speaker); evaluation is refused — returning false — if that speaker is not owned by the running user (a GM-fired event owns every actor).

    Recognized context.scope fields (all optional — see sohl.entity.result.SuccessTestResult.ContextScope for the typed shape and per-field docs). Supplying these is how a generic success test becomes a bespoke graded test without a subclass (see the pass-data pattern):

    • successStarTable — the result-description table mapping each rung to its label / description / star count. Defaults to this modifier's testDescTable.
    • targetValueFunc — remaps the value the outcome grades against when the test keys off something other than the raw mastery level. Defaults to identity (sl => sl).
    • priorTestResult — reuse an already-rolled result instead of rolling fresh (Fate, GM edits, opposed resume); the die is not re-rolled.
    • situationalModifier — the pre-roll modifier applied when skipDialog bypasses the dialog.
    • canFate — whether the posted card may offer a Fate spend (default true; the Fate test itself passes false).

    Context-level (not scope) switches: context.skipDialog bypasses the pre-roll dialog; context.noChat suppresses the result card (post it yourself with sohl.entity.result.SuccessTestResult.toChat — e.g. to attach follow-up buttons).

    If SuccessTestResult construction fails (internal error).