The central runtime object for Song of Heroic Lands — and what the global sohl variable points at.

A single SohlSystem instance is created during Foundry's init hook (via getInstance) and installed as globalThis.sohl, so it is reachable from init onward — before ready (SohlSystem.ready flips to true once ready-hook setup finishes). Macros, modules, and Script Actions reach SoHL's system-wide services through it. This is the canonical reference for that sohl surface; the members below are the full list.

For working with one specific actor or item, prefer that document's .logic (the "document surface") over walking these collections — see the The SoHL API how-to guide for the two-surface model. What sohl offers, by category:

Properties

apps: apps

The apps namespace tree (sohl.apps.foundry.CalendarSettingsMenu, …). Bound at init.

core: core

The core namespace tree (sohl.core.logic.SohlSystem, …). Bound at init.

document: document

The document namespace tree — sohl.document.effect.foundry.SohlActiveEffect, etc. Bound to the barrel namespace at init (in sohl.ts); the type is declared here without a runtime import so the binding stays cycle-free.

entity: SohlEntitySurface & EntityNamespaces

The sohl.entity surface — the constructable entity-class registry (the outside-SoHL surface for macros and variant modules to new, subclass, or override via sohl.entity.X / sohl.entity.register(...); each class is a getter, so a register() override is picked up at every construction site) merged with the entity sub-namespaces for addressing (sohl.entity.modifier.ValueModifier, …). Bound at init in sohl.ts; the type is declared via typeof import(...) so the binding stays cycle-free.

In-memory trigger/event dispatcher (sohl.events).

Localization helper (sohl.i18n).

System logger (sohl.log).

random: Rng

The process-wide pseudo-random generator (sohl.random) — the shared, ambient sohl.entity.random.Rng stream backing sohl.entity.roll.SimpleRoll, hit-location selection, and the rand() expression helper when no generator is injected. Seeded from entropy at construction; present from that point on (its own readiness signal). It is one shared stream — safe for atomic synchronous draws but not isolated; a flow needing isolation injects its own createRng instance. e2e re-seeds it through the window for reproducibility (win.sohl.random.seed(...)).

Never seed this with a fixed value in a play path — predictable dice ruin play, and a shared deterministic stream desyncs across clients anyway. Fixed seeds are strictly a test/e2e affordance.

utils: utils

The utils namespace (sohl.utils) — the Foundry-free utility superset: the sohl.utils.romanize-style helpers and the constants (ACTOR_KIND, …) re-exported at its top level, plus the nested collection sub-namespace (sohl.utils.collection.SohlMap). Bound to the barrel namespace at init (in sohl.ts); the type is declared here without a runtime import so the binding stays cycle-free. The curated constants alias (sohl.constants) is kept alongside it.

constants: __module = constants

The constants module (static access).

ready: boolean = false

Set true once the system has finished its ready-hook setup.

Accessors

  • get actorLogicClasses(): Record<
        ActorKind,
        Constructor<SohlActorLogic<any>, any[]>,
    >
  • The actor-kind → base Logic-class map (sohl.actorLogicClasses). Exposes the SoHL base classes so a variant module can subclass one before registering the override. Reads reflect any registered override.

    Returns Record<ActorKind, Constructor<SohlActorLogic<any>, any[]>>

    class MyBeing extends sohl.actorLogicClasses.being {}
    

Methods

  • Attach a Foundry Macro to doc as a SCRIPT action — sohl.addScriptAction. The clean programmatic sibling of the sheet's "create action" control and of sohl.schedule / sohl.worldHost (issue #588, deliverable §7): a module or macro hands a minimal spec ({ name, executor } plus optional overrides) and gets a persisted, runnable action back — without knowing the full actionDefs shape.

    spec.name becomes both the action's shortcode (what schedule and the [Perform] reminder address) and its default title; spec.executor is a Foundry Macro UUID (a reference, never inline code). Re-attaching the same name replaces the entry rather than duplicating it, so an init hook can run on every reload safely.

    Works on any document that carries system.actionDefs — an actor (including the _sohlworld host) or an item. Because SCRIPT entries are GM-authored, this is a no-op returning undefined for a non-GM (the same gate SohlActor/SohlItem._preUpdate enforce at the persist boundary); the caller must also be an owner of doc (a document write).

    Parameters

    Returns Promise<undefined | sohl.entity.action.SohlAction.Data>

    The persisted action def, or undefined when the current user is not a GM.

    If spec.name or spec.executor is blank.

  • Register an actor Logic class for a kind, overriding the SoHL default.

    Call from a module's init/setup hook — before the first .logic for that kind is constructed. No construction-site changes are needed: the resolution path (SohlDataModel.create) reads this map, so every document of that kind built afterward uses the registered class.

    Parameters

    Returns void

  • Schedule a recurring action on a document (issue #588) — sohl.schedule. Persists the schedule to the document's system.scheduledActions (the durable record, anchored at the current world time) and arms the event queue (the live entry), so when it comes due the queue offers it as a [Perform] reminder. Re-call it (e.g. from the action after it performs) to reschedule the next occurrence.

    Works on any document whose data model extends the base SohlDataModel and so carries a system.scheduledActions field — an actor (including the _sohlworld host) or an item (a wound, an affliction). Scenes and active effects extend TypeDataModel directly and cannot host a schedule. Must run as an owner of doc (a document write). Both halves derive the fire time from the same anchor + interval, so they cannot drift.

    A schedule may be scene-bound (issue #590): pass sceneUuid and the [Perform] reminder is offered only while that scene is the active scene — a bandit check at a hideout does not fire while the party is elsewhere, and a check that came due while away surfaces when they return. Omit sceneUuid (or pass undefined) for a world-wide schedule that fires regardless of the active scene.

    Parameters

    • doc: Schedulable

      The document to schedule on (its logic hosts actionName).

    • actionName: string

      The action shortcode to run when due.

    • interval: number

      Seconds until the next fire.

    • Optionalpayload: Record<string, unknown>

      Opaque scope handed to the action on [Perform].

    • OptionalsceneUuid: string

      The scene the schedule is bound to, or undefined for a world-wide schedule.

    • OptionaltriggerName: string

      The lifecycle trigger to bind to (issue #622). Omitted or "updateWorldTime" ⇒ a time-based schedule fired at now + interval (the default); any other value ("turnEnd", "combatStart", …) ⇒ an event-driven schedule (interval is then unused).

    • Optionalpredicate: string

      Optional sohl.entity.expr.SafeExpression source gating an event-driven schedule (issue #569; subscriberUuid is bound to doc). Ignored for a time schedule.

    • Optionalanchor: number

      World time the recurrence is measured from, defaulting to now. A recurring *Test passes the due time of the occurrence it just performed, not the moment the player pressed the button, so a check answered late does not push the whole cadence later (issue #1181). The resulting fire time may therefore already be in the past, in which case the schedule is armed due and its *Check fires at the next dispatch.

    Returns Promise<void>

    A promise that resolves once the schedule is persisted and armed.

  • Register every actor, item, active-effect, and scene sheet with Foundry and make them the default for their document types. Called once during system initialization.

    Returns void

  • Remove a recurring schedule for actionName on docsohl.unschedule. Clears the persisted system.scheduledActions entry and unsubscribes it from the event queue.

    Parameters

    • doc: Schedulable

      The document to unschedule on.

    • actionName: string

      The schedule to remove.

    Returns Promise<void>

    A promise that resolves once the schedule is removed.

  • Find (or, for a GM, create) the singleton world host actor — sohl.worldHost(). It is the document world-scoped scheduled actions and events hang off of (issue #588): an Actor, so it already has the execution surface (onChatCardButton + an actions collection) that a scheduled action's [Perform] needs.

    Identified by the reserved shortcode WORLD_HOST_SHORTCODE. Created with ownership default NONE, so only the GM ever sees it. If it has been deleted, a GM call recreates it (its stored schedule is lost and must be re-registered). A non-GM who cannot see it gets undefined.

    Returns Promise<any>

    The world-host actor, or undefined (non-GM, not visible).

  • Apply a registered calendar to SOHLCONFIG.time, and re-initialize game.time so the change takes effect without a reload. Safe to call during the init hook before game.time exists.

    Parameters

    • id: string

      The identifier of the registered calendar to apply.

    Returns void

    Error if no calendar is registered under id (the message lists the available ids).

  • Remove a calendar registration. A no-op if id is not registered.

    Parameters

    • id: string

      The identifier of the calendar to remove.

    Returns void

    Error if id names a built-in calendar — built-ins cannot be deleted, only imported calendars can.