Construct an empty mastery level modifier owned by parent — shorthand
for new MasteryLevelModifier({}, { parent }).
The owning sohl.core.logic.SohlLogic.
Builds a mastery level modifier, applying defaults for target bounds, crit-digit lists, success/value tables, and the test type and title.
Test data; targets default to unbounded, crit-digit lists to
the canonical multiple-of-5 set ([0, 5]), and the description/value
tables to the standard ones.
Must provide options.parent (base ValueModifier).
OptionalbaseThe base value before deltas (undefined until set; treated as 0 by base).
Roll last-digits that make a failure critical (e.g. [0]).
Roll last-digits that make a success critical (e.g. [5]).
OptionalcustomHandler invoked by a CUSTOM delta to compute a value, when one is used.
The list of ValueDelta modifiers applied on top of the base.
Reason the value is disabled as an i18n key; empty string means enabled. Localize for display via disabledLabel. See disabled.
Upper clamp on the effective mastery level — the roll-under target can't exceed this.
Lower clamp on the effective mastery level — the roll-under target can't go below this.
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.
Description table mapping success levels to labels and star ratings for chat output.
Display title for the test.
Identifier for the kind of test (used in dialogs and chat).
The base value (0 when unset).
Setter form of setBase.
Render the deltas as an HTML breakdown (name + adjustment per row) for chat cards and tooltips; when disabled, renders the localized disabled reason instead.
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.
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).
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.
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.
The computed effective value — the base with all deltas applied (always 0 when disabled). Recomputed lazily on access.
Whether no deltas have been applied.
Whether a base value has been explicitly set.
A coarse index derived from the base value (base / 10, truncated).
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.
The deltas' net contribution to the value — effective − base.
Add an additive (+value) delta. A new delta replaces any existing one
with the same abbrev.
this, for chaining.
Two forms: (abbrev, value) resolves the display name from the
VALUE_DELTA_INFO registry (and throws on an unknown abbrev);
(name, abbrev, value) supplies both explicitly for ad-hoc deltas.
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 additive — other'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.
The modifier to merge from.
Merge options.
OptionalincludeBase?: booleanWhen set, replace this modifier's base with
other's base.
this, for chaining.
Add a DOWNGRADE (ceiling / maximum) delta: the effective value cannot
exceed value. Same argument forms as add.
this, for chaining.
Deep-copy this entity, re-parenting the copy under parent with no other
changes. Shorthand for clone({}, { parent }).
The Logic to own the cloned entity.
The cloned entity.
Deep-copy this entity, optionally overriding fields and clone options.
Field overrides applied to the clone.
Clone options (e.g. a new parent).
The cloned entity.
Add an UPGRADE (floor / minimum) delta: the effective value cannot drop
below value. Same argument forms as add.
this, for chaining.
Find the delta with the given abbrev.
The delta abbrev to look up.
The matching ValueDelta, or undefined.
Add a multiplicative (×value) delta. Same argument forms as
add.
this, for chaining.
Complete an opposed test by rolling the target's side and evaluating the contest.
The action context; must carry the in-progress opposed
result in scope.priorTestResult (from opposedTestStart).
The evaluated OpposedTestResult, false if a side's test
was cancelled or failed, or null.
Perform an opposed test
The action context; context.scope.priorTestResult
resumes an existing opposed test, otherwise a new one is started.
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.
Add an OVERRIDE delta that replaces the value, ignoring all other
modifiers. Same argument forms as add.
this, for chaining.
Chainable form of the disabled setter.
A reason string, or a boolean (true applies a default
reason; false clears it).
this, for chaining.
Performs a Mastery Level success test.
The context in which to perform the test. Its scope is a
Partial<sohl.entity.result.SuccessTestResult.ContextScope>;
the recognized fields are listed above.
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).
Perform a success value test — a success test whose outcome is graded into a quality/quantity result (a "success value") via svTable, rather than reported as a simple pass/fail.
The action context for the test.
The evaluated sohl.entity.result.SuccessTestResult, null if cancelled, or
false on error.
Serialize to a plain object satisfying MasteryLevelModifier.Data: the inherited ValueModifier fields plus the test-resolution parameters (target clamps, crit digits, tables, type, title).
The plain-object representation.
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.
Test-specific properties
constrainedEffectivereturns the clamped value.[0, 5]means rolls ending in 0 or 5 are critical).Test methods
Lifecycle
Created during the owning logic's
initializephase 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.