Includes full bot source (Phases 1–4), plus five new features: - Epic 1: emoji reaction state machine (👀⏳✅🎲) + burst queue cap at 2 with in-world drop notices - Epic 2: per-encounter tone field in YAML injected into LLM system prompt - Epic 3: player pronouns via modal registration + system prompt players block - Epic 4: strengthened skill_check_emit tool contract + missed-skill-check diagnostic Also includes UX design docs, epics, and story files under Docs/. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
226 lines
5.7 KiB
TypeScript
226 lines
5.7 KiB
TypeScript
// src/types/index.ts
|
|
// Shared types used across all layers of the Mardonar Encounter Engine.
|
|
// Import from here only — do not duplicate type definitions elsewhere.
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Players
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface Player {
|
|
discordId: string;
|
|
dndName: string;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Encounter Spec
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface NpcPersona {
|
|
/** Unique stable ID used as Neo4j node key. e.g. "miriam-vendor-mardonar" */
|
|
id: string;
|
|
name: string;
|
|
role: string;
|
|
/** Full persona description injected into the system prompt. */
|
|
persona: string;
|
|
/**
|
|
* If set, NPC memories are loaded from Neo4j at session start
|
|
* and written back on encounter_resolve.
|
|
*/
|
|
memoryKey?: string;
|
|
}
|
|
|
|
export interface EncounterGoal {
|
|
id: string;
|
|
label: string;
|
|
}
|
|
|
|
export interface EncounterGoals {
|
|
hidden: boolean;
|
|
primary: EncounterGoal[];
|
|
secondary: EncounterGoal[];
|
|
}
|
|
|
|
export interface EncounterSetting {
|
|
location: string;
|
|
mood: string;
|
|
ambientNpcs: string;
|
|
}
|
|
|
|
export interface EncounterSpec {
|
|
encounterId: string;
|
|
title: string;
|
|
setting: EncounterSetting;
|
|
openingNarrative: string;
|
|
npcs: NpcPersona[];
|
|
goals: EncounterGoals;
|
|
sportsmanshipRules: string[];
|
|
/** Skill check DCs and notes keyed by check name e.g. "chase_dc" → 13 */
|
|
skillChecks: Record<string, number | string>;
|
|
dmNotes?: string;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Session State
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export type SessionPhase = 'open' | 'active' | 'resolved';
|
|
|
|
export interface SessionState {
|
|
encounterId: string;
|
|
/** Discord thread snowflake ID — used as the primary session key in Redis. */
|
|
threadId: string;
|
|
guildId: string;
|
|
spec: EncounterSpec;
|
|
/** Map of discordId → Player for all players who have entered the session. */
|
|
players: Record<string, Player>;
|
|
history: ChatMessage[];
|
|
phase: SessionPhase;
|
|
/** Messages held while waiting for a player to register their DnD name. */
|
|
heldMessages: HeldMessage[];
|
|
/** Outcome goal ID set when the encounter resolves. */
|
|
outcome?: string;
|
|
outcomeSummary?: string;
|
|
createdAt: number;
|
|
updatedAt: number;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Chat History
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface ChatMessage {
|
|
role: 'system' | 'user' | 'assistant';
|
|
content: string;
|
|
/**
|
|
* Pinned messages are never removed during history trimming.
|
|
* Use for: opening narrative, goal block.
|
|
*/
|
|
pinned?: boolean;
|
|
timestamp: number;
|
|
}
|
|
|
|
export interface HeldMessage {
|
|
discordUserId: string;
|
|
content: string;
|
|
timestamp: number;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// LLM Harness
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface ToolCallBlock {
|
|
tool: ToolName;
|
|
args: Record<string, unknown>;
|
|
}
|
|
|
|
export interface LLMResponse {
|
|
/** Narrative text to post to the Discord thread. */
|
|
narrative: string;
|
|
/** Parsed tool call block, if the LLM emitted one. */
|
|
toolCall?: ToolCallBlock;
|
|
/** Raw token count returned by Ollama (eval_count). */
|
|
rawTokensUsed?: number;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tools
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export type ToolName =
|
|
| 'skill_check_emit'
|
|
| 'skill_check_resolve'
|
|
| 'event_log_append'
|
|
| 'npc_memory_read'
|
|
| 'npc_memory_write'
|
|
| 'encounter_resolve';
|
|
|
|
export interface SkillCheckEmitArgs {
|
|
player: string;
|
|
prompt: string;
|
|
dc: number;
|
|
}
|
|
|
|
export interface SkillCheckResolveArgs {
|
|
player: string;
|
|
skill: string;
|
|
roll: number;
|
|
modifier: number;
|
|
dc: number;
|
|
success: boolean;
|
|
}
|
|
|
|
export interface EventLogAppendArgs {
|
|
sessionId: string;
|
|
eventType: EventType;
|
|
description: string;
|
|
}
|
|
|
|
export interface NpcMemoryReadArgs {
|
|
npcId: string;
|
|
}
|
|
|
|
export interface NpcMemoryWriteArgs {
|
|
npcId: string;
|
|
memoryFact: string;
|
|
}
|
|
|
|
export interface EncounterResolveArgs {
|
|
sessionId: string;
|
|
outcomeId: string;
|
|
summary: string;
|
|
}
|
|
|
|
export type EventType =
|
|
| 'player_action'
|
|
| 'skill_check'
|
|
| 'npc_action'
|
|
| 'outcome'
|
|
| 'sportsmanship'
|
|
| 'session_start'
|
|
| 'player_joined';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Neo4j
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface NpcNode {
|
|
id: string;
|
|
name: string;
|
|
personaSummary: string;
|
|
memoryFacts: string[];
|
|
lastSeenEncounter: string | null;
|
|
}
|
|
|
|
export interface EncounterNode {
|
|
id: string;
|
|
title: string;
|
|
resolved: boolean;
|
|
outcomeId: string | null;
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface EncounterEventNode {
|
|
timestamp: string;
|
|
type: EventType;
|
|
description: string;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Context Budget
|
|
// (Exported as a const so all callers use the same values)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export const CONTEXT_BUDGET = {
|
|
/** Maximum system prompt size including all NPC personas. */
|
|
SYSTEM: 4_000,
|
|
/** Pinned messages (opening narrative + goal block). Never trimmed. */
|
|
PINNED: 2_000,
|
|
/** Sliding history window. */
|
|
HISTORY: 118_000,
|
|
/** Hard floor — stop trimming here even if still over budget. */
|
|
SAFETY: 3_500,
|
|
/** Total context window of gemma4-it:e2b. */
|
|
TOTAL: 128_000,
|
|
} as const;
|