Adds tests/PLAN.md — the test plan that implements HOOK_CONTRACT.md section 14. Defines what we test (sections A-G: envelope shape, subscriber API, error containment, lifecycle, anti-corruption, performance budget, system adapter loading), what we don't test (consumer domain logic, Foundry's correctness, system-specific derived events, older Foundry versions), and the definition of done (100% coverage of 'what we test', smoke <2s, Foundry <30s, perf budget <0.1ms median per fire). The v0.1.0 verify-hooks-lib.mjs (20 assertions) covers the wrong shape — it's slated for archive when v0.2.0 test files ship. Listed in 'future turns' as a git mv into tests/archive/v0.1.0/. Push: Gitea only.
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 + summaryIts-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-roundcombatant-add,combatant-removepre-update-actor,update-actor,pre-update-token,update-token,pre-update-item,update-itemcreate-active-effect,delete-active-effecttoken-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 }]). Thecoreadapter is always present; other adapters are filtered bymatch().registerAllEvents(systems, onEvent)→ registers every event from every active system viaHooks.on(...). TheonEventcallback 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.jsfilters system adapters bygame.system.id. Adding a new system = add one file inscripts/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