feat(01-03): add spell-slot overlay types with Wizard worked example

- Define SpellTradition type and SpellSlotOverlayEntry interface as the contract Plan 03b appends to
- Wizard fully populated L1..L19 with ARCANE slot progression and L1 cantrip count (5)
- Stub keys for the other 15 D-16 classes set to empty arrays for Plan 03b to populate
- No 'any' types; type-clean against tsconfig.json
This commit is contained in:
2026-04-27 14:44:05 +02:00
parent 65676657fc
commit 29fe01df82

View File

@@ -0,0 +1,83 @@
/**
* Hand-curated spell-slot / cantrip / repertoire progression overlay.
*
* WHY HAND-CURATED: Foundry pf2e encodes slot tables in description prose, not machine-
* readable rules (Pitfall #6 / verified 2026-04-27 against `wizard-spellcasting.json`).
* NLP-parsing prose is fragile; the canonical PF2e tables fit in ~300 lines and are stable
* across reprints.
*
* SOURCE: Archives of Nethys — Pathfinder 2e Player Core + Advanced Player's Guide
* (https://2e.aonprd.com). Per-class spell-slot tables, cantrip counts, and repertoire
* sizes (spontaneous casters only).
*
* SCOPE: 16 D-16 classes (Core + APG). This file (Plan 03) ships the type definitions
* and Wizard fully populated as the worked example. Plan 03b appends entries for the
* remaining 6 caster classes (Cleric, Druid, Witch, Bard, Sorcerer, Oracle) and the
* empty-array entries for non-casters.
*
* SPONTANEOUS vs PREPARED: Spontaneous casters (Bard, Sorcerer, Oracle) get
* repertoireIncrement entries on level-up. Prepared casters (Cleric, Druid, Witch, Wizard)
* get spellSlotIncrement only. Both get cantripIncrement at L1.
*/
export type SpellTradition = 'ARCANE' | 'DIVINE' | 'OCCULT' | 'PRIMAL';
export interface SpellSlotOverlayEntry {
level: number;
spellSlotIncrement?: { tradition: SpellTradition; spellLevel: number; count: number };
cantripIncrement?: number;
repertoireIncrement?: number;
}
/**
* Each class maps to an array of overlay entries. Multiple entries per level are allowed
* (e.g. L1 Wizard gets 5 cantrips AND 2 grade-1 slots — two separate entries).
* Order within a level does not matter — the seed script merges them per (class, level).
*/
export const SPELL_SLOT_OVERLAY: Record<string, SpellSlotOverlayEntry[]> = {
// === PREPARED CASTER — WIZARD (worked example, fully populated in Plan 03) ===
Wizard: [
{ level: 1, cantripIncrement: 5 },
{ level: 1, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 1, count: 2 } },
{ level: 2, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 1, count: 1 } },
{ level: 3, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 2, count: 2 } },
{ level: 4, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 2, count: 1 } },
{ level: 5, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 3, count: 2 } },
{ level: 6, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 3, count: 1 } },
{ level: 7, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 4, count: 2 } },
{ level: 8, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 4, count: 1 } },
{ level: 9, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 5, count: 2 } },
{ level: 10, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 5, count: 1 } },
{ level: 11, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 6, count: 2 } },
{ level: 12, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 6, count: 1 } },
{ level: 13, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 7, count: 2 } },
{ level: 14, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 7, count: 1 } },
{ level: 15, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 8, count: 2 } },
{ level: 16, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 8, count: 1 } },
{ level: 17, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 9, count: 2 } },
{ level: 18, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 9, count: 1 } },
// L19 Magnum Opus = 1 grade-10 slot per day
{ level: 19, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 10, count: 1 } },
// L20: no slot increment (capstone is qualitative)
],
// === STUBS — populated by Plan 03b ===
// Caster stubs (Plan 03b will replace [] with full L1..L20 entries):
Cleric: [],
Druid: [],
Witch: [],
Bard: [],
Sorcerer: [],
Oracle: [],
Champion: [], // focus-only; minimal entries
// Non-caster stubs (Plan 03b confirms these stay empty):
Alchemist: [],
Barbarian: [],
Fighter: [],
Investigator: [],
Monk: [],
Ranger: [],
Rogue: [],
Swashbuckler: [],
};