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

100 lines
3.5 KiB
Markdown

# 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`)
```js
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
```bash
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