Construct an empty success-test result owned by parent — shorthand for
new SuccessTestResult({}, { parent }) (skips the options.testResult
merge).
The owning sohl.core.logic.SohlLogic.
Constructs a success-test result, seeding state from the given data and options (and from a prior serialized result when one is provided).
Test data; all fields are optional and defaulted. When
options.testResult is supplied, its serialized state is merged in
first, so a result can be reconstructed from a prior one (e.g. an
evaluated snapshot crossing clients).
Result options; options.parent is required (base
TestResult). options.testResult, options.mlMod, and
options.chatSpeaker seed the corresponding fields when present.
Foundry roll mode (public / private GM / blind / self) used when posting to chat.
Context-menu responses available as follow-ups to this result — e.g. resuming an opposed test when this is the opening roll.
Whether a Fate Point may be spent on this test — true only when the owning
item has an eligible, charged Fate Mystery (availableFate) and the test
permits it. Fate is a post-roll success-level bump, never a re-roll: a
spend raises this result's stored successLevel (#854).
Whether criticals are possible — i.e. the modifier defines any critical success or failure digits.
Human-readable description shown on the result's chat card.
Whether the effective mastery level was constrained (capped) below its raw effective value.
Whether this result is a critical (success or failure). Always false when
critAllowed is false — except a forced auto-Critical-Failure
(#568), which is always critical.
Whether the test succeeded (success level at marginal success or better).
Whether this is a Success Value test (#848) — its roll is graded into a Success Value and Success Stars rather than a plain pass/fail. Drives the card's Success Value / Success Stars rows.
The item logic this test was rolled from (its skill/attribute/weapon).
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 ones digit of the roll total, tested against the modifier's critical digit lists.
The mastery-level modifier rolled against; its constrainedEffective value is the roll-under target for this test.
Set of mishap codes flagged for this result (e.g. fumble, stumble); lazily initialized.
Tactical movement state recorded for this test (stationary, etc.).
Internal identifier for this result (distinct from the display title).
Success level normalized to the canonical four-point scale (−1/0/1/2) from isSuccess and isCritical. Opposed and combat resolution compare two results by this value.
Success level before the four-point clamp — the stored level with every
successLevelMod folded in, so it can sit outside −1…2 (a Critical Failure
pushed down by −1 reads −2).
Opposed resolution compares this rather than successLevel because a contest's victory margin has no ceiling: each step between the two levels is one Victory Star, and a modifier that shifts a level widens the margin accordingly. Everything that asks "did it succeed, and how well?" wants the clamped successLevel / normSuccessLevel instead.
Longer result description for the chat card, derived on read from the description table (empty when no table is supplied). Never stored — see successStars.
Short result label for the chat card, derived on read from the description table (empty when no table is supplied). Never stored — see successStars.
The d100 SimpleRoll. May be pre-seeded before evaluate (e.g. for fate or a deterministic outcome).
Speaker identity (actor/token/user) used when posting this result to chat.
Success level clamped to the four-point scale: critical failure (−1),
marginal failure (0), marginal success (1), or critical success (2). The
raw internal level (which successLevelMod can push beyond this range) is
normalized here.
Number of success "stars" (quality grade), derived on read from the description table. Never stored (issue #205) — recomputed from the table plus the evaluated success level / target value / roll last-digit.
The test's target value — targetValueFunc(successLevel). For a plain
success test this is just the success level; success-value tests map it to
a quality/quantity outcome used to index the
description table.
Which kind of test this is — a TEST_TYPE id (e.g. success test, attack, block).
Title shown at the top of the result's chat card.
The token this test is associated with, if any.
Raise this result's stored success level by delta — the post-roll Fate
bump (#854). This mutates the already-settled outcome: it does not
re-roll and does not re-evaluate. Because the outcome text/stars are
derived on read (see resultText / successStars), re-posting
the card after a bump re-resolves the description table against the new
level automatically.
Fate is defined as successLevel += delta on the original result's stored
level; the successLevel getter re-clamps to the four-point scale on
read (e.g. a marginal failure bumped by +2 reads as a critical success).
Success levels to add (Fate contributes +1 or +2).
This result, 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.
Re-open the standard test dialog on this already-settled result, pre-filled with its current situational and success-level modifiers, and fold the submitted values back into masteryLevelModifier.
How to collect the new modifiers; see SuccessTestResult.ModifierEditOptions.
{ changed } — whether either modifier actually moved — or
undefined when the dialog was dismissed, which cancels the edit.
This is the shared core of the GM result-edit: the single-test pencil (sohl.document.item.logic.SohlItemBaseLogic.resultEdit, #856) and the opposed-contest pencil (sohl.document.actor.logic.SohlActorBaseLogic.opposedResultEdit, #1082) both fold their sides through it. It never rolls — the die stays frozen and the caller re-evaluates on it.
A situational modifier of 0 removes the delta rather than recording a
zero, so an edited target is never left carrying a stale modifier.
Roll the d100 (unless a die was supplied) and resolve the outcome against the modifier's constrained effective mastery level (roll-under: rolling at or below it succeeds).
false if the base evaluation disallows the result, or if the
current user does not own the speaker (it cannot roll on their behalf);
otherwise true.
The die is cast here only when the caller did not supply one (see
_rollSupplied): a fresh test rolls a new d100, while a supplied die
— fate replaying a prior roll, or the attacker's die reconstructed on the
defender's client — is resolved untouched. It then sets the success level
from the roll, promoting it to a critical when the last digit appears in
the modifier's critical-success/-failure digit lists, applies
successLevelMod, and — when criticals are disallowed — clamps the level
to marginal failure/success and selects the localized description. The
result text and success-star count are not set here: they derive on read
from the description table (see successStars).
Open the pre-roll dialog and fold its inputs into this result.
Extra template data merged into the dialog.
Invoked with the submitted form data once the dialog inputs have been applied.
The dialog render/submit result.
The dialog collects a situational modifier and a success-level modifier
(both applied to masteryLevelModifier), the rollMode, and
movement/mishap options. After the user submits, the supplied callback
is chained with the form data. This does not roll — call evaluate
afterward.
Render this result with the standard test chat card
(templates/chat/standard-test-card.hbs) and post it via the
speaker, attaching the Foundry roll and the dice sound.
Extra template data merged into the card. A buttons key
(ActionCardButton or ActionCardButton[]) becomes follow-up
action buttons on the card.
The derived display outcome (resultText, resultDesc, successStars)
is not carried by toJSON — it is folded into the card data here,
rendered once by the sender with a live targetValueFunc (issue #205).
An optional buttons entry in data (one ActionCardButton or an
array) is folded through toRenderableButtons — the same normalizer
the action-card framework uses — so the standard card can carry arbitrary
follow-up consent buttons (a graded test = successStarTable mapping +
buttons follow-ups), dispatched through the shared chat-card chokepoint
exactly like an action card. Nothing auto-fires (#853).
Serialize to a plain object satisfying SuccessTestResult.Data: the inherited TestResult fields plus the roll, mastery-level modifier, evaluated (raw) success level, and the test's descriptive/config state.
The plain-object representation.
The associated token is persisted by tokenUuid (the owning _item
Logic is re-supplied via options.parent, not carried in the payload).
The raw _successLevel is emitted so an evaluated snapshot survives the
trip; the successLevel getter normalizes it on read. _targetValueFunc
is a live function and is not serializable — it defaults back to identity
on reconstruction. The derived outcome data (resultText, resultDesc,
successStars) is deliberately not emitted — it recomputes on read
from the serialized table
plus the success level (see the getters and issue #205).
Two fields are carried in full as a deliberate exception to the "store only the minimum" corollary of the reference-on-wire rule (see issue #202):
masteryLevelModifier carries its complete delta breakdown across the
wire because the receiver renders it verbatim for combat transparency —
mlMod.chatHtml (the per-delta name/adjustment breakdown) is shown on
the reconstructed result in standard-test-card.hbs and
opposed-result-card.hbs. A summarized form would lose that breakdown,
so the full modifier is intentionally serialized.successStarTable is serialized as data (not a table reference)
because custom, per-result tables are a supported design goal; the
table is the datum the receiver renders against, so it travels with the
result rather than through a registry (see issue #206).
The result of a d100 roll-under mastery level test — the most common resolution mechanic in SoHL.
A success test rolls 1d100 against a constrained effective mastery level. The roll determines the success level (how far above or below the target), which maps to descriptive outcomes via the test description table.
Key properties
Evaluation flow
Result text and success stars are then derived on read from the description table (see successStars); they are not stored on the result.
Chat output
toChat renders the result using
templates/chat/standard-test-card.hbsand posts it via the speaker.Subclasses