Files
2026-05-27 23:29:53 +01:00

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

  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

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.