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.

  1. If no prior roll exists, a new d100 is rolled via SimpleRoll.
  2. Success level = constrained ML − roll result.
  3. Critical success/failure checked against last-digit lists.

Result text and success stars are then derived on read from the description table (see successStars); they are not stored on the result.

toChat renders the result using templates/chat/standard-test-card.hbs and posts it via the speaker.

  • AttackResult — attacker's roll, with impact dice and aim
  • DefendResult — defender's roll with situational modifiers

Hierarchy (View Summary)

Constructors

Properties

rollMode: string

Foundry roll mode (public / private GM / blind / self) used when posting to chat.

Accessors

  • get canFate(): boolean
  • 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).

    Returns boolean

  • get description(): string
  • Human-readable description shown on the result's chat card.

    Returns string

  • get isSuccessValue(): boolean
  • 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.

    Returns boolean

  • 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 name(): string
  • Internal identifier for this result (distinct from the display title).

    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>

  • get rawSuccessLevel(): number
  • 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).

    Returns number

    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.

  • get successLevel(): number
  • 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.

    Returns number

  • get successStars(): number
  • 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.

    Returns number

  • get targetValue(): number
  • 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.

    Returns number

Methods

  • 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).

    Parameters

    • delta: number

      Success levels to add (Fate contributes +1 or +2).

    Returns this

    This result, for chaining.

  • 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).

    Returns Promise<boolean>

    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.

    Parameters

    • data: PlainObject = {}

      Extra template data merged into the dialog.

    • callback: (formData: StrictObject<string | number>) => void

      Invoked with the submitted form data once the dialog inputs have been applied.

    Returns Promise<any>

    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.

    Parameters

    • data: PlainObject = {}

      Extra template data merged into the card. A buttons key (ActionCardButton or ActionCardButton[]) becomes follow-up action buttons on the card.

    Returns Promise<void>

    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.

    Returns PlainObject

    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).