Commit Graph

3 Commits

Author SHA1 Message Date
NilsBriggen cac5979820 D6: AI Director polish + parity hardening
- pf2e parity: a unit test confirms the director carries the system through
  (systemConstraint + prompt say Pathfinder 2e, no cross-system leak) and still
  produces a grounded deterministic turn. (Degree-of-success/conditions already
  branch per system via the shared kernel.)
- UX: the transcript pane is now a bounded, scrollable region (max-h-[60vh]) so
  long sessions don't blow the page layout; it still auto-scrolls to the newest line.
- e2e: e2e/director.spec.ts exercises the full flow against a real browser — DM
  narrates a grounded scene with no API key (deterministic badge, empty-state
  clears, Continue appears), and the player persona seats a party character and
  takes a turn.

396 unit tests + 2 director e2e green; lint clean; build OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 03:25:41 +02:00
NilsBriggen 2f0700f8d3 D3: AI Player — pilot a real PC, with caster actions
The director can now play a party character, not just DM. A Mode switch
(Dungeon Master / Player) plus an AI-seat picker choose which PC the AI pilots.

- Scene injection: when seated, the controlled PC's real sheet detail is fed to
  the director — attacks with derived to-hit (sys.weaponAttack) + damage
  expressions, spells (level/concentration), resources, and spell slots — so the
  AI player acts from true numbers, in first person.
- Persona-aware prompt + cue already route DM→runs monsters / player→"it is your
  turn"; the seat sets controlledName.
- Caster actions (castSpell/spendResource) flow through the D2 approve-each apply
  bridge unchanged — slot/resource spend is enforced by the kernel, concentration
  mirrors to the combatant.
- UI: SceneControls (mode + seat), seat-gating ("pick a character"), and
  player-flavored buttons (Take turn). directorStore gains controlledCharacterId.

Verified in-app: switching to Player mode, seating "Lia the Brave · Fighter 3",
and taking a turn produced first-person narration. 388 unit tests green (incl. new
player-persona scene/ prompt tests); lint clean; build OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 03:13:47 +02:00
NilsBriggen 961fe8655a D1: AI Director (MVP) — grounded AI DM narration + no-auto-roll dice buttons
The first user-visible slice of the AI DM / AI Player "director": a new
/director page where the AI narrates the scene, voices NPCs/monsters, and runs
enemies — grounded entirely in the campaign's real party and active encounter,
and degrading to a deterministic narrator when no AI key is set.

Engine (pure, framework-free) in src/lib/assistant/director/:
- schema.ts: DeepSeek-tolerant directorTurnSchema (z.preprocess structural
  repair + z.coerce + .catch() enums + per-element safeParse-drop) → a single
  validated turn { narration, rollRequests[], actions[], suggestions[] }.
- context.ts: buildDirectorScene assembles a CLOSED roster from the active
  encounter (or the party) with deriveState badges — the only entities the
  director may name.
- prompt.ts: persona-aware system prompt (DM/player) leading with the
  systemConstraint; multi-turn message mapping from the transcript.
- engine.ts: runDirectorTurn + sanitizeTurn — the anti-hallucination gate that
  drops any action referencing an off-roster entity (and ungrounded
  addCombatant), mirroring the encounter advisor's candidate filter.
- fallback.ts: deterministic director that still surfaces roll buttons.

Hard rules, enforced:
- NEVER auto-roll: a roll fires only from RollRequestCard's onClick (rollAndShow).
  A unit test asserts the engine layer never imports the roll seam, making
  auto-roll structurally impossible.
- Approve-each: D1 is read-only (actions render as previews; the Apply bridge
  lands in D2). The director never writes game state.
- Always-on fallback: works with no API key.

Also: Dexie v18 `aiSessions` table + aiSessionsRepo (+ cascade delete), a small
directorStore, a Settings card, and the /director route + nav entry.

Verified in-app on the sample 5e campaign: grounded narration named the real
current combatant; on an enemy's turn a "Aller Rosk attack vs Pip Underbough"
roll card appeared with diceRolls UNCHANGED until the button was clicked, which
then fired exactly one roll and recorded the result back into the transcript.
381 unit tests green; lint clean (3 pre-existing); build OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 02:55:42 +02:00