From f75b50adb85f66c1057a014f805c851bef61c17a Mon Sep 17 00:00:00 2001 From: Nils Briggen Date: Tue, 9 Jun 2026 10:51:28 +0200 Subject: [PATCH] pf2e character creator: brief, readable overviews matching 5e MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pf2e creator showed long, truncated run-ons where 5e shows clean, brief overviews: classes were cut mid-word at 400 chars, backgrounds rendered the raw Foundry " Source Core Rulebook pg. N …" citation prefix, and ancestry summaries ended in a stray ellipsis. - new src/features/characters/builder/overview.ts: briefOverview() strips the Foundry citation prefix, drops a trailing ellipsis, and keeps the first clean sentence (or a word-boundary trim — never mid-word). A no-op on already-short text, so 5e is unchanged. Applied to pf2e class/ancestry/background overviews. - dedupeByName(): the pf2e data carries reprints under duplicate names (94 ancestries, 612 backgrounds) which duplicated options and triggered React duplicate-key warnings — now de-duplicated at load. - 6 unit tests for both helpers. Verified live: pf2e class cards now read as clean single sentences; no citation junk; no duplicate-key warnings. Gate: 289 unit + 35 e2e + 2 realtime, build clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../characters/builder/CreationWizard.tsx | 11 +++--- .../characters/builder/overview.test.ts | 38 +++++++++++++++++++ src/features/characters/builder/overview.ts | 35 +++++++++++++++++ tsconfig.app.tsbuildinfo | 2 +- 4 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 src/features/characters/builder/overview.test.ts create mode 100644 src/features/characters/builder/overview.ts diff --git a/src/features/characters/builder/CreationWizard.tsx b/src/features/characters/builder/CreationWizard.tsx index 8517043..60e9f6a 100644 --- a/src/features/characters/builder/CreationWizard.tsx +++ b/src/features/characters/builder/CreationWizard.tsx @@ -14,6 +14,7 @@ import { createRng } from '@/lib/rng'; import { newId } from '@/lib/ids'; import { formatModifier } from '@/lib/format'; import { getClassTip, raceSynergyNote } from '@/lib/assistant/builder'; +import { briefOverview, dedupeByName } from './overview'; import { Modal } from '@/components/ui/Modal'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Codex'; @@ -84,7 +85,7 @@ export function CreationWizard({ campaign, onClose }: { campaign: Campaign; onCl } else { void loadPf2e('ancestries').then((rs) => { if (!on) return; - setOrigins(rs.map((r) => { + setOrigins(dedupeByName(rs.map((r) => { const hp = typeof r.hp === 'number' ? r.hp : undefined; const speed = (r.speed as { land?: number } | undefined)?.land; const attrArr = Array.isArray(r.attribute) ? (r.attribute as string[]).filter((a) => a !== 'Free') : []; @@ -92,14 +93,14 @@ export function CreationWizard({ campaign, onClose }: { campaign: Campaign; onCl const meta = [attrSummary, hp !== undefined ? `${hp} HP` : ''].filter(Boolean).join(' · '); return { name: String(r.name), - desc: String((r.summary ?? r.text) ?? ''), + desc: briefOverview(String((r.summary ?? r.text) ?? '')), ...(hp !== undefined ? { hp } : {}), ...(speed ? { speed } : {}), meta, }; - })); + }))); }); - void loadPf2e('backgrounds').then((bs) => on && setBackgrounds(bs.map((b) => ({ name: String(b.name), desc: String((b.description ?? b.text) ?? '') })))); + void loadPf2e('backgrounds').then((bs) => on && setBackgrounds(dedupeByName(bs.map((b) => ({ name: String(b.name), desc: briefOverview(String((b.description ?? b.text) ?? '')) }))))); } return () => { on = false; }; }, [system]); @@ -308,7 +309,7 @@ export function CreationWizard({ campaign, onClose }: { campaign: Campaign; onCl {playstyle(c)}
{system === 'pf2e' ? `${c.hitDie} HP/lvl` : `d${c.hitDie}`}{c.keyAbilities.length ? ` · ${c.keyAbilities.map((k) => ABILITY_ABBR[k as AbilityKey]).join('/')}` : ''}{c.caster !== 'none' ? ' · caster' : ''}
- {c.description &&

{c.description}

} + {c.description &&

{briefOverview(c.description)}

} ))} {classes.length === 0 &&

Loading classes…

} diff --git a/src/features/characters/builder/overview.test.ts b/src/features/characters/builder/overview.test.ts new file mode 100644 index 0000000..8b68474 --- /dev/null +++ b/src/features/characters/builder/overview.test.ts @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest'; +import { briefOverview, dedupeByName } from './overview'; + +describe('briefOverview', () => { + it('leaves short text untouched (5e feature names)', () => { + expect(briefOverview('Rage')).toBe('Rage'); + expect(briefOverview('Fighting Style')).toBe('Fighting Style'); + }); + + it('keeps the first clean sentence of a long run-on, no mid-word cut', () => { + const fighter = 'Fighting for honor, greed, loyalty, or simply the thrill of battle, you are an undisputed master of weaponry and combat techniques. You combine your actions through clever combinations…'; + expect(briefOverview(fighter)).toBe('Fighting for honor, greed, loyalty, or simply the thrill of battle, you are an undisputed master of weaponry and combat techniques.'); + }); + + it('strips the Foundry "Name Source … pg. N" citation prefix, then takes the first sentence', () => { + const bg = ' Acolyte Source Core Rulebook pg. 60 You spent your early days in a religious monastery or cloister. You may have traveled out into the world to spread the message of your religion or because you cast away the teachings of your faith.'; + expect(briefOverview(bg)).toBe('You spent your early days in a religious monastery or cloister.'); + }); + + it('drops a stray trailing ellipsis from a pre-truncated summary', () => { + expect(briefOverview('Dwarves are a stoic and stern people…')).toBe('Dwarves are a stoic and stern people'); + }); + + it('falls back to a word-boundary trim when the first sentence is very long', () => { + const long = `${'word '.repeat(60)}end.`; + const out = briefOverview(long); + expect(out.endsWith('…')).toBe(true); + expect(out.length).toBeLessThanOrEqual(141); + expect(out).not.toMatch(/wor…$/); // never cuts mid-word + }); +}); + +describe('dedupeByName', () => { + it('keeps the first of each name, case-insensitively', () => { + const out = dedupeByName([{ name: 'Human' }, { name: 'Elf' }, { name: 'human' }, { name: 'Elf' }]); + expect(out.map((x) => x.name)).toEqual(['Human', 'Elf']); + }); +}); diff --git a/src/features/characters/builder/overview.ts b/src/features/characters/builder/overview.ts new file mode 100644 index 0000000..23d5a84 --- /dev/null +++ b/src/features/characters/builder/overview.ts @@ -0,0 +1,35 @@ +/** + * Helpers that make the pf2e creator read as cleanly as the 5e one. The pf2e + * (Foundry) data carries long, citation-prefixed descriptions and reprinted + * entries under duplicate names; these trim and de-duplicate at the edge. + */ + +/** + * A short, clean overview from possibly-long text. Strips the + * " Source Core Rulebook pg. N" citation prefix Foundry entries carry, + * drops a stray trailing ellipsis from prior truncation, then keeps the first + * sentence (or a clean word-boundary trim). A no-op on already-short text. + */ +export function briefOverview(text: string): string { + let t = (text || '').replace(/\s+/g, ' ').trim(); + const citation = /^.{0,80}?\bSource\b.*?\bpg\.\s*\d+\s*/i.exec(t); + if (citation) t = t.slice(citation[0].length).trim(); + t = t.replace(/…\s*$/, '').trim(); + if (t.length <= 140) return t; + const sentence = /^(.{40,180}?[.!?])(?:\s|$)/.exec(t); + if (sentence) return sentence[1]!; + const cut = t.slice(0, 140); + const sp = cut.lastIndexOf(' '); + return `${(sp > 40 ? cut.slice(0, sp) : cut).trim()}…`; +} + +/** Keep the first entry of each name (case-insensitive) — drops reprints/variants. */ +export function dedupeByName(items: T[]): T[] { + const seen = new Set(); + return items.filter((it) => { + const k = it.name.trim().toLowerCase(); + if (seen.has(k)) return false; + seen.add(k); + return true; + }); +} diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo index 2f254f0..1925101 100644 --- a/tsconfig.app.tsbuildinfo +++ b/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/router.tsx","./src/vite-env.d.ts","./src/app/RootLayout.tsx","./src/components/CommandPalette.tsx","./src/components/ui/Button.tsx","./src/components/ui/Codex.tsx","./src/components/ui/ErrorBoundary.tsx","./src/components/ui/Input.tsx","./src/components/ui/Modal.tsx","./src/components/ui/NumberField.tsx","./src/components/ui/Page.tsx","./src/components/ui/RollTray.tsx","./src/features/assistant/AssistantPage.tsx","./src/features/assistant/CampaignInsights.tsx","./src/features/assistant/EncounterTipCard.tsx","./src/features/assistant/NpcGenCard.tsx","./src/features/assistant/SessionPrepCard.tsx","./src/features/assistant/SignalsBell.tsx","./src/features/assistant/useEncounterAdvisor.ts","./src/features/assistant/useLevelUpAdvisor.ts","./src/features/assistant/useSignalAction.ts","./src/features/campaigns/CampaignsPage.tsx","./src/features/campaigns/hooks.ts","./src/features/characters/CharacterSheet.tsx","./src/features/characters/CharacterSheetPage.tsx","./src/features/characters/CharactersPage.tsx","./src/features/characters/hooks.ts","./src/features/characters/builder/CreationWizard.tsx","./src/features/characters/sheet/AbilityGenModal.tsx","./src/features/characters/sheet/AttacksSection.tsx","./src/features/characters/sheet/DefensesSection.tsx","./src/features/characters/sheet/FeatsSection.tsx","./src/features/characters/sheet/InventorySection.tsx","./src/features/characters/sheet/LevelUpAdvisor.tsx","./src/features/characters/sheet/LevelUpModal.tsx","./src/features/characters/sheet/ResourcesSection.tsx","./src/features/characters/sheet/SpellcastingSection.tsx","./src/features/characters/sheet/common.tsx","./src/features/cloud/SyncStatusIndicator.tsx","./src/features/cloud/useCloudAutosave.ts","./src/features/combat/CombatPage.tsx","./src/features/combat/EncounterTracker.tsx","./src/features/combat/hooks.ts","./src/features/combat/useConditionGlossary.ts","./src/features/compendium/CompendiumPage.tsx","./src/features/compendium/MonsterDetail.tsx","./src/features/compendium/details.tsx","./src/features/compendium/registry.test.ts","./src/features/compendium/registry.tsx","./src/features/dice/DicePage.tsx","./src/features/play/HandoutControl.tsx","./src/features/play/PlayerConnection.tsx","./src/features/play/SessionControl.tsx","./src/features/play/SessionSidebar.tsx","./src/features/play/usePlayerConnection.ts","./src/features/play/useSessionBroadcaster.ts","./src/features/player/ActionGuide.tsx","./src/features/player/MyCharacterPanel.tsx","./src/features/player/PlayerBoards.tsx","./src/features/player/PlayerMapView.tsx","./src/features/player/PlayerSetupPage.tsx","./src/features/player/PlayerViewPage.tsx","./src/features/player/RollFeed.tsx","./src/features/player/SeatClaimScreen.tsx","./src/features/settings/AssistantSettings.tsx","./src/features/settings/CloudCampaigns.tsx","./src/features/settings/SettingsPage.tsx","./src/features/world/CalendarPage.tsx","./src/features/world/DashboardPage.tsx","./src/features/world/HomebrewPage.tsx","./src/features/world/MapsPage.tsx","./src/features/world/NotesPage.tsx","./src/features/world/NpcsPage.tsx","./src/features/world/QuestsPage.tsx","./src/features/world/homebrew.ts","./src/features/world/hooks.ts","./src/features/world/map/MapCanvas.tsx","./src/features/world/map/MapEditor.tsx","./src/features/world/map/TokenPalette.test.ts","./src/features/world/map/TokenPalette.tsx","./src/features/world/map/tokens.ts","./src/lib/cn.ts","./src/lib/format.ts","./src/lib/ids.ts","./src/lib/rng.ts","./src/lib/sample.ts","./src/lib/useDebouncedCallback.ts","./src/lib/useRoll.ts","./src/lib/wikilinks.test.ts","./src/lib/wikilinks.ts","./src/lib/assistant/actions.ts","./src/lib/assistant/advisors.test.ts","./src/lib/assistant/advisors.ts","./src/lib/assistant/builder.ts","./src/lib/assistant/context.test.ts","./src/lib/assistant/context.ts","./src/lib/assistant/encounter.ts","./src/lib/assistant/levelup.test.ts","./src/lib/assistant/levelup.ts","./src/lib/assistant/patterns.test.ts","./src/lib/assistant/patterns.ts","./src/lib/assistant/prompts.test.ts","./src/lib/assistant/prompts.ts","./src/lib/assistant/session.ts","./src/lib/assistant/signals.test.ts","./src/lib/assistant/signals.ts","./src/lib/cloud/campaigns.ts","./src/lib/cloud/client.ts","./src/lib/combat/budget.test.ts","./src/lib/combat/budget.ts","./src/lib/combat/engine.test.ts","./src/lib/combat/engine.ts","./src/lib/combat/playerProjection.test.ts","./src/lib/combat/playerProjection.ts","./src/lib/compendium/index.ts","./src/lib/compendium/types.ts","./src/lib/db/db.ts","./src/lib/db/repositories.test.ts","./src/lib/db/repositories.ts","./src/lib/dice/check.test.ts","./src/lib/dice/check.ts","./src/lib/dice/notation.test.ts","./src/lib/dice/notation.ts","./src/lib/img/resize.test.ts","./src/lib/img/resize.ts","./src/lib/io/backup.test.ts","./src/lib/io/backup.ts","./src/lib/io/character.test.ts","./src/lib/io/character.ts","./src/lib/io/file.ts","./src/lib/io/pathbuilder.test.ts","./src/lib/io/pathbuilder.ts","./src/lib/llm/client.test.ts","./src/lib/llm/client.ts","./src/lib/llm/types.ts","./src/lib/map/distance.ts","./src/lib/map/fog.ts","./src/lib/map/grid.ts","./src/lib/map/index.ts","./src/lib/map/map.test.ts","./src/lib/map/projection.test.ts","./src/lib/map/projection.ts","./src/lib/map/shapes.ts","./src/lib/map/types.ts","./src/lib/map/viewport.test.ts","./src/lib/map/viewport.ts","./src/lib/map/vision.test.ts","./src/lib/map/vision.ts","./src/lib/mechanics/cast.ts","./src/lib/mechanics/conditionEffects.ts","./src/lib/mechanics/creatureState.test.ts","./src/lib/mechanics/creatureState.ts","./src/lib/mechanics/deathSaves.ts","./src/lib/mechanics/enforce.test.ts","./src/lib/mechanics/index.ts","./src/lib/mechanics/resources.ts","./src/lib/mechanics/result.ts","./src/lib/mechanics/types.ts","./src/lib/mechanics/normalize/armor.ts","./src/lib/mechanics/normalize/monsterDefenses.ts","./src/lib/mechanics/normalize/normalize.test.ts","./src/lib/mechanics/normalize/spell.ts","./src/lib/rules/abilities.ts","./src/lib/rules/abilityGen.test.ts","./src/lib/rules/abilityGen.ts","./src/lib/rules/conditions.ts","./src/lib/rules/depth.test.ts","./src/lib/rules/index.ts","./src/lib/rules/multiclass.test.ts","./src/lib/rules/progression.test.ts","./src/lib/rules/progression.ts","./src/lib/rules/rest.ts","./src/lib/rules/rules.test.ts","./src/lib/rules/types.ts","./src/lib/rules/dnd5e/index.ts","./src/lib/rules/dnd5e/progression.ts","./src/lib/rules/dnd5e/skills.ts","./src/lib/rules/pf2e/index.ts","./src/lib/rules/pf2e/progression.ts","./src/lib/rules/pf2e/skills.ts","./src/lib/ruleset/normalize.test.ts","./src/lib/ruleset/normalize.ts","./src/lib/schemas/campaign.ts","./src/lib/schemas/character.ts","./src/lib/schemas/common.ts","./src/lib/schemas/dice.ts","./src/lib/schemas/encounter.ts","./src/lib/schemas/index.ts","./src/lib/schemas/sessionLog.ts","./src/lib/schemas/world.ts","./src/lib/sync/index.ts","./src/lib/sync/messages.test.ts","./src/lib/sync/messages.ts","./src/lib/sync/playerLink.test.ts","./src/lib/sync/playerLink.ts","./src/lib/sync/snapshot.ts","./src/lib/sync/wsSync.ts","./src/lib/vtt/uvtt.test.ts","./src/lib/vtt/uvtt.ts","./src/stores/assistantStore.ts","./src/stores/connectivityStore.ts","./src/stores/macroStore.ts","./src/stores/playerSessionStore.ts","./src/stores/rollStore.ts","./src/stores/sessionStore.ts","./src/stores/uiStore.ts","./vitest.setup.ts"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/main.tsx","./src/router.tsx","./src/vite-env.d.ts","./src/app/RootLayout.tsx","./src/components/CommandPalette.tsx","./src/components/ui/Button.tsx","./src/components/ui/Codex.tsx","./src/components/ui/ErrorBoundary.tsx","./src/components/ui/Input.tsx","./src/components/ui/Modal.tsx","./src/components/ui/NumberField.tsx","./src/components/ui/Page.tsx","./src/components/ui/RollTray.tsx","./src/features/assistant/AssistantPage.tsx","./src/features/assistant/CampaignInsights.tsx","./src/features/assistant/EncounterTipCard.tsx","./src/features/assistant/NpcGenCard.tsx","./src/features/assistant/SessionPrepCard.tsx","./src/features/assistant/SignalsBell.tsx","./src/features/assistant/useEncounterAdvisor.ts","./src/features/assistant/useLevelUpAdvisor.ts","./src/features/assistant/useSignalAction.ts","./src/features/campaigns/CampaignsPage.tsx","./src/features/campaigns/hooks.ts","./src/features/characters/CharacterSheet.tsx","./src/features/characters/CharacterSheetPage.tsx","./src/features/characters/CharactersPage.tsx","./src/features/characters/hooks.ts","./src/features/characters/builder/CreationWizard.tsx","./src/features/characters/builder/overview.test.ts","./src/features/characters/builder/overview.ts","./src/features/characters/sheet/AbilityGenModal.tsx","./src/features/characters/sheet/AttacksSection.tsx","./src/features/characters/sheet/DefensesSection.tsx","./src/features/characters/sheet/FeatsSection.tsx","./src/features/characters/sheet/InventorySection.tsx","./src/features/characters/sheet/LevelUpAdvisor.tsx","./src/features/characters/sheet/LevelUpModal.tsx","./src/features/characters/sheet/ResourcesSection.tsx","./src/features/characters/sheet/SpellcastingSection.tsx","./src/features/characters/sheet/common.tsx","./src/features/cloud/SyncStatusIndicator.tsx","./src/features/cloud/useCloudAutosave.ts","./src/features/combat/CombatPage.tsx","./src/features/combat/EncounterTracker.tsx","./src/features/combat/hooks.ts","./src/features/combat/useConditionGlossary.ts","./src/features/compendium/CompendiumPage.tsx","./src/features/compendium/MonsterDetail.tsx","./src/features/compendium/details.tsx","./src/features/compendium/registry.test.ts","./src/features/compendium/registry.tsx","./src/features/dice/DicePage.tsx","./src/features/play/HandoutControl.tsx","./src/features/play/PlayerConnection.tsx","./src/features/play/SessionControl.tsx","./src/features/play/SessionSidebar.tsx","./src/features/play/usePlayerConnection.ts","./src/features/play/useSessionBroadcaster.ts","./src/features/player/ActionGuide.tsx","./src/features/player/MyCharacterPanel.tsx","./src/features/player/PlayerBoards.tsx","./src/features/player/PlayerMapView.tsx","./src/features/player/PlayerSetupPage.tsx","./src/features/player/PlayerViewPage.tsx","./src/features/player/RollFeed.tsx","./src/features/player/SeatClaimScreen.tsx","./src/features/settings/AssistantSettings.tsx","./src/features/settings/CloudCampaigns.tsx","./src/features/settings/SettingsPage.tsx","./src/features/world/CalendarPage.tsx","./src/features/world/DashboardPage.tsx","./src/features/world/HomebrewPage.tsx","./src/features/world/MapsPage.tsx","./src/features/world/NotesPage.tsx","./src/features/world/NpcsPage.tsx","./src/features/world/QuestsPage.tsx","./src/features/world/homebrew.ts","./src/features/world/hooks.ts","./src/features/world/map/MapCanvas.tsx","./src/features/world/map/MapEditor.tsx","./src/features/world/map/TokenPalette.test.ts","./src/features/world/map/TokenPalette.tsx","./src/features/world/map/tokens.ts","./src/lib/cn.ts","./src/lib/format.ts","./src/lib/ids.ts","./src/lib/rng.ts","./src/lib/sample.ts","./src/lib/useDebouncedCallback.ts","./src/lib/useRoll.ts","./src/lib/wikilinks.test.ts","./src/lib/wikilinks.ts","./src/lib/assistant/actions.ts","./src/lib/assistant/advisors.test.ts","./src/lib/assistant/advisors.ts","./src/lib/assistant/builder.ts","./src/lib/assistant/context.test.ts","./src/lib/assistant/context.ts","./src/lib/assistant/encounter.ts","./src/lib/assistant/levelup.test.ts","./src/lib/assistant/levelup.ts","./src/lib/assistant/patterns.test.ts","./src/lib/assistant/patterns.ts","./src/lib/assistant/prompts.test.ts","./src/lib/assistant/prompts.ts","./src/lib/assistant/session.ts","./src/lib/assistant/signals.test.ts","./src/lib/assistant/signals.ts","./src/lib/cloud/campaigns.ts","./src/lib/cloud/client.ts","./src/lib/combat/budget.test.ts","./src/lib/combat/budget.ts","./src/lib/combat/engine.test.ts","./src/lib/combat/engine.ts","./src/lib/combat/playerProjection.test.ts","./src/lib/combat/playerProjection.ts","./src/lib/compendium/index.ts","./src/lib/compendium/types.ts","./src/lib/db/db.ts","./src/lib/db/repositories.test.ts","./src/lib/db/repositories.ts","./src/lib/dice/check.test.ts","./src/lib/dice/check.ts","./src/lib/dice/notation.test.ts","./src/lib/dice/notation.ts","./src/lib/img/resize.test.ts","./src/lib/img/resize.ts","./src/lib/io/backup.test.ts","./src/lib/io/backup.ts","./src/lib/io/character.test.ts","./src/lib/io/character.ts","./src/lib/io/file.ts","./src/lib/io/pathbuilder.test.ts","./src/lib/io/pathbuilder.ts","./src/lib/llm/client.test.ts","./src/lib/llm/client.ts","./src/lib/llm/types.ts","./src/lib/map/distance.ts","./src/lib/map/fog.ts","./src/lib/map/grid.ts","./src/lib/map/index.ts","./src/lib/map/map.test.ts","./src/lib/map/projection.test.ts","./src/lib/map/projection.ts","./src/lib/map/shapes.ts","./src/lib/map/types.ts","./src/lib/map/viewport.test.ts","./src/lib/map/viewport.ts","./src/lib/map/vision.test.ts","./src/lib/map/vision.ts","./src/lib/mechanics/cast.ts","./src/lib/mechanics/conditionEffects.ts","./src/lib/mechanics/creatureState.test.ts","./src/lib/mechanics/creatureState.ts","./src/lib/mechanics/deathSaves.ts","./src/lib/mechanics/enforce.test.ts","./src/lib/mechanics/index.ts","./src/lib/mechanics/resources.ts","./src/lib/mechanics/result.ts","./src/lib/mechanics/types.ts","./src/lib/mechanics/normalize/armor.ts","./src/lib/mechanics/normalize/monsterDefenses.ts","./src/lib/mechanics/normalize/normalize.test.ts","./src/lib/mechanics/normalize/spell.ts","./src/lib/rules/abilities.ts","./src/lib/rules/abilityGen.test.ts","./src/lib/rules/abilityGen.ts","./src/lib/rules/conditions.ts","./src/lib/rules/depth.test.ts","./src/lib/rules/index.ts","./src/lib/rules/multiclass.test.ts","./src/lib/rules/progression.test.ts","./src/lib/rules/progression.ts","./src/lib/rules/rest.ts","./src/lib/rules/rules.test.ts","./src/lib/rules/types.ts","./src/lib/rules/dnd5e/index.ts","./src/lib/rules/dnd5e/progression.ts","./src/lib/rules/dnd5e/skills.ts","./src/lib/rules/pf2e/index.ts","./src/lib/rules/pf2e/progression.ts","./src/lib/rules/pf2e/skills.ts","./src/lib/ruleset/normalize.test.ts","./src/lib/ruleset/normalize.ts","./src/lib/schemas/campaign.ts","./src/lib/schemas/character.ts","./src/lib/schemas/common.ts","./src/lib/schemas/dice.ts","./src/lib/schemas/encounter.ts","./src/lib/schemas/index.ts","./src/lib/schemas/sessionLog.ts","./src/lib/schemas/world.ts","./src/lib/sync/index.ts","./src/lib/sync/messages.test.ts","./src/lib/sync/messages.ts","./src/lib/sync/playerLink.test.ts","./src/lib/sync/playerLink.ts","./src/lib/sync/snapshot.ts","./src/lib/sync/wsSync.ts","./src/lib/vtt/uvtt.test.ts","./src/lib/vtt/uvtt.ts","./src/stores/assistantStore.ts","./src/stores/connectivityStore.ts","./src/stores/macroStore.ts","./src/stores/playerSessionStore.ts","./src/stores/rollStore.ts","./src/stores/sessionStore.ts","./src/stores/uiStore.ts","./vitest.setup.ts"],"version":"5.9.3"} \ No newline at end of file