The Cohort data interface.
Binds this logic to its parent data model and builds the actions map from the parent's intrinsic and scripted action definitions, selecting a default action.
Reserved base data (unused by the base class).
Must provide options.parent, the data model this logic
is embedded in; the parent's actionDefs are used to build
actions.
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).
The actor's tactical move (feet per combat round) as a sohl.entity.modifier.ValueModifier so runtime modifiers (haste, encumbrance, etc.) can layer on. Seeded from the active moveProfile when it is enabled.
The actor's overland travel speed (leagues per watch) as a sohl.entity.modifier.ValueModifier. Seeded from the active moveProfile when it is enabled.
The active movement profile, selected during initialize by this actor's SohlActorData.currentMoveMedium. A disabled MOVEMENT_MEDIUM.NONE profile when the actor has no matching profile (a non-mover).
The owning SohlActor — the document itself when it is an actor,
otherwise its owning actor (for an item, combatant, or effect), or null.
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.
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.
The logic instances of every embedded item — the logic-layer analogue of Foundry's foundry.documents.Actor#allItems.
One SohlItemLogic per embedded item, in items order.
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.
Convenience accessor for parent.
Whether the actor is owned by at least one player (non-GM) user.
The owning document's id.
The owning SohlItem.
The owning document's kind (its actor or item type id).
Localized display label combining the typeLabel and the document's name.
The row of the member leading this cohort, or undefined when it has no
leader. See leaderCode for what "no leader" covers.
The leader's member row, or undefined.
The handle of the member leading this cohort, or null when it has no
leader.
The leader is always one of the members: a stored CohortData.leaderCode that names nobody in the current list — the leader was removed, or the handle was never a member — reads as no leader at all, rather than as a stale name the sheet would go on displaying.
The leading member's handle, or null.
Every item's logic instance grouped by item kind — the logic-layer analogue of Foundry's foundry.documents.Actor#itemTypes.
A record of item kind → that kind's logic instances.
Every ItemKind key is present; kinds with no items map to an empty
array. Each group is typed to the concrete logic for that kind, so
logicTypes.skill is SkillLogic[].
The logic of every member actor this cohort can resolve.
Each CohortData.members entry names its world actor by
shortcodeOrUuid — a system.shortcode for a world or compendium actor,
a UUID for an unlinked Token Actor — resolved through
fvttActorByRef. Entries that no longer
resolve (the actor was deleted, or this client cannot see it) are simply
absent; a cohort with a stale member still lists the rest.
One logic per resolvable member, in members order.
The cohort's membership as display rows — one per stored entry, in
members order, each joined to the actor its handle resolves to.
This is the single seam the Members tab, the leader readout, and the member-management actions all read: a member is named by its actor where that actor resolves, and by its raw handle where it does not (see CohortMemberRow).
One row per member entry.
The owning document's name.
The gear this cohort's members have shared with it (issue #76).
A cohort carries nothing of its own: this walks each resolvable member and collects the gear whose sharing list names this cohort, pairing every item with the member that actually carries it. The result is read-only — the item stays on its custodian, and it is edited there.
One entry per shared item, ordered by carrier then item name.
Every reference by which a gear item may name this cohort in its
system.sharedWithCohortIds — the cohort's shortcode, its document id,
and its UUID. The inverse of
sohl.document.item.logic.GearLogic.sharedWithCohorts.
The non-empty reference keys identifying this cohort.
A SohlSpeaker for the owning actor/item (a blank speaker if neither resolves).
Localized type (and sub-type, when present) label for the owning document.
The owning document's UUID — the opaque identity token from the data port.
Add a member to this cohort.
Intrinsic-action executor for the addMember action. Asks for the
member's handle — a shortcode (a world or compendium actor) or a
UUID (a Token Actor: a band of orcs are each unlinked actors of one
common orc world actor, which no shortcode can tell apart) — and the role
it takes in the cohort. Anything already in context.scope pre-fills the
dialog rather than replacing it.
The handle must resolve to an Actor this client can see, and must not already be a member; otherwise nothing is added and the user is told why.
The action context; context.scope.shortcodeOrUuid and
context.scope.role pre-fill the dialog, and skipDialog (with a
handle already supplied) adds without asking.
Resolves once the member is added, or immediately if not.
Build an update() payload that appends a member to CohortData.members.
The member entry to add.
This member's role within the cohort (e.g. leader, follower).
How this member's actor is found: its system.shortcode (a world or
compendium actor) or a UUID (a Token Actor, which no shortcode can
reliably identify). Resolved through fvttActorByRef.
An update payload (does not itself persist the change).
Delete the owning document, after confirming with the user.
The action context; unused.
Open the sheet for the owning document.
The action context; unused.
Compute derived values that depend on sibling items being initialized.
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.
Execute an action by shortcode, using the provided context or creating a new one.
The shortcode of the action to execute.
Optionalcontext: sohl.entity.action.SohlActionContextThe action context to use, if any.
The result of the action execution, or undefined if the action was not found or could not be executed.
Resolve cross-item dependencies that require all items to have been evaluated.
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.
The available context-menu entries.
Find an embedded item's logic by its shortcode and item kind.
The item kind, inferred from the type argument.
The matching item's logic typed for type, or undefined if
no item matches.
Both arguments are required: a shortcode is only unique within a given
item kind, so matching the type as well prevents returning an
unexpected item that happens to share the shortcode. The item kind also
drives the return type — the concrete logic for that kind is resolved
from ItemLogicByKind, so no cast is needed at the call site:
const stealth = actor.logic.getItemLogic("stealth", ITEM_KIND.SKILL);
// ^? SkillLogic | undefined
Find an embedded item's logic by its id.
The item's id.
The matching item's logic or undefined if no item matches.
Handle a trigger dispatched by the SoHL event queue. Override in subclasses to implement actor-specific trigger handling.
Subscription kind identifier
Trigger context (discriminated by context.name)
Optional_payload: Record<string, unknown>Optional context data attached when subscribing
Select the active movement profile from SohlActorData.currentMoveMedium and seed the feetPerRound / leaguesPerWatch modifiers.
Movement is a universal actor capability, so it is derived on the
base actor logic. Subclasses that layer encumbrance/strength effects onto
the selected profile (e.g. Being) must call super.initialize() first.
See sohl.core.logic.SohlLogic.initialize for the lifecycle phase.
Set this actor's SohlActorData.currentMoveMedium — the active movement profile — to the medium carried in the action scope.
Intrinsic-action executor for the makeDefaultMedium action.
The action context; context.scope.medium names the
MovementMedium to make current. Absent, the medium is chosen in
a dialog unless skipDialog forbids one.
Resolves once the actor update completes, or immediately when no medium was chosen.
The Profile-tab star names the medium in scope and applies it directly. Invoked any other way — from the Actions menu, a macro, a script — there is no medium to apply, so the action offers the choice (the prefer-dialog rule) rather than returning silently as it once did (#1098). The prompt lists the no-movement medium plus every medium this actor authors a profile for, preselected at the current one. A caller that suppressed the dialog cannot be prompted, so it gets a notice instead of an unexplained no-op.
GM re-edit of a settled opposed contest (#1082) — the edit pencil in the Opposed Action Result card's header, and the two-sided counterpart to the standard card's sohl.document.item.logic.SohlItemBaseLogic.resultEdit (#856).
Re-opens the standard test dialog for each side in turn, pre-filled
with that side's current situational and success-level modifiers, applies
the new values, re-evaluates the contest on both frozen rolls (never a
re-roll, no Fate cost), and reposts the Opposed Action Result card. The
settled contest rides in context.scope.opposedTestResult — the whole
result serialized into the pencil's data-scope and revived by the
chat-card dispatcher — so the edit acts on that contest.
The contest lives only in the chat log, never on a document, so an edit mutates nothing persistent: it posts a new, corrected card and leaves the original standing (see the action-card consent model). Dismissing either side's dialog cancels the whole edit — no re-evaluation, no repost.
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.
The action context; context.scope.opposedTestResult is
the settled contest. When skipDialog is set, each side's new
situationalModifier / successLevelMod are taken from
context.scope.source / context.scope.target instead of the dialogs.
The re-evaluated contest, or undefined when refused (non-GM),
missing a contest, or cancelled (a dialog dismissed).
Intrinsic action performed after finalize lifecycle stage. This is intended for modules to hook into (or ActionItems to override) to perform additional logic after the main lifecycle stages have completed.
The action context for the post-finalize hook.
Remove a member from this cohort, after confirming.
Intrinsic-action executor for the removeMember action. With no handle
in scope (invoked from the Actions tab rather than from a member's row)
it first asks which member to remove. Removing the leader also clears the
leader — see removeMemberUpdate.
Only the cohort's membership entry is removed: the member's actor, and everything on it, is untouched.
The action context; context.scope.shortcodeOrUuid
names the member to remove.
Resolves once the member is removed, or immediately if not.
Build an update() payload that removes the member with the given shortcode or UUID
from CohortData.members.
Removing the leader also clears CohortData.leaderCode in the same payload: a leader who is no longer a member is not a leader, and leaving the code behind would make the cohort silently re-acquire that leader if the same handle were ever added back.
The shortcode or UUID of the member to remove.
An update payload (does not itself persist the change).
Build an update() payload that makes the named member this cohort's
leader — or clears the leader, when the given handle is already leading
(the control is a toggle) or is null.
The member to promote, or null to clear.
An update payload, or undefined when the handle names nobody
in the member list (nothing to promote).
Sets up the intrinsic actions for this actor.
The action context to use for setup.
Make a member this cohort's leader — or, when that member already leads it, stand them down so the cohort has no leader.
Intrinsic-action executor for the toggleLeader action. Invoked from a
member's row the handle rides in scope and the toggle is immediate: it is
an explicit, single-click, self-reversing choice by the cohort's owner,
so a confirmation would only be noise. Invoked from the Actions tab, with
no handle, it asks which member to promote.
The action context; context.scope.shortcodeOrUuid
names the member to promote or stand down.
Resolves once the leader is set or cleared, or immediately if not.
Serialize this logic to a plain reference.
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.
StaticdefineDefine and return all intrinsic actions for this logic type.
Membership is managed by three actions rather than by sheet-only
handlers, so the Members-tab controls and the Actions tab (and any macro
or module) drive one implementation — the same seam gear's
toggleCarried uses. Each one acts only when a human invokes it.
The cohort's intrinsic action definitions.
A group of individuals acting as a unit.
A Cohort represents multiple actors treated as a single entity for movement, combat, and other mechanics. Examples include a party of adventurers, a squad of soldiers, a pack of animals, or a ship's crew section.
Each member names its actor by a single handle — a
shortcodefor a world or compendium actor, or a UUID for a Token Actor (several wolves sharing one base actor but tracked separately cannot be told apart by shortcode). Members also have a role within the cohort.One member may be the cohort's leader, named by CohortData.leaderCode.
When placed on a scene, a Cohort can appear as either a single group token or individual tokens per member. Single-token cohorts cannot participate in combat but are useful for representing group movement on large-scale maps.