A trained capability with a mastery level.

Skills represent learned abilities that characters use to accomplish tasks: combat techniques, social interactions, crafting, perception, and more. Each skill has a skill base formula (typically derived from one or more traits like Strength, Dexterity, or Aura) and a mastery level representing training and experience.

Skills are categorized by subType (e.g., combat, social, physical) and may be associated with a weapon group or a mystery. A skill can also reference a base skill from which it derives or shares advancement.

Skills are the primary mechanism for resolving actions in SoHL. When a character attempts a task, the relevant skill's mastery level is tested against a target number, with modifiers from traits, gear, conditions, and situational factors.

Mastery level progression, fate integration, and SDR improvement are built on sohl.entity.modifier.MasteryLevelModifier.

Type Parameters

Hierarchy (View Summary)

Indexable

  • [key: symbol]: true

Constructors

Properties

actions: SohlMap<string, sohl.entity.action.SohlAction>

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

boosts: number

The number of mastery-level boosts applied to this skill. Each boost raises the base mastery level by an amount that diminishes at higher levels (see calcMasteryBoost).

The fate mastery level as a sohl.entity.modifier.MasteryLevelModifier, used to resolve fate tests. Seeded from the actor's Aura attribute and the optionFate setting; disabled when fate does not apply.

masteryLevelSeed: number

The seeded mastery-level base — the value masteryLevel was seeded with in initialize (a stored SkillData.masteryLevelBase, or an on-actor skill's opening Skill Base × initSkillMult), captured before evaluate folds in this skill's own boosts and clamp. Cross-item effects that boost this skill (a boost Mystery) compute their contribution from this baseline rather than the mutated masteryLevel.base.

parentSkill: null | SkillLogic

The parent (base) skill this skill specializes, resolved during evaluate from SkillData.parentSkillCode, or null if this skill has no parent.

skillBase: number

The computed skill base value, derived from SkillData.skillBaseFormula — a value-returning sohl.entity.expr.SafeExpression — evaluated against the actor's attribute values (the attr.<shortcode> namespace). 0 when the formula is blank, invalid, or off an actor.

skillBaseError?: string

The Skill-Base error message when the formula failed to compile or evaluate (a sohl.entity.expr.SafeExpressionError message, or a "did not return a number" message), otherwise undefined. A non-blank value flags the skill invalid (see skillBaseValid); the sheet surfaces it and the internal skillBase falls back to 0.

skillBaseExpr: null | sohl.entity.expr.SafeExpression

The parsed Skill-Base sohl.entity.expr.SafeExpression, or null when the formula is blank or failed to compile/evaluate. Retained so attribute-dependency predicates (e.g. the Aura → no-fate gate) can walk the AST via sohl.entity.expr.SafeExpression.attrRefs rather than a regex.

The runtime strike-mode instance for a combattechnique skill, built in initialize from SkillData.strikeMode. undefined for every other skill subtype. Its attack/defense modifiers are driven by the governing mastery level in finalize (this skill's own by default, or an override skill named by the strike mode's assocSkillCode).

Accessors

  • get actor(): null | SohlActor
  • The owning SohlActor — the document itself when it is an actor, otherwise its owning actor (for an item, combatant, or effect), or null.

    Returns null | SohlActor

  • get actorLogic(): null | SohlActorLogic<any>
  • 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.

    Returns null | SohlActorLogic<any>

    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.

  • get availableFate(): MysteryLogic[]
  • The Fate Mysteries on the actor that may be spent on this skill's tests: every fate-subtype Mystery whose scope matches (a general point with no assocSkillCode, or one specific to this skill's shortcode) that still has a charge available (infinite, or charges.value > 0).

    This is the eligibility set the Fate action is gated on (available iff ≥1) and the source list a spend is drawn from. Fate Points are not a scalar — they live as charges distributed across these Mystery items (#854).

    Returns MysteryLogic[]

    The eligible-and-charged Fate MysteryLogic instances (empty off an actor).

  • get canImprove(): boolean
  • Whether the skill may be improved: true when the current user is a GM or owns the item and the mastery level is not disabled.

    Returns boolean

  • get data(): TData
  • 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.

    Returns TData

    Convenience accessor for parent.

  • get document(): TData["parent"]
  • The owning document — the actor or item this logic is embedded in.

    Returns TData["parent"]

  • get hasMeleeStrikeMode(): boolean
  • Whether this skill exposes a melee strike mode — the gate the block and counterstrike actions hang their visibility on (#1137). A missile combat technique (a flung quill, spat venom) can never block or counterstrike, so it must not offer those actions.

    Returns boolean

  • get item(): SohlItem
  • The owning SohlItem.

    Returns SohlItem

    If this logic is not embedded in an item.

  • get kind(): string
  • The owning document's kind (its actor or item type id).

    Returns string

  • get label(): string
  • The skill's display label. When this skill specializes another (its SkillData.parentSkillCode resolves to a parentSkill), the parent skill's name is appended in parentheses after the base label — e.g. Sword (Combat). The parenthetical is built from the localizable SOHL.Skill.labelWithParent format string so the convention can be adapted per language. Falls back to the inherited label when the skill has no resolvable parent.

    Returns string

  • get magicMod(): number
  • The magic modifier applied to this skill's fate mastery level. The base implementation returns 0; subclasses may override to contribute a bonus.

    Returns number

  • get skillBaseAttrs(): string[]
  • The attribute shortcodes this skill's Skill Base is based on, ordered primary first — the answer to "which attributes does this skill use?" without a caller having to parse the formula itself (#1175).

    Read off the parsed formula, so it can never drift from the formula the way a separately-stored list would:

    • When the formula calls sb(...) — every skill shipped with the system — the arguments of that call are the basis, in the order they were written: sb(attr.rea, attr.per) yields ["rea", "per"]. An attribute referenced elsewhere in the formula is excluded, because it adjusts the result rather than forming the basis: sb(attr.str, attr.dex) + attr.aur / 10 yields ["str", "dex"].
    • When the formula computes a Skill Base without sb() (e.g. (attr.str + attr.agl) / 2), every referenced attribute is the basis.
    • A blank or invalid formula has no basis and yields [].

    Consumers include the Aura → no-Fate rule (see evaluate); sheet display and character-build tooling can read the same list.

    Returns string[]

  • get skillBaseValid(): boolean
  • Whether the Skill-Base formula compiled and evaluated to a number. true for a blank formula (blank ≠ invalid — it simply yields SB 0); false only when a non-blank formula failed to compile or did not return a number (skillBaseError carries the reason).

    Returns boolean

  • get typeLabel(): string
  • Localized type (and sub-type, when present) label for the owning document.

    Returns string

  • get uuid(): string
  • The owning document's UUID — the opaque identity token from the data port.

    Returns string

Methods

  • Compute derived values that depend on sibling items being initialized.

    Returns void

    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.

  • Resolve cross-item dependencies that require all items to have been evaluated.

    Returns void

    Called on every item after ALL items have completed evaluate.

    Safe to access: all sibling items' initialized and evaluated state.

    Example: fate mastery level (which depends on an already-evaluated Aura trait); encumbrance totals summed across all evaluated gear.

  • The context-menu options — the actions currently available — for this logic's document.

    Returns ContextMenuEntry[]

    The available context-menu entries.

    One entry per action whose visible predicate currently passes (an action's trigger / domain preconditions can hide it); SCRIPT actions are additionally permission-gated when executed. Use this to discover which actions can be performed on the document.

  • Set up base state from persisted data: create ValueModifiers, set base values.

    Returns void

    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.

  • Recalculates the mastery level base using the roll formula stored in flags.sohl.rollFormula. The formula is a standard Foundry VTT roll expression where the variable sb is replaced with the skill base value (always 0 for traits).

    If no roll formula flag is set, this method does nothing.

    Returns Promise<void>

  • Build an update() payload that clears this combat technique's strike mode, setting the nullable system.strikeMode field to null.

    Returns PlainObject

    An update() payload nulling system.strikeMode.

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

    Parameters

    • context: sohl.entity.action.SohlActionContext<
          {
              priorTestResult?: sohl.entity.result.SuccessTestResult;
              rollMode?: string;
              situationalModifier?: number;
              successLevelMod?: number;
          },
      >

      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.

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

    The re-evaluated result, or undefined when refused (non-GM), cancelled (dialog dismissed), or unchanged (no-op).

  • Serialize this logic to a plain reference.

    Returns PlainObject

    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.