Add Pathfinder 2e game system mode
All checks were successful
CI / check (push) Successful in 2m21s
CI / build-image (push) Successful in 24s

Implements PF2e as an alternative game system alongside D&D 5e/5.5e.
Settings modal "Game System" selector switches conditions, bestiary,
stat block layout, and initiative calculation between systems.

- Valued conditions with increment/decrement UX (Clumsy 2, Frightened 3)
- 2,502 PF2e creatures from bundled search index (77 sources)
- PF2e stat block: level, traits, Perception, Fort/Ref/Will, ability mods
- Perception-based initiative rolling
- System-scoped source cache (D&D and PF2e sources don't collide)
- Backwards-compatible condition rehydration (ConditionId[] → ConditionEntry[])
- Difficulty indicator hidden in PF2e mode (excluded from MVP)

Closes dostulata/initiative#19

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-04-07 01:26:22 +02:00
parent 8f6eebc43b
commit e62c49434c
67 changed files with 27758 additions and 527 deletions

View File

@@ -1,5 +1,6 @@
import { useCallback, useRef, useState } from "react";
import { useAdapters } from "../contexts/adapter-context.js";
import { useRulesEditionContext } from "../contexts/rules-edition-context.js";
const BATCH_SIZE = 6;
@@ -29,7 +30,9 @@ interface BulkImportHook {
}
export function useBulkImport(): BulkImportHook {
const { bestiaryIndex } = useAdapters();
const { bestiaryIndex, pf2eBestiaryIndex } = useAdapters();
const { edition } = useRulesEditionContext();
const indexPort = edition === "pf2e" ? pf2eBestiaryIndex : bestiaryIndex;
const [state, setState] = useState<BulkImportState>(IDLE_STATE);
const countersRef = useRef({ completed: 0, failed: 0 });
@@ -40,7 +43,7 @@ export function useBulkImport(): BulkImportHook {
isSourceCached: (sourceCode: string) => Promise<boolean>,
refreshCache: () => Promise<void>,
) => {
const allCodes = bestiaryIndex.getAllSourceCodes();
const allCodes = indexPort.getAllSourceCodes();
const total = allCodes.length;
countersRef.current = { completed: 0, failed: 0 };
@@ -81,7 +84,7 @@ export function useBulkImport(): BulkImportHook {
chain.then(() =>
Promise.allSettled(
batch.map(async ({ code }) => {
const url = bestiaryIndex.getDefaultFetchUrl(code, baseUrl);
const url = indexPort.getDefaultFetchUrl(code, baseUrl);
try {
await fetchAndCacheSource(code, url);
countersRef.current.completed++;
@@ -115,7 +118,7 @@ export function useBulkImport(): BulkImportHook {
});
})();
},
[bestiaryIndex],
[indexPort],
);
const reset = useCallback(() => {