Switch PF2e data source from Pf2eTools to Foundry VTT PF2e
All checks were successful
CI / check (push) Successful in 2m25s
CI / build-image (push) Successful in 23s

Replace the stagnant Pf2eTools bestiary with Foundry VTT PF2e system
data (github.com/foundryvtt/pf2e, v13-dev branch). This gives us 4,355
remaster-era creatures across 49 sources including Monster Core 1+2 and
all adventure paths.

Changes:
- Rewrite index generation script to walk Foundry pack directories
- Rewrite PF2e normalization adapter for Foundry JSON shape (system.*
  fields, items[] for attacks/abilities/spells)
- Add stripFoundryTags utility for Foundry HTML + enrichment syntax
- Implement multi-file source fetching (one request per creature file)
- Add spellcasting section to PF2e stat block (ranked spells + cantrips)
- Add saveConditional and hpDetails to PF2e domain type and stat block
- Add size and rarity to PF2e trait tags
- Filter redundant glossary abilities (healing when in hp.details,
  spell mechanic reminders, allSaves duplicates)
- Add PF2e stat block component tests (22 tests)
- Bump IndexedDB cache version to 5 for clean migration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-04-08 21:05:00 +02:00
parent 0c235112ee
commit 1c107a500b
20 changed files with 1872 additions and 25871 deletions

View File

@@ -1,6 +1,11 @@
import { describe, expect, it } from "vitest";
const PACK_DIR_PREFIX = /^pathfinder-monster-core\//;
const JSON_EXTENSION = /\.json$/;
import {
getAllPf2eSourceCodes,
getCreaturePathsForSource,
getDefaultPf2eFetchUrl,
getPf2eSourceDisplayName,
loadPf2eBestiaryIndex,
@@ -30,7 +35,15 @@ describe("loadPf2eBestiaryIndex", () => {
it("contains a substantial number of creatures", () => {
const index = loadPf2eBestiaryIndex();
expect(index.creatures.length).toBeGreaterThan(2000);
expect(index.creatures.length).toBeGreaterThan(2500);
});
it("creatures have size and type populated", () => {
const index = loadPf2eBestiaryIndex();
const withSize = index.creatures.filter((c) => c.size !== "");
const withType = index.creatures.filter((c) => c.type !== "");
expect(withSize.length).toBeGreaterThan(index.creatures.length * 0.9);
expect(withType.length).toBeGreaterThan(index.creatures.length * 0.8);
});
it("returns the same cached instance on subsequent calls", () => {
@@ -49,20 +62,42 @@ describe("getAllPf2eSourceCodes", () => {
});
describe("getDefaultPf2eFetchUrl", () => {
it("returns Pf2eTools GitHub URL with lowercase source code", () => {
const url = getDefaultPf2eFetchUrl("B1");
it("returns Foundry VTT PF2e base URL", () => {
const url = getDefaultPf2eFetchUrl("pathfinder-monster-core");
expect(url).toBe(
"https://raw.githubusercontent.com/Pf2eToolsOrg/Pf2eTools/dev/data/bestiary/creatures-b1.json",
"https://raw.githubusercontent.com/foundryvtt/pf2e/v13-dev/packs/pf2e/",
);
});
it("normalizes custom base URL with trailing slash", () => {
const url = getDefaultPf2eFetchUrl(
"pathfinder-monster-core",
"https://example.com/pf2e",
);
expect(url).toBe("https://example.com/pf2e/");
});
});
describe("getPf2eSourceDisplayName", () => {
it("returns display name for a known source", () => {
expect(getPf2eSourceDisplayName("B1")).toBe("Bestiary");
const name = getPf2eSourceDisplayName("pathfinder-monster-core");
expect(name).toBe("Monster Core");
});
it("falls back to source code for unknown source", () => {
expect(getPf2eSourceDisplayName("UNKNOWN")).toBe("UNKNOWN");
});
});
describe("getCreaturePathsForSource", () => {
it("returns file paths for a known source", () => {
const paths = getCreaturePathsForSource("pathfinder-monster-core");
expect(paths.length).toBeGreaterThan(100);
expect(paths[0]).toMatch(PACK_DIR_PREFIX);
expect(paths[0]).toMatch(JSON_EXTENSION);
});
it("returns empty array for unknown source", () => {
expect(getCreaturePathsForSource("nonexistent")).toEqual([]);
});
});