Body, Mind & Soul — API Reference
    Preparing search index...

    Owns one combat's in-progress resolve-pass state: the mutable downs snapshot every concurrent resolve caller reads/writes, which action ids are currently claimed (inside _resolveOneAction, by any caller), and which ids have already completed resolution this pass.

    Lifecycle: multiple independent operations (a natural advance, an instant commit-and-resolve) can be in flight for the same combat at once. Each one calls ResolvePass#attach when it starts needing the pass and ResolvePass#detach when it's done with it; the pass itself carries no more state once every attacher has detached, at which point the owning module-level registry (_resolvePasses in combat.mjs) discards it. A fresh ResolvePass is created the next time any operation needs one for that combat id.

    Index
    • Parameters

      • downs: object[]

        Initial mutable downs snapshot, same shape as system.bms.downs.toObject() (an array of { downNumber, actions, effects } plain objects, where actions/effects are objects keyed by each entry's own id). Stored by reference and mutated in place by this pass's own methods and by external callers that read ResolvePass#downs.

      Returns ResolvePass

    _pendingNewActionIds: Set<string>
    _pendingNewEffectIds: Set<string>
    _pendingSweptActionIds: Set<string>
    _pendingSweptEffectIds: Set<string>
    claimedIds: Set<string>
    downs: object[]
    openOperations: number
    resolvedIds: Set<string>
    • Register interest in this pass, incrementing its refcount. Call once per logical operation (a natural advance call, an instant commit-to-resolve span) that needs the pass to stay alive for its duration.

      Returns void

    • Capture the set of action/effect ids currently present in downs as a baseline to later pass into mergeFromLive, replacing what used to be locally-scoped originalActionIds/originalEffectIds variables recomputed by each caller. Call this immediately before a mutation phase begins (e.g. the rt-decrement/sweep loop in _processCombatSteps, or right before an instant commit's push into downs[0].actions) so the caller can later hand the returned baseline to mergeFromLive to distinguish genuinely new entries (added by a trigger outcome mid-pass) from ones that were already present when this baseline was captured.

      Deliberately returns the baseline instead of storing it on this: because ResolvePass instances are shared per-combat across concurrent callers (a natural advance and an in-flight instant resolve can attach to the same pass — see the file doc comment), a singleton baseline field would let a second caller's beginMutationPhase call silently overwrite the first caller's baseline before the first caller's own mergeFromLive runs, corrupting its new/moved/deleted classification. Returning the baseline forces every caller to carry its own copy through to its own mergeFromLive call, closing that cross-talk window.

      Returns { actionIds: Set<string>; effectIds: Set<string> }

      Baseline to pass as the baseline argument of a later mergeFromLive call.

    • Claim an action id for the duration of a _resolveOneAction call, so a second concurrent caller sees it as already claimed and skips it instead of resolving it a second time.

      Parameters

      • id: string

        Action id to claim.

      Returns void

    • Release interest in this pass, decrementing its refcount.

      Returns boolean

      true if this was the last attached operation (refcount hit 0) and the caller should discard/deregister this pass; false if other operations are still attached and the pass must stay alive.

    • Drain (return and clear) the pending-swept ids accumulated by every reentrant _mutateDowns call since the last flush, so the caller can union them into its own sweptActionIds/sweptEffectIds before calling mergeFromLive. Called from _flushResolvePass for every flush, regardless of caller, since any flush writes the pass's full current downs and is therefore the right place to finally reconcile a reentrant deletion that skipped its own flush.

      Returns { actionIds: Set<string>; effectIds: Set<string> }

    • Find an action entry by id across every down in downs.

      Parameters

      • id: string

        Action id to find.

      Returns object | undefined

      The plain action object, or undefined if not found.

    • Parameters

      • id: string

        Action id to check.

      Returns boolean

      true if id is currently claimed by any caller.

    • Parameters

      • id: string

        Action id to check.

      Returns boolean

      true if id has completed resolution this pass (always false for holds, which markResolved never records).

    • Mark an action as having genuinely completed resolution this pass — must only be called after the real resolve work (_resolveOneAction) has finished, never pre-emptively before it starts. No-ops entirely for holds: hold-type actions re-fire every cycle while pinned at resolutionTime === 0 and must never transition to a "resolved" state, so this neither adds the id to resolvedIds nor mutates its downs entry when isHold is true.

      Also mutates the matching entry's resolved field in downs (the first matching action found across all downs), mirroring what the old _resolveOnce wrote directly into its local downs snapshot — kept here so downs and resolvedIds never disagree about an action's state.

      Parameters

      • id: string

        Action id that finished resolving.

      • Optionaloptions: { isHold?: boolean } = {}
        • OptionalisHold?: boolean

          True when the resolved action's type is "hold".

      Returns void

    • Reconcile downs with a fresh read of the live document's downs, using the caller-supplied baseline (the value returned by that same caller's own beginMutationPhase call). This is a direct, logic-preserving port of the method body that used to live as BodyMindSoulCombat#_mergePostResolveDowns in combat.mjs — same three-step reconciliation, now operating on this.downs/baseline instead of a caller-local downs/originalActionIds/originalEffectIds.

      baseline is an explicit parameter rather than an instance field specifically so two independent operations sharing one ResolvePass (see the file doc comment) can never clobber each other's in-flight baseline — each caller must thread through the exact object its own beginMutationPhase call returned, not whatever the pass most recently captured for someone else.

      Three things this reconciles:

      • New entries (trigger-spawned since the baseline was captured): merged in from liveDowns.
      • Moved entries (an entry — original or new-this-phase — now sits on a different down in liveDowns than the snapshot's current bucket): removed from its snapshot position and re-inserted at the live down, using the pass's own current copy (preserves in-memory mutations like resolved: true), never the live document's copy.
      • Deleted originals (an original entry no longer exists in liveDowns): removed from the snapshot so it isn't re-added by this pass's own write.

      Idempotency guarantee — repeated flushes of the same pass against the same baseline must never create a duplicate. A single swing resolution can legitimately be flushed twice against one unchanged baseline: once by the per-action flush inside _resolveOnce (right after markResolved), and again by the caller's own end-of-cycle flush (e.g. resolveCurrentDown()'s final flush). Between those two flushes, the first flush's combat.update() write round-trips through BodyMindSoulCombatModel.redistributeEntries() (called from _preUpdate), which re-buckets every action by its current resolutionTime — so a next-swing entry freshly queued into downs[0] by system.resolve() (see module/data/item-action.mjs) is very likely sitting on a different down in the live document by the second flush than the down it still occupies in this pass's own in-memory snapshot. Earlier versions of this method only ever repositioned entries already present in baseline (the "moved originals" case), so a new entry's stale in-memory copy was left in place and a second copy was merged in from the live read at its now-correct down — two physical copies of the same id. The move-detection below is therefore keyed on "does an entry with this id exist in liveDowns at a different down than its current snapshot bucket", with no dependency on whether the id is in baseline — new-this-phase entries are repositioned exactly like original ones, so a second flush against the same baseline can only ever converge an id onto its one live-authoritative down, never duplicate it.

      sweptActionIds/sweptEffectIds name entries that were deliberately filtered out of downs this pass (rt decremented below 0) — their liveDowns state is stale (pre-advance) and must NOT be re-added by the merge-new-entries step, even though they're absent from the snapshot.

      No explicit empty-array guard is needed for a 0-combatant combat or an empty liveDowns — every loop/.filter/.find below degrades gracefully on empty input.

      Parameters

      • liveDowns: object[]

        A fresh plain-object downs array read directly from the live document (e.g. combat.system.toObject().bms.downs).

      • Optionalbaseline: { actionIds?: Set<string>; effectIds?: Set<string> } = {}

        The value returned by this same caller's own beginMutationPhase call. Missing sets default to empty (nothing treated as "original").

      • OptionalsweptActionIds: Set<string> = ...

        Action ids swept out of downs this pass (rt went negative) — excluded from re-merge.

      • OptionalsweptEffectIds: Set<string> = ...

        Effect ids swept out of downs this pass (rt went negative) — excluded from re-merge.

      Returns void

    • Record ids added/removed by a REENTRANT _mutateDowns call that skipped its own flush. See the constructor's doc comment for why this exists. Removing an id that was itself still pending-new (added and then removed again, all before ever being flushed) simply forgets it — it was never live, so there's nothing to sweep.

      Parameters

      • Optionaladded: { actionIds?: Set<string>; effectIds?: Set<string> } = {}

        Ids present after the reentrant call's mutatorFn ran that weren't present in its own local pre-mutation baseline.

      • Optionalremoved: { actionIds?: Set<string>; effectIds?: Set<string> } = {}

        Ids present in the reentrant call's own local pre-mutation baseline that are no longer present after mutatorFn ran.

      Returns void

    • Release a previously-claimed action id. Always call this in a finally block paired with claim, regardless of success or failure of the work done while claimed.

      Parameters

      • id: string

        Action id to release.

      Returns void