Kaysser Kayyali 8e3db117f9 docs: HOOK_CONTRACT.md for v0.2.0 (generic Foundry facade)
Adds docs/HOOK_CONTRACT.md — the spec for hooks-lib v0.2.0. The
v0.1.0 implementation (18 hand-written event handlers, the
scripts/systems/ adapter pair, the encounter.js stub) is the wrong
shape. v0.2.0 is a generic Foundry hook facade:

- Subscribes to every Foundry hook in §6 (lifecycle, document CRUD
  generic, combat, chat/canvas/UI, dnd5e v2 roll hooks)
- Emits a uniform {ts, hook, args} envelope with no domain
  interpretation
- Provides subscribe / subscribeMany / subscribeAll / unsubscribeAll
  primitives
- Registers system adapters (separate repos) via a manifest with
  semver range matching for system + Foundry versions
- Dispatches sync for pre-* / combat* / applyActiveEffect /
  get*Context hooks; async (microtask) for everything else
- Absorbs Foundry rename churn (renderChatInput vs renderChatLog)
  and arity shifts (combatRound signature) so consumers stay stable

No code changed. The contract is the gate for v0.2.0 implementation.

Push: Gitea only (this turn). No GitHub mirror.
2026-06-20 02:10:09 -04:00

Hax's Tools — Hooks Lib

Foundry VTT module (id: hax-hooks-lib) that turns Foundry's hook soup (dnd5e, combat, token updates) into a clean, normalized event stream. Library-only — no UI, no settings, no chat output. Designed to be consumed by any module that wants Foundry events in a stable shape.

Part of the Hax's Tools umbrella. Consumers today:

  • battle-focus — encounter + journal + summary
  • Its-Achievable — achievements, rewards, wall, HUD

Status

v0.1.0 — initial extraction from battle-focus v0.5.0-alpha.12. The scripts/events/ and scripts/systems/ content is a verbatim copy of the battle-focus versions, with module-id log strings retagged from battle-focus to hax-hooks-lib.

Stage 1 of the split plan: ship this repo without changing battle-focus's behavior. Stage 2 will flip battle-focus to import from here and delete the local copy.

Event Shape

Every event is a plain object with at minimum:

{
  kind: "combat-start",  // stable string id
  ts:   1719000000000,   // epoch ms when handler fired
  // ... kind-specific fields
}

Handler functions in scripts/events/core/ and scripts/events/dnd5e/ return the event object (or null to drop it). The registry calls the consumer's onEvent(event) callback for any non-null return.

Event catalog

Core (system-agnostic, always loaded):

  • combat-start, combat-end, combat-inactive, combat-turn, combat-round
  • combatant-add, combatant-remove
  • pre-update-actor, update-actor, pre-update-token, update-token, pre-update-item, update-item
  • create-active-effect, delete-active-effect
  • token-avatar-change

D&D 5e (loaded when game.system.id === "dnd5e"):

  • attack-roll, damage-roll

Public API (on game.modules.get("hax-hooks-lib").api)

  • loadSystems({ currentSystemId, systemVersion }) → array of active system adapters ([{ id, label, match, events }]). The core adapter is always present; other adapters are filtered by match().
  • registerAllEvents(systems, onEvent) → registers every event from every active system via Hooks.on(...). The onEvent callback is invoked once per non-null handler return, awaited. Returns the array of registered event defs (for introspection).

Consumers should call registerAllEvents from their ready hook and provide their own onEvent(event) to drive their downstream pipeline.

Dependencies

None. This is a leaf library.

Architecture notes

  • Single chokepoint: registerEvent() is the only place that wraps a Foundry hook into the normalized shape. Adding a new event type = add one file + one line in the relevant system adapter.
  • System loader: scripts/systems/loader.js filters system adapters by game.system.id. Adding a new system = add one file in scripts/systems/ + one import line.
  • Context helper: $ctx() returns the active event's metadata for the duration of the handler call. Use it inside a handler to log with the event id, or to read which system emitted it.

Tests

tests/verify-hooks-lib.mjs is a minimal smoke test that exercises the registry + a single event without booting Foundry. Foundry-load verification happens in battle-focus's E2E suite (the test driver registers hooks-lib's events through battle-focus's consumer path and confirms the event stream flows end-to-end).

npm test

Maintained by Kaysser Taylor + Hermes

Description
Hax's Tools � Foundry VTT hook normalization library (battle-focus slice 1)
Readme 464 KiB
Languages
JavaScript 100%