<div align="center">

  <img src="assets/abyssal-engine-banner.png" alt="Abyssal Engine" width="600">

  # HeroLab to Foundry, Powered by Abyssal Engine

  *A Foundry VTT module that imports Hero Lab Online characters directly into Pathfinder 2e and Starfinder 2e games, using HLO's official Public API. A from-scratch modernization of the dormant zarmstrong/hlo-importer, built to last.*

  **Status:** **Alpha-1.0.0 launch ready.** Code at v1.0.0 (local), v0.7.1 live on Bluehost (v1.0.0 cascade pending upload), v1.0.0 listing submitted to the Foundry package registry on 2026-05-16 pending review (foundryvtt.com username `shokotsu`). All 6 PF2e/SF2e test characters import cleanly; PFS boons land as homebrew feat items per Steven's design call that PF2e/SF2e compendiums lack most published boons. Phases 0 through 7 are functional: parser with 24 compset handlers including all PFS-specific ones (Faction routes to actor metadata, CustomReward/CustomBoon embed as feat items with category=pfsboon, MagicItem/Wand/CustomArmor/GearItem route to equipment-srd, Spellbook is SKIP_BY_DESIGN per PF2e auto-provisioning, Rune and Material emit apply-to-parent discriminators that merge into the parent weapon/armor's system.runes/system.material), matcher (deterministic pack-index lookup + slug pipeline with PRECIOUS_MATERIALS step 3.6 and baseName step 3.7 + curated overrides + rune extraction via util-rune-parser with PF2e canonical camelCase rune slugs), builder, V14 import dialog with grant resolver and wizard silencer and applyRunesToItem hook, HLO Public API client, sheet-header import button, end-of-import unmatched modal. SF2e Operative end-to-end verified 100% clean against Noxora fixture in V14. Five PF2e characters (Berial PFS, Berial ETT, Bhel'thal Xorlarrin, Bhelroth EC, Revascis RotD, Revascis Virholt L20) verified post-v0.7.0/v0.7.1: every compendium-eligible item resolves correctly; vampire/PFS/magic-item edge cases handled. V14 is the primary verification target; V13 compatibility is preserved but secondary. The codebase is organized as 49 cogs under `src/cogs/`, auto-discovered at build time. See [`docs/HANDOFF_v1_20260512.md`](docs/HANDOFF_v1_20260512.md) for the full project handoff and design.

  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
  [![Foundry V14](https://img.shields.io/badge/Foundry-V14.361-purple)](https://foundryvtt.com)
  [![PF2e](https://img.shields.io/badge/system-pf2e-blue)](https://github.com/foundryvtt/pf2e)
  [![SF2e](https://img.shields.io/badge/system-sf2e-blue)](https://foundryvtt.com)
  [![Ko-Fi](https://img.shields.io/badge/Ko--Fi-Support-ff5e5b)](https://ko-fi.com/adamantiteadventurer)

</div>

---

## What this is

| | |
|---|---|
| **Module ID** | `hlo-importer-abyssal-engine` |
| **HLO tool name** | `HeroLab to Foundry - Powered by Abyssal Engine` |
| **Author** | Shinra CC |
| **License** | [MIT](LICENSE) |
| **Foundry compat** | V14 verified (14.361), V13 supported as a fallback |
| **Target systems** | `pf2e`, `sf2e` |
| **Brand family** | Part of the [Abyssal Engine](https://shinra.cc) suite |
| **In-app entry point** | "Abyssal-Engine Import" button in the actor sheet header (PF2e and SF2e characters); theme-aware, so it stays legible on both dark and light sheets |

## Why this exists

This module exists because the previous two, zarmstrong's `hlo-importer` and its companion conversion service, are dead. They served the Pathfinder 2e Foundry community well from 2020 through September 2022, and then they stopped. The PHP backend was the single point of failure, and once it stopped getting updated the whole pipeline froze at PF2e Foundry data version 3.13.5. Anyone running a current Foundry world has been stuck pasting characters by hand for years.

I'm an avid PF2e and SF2e player, and HeroLab Online is the authoritative character builder for both systems. The slot is empty, nobody else has stepped up to fill it, and the people who would benefit from this module have been quietly going without. So I'm building it again, properly this time: the conversion logic lives inside the module, there is no third-party service in the critical path, and Starfinder 2e is supported from day one rather than bolted on later. If you love HeroLab and you live in Foundry, this is for you. If you used the old hlo-importer and miss it, this is for you too.

## Roadmap (abbreviated)

The full phased roadmap lives in the handoff document. High level:

- **Phase 0**, Scaffolding and module skeleton (done)
- **Phase 1**, JSON-paste importer with PF2e support, using Noxora as the test fixture (done)
- **Phase 2**, Item compsets for PF2e (feats, spells, weapons, armor, gear) (done)
- **Phase 3**, Starfinder 2e content coverage (Operative, Technomancer, Mystic, Solarian, Envoy, Witchwarper, augmentations, batteries, expert/master role tracks) (done)
- **Phase 4**, Direct HLO Public API integration via element token plus user token (done; verified end-to-end against live HLO API on V14)
- **Phase 5**, Upload-JSON mode (thin variant on the paste path) (done)
- **Phase 6**, Polish and public release on the Foundry package registry (done: branded theme-aware sheet button, two-phase actor create, name-fallback re-import, conversion-log diagnostics, deterministic pack-index lookup, SF2e grant resolver, wizard silencer, unmatched modal, twenty-agent matcher audit for v0.6.13). v1.0.0 listing submitted 2026-05-16 awaiting Foundry review.
- **Phase 7 (future)**, SignalR live-sheet subscriptions, bulk import, differential re-import, parser handlers for the 9 remaining unknown compsets (Rune, MagicItem, Wand, Spellbook, GearItem, Faction, CustomReward, CustomBoon, CustomArmor).

## Architecture in one paragraph

Foundry module sends a request directly to `https://api.herolab.online`, runs the response through an in-module parser, then through an in-module compendium matcher (hybrid strategy: slug-first, fuzzy-name second, curated overrides third), then through an in-module Foundry actor builder, and finally hands the result to `Actor.create()` (or `Actor.update()` for re-imports). There is no third-party hosted service in the critical path. Three input modes feed the same parser: element token (API), pasted JSON, and uploaded JSON file.

## Cog architecture (post-Wave-6 layout)

The entire functional surface lives in `src/cogs/` as 49 individually-named files, each owning one concern. The directory is flat by design: file naming carries the grouping (`api-*`, `builder-*`, `import-*`, `matcher-*`, `parser-*`, `sf2e-*`, `system-detection`, `ui-*`, `util-*`), and `tools/regenerate-cog-index.mjs` walks the folder at build time to emit `_index.js`. `src/main.js` is the only file outside `cogs/`; it is the Foundry entrypoint declared in `module.json`'s `esmodules` array, and it bootstraps by importing four cogs (`util-version`, `util-logger`, `ui-import-dialog`, `ui-settings`). The cog architecture mirrors how the Abyssal Engine Discord bot and webapp organize their feature code.

| Group | Cogs | Purpose |
|-------|------|---------|
| `api-*` | 2 | HLO Public API client + access-token cache |
| `builder-*` | 6 | Foundry actor (PF2e + SF2e) + items + attachments + HP baseline + class key-ability defaults |
| `import-*` | 3 | Conversion-journal persistence, idempotency lookup, override stub extractor |
| `matcher-*` | 7 | Hybrid match orchestrator + pack-index (deterministic canonical lookup) + slug table + overrides + fuzzy + 2 resolvers |
| `parser-*` | 23 | Portfolio + actor + items + 18 per-compset normalizers (covers Ability/Action/Ancestry/Armor/Augmentation/Class/CustomArmor/CustomReward+Boon/Faction/Feat/Gear/GearItem/MagicItem/Material/Rune/Spell+FocSpell/Spellbook/Wand/Weapon) + registry + actor-type |
| `sf2e-*` | 1 | SF2e grant resolver (pre-fills ChoiceSet selections, writes itemGrants chains, bypasses SF2e wizard cascade) |
| `system-detection` | 1 | SF2e vs PF2e identification (single source of truth) |
| `ui-*` | 3 | Module settings registration + ApplicationV2 import dialog + end-of-import unmatched modal |
| `util-*` | 3 | Module identity constants + console logger + rune parser (PF2e/SF2e potency/striking/property rune extraction) |

## Quick start (developers)

```
git clone https://github.com/ShinraEngineer/hlo-importer-abyssal-engine.git
cd hlo-importer-abyssal-engine
npm test              # sanity check + manifest check + both branch fixtures
```

Symlink (or copy) the cloned directory into your Foundry user data path under `Data/modules/hlo-importer-abyssal-engine/` and enable the module in a PF2e or SF2e world.

Useful scripts (no production dependencies, Node 20+):

| Command | What it does |
|---------|--------------|
| `npm test` | Runs every automated check: sanity invariants, module.json validation, SF2e parser fixture (Noxora), PF2e parser fixture (Bren). |
| `npm run sanity` | Compset registry coverage, override well-formedness, slug derivation invariants. |
| `npm run check:manifest` | Validates `module.json` against required fields and project conventions. |
| `npm run parse:noxora` | Full pipeline dump against the Noxora SF2e Operative fixture. |
| `npm run parse:fighter` | Full pipeline dump against the synthetic PF2e Dwarf Fighter fixture. |
| `npm run release` | Packages a versioned .zip (`hlo-ab-v{version}.zip`) ready for upload to `shinra.cc/hlo-importer-abyssal-engine/releases/`. |
| `npm run deploy` | Gates on `npm test`, stages `module.json` and the release zip into `shinra-cc-site/`, populates `fileStagingArea/` with only the files that changed this round, and prints the manual WinSCP upload reminder. |
| `npm run deploy:full` | Same as `deploy` plus runs `npm run release` first so the new versioned zip lands in the staging bundle. |
| `npm run sync:v14` | Wipes the Foundry V14 Electron cache and copies the current source into the bundled V14 test install. Foundry must be fully closed during sync. |
| `npm run cogs:regen` | Re-scans `src/cogs/` and writes the auto-generated `_index.js` registry. Run after adding, removing, or renaming any cog file. |

The full contributor guide lives at [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md). The in-Foundry test plan lives at [`docs/PHASE_1_TO_5_TEST_PLAN.md`](docs/PHASE_1_TO_5_TEST_PLAN.md).

## Acknowledgements

This project stands on the shoulders of `zarmstrong/hlo-importer` and its companion conversion service, which served the PF2e Foundry community from 2020 to 2022. The original is dormant; this is a from-scratch reimplementation rather than a fork, but the architecture borrows lessons from 134 versions of zarmstrong's published changelog.

Hero Lab and the Hero Lab logo are registered trademarks of LWD Technology, Inc. Pathfinder, Starfinder, and associated trademarks are property of Paizo Inc. Foundry Virtual Tabletop is a trademark of Foundry Gaming, LLC. This project is not affiliated with, endorsed by, or sponsored by any of these companies.

## Contributing

The repository is in a closed-design phase until Phase 1 is functional end-to-end. Issues and discussion are welcome; pull requests may be deferred until the core import pipeline is in place. See the handoff document for context before opening a substantial issue.

## Support and contact

- **Ko-Fi:** https://ko-fi.com/adamantiteadventurer (supporting future development is appreciated, not required)
- **Discord (direct message):** [shinra.engineer](https://discordapp.com/users/310193940106313729)
- **GitHub Issues:** https://github.com/ShinraEngineer/hlo-importer-abyssal-engine/issues
- **Brand home:** [shinra.cc](https://shinra.cc)

---

<div align="center">
  <img src="icons/abyssal-engine-icon.png" alt="Abyssal Engine" width="96">
  <br>
  <sub>Documentation hub: <a href="docs/">docs/</a> · Maintainer: <a href="https://github.com/ShinraEngineer">@ShinraEngineer</a></sub>
</div>
