Files
Its-Achievable/README.md
Kaysser Kayyali f2ef1ef4f3 v0.1.0 — initial extraction from battle-focus v0.5.0-alpha.12
Stage 2 of the Hax's Tools split. its-achievable ships as a
standalone module that subscribes to hax-hooks-lib's envelope
stream and provides achievements + custom rules + rewards +
achievement wall + combat HUD.

## What's new

scripts/ — moved from battle-focus/scripts/, MODULE_ID retagged
battle-focus → its-achievable:
- achievement-rules.js (323 lines) — rule engine: OPERATORS,
  TRIGGER_TYPES, evaluateCondition(s), testRule, evaluateRulesFor*
- achievements.js (1150 lines) — 24-entry catalog + award path,
  per-event evaluators, encounter-end + career-update evaluation
- achievement-wall.js (333 lines) — renderAchievementWall,
  getAchievementWallProgress, renderAchievementPopover
- custom-achievements-app.js (270 lines) — GM FormApplication
  for editing custom rules
- hud.js (624 lines) — combat HUD (ApplicationV2 +
  HandlebarsApplicationMixin); removed dead import of
  battle-focus's encounter.js (it was unused even in the
  original)

scripts/main.js — Foundry entry point. Registers settings at
its-achievable.* namespace; exposes the public API on mod.api;
registers chatBubble popover listener + HUD singleton on ready.

templates/ + styles/ — moved verbatim.

tests/PLAN.md — per-project test plan (sections A-F).
tests/test-helpers.mjs — Foundry stub.
tests/verify-achievable-v1.mjs — smoke test, 75 assertions
covering rule engine, catalog, awards, hooks-lib wiring, HUD
payload derivation, and wall/popover rendering. Runs in <2s.

## Architecture

- **Settings namespace**: its-achievable.* (was battle-focus.*).
  No migration (per Kaysser's decision); users with existing
  worlds re-create their custom rules. Documented in README.
- **HUD derives its own state from hooks-lib envelopes.** Stage 2
  keeps the legacy battle-focus:hud-update broadcast subscription
  for now (battle-focus still emits it); Stage 3 will switch the
  HUD to subscribe to hooks-lib directly and remove the
  battle-focus broadcasts.
- **Encounter singleton**: accessed via battle-focus's public
  api.getActiveEncounter() — no direct import of battle-focus's
  encounter.js.

## Dependencies

- hax-hooks-lib ^0.2.0 (declared in module.json relationships).
- battle-focus (soft, runtime) — provides the encounter singleton.

## Tests

- 75/75 smoke assertions pass in 0.07s.
- Module manifest validates: 0 errors, 1 warning (no icon —
  Stage 2+ work).

Push: Gitea only.
2026-06-20 14:04:56 -04:00

3.5 KiB

Hax's Tools — It's Achievable (its-achievable)

Foundry VTT module: achievements engine, custom rules, rewards, achievement wall, and combat HUD. Consumes the generic Foundry hook facade from hax-hooks-lib and the encounter state from battle-focus.

Status

v0.1.0 — first release as a standalone repo. Sourced from battle-focus/scripts/{achievements.js, achievement-wall.js, custom-achievements-app.js, hud.js, achievement-rules.js} at battle-focus v0.5.0-alpha.12 (99cf757 on Gitea).

Stage 2 of the Hax's Tools split. The achievement code is now an independent module with its own:

  • Settings namespace (its-achievable.*)
  • Module id (its-achievable, lowercase kebab)
  • Achievements catalog and rule engine
  • HUD subscription to hooks-lib's v0.2.0 envelope stream
  • Chat-bar popover and form application

battle-focus retains its own copies of these files until Stage 3 of the split ships (which will delete the copies and add its-achievable as a soft dependency of battle-focus).

Dependencies

  • hax-hooks-lib (≥0.2.0) — provides the Foundry event stream via the generic {ts, hook, args} envelope facade. See hooks-lib/docs/HOOK_CONTRACT.md.
  • battle-focus (soft) — provides the encounter singleton via game.modules.get("battle-focus").api.getActiveEncounter().

If either is missing, its-achievable logs a warning and degrades gracefully (achievements inactive).

Public API (on game.modules.get("its-achievable").api)

const api = game.modules.get("its-achievable").api;

// Catalog
api.getAchievementCatalog();
api.getActorAchievements(actorKey);

// Rule engine
api.evaluateRulesForEvent(event, encounter, actor, targetActor);
api.evaluateRulesForEncounterEnd(encounter);
api.evaluateRulesForCareerUpdate(career);

// Awarding
api.processEventForAchievements(event, encounter);
api.evaluateCombatAchievements(stats);
api.evaluateCareerAchievements(pcName, career, encounterId);
api.awardAchievement(actorKey, achievementId, encounterId);

// Wall + HUD
api.renderAchievementWall(actorId, actorName, opts);
api.getAchievementWallProgress(actorId, actorName);
api.renderAchievementPopover(unlocks, viewerName);
api.buildHudUpdatePayload(encounter, event);
api.openCustomAchievementsApp();

Settings namespace

Old (battle-focus.*) New (its-achievable.*)
achievementsByActor achievementsByActor
customAchievementRules customAchievementRules
enableRewards enableRewards

No automatic migration. Per the Stage 2 decision, users with existing worlds will need to re-create their custom rules and re-trigger any past achievement awards. Documented in CHANGELOG.

Tests

npm test    # smoke test, no Foundry needed

tests/PLAN.md describes what we test and what we don't. Real-Foundry integration testing happens when battle-focus migrates in Stage 3 and the existing E2E suite exercises the moved code.

Architecture notes

  • Hooks-lib first. its-achievable subscribes to hax-hooks-lib's envelope stream. Combat lifecycle, document CRUD, dnd5e rolls — all via hooksLib.api.subscribeMany({...}). No direct Hooks.on(...) calls.
  • Encounter via battle-focus's API. its-achievable does not import battle-focus's encounter.js directly; it calls battle-focus.api.getActiveEncounter() to resolve the singleton.
  • HUD derives its payload locally. buildHudUpdatePayload lives in its-achievable (moved from battle-focus). It derives the HUD state from hooks-lib envelopes, not from a battle-focus broadcast.

Maintained by Kaysser Taylor + Hermes