Initial Commit
This commit is contained in:
160
openspec/changes/enterprise-gaming-dialogue/proposal.md
Normal file
160
openspec/changes/enterprise-gaming-dialogue/proposal.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# 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<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
|
||||
|
||||
```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`.
|
||||
Reference in New Issue
Block a user