Files
Its-Achievable/README.md
Kaysser Kayyali 888790e5ff v0.1.1: track the hax-hooks-lib -> foundry-hooks-lib rename
User callout: 'Hax' is Kaysser's nickname, drop it from module
names. hooks-lib v0.3.0 renamed its module id; this commit
follows:

- relationships.modules[0].id: hax-hooks-lib -> foundry-hooks-lib
- relationships.modules[0].compatibility.minimum: 0.2.0 -> 0.3.0
  (we now require the renamed version)
- module.json: version 0.1.0 -> 0.1.1, download URL updated
- README.md, scripts/main.js, tests/PLAN.md, tests/test-helpers.mjs:
  branding text updates
- .gitignore: un-ignore the new 0.1.1 zip

Verification: 75/75 smoke assertions pass, no logic change.

Module title is now 'It's Achievable' (dropped the
'Hax's Tools' umbrella prefix per the same callout). 2 module
text changes are non-functional; pure branding.
2026-06-20 16:55:29 -04:00

3.5 KiB

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 foundry-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 Foundry module 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

  • foundry-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 foundry-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