chore(phase-01): checkpoint after Wave 3 Task 1 — pause for resume on another machine
All checks were successful
Deploy Dimension47 / deploy (push) Successful in 49s

Phase 1 execution state at pause:
- Wave 1 ✓ (01-01 schema/migration, 01-02 pure-function lib)
- Wave 2 ✓ (01-03 seed pipeline + Wizard worked example)
- Wave 3 partial (01-03b Task 1 of 3 done — 6 caster overlays salvaged from c5d40cd)
- Wave 4 pending (01-04 LevelingModule)
- Wave 5 pending (01-05 React wizard UI)

To resume: \`/gsd-execute-phase 1\` discovers SUMMARY files for completed plans
and picks up at 01-03b Task 2 (class-feature-options bulk curation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-27 15:13:50 +02:00
parent c5d40cdae8
commit a8c4944bb6
8 changed files with 2322 additions and 124 deletions

View File

@@ -2,7 +2,24 @@
"permissions": {
"allow": [
"Bash(npx tsc:*)",
"Bash(npm install:*)"
"Bash(npm install:*)",
"Bash(npm run db:migrate:dev:*)",
"Bash(npm run db:seed:*)",
"Bash(npx tsx:*)",
"Bash(npm run db:generate:*)",
"WebFetch(domain:docs.anthropic.com)",
"Bash(npm run build:*)",
"Bash(npm run db:seed:equipment:*)",
"Bash(find:*)",
"Bash(npx prisma migrate dev:*)",
"WebFetch(domain:2e.aonprd.com)",
"WebFetch(domain:rpgbot.net)",
"Bash(node -e:*)",
"Bash(npx ts-node:*)",
"Bash(ssh:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push)"
]
}
}

View File

@@ -32,14 +32,15 @@ Decimal phases appear between their surrounding integers in numeric order.
3. Nach Bestätigung sind HP-Max, Save-Boni, AC, Klassen-DC und Wahrnehmung gemäß PF2e neu berechnet (Boost-Cap-bei-18 wird respektiert: bestehender Wert ≥18 → +1, sonst +2), und alle anderen Mitspieler sehen den neuen Level + neuen Stat-Block in Echtzeit über WebSocket.
4. Spellcaster-Charaktere sehen nach dem Aufstieg korrekte Slot- und Cantrip-Progression gemäß ihrer Klasse/Tradition; spontane Caster sehen zusätzlich erhöhtes Repertoire-Limit.
5. Bestätigtes Level-Up erzeugt einen nachvollziehbaren Historieneintrag (Snapshot-Vorher in JSON) und ist atomar persistiert — kein halbes Level-Up bei Fehler mitten im Commit.
**Plans:** 5 plans
**Plans:** 6 plans
Plans:
- [ ] 01-01-PLAN.md — Wave 0: Prisma schema + migration + partial unique index + Jest infrastructure proof
- [ ] 01-02-PLAN.md — Wave 1: 5 pure-function lib modules (boost, skill-cap, prereq, recompute, applicable-steps) with full TDD
- [ ] 01-03-PLAN.md — Wave 2: ClassProgression + ClassFeatureOption seed pipeline (Foundry pf2e + hand-curated overlays, 16 classes × 20 levels)
- [ ] 01-04-PLAN.md — Wave 3: LevelingModule (REST + atomic commit transaction + WebSocket broadcast) + pathbuilder-import integration + integration tests
- [ ] 01-05-PLAN.md — Wave 4: React wizard against UI-SPEC + character-sheet integration + human verification checkpoint
- [x] 01-01-PLAN.md — Wave 0: Prisma schema + migration + partial unique index + Jest infrastructure proof
- [x] 01-02-PLAN.md — Wave 1: 5 pure-function lib modules (boost, skill-cap, prereq, recompute, applicable-steps) with full TDD
- [x] 01-03-PLAN.md — Wave 2: ClassProgression + ClassFeatureOption seed pipeline (Foundry pf2e + Wizard worked example)
- [ ] 01-03b-PLAN.md — Wave 3: bulk curation of caster spell-slot overlays + class-feature-options for the other 15 D-16 classes (sequential after 01-03 — same data files)
- [ ] 01-04-PLAN.md — Wave 4: LevelingModule (REST + atomic commit transaction + WebSocket broadcast) + pathbuilder-import integration + integration tests
- [ ] 01-05-PLAN.md — Wave 5: React wizard against UI-SPEC + character-sheet integration + human verification checkpoint
**UI hint**: yes

View File

@@ -2,14 +2,14 @@
gsd_state_version: 1.0
milestone: v1.0
milestone_name: milestone
status: planning
stopped_at: Phase 1 context gathered
last_updated: "2026-04-27T08:50:25.053Z"
last_activity: 2026-04-27 — Roadmap created from research-recommended 7-phase build order
status: executing
stopped_at: Phase 1 UI-SPEC approved
last_updated: "2026-04-27T11:58:04.508Z"
last_activity: 2026-04-27 -- Phase 01 execution started
progress:
total_phases: 7
completed_phases: 0
total_plans: 0
total_plans: 6
completed_plans: 0
percent: 0
---
@@ -21,14 +21,14 @@ progress:
See: .planning/PROJECT.md (updated 2026-04-27)
**Core value:** Am Tisch funktioniert alles in Echtzeit und regelkonform — Charakterbogen am Handy, Battle-Steuerung am Laptop, Tisch-Display als read-only Spielsicht, ohne Reibung, ohne falsche Werte, ohne Reload.
**Current focus:** Phase 1 — Level-Up (PF2e regelkonform)
**Current focus:** Phase 01 — level-up-pf2e-regelkonform
## Current Position
Phase: 1 of 7 (Level-Up (PF2e regelkonform))
Plan: 0 of TBD in current phase
Status: Ready to plan
Last activity: 2026-04-27 — Roadmap created from research-recommended 7-phase build order
Phase: 01 (level-up-pf2e-regelkonform) — EXECUTING
Plan: 1 of 6
Status: Executing Phase 01
Last activity: 2026-04-27 -- Phase 01 execution started
Progress: [░░░░░░░░░░] 0%
@@ -95,6 +95,6 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity
Last session: 2026-04-27T08:50:25.043Z
Stopped at: Phase 1 context gathered
Resume file: .planning/phases/01-level-up-pf2e-regelkonform/01-CONTEXT.md
Last session: 2026-04-27T09:17:39.142Z
Stopped at: Phase 1 UI-SPEC approved
Resume file: .planning/phases/01-level-up-pf2e-regelkonform/01-UI-SPEC.md

View File

@@ -35,7 +35,8 @@
"plan_bounce": false,
"plan_bounce_script": null,
"plan_bounce_passes": 2,
"auto_prune_state": false
"auto_prune_state": false,
"_auto_chain_active": false
},
"hooks": {
"context_warnings": true

View File

@@ -0,0 +1,502 @@
---
phase: 01-level-up-pf2e-regelkonform
plan: 03b
type: execute
wave: 3
depends_on: ["01-01", "01-02", "01-03"]
files_modified:
- server/prisma/data/spell-slot-overlays.ts
- server/prisma/data/class-feature-options.ts
autonomous: true
requirements: [LVL-08, LVL-14]
tags: [seeding, curation, spellcaster, class-features, level-up, data-only]
must_haves:
truths:
- "After Plan 03b appends data and the seed re-runs, spell-slot-overlays.ts has L1..L20 entries for the 6 remaining caster classes (Cleric, Druid, Witch, Bard, Sorcerer, Oracle) and minimal Champion entries — total ≥120 spell-slot/cantrip/repertoire entries across all casters."
- "After Plan 03b appends data and the seed re-runs, class-feature-options.ts contains the joint-goal of ≥50 ClassFeatureOption entries across all classes that have L1 (or other) choice points (Plan 03 ships ≥1; Plan 03b ships ≥49 more)."
- "Spontaneous casters (Bard, Sorcerer, Oracle) have at least one repertoireIncrement entry per the D-18 contract."
- "Re-running `npm run db:seed:class-progression` after the data appends results in `0 created, ≥320 updated` for ClassProgression and ≥49 newly created ClassFeatureOption rows on the first re-run."
- "No code changes to seed-class-progression.ts — the script's iteration over D16_CLASS_NAMES + array iteration over CLASS_FEATURE_OPTIONS already handles the new entries."
- "Cross-references are intact: every choiceOptionsRef set in seed-class-progression.ts L1_CHOICE_MAP has at least one matching ClassFeatureOption row after this plan."
artifacts:
- path: "server/prisma/data/spell-slot-overlays.ts"
provides: "Entries for Cleric/Druid/Witch/Bard/Sorcerer/Oracle/Champion appended (replacing the [] stubs from Plan 03). Wizard remains untouched."
contains: "SPELL_SLOT_OVERLAY"
- path: "server/prisma/data/class-feature-options.ts"
provides: "≥49 additional ClassFeatureOption entries appended (Cleric Doctrines, Champion Causes, Druid Orders, Sorcerer Bloodlines, Bard Muses, Barbarian Instincts, Witch Patrons, Oracle Mysteries, Investigator Methodologies, Ranger Edges, Rogue Rackets, Swashbuckler Styles, Alchemist Research Fields, plus remaining Wizard Schools)."
contains: "CLASS_FEATURE_OPTIONS"
key_links:
- from: "spell-slot-overlays.ts"
to: "ClassProgression rows (via seed-class-progression.ts)"
via: "seed re-run picks up the appended entries automatically"
pattern: "SPELL_SLOT_OVERLAY"
- from: "class-feature-options.ts"
to: "ClassFeatureOption table"
via: "seed re-run iterates over CLASS_FEATURE_OPTIONS array"
pattern: "CLASS_FEATURE_OPTIONS"
- from: "class-feature-options.ts optionsRef"
to: "seed-class-progression.ts L1_CHOICE_MAP / HIGHER_LEVEL_CHOICES"
via: "stable string match (e.g. 'cleric-doctrine')"
pattern: "optionsRef.*[a-z-]+"
---
<objective>
Bulk curation pass for Phase 1 ClassProgression seeding. Plan 03 owns the pipeline + Wizard worked example; Plan 03b owns the data entry for the remaining 15 classes — populating `spell-slot-overlays.ts` for the 6 other caster classes and `class-feature-options.ts` for all classes with L1 (or other) choice points.
Why split: this is curation work transcribed from Archives of Nethys per-class entries. Independent rows, no shared logic, no schema changes. Splitting it out gives the curation a separate review surface (a reviewer can audit L1 Cleric Doctrines without paging through pipeline code) and isolates the data-entry pass into its own wave. Plan 03b runs in Wave 3 directly after Plan 03 (Wave 2) because both modify the same data files (`spell-slot-overlays.ts`, `class-feature-options.ts`), so file-ownership rules require sequential execution.
Purpose: After Plan 03b, the seed produces a complete dataset that satisfies LVL-08 + LVL-14 across all 16 D-16 classes — Plan 04's atomic commit transaction can recompute correctly for any class/level combination.
Output: Two data files extended with appended entries. No code changes. Re-running the seed bulk-updates the database.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/REQUIREMENTS.md
@.planning/phases/01-level-up-pf2e-regelkonform/01-CONTEXT.md
@.planning/phases/01-level-up-pf2e-regelkonform/01-RESEARCH.md
@.planning/phases/01-level-up-pf2e-regelkonform/01-03-SUMMARY.md
@server/prisma/data/spell-slot-overlays.ts
@server/prisma/data/class-feature-options.ts
@server/prisma/seed-class-progression.ts
<interfaces>
<!-- Plan 03 already established these contracts; Plan 03b appends only -->
<!-- spell-slot-overlays.ts shape (from Plan 03 Task 2): -->
```typescript
export type SpellTradition = 'ARCANE' | 'DIVINE' | 'OCCULT' | 'PRIMAL';
export interface SpellSlotOverlayEntry {
level: number;
spellSlotIncrement?: { tradition: SpellTradition; spellLevel: number; count: number };
cantripIncrement?: number;
repertoireIncrement?: number;
}
export const SPELL_SLOT_OVERLAY: Record<string, SpellSlotOverlayEntry[]> = {
Wizard: [ ... 19 entries ... ], // <-- DO NOT TOUCH (Plan 03 owns Wizard)
Cleric: [], // <-- Plan 03b populates this
Druid: [], // <-- Plan 03b populates this
Witch: [], // <-- Plan 03b populates this
Bard: [], // <-- Plan 03b populates this (with repertoireIncrement entries — D-18)
Sorcerer: [], // <-- Plan 03b populates this (with repertoireIncrement entries — D-18)
Oracle: [], // <-- Plan 03b populates this (with repertoireIncrement entries — D-18)
Champion: [], // <-- Plan 03b populates this (focus-only, minimal)
Alchemist: [], // <-- Plan 03b confirms empty
Barbarian: [], // <-- Plan 03b confirms empty
Fighter: [], // <-- Plan 03b confirms empty
Investigator: [], // <-- Plan 03b confirms empty
Monk: [], // <-- Plan 03b confirms empty
Ranger: [], // <-- Plan 03b confirms empty
Rogue: [], // <-- Plan 03b confirms empty
Swashbuckler: [], // <-- Plan 03b confirms empty
};
```
<!-- class-feature-options.ts shape (from Plan 03 Task 3): -->
```typescript
export interface ClassFeatureOptionEntry {
optionsRef: string;
optionKey: string;
name: string;
nameGerman?: string;
description: string;
grants: string[];
proficiencyChanges?: Partial<Record<'fortitude' | 'reflex' | 'will' | 'perception' | 'classDc' | 'ac', Proficiency>>;
}
export const CLASS_FEATURE_OPTIONS: ClassFeatureOptionEntry[] = [
// 1 Wizard School entry from Plan 03 (battle-magic) — DO NOT TOUCH
// Plan 03b appends ≥49 more entries below
];
```
<!-- L1_CHOICE_MAP (from seed-class-progression.ts in Plan 03 Task 4) -->
<!-- Stable optionsRef strings that Plan 03b's CLASS_FEATURE_OPTIONS entries MUST match: -->
<!-- 'cleric-doctrine', 'wizard-school', 'champion-cause', 'druid-order', -->
<!-- 'sorcerer-bloodline', 'bard-muse', 'barbarian-instinct', 'witch-patron', -->
<!-- 'oracle-mystery', 'investigator-methodology', 'ranger-edge', 'rogue-racket', -->
<!-- 'swashbuckler-style', 'alchemist-research-field' -->
<!-- + Higher-level: 'fighter-weapon-mastery' (L5) -->
</interfaces>
</context>
<tasks>
<task type="auto" tdd="false">
<name>Task 1: Append spell-slot overlay entries for the 6 remaining caster classes (Cleric, Druid, Witch, Bard, Sorcerer, Oracle) + minimal Champion</name>
<files>server/prisma/data/spell-slot-overlays.ts</files>
<read_first>
- server/prisma/data/spell-slot-overlays.ts (the Plan 03 file with stubs in place — append values into the existing keys)
- .planning/phases/01-level-up-pf2e-regelkonform/01-CONTEXT.md (D-18 — spontaneous casters get repertoire)
- .planning/phases/01-level-up-pf2e-regelkonform/01-RESEARCH.md (lines 656-661 — Pitfall #6 hand-curation rationale)
- Archives of Nethys class entries (cited as authoritative source for hand-curation):
- Cleric: https://2e.aonprd.com/Classes.aspx?ID=4
- Druid: https://2e.aonprd.com/Classes.aspx?ID=6
- Witch: https://2e.aonprd.com/Classes.aspx?ID=27
- Bard: https://2e.aonprd.com/Classes.aspx?ID=2
- Sorcerer: https://2e.aonprd.com/Classes.aspx?ID=15
- Oracle: https://2e.aonprd.com/Classes.aspx?ID=12
- Champion: https://2e.aonprd.com/Classes.aspx?ID=3
(URL IDs are illustrative — verify current canonical URLs at execution time.)
</read_first>
<action>
Open `server/prisma/data/spell-slot-overlays.ts`. The file (from Plan 03) already declares all 16 D-16 keys; the 6 caster classes + Champion currently hold `[]`. **Replace each `[]` with the curated array** for that class.
**Reference template (Wizard, fully populated by Plan 03 — DO NOT modify Wizard):**
```typescript
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 } },
// ... continues to L19 with the +2/+1 cadence
],
```
**Per-class curation requirements:**
**Cleric** (PREPARED, DIVINE) — same +2/+1 cadence as Wizard, replacing tradition with `'DIVINE'`. L1: `cantripIncrement: 5` + `spellSlotIncrement: { tradition: 'DIVINE', spellLevel: 1, count: 2 }`. Continue per AoN Cleric spell-slot table through L19. Expected ~19-20 entries.
**Druid** (PREPARED, PRIMAL) — same cadence, tradition `'PRIMAL'`. L1: 5 cantrips + 2 grade-1 slots. Continue per AoN Druid table through L19. Expected ~19-20 entries.
**Witch** (PREPARED) — Witch tradition depends on patron (which the player picks at L1). For the overlay, default to `'ARCANE'` and document the caveat in a comment above the Witch array:
```typescript
// CAVEAT: Witch tradition depends on patron (Plan 03b — picked in wizard at L1).
// The overlay defaults to ARCANE for slot bookkeeping; the recompute pipeline (Plan 04)
// may need to remap this to the actual patron's tradition at commit time. For Phase 1
// we accept the default — the slot count is the same regardless of tradition.
```
Same +2/+1 cadence per AoN Witch table. Expected ~19-20 entries.
**Bard** (SPONTANEOUS, OCCULT) — D-18 requires `repertoireIncrement` entries on each level-up. L1: `cantripIncrement: 5` + `spellSlotIncrement: { tradition: 'OCCULT', spellLevel: 1, count: 2 }`. From L2 onward, add `repertoireIncrement: 1` per level the spontaneous caster table grants a new spell-known. Per AoN Bard repertoire growth: typically +1 spell per spell-level per level after L2. Expected ~25-30 entries (slot rows + repertoire rows). Use multiple entries per level when both apply (e.g. L3 Bard gets a grade-2 slot AND a repertoire-pick — two separate entries).
**Sorcerer** (SPONTANEOUS) — Tradition depends on bloodline (similar caveat as Witch). Default to `'ARCANE'`. Per AoN Sorcerer table:
- L1: `cantripIncrement: 5` + `spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 1, count: 3 }` (Sorcerer L1 = 3 grade-1 slots)
- Add `repertoireIncrement` entries per level per AoN.
Document the bloodline-tradition caveat in a comment.
Expected ~25-30 entries.
**Oracle** (SPONTANEOUS, DIVINE) — Tradition is fixed (DIVINE — Mystery doesn't change it). L1: `cantripIncrement: 5` + `spellSlotIncrement: { tradition: 'DIVINE', spellLevel: 1, count: 3 }` (3 grade-1 slots like Sorcerer). Add `repertoireIncrement` entries per level per AoN. Expected ~25-30 entries.
**Champion** — focus-only caster (Devotion Spells via focus pool, NOT slot-based). Phase 1 records minimal entries:
```typescript
Champion: [
// Focus-only caster -- focus-spell mechanics handled outside slot table (Plan 04 Phase 2 may
// add focus-pool tracking). Phase 1 treats Champion as effectively non-caster for slots.
// No cantrip/slot entries here.
],
```
Champion remains `[]` (or with the explanatory comment) — this is correct.
**Total expected entries across the 7 added classes:** 19 (Cleric) + 19 (Druid) + 19 (Witch) + 28 (Bard) + 28 (Sorcerer) + 28 (Oracle) + 0 (Champion) ≈ **141 new entries**, comfortably above the ≥120 must_haves goal.
**Constraints:**
- Do NOT modify the Wizard array (Plan 03 owns it).
- Do NOT remove any of the 16 keys (the seed depends on key presence, even if the value is `[]`).
- Every entry strictly typed against `SpellSlotOverlayEntry` — no `: any`.
- Cite the AoN source (URL or page) in a comment block above each newly-populated class.
</action>
<verify>
<automated>cd server &amp;&amp; npx tsc --noEmit -p tsconfig.json 2&gt;&amp;1 | grep -E "spell-slot-overlays" || echo "tsc clean"</automated>
</verify>
<acceptance_criteria>
- File `server/prisma/data/spell-slot-overlays.ts` still contains all 16 D-16 class keys (no key removed)
- Wizard array still has at least 19 entries (Plan 03 unchanged)
- Cleric, Druid, Witch arrays each have at least 18 entries
- Bard, Sorcerer, Oracle arrays each have at least 1 `repertoireIncrement` entry (D-18) AND at least 18 entries total
- Total entries across all caster classes is at least 120 (verifiable via grep counting `level:` keys outside the Wizard block)
- File contains NO `: any` outside comments
- `cd server && npx tsc --noEmit` exits 0
</acceptance_criteria>
<done>
Spell-slot overlay file extended with curated data for the 6 other casters + Champion. Type-clean. Wizard unchanged.
</done>
</task>
<task type="auto" tdd="false">
<name>Task 2: Append ClassFeatureOption entries for the 13 remaining classes (Cleric, Champion, Druid, Sorcerer, Bard, Barbarian, Witch, Oracle, Investigator, Ranger, Rogue, Swashbuckler, Alchemist) + remaining Wizard Schools</name>
<files>server/prisma/data/class-feature-options.ts</files>
<read_first>
- server/prisma/data/class-feature-options.ts (the Plan 03 file — append into CLASS_FEATURE_OPTIONS array using the section anchors)
- server/prisma/seed-class-progression.ts (Plan 03 — verify L1_CHOICE_MAP optionsRef strings match exactly)
- .planning/phases/01-level-up-pf2e-regelkonform/01-CONTEXT.md (D-15 — choiceOptionsRef; D-19 — Wahl-Klassenmerkmale)
- server/prisma/schema.prisma (verify ResearchField enum for Alchemist optionKey alignment)
- Archives of Nethys class entries — see Task 1's URL list, plus:
- Investigator: https://2e.aonprd.com/Classes.aspx?ID=8
- Ranger: https://2e.aonprd.com/Classes.aspx?ID=13
- Rogue: https://2e.aonprd.com/Classes.aspx?ID=14
- Swashbuckler: https://2e.aonprd.com/Classes.aspx?ID=16
- Alchemist: https://2e.aonprd.com/Classes.aspx?ID=1
- Barbarian: https://2e.aonprd.com/Classes.aspx?ID=2 (verify ID — this clashes with Bard above; use the canonical AoN landing page)
</read_first>
<action>
Open `server/prisma/data/class-feature-options.ts`. The file (from Plan 03) already has the type definitions and 1 Wizard School entry. **Append additional entries to the `CLASS_FEATURE_OPTIONS` array**, organized into the section blocks the Plan 03 file already comments out.
**Per-class curation requirements (joint goal: ≥50 entries total across this file; Plan 03 ships 1 → Plan 03b ships ≥49 more):**
1. **Wizard Schools** (optionsRef: `'wizard-school'`) — Plan 03 has `battle-magic`. Add: `civic-wizardry`, `mentalism`, `protean-form`, `unified-magical-theory`, `universalist`. Expected: 5 more.
2. **Cleric Doctrines** (optionsRef: `'cleric-doctrine'`) — Player Core: `cloistered-cleric`, `warpriest`. Add any APG additions. Expected: 2-4.
3. **Champion Causes** (optionsRef: `'champion-cause'`) — `liberator`, `paladin`, `redeemer`, plus Evil-aligned `antipaladin`, `tyrant`, `desecrator`, plus any neutral or remaster-equivalent. Expected: 6.
4. **Druid Orders** (optionsRef: `'druid-order'`) — `animal`, `leaf`, `storm`, `wild`, plus APG additions. Expected: 4-5.
5. **Sorcerer Bloodlines** (optionsRef: `'sorcerer-bloodline'`) — `aberrant`, `angelic`, `demonic`, `diabolic`, `draconic`, `elemental`, `fey`, `genie`, `hag`, `imperial`, `nymph`, `phoenix`, `psychopomp`, `shadow`, `undead`, plus APG additions. Expected: ≥15.
6. **Bard Muses** (optionsRef: `'bard-muse'`) — `enigma`, `maestro`, `polymath`, plus APG (`warrior` etc.). Expected: 3-4.
7. **Barbarian Instincts** (optionsRef: `'barbarian-instinct'`) — `animal`, `dragon`, `fury`, `giant`, `spirit`, plus APG (`superstition` etc.). Expected: 5-6.
8. **Witch Patrons** (optionsRef: `'witch-patron'`) — `faith`, `fervor`, `mosquito-witch`, `resentment`, `silence`, `spinner-of-threads`, `starless-shadow`, `wilding`, plus any Curse-of-the-Hag-Eye etc. Expected: ≥5.
9. **Oracle Mysteries** (optionsRef: `'oracle-mystery'`) — `battle`, `bones`, `cosmos`, `flames`, `life`, `lore`, `tempest`, plus APG. Expected: ≥7.
10. **Investigator Methodologies** (optionsRef: `'investigator-methodology'`) — `empiricism`, `forensic-medicine`, `interrogation`, `sensate`, `stratagem` (or whatever exists in APG). Expected: 3-5.
11. **Ranger Edges** (optionsRef: `'ranger-edge'`) — `flurry`, `outwit`, `precision`. Expected: 3.
12. **Rogue Rackets** (optionsRef: `'rogue-racket'`) — `eldritch-trickster`, `mastermind`, `ruffian`, `scoundrel`, `thief`. Expected: 5.
13. **Swashbuckler Styles** (optionsRef: `'swashbuckler-style'`) — `battledancer`, `braggart`, `fencer`, `gymnast`, `wit`. Expected: 5.
14. **Alchemist Research Fields** (optionsRef: `'alchemist-research-field'`) — `bomber`, `chirurgeon`, `mutagenist`, `toxicologist`. **CRITICAL:** these `optionKey` values must match the existing `ResearchField` enum in `server/prisma/schema.prisma` (lowercase-kebab transformation of `BOMBER`, `CHIRURGEON`, etc.) so the existing `CharacterAlchemyState.researchField` column can be set from the option pick. Verify the enum values against the schema before appending. Expected: 4.
**Total entry count check:** 5 + 4 + 6 + 5 + 15 + 4 + 6 + 5 + 7 + 5 + 3 + 5 + 5 + 4 = **79 entries** added. Plus Plan 03's 1 Wizard School entry = **80 total** — comfortably above the ≥50 joint goal.
**Per-entry shape:**
```typescript
{
optionsRef: 'cleric-doctrine',
optionKey: 'cloistered-cleric',
name: 'Cloistered Cleric',
// nameGerman omitted — TranslationsService handles on demand (D-15)
description: 'You have devoted your life to the study of religion and the casting of divine spells…',
grants: ['Cloistered Cleric Doctrine'],
// proficiencyChanges optional — only set if the option modifies a proficiency level at L1 (e.g. Warpriest grants martial weapon trained)
},
```
For options with proficiency effects (e.g. **Warpriest** grants martial trained at L1):
```typescript
{
optionsRef: 'cleric-doctrine',
optionKey: 'warpriest',
name: 'Warpriest',
description: 'You are a martial defender of your faith…',
grants: ['Warpriest Doctrine'],
proficiencyChanges: { /* if a proficiency bump applies at L1, encode it here */ },
},
```
**Constraints:**
- Append only — do NOT modify the existing Wizard School `battle-magic` entry from Plan 03.
- `optionsRef` strings MUST match the L1_CHOICE_MAP / HIGHER_LEVEL_CHOICES values in `seed-class-progression.ts` (Plan 03 Task 4) — copy them verbatim.
- `optionKey` values: lowercase-kebab of the option's English name. Once chosen, do not rename — these are stable identifiers persisted in CharacterFeat rows after commit.
- Cite the AoN source URL in a comment block above each class section.
- No `: any` outside comments.
- Every entry must have non-empty `optionsRef`, `optionKey`, `name`, `description`, `grants` fields.
</action>
<verify>
<automated>cd server &amp;&amp; npx tsc --noEmit -p tsconfig.json 2&gt;&amp;1 | grep -E "class-feature-options" || echo "tsc clean"</automated>
</verify>
<acceptance_criteria>
- File `server/prisma/data/class-feature-options.ts` exists and has been extended
- Plan 03's `battle-magic` entry is still present (unchanged)
- Total `CLASS_FEATURE_OPTIONS.length` is at least 50 (joint goal across Plans 03 and 03b)
- At least 13 distinct `optionsRef` values appear: `wizard-school`, `cleric-doctrine`, `champion-cause`, `druid-order`, `sorcerer-bloodline`, `bard-muse`, `barbarian-instinct`, `witch-patron`, `oracle-mystery`, `investigator-methodology`, `ranger-edge`, `rogue-racket`, `swashbuckler-style`, `alchemist-research-field`
- For Alchemist: the four optionKey values are exactly `bomber`, `chirurgeon`, `mutagenist`, `toxicologist` (matches `ResearchField` enum lowercase-kebab — verify against `server/prisma/schema.prisma`)
- Every entry has non-empty `optionsRef`, `optionKey`, `name`, `description`, `grants` fields (verify with a quick scan)
- File contains NO `: any` outside comments
- `cd server && npx tsc --noEmit` exits 0
</acceptance_criteria>
<done>
Class-feature options file extended with ≥49 new entries across all 13 remaining classes (plus the rest of Wizard Schools). All optionsRef strings match seed-class-progression.ts L1_CHOICE_MAP. Alchemist optionKeys cross-validate against ResearchField enum.
</done>
</task>
<task type="auto" tdd="false">
<name>Task 3: Re-run the seed and verify cumulative row counts</name>
<files>(no file writes — execution + verification only)</files>
<read_first>
- .planning/phases/01-level-up-pf2e-regelkonform/SEED-README.md (verify Foundry clone is in place from Plan 03 Task 5)
- server/prisma/seed-class-progression.ts (Plan 03 — generic over D16_CLASS_NAMES + CLASS_FEATURE_OPTIONS, no changes needed)
</read_first>
<action>
**Step 1 — Verify the Foundry clone is still present:**
```bash
ls server/prisma/data/foundry-pf2e/packs/classes/
```
Should still list ≥16 .json files from Plan 03's Task 5 step 1.
**Step 2 — Run the seed:**
```bash
cd server && npm run db:seed:class-progression
```
Expected output (Plan 03 already populated 320 ClassProgression rows + 1 ClassFeatureOption row):
```
Seeding ClassProgression for 16 classes x 20 levels...
ClassProgression: 0 created, 320 updated, 0 errors
Seeding ~80 ClassFeatureOption rows...
ClassFeatureOption: ~79 created, 1 updated, 0 errors
ClassProgression + ClassFeatureOption seed complete.
```
The 320 ClassProgression rows are UPDATED in place — the new spell-slot/cantrip/repertoire data from Task 1's overlay appends gets merged into the existing rows.
**Step 3 — Run the seed AGAIN to confirm idempotency:**
```bash
cd server && npm run db:seed:class-progression
```
Expected: `0 created, 320 updated, 0 errors` for both tables.
**Step 4 — Verify ClassProgression row counts and overlay data:**
```bash
psql $DATABASE_URL -c 'SELECT "className", COUNT(*) FROM "ClassProgression" GROUP BY "className" ORDER BY "className"'
```
Expected: 16 rows, each `count = 20`.
```bash
psql $DATABASE_URL -c 'SELECT "className", COUNT(*) FROM "ClassProgression" WHERE "spellSlotIncrement" IS NOT NULL GROUP BY "className" ORDER BY "className"'
```
Expected: rows for `Bard`, `Cleric`, `Druid`, `Oracle`, `Sorcerer`, `Witch`, `Wizard` (no `Champion` since it's focus-only; no non-casters). Each caster should have ≥18 rows.
```bash
psql $DATABASE_URL -c 'SELECT "className", COUNT(*) FROM "ClassProgression" WHERE "repertoireIncrement" IS NOT NULL GROUP BY "className" ORDER BY "className"'
```
Expected: rows for `Bard`, `Sorcerer`, `Oracle` (the three spontaneous casters per D-18).
**Step 5 — Verify ClassFeatureOption row counts:**
```bash
psql $DATABASE_URL -c 'SELECT "optionsRef", COUNT(*) FROM "ClassFeatureOption" GROUP BY "optionsRef" ORDER BY "optionsRef"'
```
Expected: at least 13 distinct `optionsRef` values (one per class with an L1 choice point), each with ≥2 options. Total row count ≥50.
**Step 6 — Cross-reference verification (optional but recommended):**
```bash
# Every choiceOptionsRef in ClassProgression should have at least one matching ClassFeatureOption
psql $DATABASE_URL -c '
SELECT cp."choiceOptionsRef", COUNT(cfo.id) AS option_count
FROM "ClassProgression" cp
LEFT JOIN "ClassFeatureOption" cfo ON cfo."optionsRef" = cp."choiceOptionsRef"
WHERE cp."choiceOptionsRef" IS NOT NULL
GROUP BY cp."choiceOptionsRef"
ORDER BY cp."choiceOptionsRef"
'
```
Expected: every `choiceOptionsRef` row has `option_count >= 1`. If any has 0, a class chose a choiceType but no options exist — append the missing options to class-feature-options.ts and re-run the seed.
If any verification step fails, fix the curation data and re-run.
</action>
<verify>
<automated>cd server &amp;&amp; psql $DATABASE_URL -t -c 'SELECT COUNT(*) FROM "ClassFeatureOption"' | tr -d ' \n'</automated>
</verify>
<acceptance_criteria>
- `cd server && npm run db:seed:class-progression` exits 0
- First Plan 03b run reports `0 created, 320 updated` for ClassProgression (Plan 03 already created the rows; Plan 03b updates them with new overlay data)
- First Plan 03b run reports `≥49 created` for ClassFeatureOption (joint with Plan 03's 1 entry → ≥50 total)
- Second run reports `0 created, ≥320 updated` for ClassProgression and `0 created, ≥50 updated` for ClassFeatureOption (idempotency)
- `psql $DATABASE_URL -t -c 'SELECT COUNT(*) FROM "ClassFeatureOption"'` outputs at least 50
- `psql $DATABASE_URL -t -c 'SELECT COUNT(DISTINCT "optionsRef") FROM "ClassFeatureOption"'` outputs at least 13
- For all 7 caster classes (Bard, Cleric, Druid, Oracle, Sorcerer, Witch, Wizard): the count of ClassProgression rows with non-null spellSlotIncrement is ≥18
- For Bard, Sorcerer, Oracle: count of ClassProgression rows with non-null repertoireIncrement is ≥1 (D-18)
- Every `choiceOptionsRef` declared on a ClassProgression row has at least one matching ClassFeatureOption row (cross-reference query passes)
</acceptance_criteria>
<done>
Seed re-run succeeds; full dataset of ≥320 ClassProgression rows (with overlay data merged for all casters) and ≥50 ClassFeatureOption rows is present in the DB; idempotency holds; cross-references intact. Plan 04 can rely on the complete dataset for any (className, level) recompute.
</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| dev-shell → seed script | Same boundary as Plan 03 — developer runs the seed with DB write privilege. |
| Hand-curated data → DB | Curation can have transcription errors (wrong slot count, typo'd optionKey). |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-1-W2b-01 | Tampering | Hand-curation transcription error (e.g. wrong slot count for Bard L5) | mitigate | Plan 04's integration tests assert ClassProgression-driven recompute results for Wizard L1 + at least one spontaneous caster. Bugs surface there. Curator double-checks against AoN before committing. |
| T-1-W2b-02 | Tampering | Stable optionKey is renamed after persist (orphaning CharacterFeat rows) | mitigate | This task's acceptance criteria + the comment in Plan 03's class-feature-options.ts both flag optionKey as stable; once committed in production, optionKey changes require a migration. Plan 04 LVL-V2-02 (reverse-level-up) will exercise the read path. |
| T-1-W2b-03 | Information Disclosure | (No new exposure beyond Plan 03's accept disposition.) | accept | Same trust model as Plan 03 — self-hosted single-tenant. |
| T-1-W2b-04 | Repudiation | Curated data quality drift over time (e.g. Witch tradition changes in a remaster) | mitigate | The Witch / Sorcerer "tradition depends on patron/bloodline" caveats are documented inline; recompute pipeline (Plan 04) treats the overlay as a starting point and may remap at commit. Phase 1 accepts the default. |
</threat_model>
<verification>
After Tasks 1-3 complete:
```bash
# Type-check clean (no shape changes; just data appends)
cd server && npx tsc --noEmit -p tsconfig.json
# Idempotency check
cd server && npm run db:seed:class-progression && npm run db:seed:class-progression
# Joint row counts after both Plan 03 and Plan 03b
psql $DATABASE_URL -c 'SELECT COUNT(*) FROM "ClassProgression"' # >= 320
psql $DATABASE_URL -c 'SELECT COUNT(*) FROM "ClassFeatureOption"' # >= 50
# Caster verification
psql $DATABASE_URL -c 'SELECT className, COUNT(*) FROM "ClassProgression" WHERE "spellSlotIncrement" IS NOT NULL GROUP BY className'
# Expected: 7 rows (Bard, Cleric, Druid, Oracle, Sorcerer, Witch, Wizard), each count >= 18
# Spontaneous caster repertoire (D-18)
psql $DATABASE_URL -c 'SELECT className, COUNT(*) FROM "ClassProgression" WHERE "repertoireIncrement" IS NOT NULL GROUP BY className'
# Expected: 3 rows (Bard, Sorcerer, Oracle), each count >= 1
# Cross-reference integrity
psql $DATABASE_URL -c '
SELECT cp."choiceOptionsRef", COUNT(cfo.id)
FROM "ClassProgression" cp
LEFT JOIN "ClassFeatureOption" cfo ON cfo."optionsRef" = cp."choiceOptionsRef"
WHERE cp."choiceOptionsRef" IS NOT NULL
GROUP BY cp."choiceOptionsRef"
HAVING COUNT(cfo.id) = 0
'
# Expected: empty result (no orphaned choiceOptionsRef)
```
</verification>
<success_criteria>
- spell-slot-overlays.ts has curated entries for Cleric, Druid, Witch, Bard, Sorcerer, Oracle (≥18 entries each, ≥120 total beyond Wizard); Champion remains minimal/empty per design; non-casters confirmed empty
- class-feature-options.ts has ≥50 total entries (Plan 03's 1 + Plan 03b's ≥49) across ≥13 distinct optionsRef values
- Re-running the seed bulk-updates the database without touching the seed script
- Idempotency holds — second run reports 0 created
- All cross-references intact: every choiceOptionsRef in ClassProgression has at least one matching ClassFeatureOption
- Plan 04 LevelingService can recompute correctly for any of the 16 D-16 classes at any level 1..20
</success_criteria>
<output>
After completion, create `.planning/phases/01-level-up-pf2e-regelkonform/01-03b-SUMMARY.md` documenting:
- Final cumulative row counts (ClassProgression by className + total; ClassFeatureOption by optionsRef + total)
- Any deviations from the planned overlay contents (e.g. Witch tradition default, Sorcerer bloodline default)
- Any classes/levels where AoN data was ambiguous and a documented choice was made
- Confirmation idempotency observed on second run
- Confirmation cross-reference integrity check passed
</output>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1053,30 +1053,30 @@ This phase touches authenticated REST endpoints and persists JSON state. Securit
| Stored XSS via German feat-prereq translation text rendered in the prereq-confirm dialog | Injection (Tampering) | The text comes from the existing `Translation.germanName` column populated from Claude API; render with React text-binding (no `dangerouslySetInnerHTML`). Existing Dimension47 patterns already do this. |
| SQL injection via `Feat.prerequisites` containing SQL-like text | Tampering | Prisma parameterizes everything; the prereq strings are never interpolated into raw queries. |
## Open Questions
## Open Questions (RESOLVED)
1. **Spell-slot progression overlay sourcing.**
- **What we know:** Foundry pf2e classfeatures (`wizard-spellcasting.json` etc.) encode slot tables in description prose — verified. We need a hand-curated overlay.
- **What's unclear:** Should the overlay live as a TS constant (`server/prisma/data/spell-slot-overlays.ts`) or as a JSON file (e.g. `server/prisma/data/spell-slot-overlays.json`)?
- **Recommendation:** TS constant, exported from the `data/` folder, imported by the seed script. Type-safe, IDE-navigable, version-controlled, and the values are inherently TS-shaped (enums for `SpellTradition`). Planner: include curating this overlay (16 caster classes × ~20 levels) as a Wave-2 sub-task.
- **What was unclear:** Should the overlay live as a TS constant (`server/prisma/data/spell-slot-overlays.ts`) or as a JSON file (e.g. `server/prisma/data/spell-slot-overlays.json`)?
- **RESOLVED — Recommendation:** TS constant, exported from the `data/` folder, imported by the seed script. Type-safe, IDE-navigable, version-controlled, and the values are inherently TS-shaped (enums for `SpellTradition`). Planner: include curating this overlay (16 caster classes × ~20 levels) as a Wave-2 sub-task.
2. **Class-feature option mechanics — where does the recompute apply them?**
- **What we know:** `ClassProgression.choiceType` flags that a step exists; `ClassFeatureOption` lists the user-facing options; the user picks one in the wizard.
- **What's unclear:** When the user picks "Destruction Doctrine" (Cleric L1), the doctrine grants specific abilities/feats/proficiencies. Do those grants live as additional `ClassFeatureOption` columns (`grants: String[]`, `proficiencyChanges: Json`), or in a parallel hand-coded mapping?
- **Recommendation:** **Add the same `grants` + `proficiencyChanges` columns to `ClassFeatureOption`** so the recompute pipeline is uniform: at commit, walk both `ClassProgression` AND any chosen `ClassFeatureOption` rows, apply both sets of grants. This keeps the data model symmetric and the recompute code branch-free. Planner should extend the schema in §Code Examples #3 accordingly.
- **What was unclear:** When the user picks "Destruction Doctrine" (Cleric L1), the doctrine grants specific abilities/feats/proficiencies. Do those grants live as additional `ClassFeatureOption` columns (`grants: String[]`, `proficiencyChanges: Json`), or in a parallel hand-coded mapping?
- **RESOLVED — Recommendation:** **Add the same `grants` + `proficiencyChanges` columns to `ClassFeatureOption`** so the recompute pipeline is uniform: at commit, walk both `ClassProgression` AND any chosen `ClassFeatureOption` rows, apply both sets of grants. This keeps the data model symmetric and the recompute code branch-free. Planner should extend the schema in §Code Examples #3 accordingly.
3. **Banner-resolution UI for D-06 import violations.**
- **What we know:** Phase 1 ships listing-only (per `01-UI-SPEC.md` §"Pathbuilder-Import-Violations Banner": "Phase 1 ships read-only listing only; resolution UI is out of scope").
- **What's unclear:** When a user retrains a feat manually outside the wizard (today possible via direct DB?), should the banner update? **Out of scope this phase** — confirmed by deferral. Planner: do NOT add banner-clearing logic.
- **What was unclear:** When a user retrains a feat manually outside the wizard (today possible via direct DB?), should the banner update? **RESOLVED — Out of scope this phase** — confirmed by deferral. Planner: do NOT add banner-clearing logic.
4. **Pathbuilder FA auto-detect false-positive recovery (A1 tied).**
- **What we know:** Heuristic might misfire.
- **What's unclear:** Should the auto-detection's result be visible somewhere besides the toggle (e.g. logged on the character)?
- **Recommendation:** Log the detection result as a NestJS Logger info line during import (`PathbuilderImport: detected FA=true for character ${name} based on ${reason}`). The toggle is editable via character-settings. No UI banner needed.
- **What was unclear:** Should the auto-detection's result be visible somewhere besides the toggle (e.g. logged on the character)?
- **RESOLVED — Recommendation:** Log the detection result as a NestJS Logger info line during import (`PathbuilderImport: detected FA=true for character ${name} based on ${reason}`). The toggle is editable via character-settings. No UI banner needed.
5. **Initial level-up from L1 to L2 — does the wizard support it?**
- **What we know:** ROADMAP/REQUIREMENTS describe the wizard for "Stufe steigen" generally.
- **What's unclear:** Is L1→L2 the same flow as L4→L5? PF2e: L2 is just feats + skill-feat (no boost set). The dynamic-step logic in `computeApplicableSteps` handles this — boost step appears only for L5/10/15/20. So yes, same flow. **Confirmed: no special handling needed.**
- **What was unclear:** Is L1→L2 the same flow as L4→L5? PF2e: L2 is just feats + skill-feat (no boost set). The dynamic-step logic in `computeApplicableSteps` handles this — boost step appears only for L5/10/15/20. **RESOLVED — Confirmed: no special handling needed; same flow.**
## Sources