# OpenSpec Change: enterprise-gaming-dialogue - **Change ID**: enterprise-gaming-dialogue - **Package**: `@suaveplan/dialogue` - **Category**: gaming/narrative - **Target path**: `gaming/narrative/dialogue` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/dialogue.md` --- ## Summary Deliver `@suaveplan/dialogue` on the genesis branch from scratch at enterprise quality. The package provides branching dialogue trees with conditional choices, a bark system for ambient NPC speech, and conversation session management. A `DialogueTree` is a directed graph of `DialogueNode` objects; a `ConversationSession` advances through nodes given player choice input, evaluates `when` conditions against a context blackboard, and fires `@suaveplan/events` on node entry/exit. A `BarkSystem` selects one-shot NPC lines from tagged pools with cooldown deduplication. Genesis adds Zod node schemas, structured errors, OTel conversation counter, injectable clock, and 100% coverage. --- ## Current-state audit (genesis) Package does not exist on genesis yet — fresh build. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | `DialogueError`; `DIALOGUE.*` codes | | `@suaveplan/types` (`workspace:^`) | `Clock` | | `@suaveplan/events` (`workspace:^`) | Dialogue event bus | | `zod` (`catalog:forms`) | Node + tree schemas | | `@opentelemetry/api` (`catalog:observability`) | Conversation start/end counter | --- ## Public API surface ```ts interface DialogueNode { id: string speaker?: string text: string choices?: DialogueChoice[] action?: string // action name fired on node entry isExit?: boolean } interface DialogueChoice { id: string text: string nextNodeId: string when?: string // expression evaluated against context; e.g. "gold >= 100" hidden?: boolean // hide choice if condition fails (vs. show disabled) } function createDialogueTree(nodes: DialogueNode[], rootId: string): DialogueTree interface DialogueTree { getNode(id: string): DialogueNode | undefined getChoices(nodeId: string, context: Record): DialogueChoice[] } function createConversationSession(tree: DialogueTree, options?: SessionOptions): ConversationSession interface SessionOptions { context?: Record clock?: Clock } interface ConversationSession { readonly currentNode: DialogueNode readonly isComplete: boolean choose(choiceId: string): DialogueNode advance(): void // moves to next node if no choices setContext(key: string, value: unknown): void reset(): void } // Bark system function createBarkSystem(options?: BarkOptions): BarkSystem interface BarkOptions { defaultCooldown?: number // seconds between same tag, default 10 clock?: Clock } interface BarkSystem { registerBarks(tag: string, lines: string[]): void bark(speakerId: string, tag: string, rng: Rng): string | null // null if on cooldown clearCooldown(speakerId: string, tag?: string): void } ``` ### Error codes ```ts type DialogueErrorCode = | 'DIALOGUE_NODE_NOT_FOUND' | 'DIALOGUE_CHOICE_NOT_FOUND' | 'DIALOGUE_CONDITION_ERROR' | 'DIALOGUE_TREE_INVALID' | 'DIALOGUE_NO_LINES' ``` --- ## New additions for genesis 1. **Condition evaluator** — `when` strings parsed as simple expressions (`gold >= 100`, `hasItem("key")`); safe no-eval evaluation via token parser. 2. **Hidden vs. disabled choices** — `hidden: true` removes choice from list; false shows it greyed. 3. **Bark cooldown** — per-speaker per-tag cooldown tracked with injectable clock. --- ## File layout ```text gaming/narrative/dialogue/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/index.ts types/common.ts types/common.md core/ dialogue-tree/ dialogue-tree.ts dialogue-tree.md dialogue-tree.types.ts dialogue-tree.test.ts index.ts conversation-session/ conversation-session.ts conversation-session.md conversation-session.types.ts conversation-session.test.ts index.ts bark-system/ bark-system.ts bark-system.md bark-system.types.ts bark-system.test.ts index.ts utils/ condition-evaluator.ts condition-evaluator.md condition-evaluator.test.ts tests/ integration.test.ts ``` --- ## Impact **Downstream**: `@suaveplan/react-dialogue` wraps session state in React hooks. Prerequisites: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`.