Phase 13: grounding context + deterministic pattern detection
- src/lib/assistant/context.ts: buildCampaignContext() assembles a system-aware snapshot led by an explicit systemConstraint (anti cross-system hallucination), party, recent encounters with computed difficulty, quest/note summaries. pickCreatureCandidates() pulls level/CR-appropriate creatures from the correct bestiary so LLM tips are grounded in real data. - src/lib/assistant/patterns.ts: difficultyTendency() + detectThemes() classify whether the DM's encounters skew too easy/hard, rating each fight against its party-level snapshot. - Encounter schema gains optional partyLevelsSnapshot + outcome (Dexie v7, additive). Combat tracker snapshots PC levels at start and rates difficulty against them. 17 assistant unit tests (context + patterns). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,8 +50,11 @@ export function EncounterTracker({ encounter, campaign }: { encounter: Encounter
|
||||
mutate((e) => logEvent(applyInitiatives(e, rolls), 'Rolled initiative for all combatants'));
|
||||
};
|
||||
|
||||
// Difficulty budget from monster combatants vs the campaign's PCs.
|
||||
const partyLevels = characters.filter((c) => c.kind === 'pc').map((c) => c.level);
|
||||
// Difficulty budget from monster combatants vs the campaign's PCs. Once combat
|
||||
// has started, rate against the levels captured at that time (snapshot) so the
|
||||
// reading — and the assistant's history — reflects the party as it was.
|
||||
const currentLevels = characters.filter((c) => c.kind === 'pc').map((c) => c.level);
|
||||
const partyLevels = encounter.partyLevelsSnapshot?.length ? encounter.partyLevelsSnapshot : currentLevels;
|
||||
const monsters = encounter.combatants
|
||||
.filter((c) => c.kind === 'monster')
|
||||
.map((c) => ({ cr: c.cr, level: c.level }));
|
||||
@@ -97,7 +100,10 @@ export function EncounterTracker({ encounter, campaign }: { encounter: Encounter
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={encounter.combatants.length === 0}
|
||||
onClick={() => mutate(startEncounter)}
|
||||
onClick={() => mutate((e) => ({
|
||||
...startEncounter(e),
|
||||
...(currentLevels.length ? { partyLevelsSnapshot: currentLevels } : {}),
|
||||
}))}
|
||||
>
|
||||
Start combat
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user