4.6 KiB
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
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<string, unknown>): DialogueChoice[]
}
function createConversationSession(tree: DialogueTree, options?: SessionOptions): ConversationSession
interface SessionOptions {
context?: Record<string, unknown>
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
type DialogueErrorCode =
| 'DIALOGUE_NODE_NOT_FOUND'
| 'DIALOGUE_CHOICE_NOT_FOUND'
| 'DIALOGUE_CONDITION_ERROR'
| 'DIALOGUE_TREE_INVALID'
| 'DIALOGUE_NO_LINES'
New additions for genesis
- Condition evaluator —
whenstrings parsed as simple expressions (gold >= 100,hasItem("key")); safe no-eval evaluation via token parser. - Hidden vs. disabled choices —
hidden: trueremoves choice from list; false shows it greyed. - Bark cooldown — per-speaker per-tag cooldown tracked with injectable clock.
File layout
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.