Initial Commit
This commit is contained in:
168
openspec/changes/enterprise-gaming-quest/proposal.md
Normal file
168
openspec/changes/enterprise-gaming-quest/proposal.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# OpenSpec Change: enterprise-gaming-quest
|
||||
|
||||
- **Change ID**: enterprise-gaming-quest
|
||||
- **Package**: `@suaveplan/quest`
|
||||
- **Category**: gaming/narrative
|
||||
- **Target path**: `gaming/narrative/quest`
|
||||
- **Status**: PROPOSED
|
||||
- **Date**: 2026-04-24
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/quest.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/quest` on the genesis branch from scratch at enterprise quality. The package provides a quest/mission system with multi-objective tracking, quest chains, rewards, and event-driven triggers. A `QuestDefinition` describes objectives (kill, collect, reach, talk, craft), prerequisites, and rewards; a `QuestLog` per player tracks active/completed quests with objective progress; a `QuestEventHandler` listens for game events and advances objectives automatically. Genesis adds Zod definition schemas, structured errors, OTel quest-completion 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:^`) | `QuestError`; `QUEST.*` codes |
|
||||
| `@suaveplan/types` (`workspace:^`) | `Clock`, `IdGenerator` |
|
||||
| `@suaveplan/events` (`workspace:^`) | Quest lifecycle events + game-event listener |
|
||||
| `zod` (`catalog:forms`) | `QuestDefinition` schema |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | Quest completion counter |
|
||||
|
||||
---
|
||||
|
||||
## Public API surface
|
||||
|
||||
```ts
|
||||
type ObjectiveType = 'kill' | 'collect' | 'reach' | 'talk' | 'craft' | 'custom'
|
||||
|
||||
interface ObjectiveDefinition {
|
||||
id: string
|
||||
type: ObjectiveType
|
||||
description: string
|
||||
targetId?: string // NPC/item/location id
|
||||
required: number // quantity or count
|
||||
optional?: boolean
|
||||
}
|
||||
|
||||
interface RewardDefinition {
|
||||
xp?: number
|
||||
gold?: number
|
||||
items?: { itemId: string; quantity: number }[]
|
||||
unlocksQuestId?: string
|
||||
}
|
||||
|
||||
interface QuestDefinition {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
objectives: ObjectiveDefinition[]
|
||||
rewards: RewardDefinition
|
||||
prerequisites?: string[] // quest IDs that must be completed
|
||||
chainNextId?: string // auto-starts after completion
|
||||
timeLimit?: number // seconds; undefined = no limit
|
||||
}
|
||||
|
||||
function createQuestRegistry(): QuestRegistry
|
||||
interface QuestRegistry {
|
||||
register(def: QuestDefinition): void
|
||||
get(id: string): QuestDefinition | undefined
|
||||
}
|
||||
|
||||
function createQuestLog(playerId: string, registry: QuestRegistry, options?: QuestLogOptions): QuestLog
|
||||
|
||||
interface QuestLog {
|
||||
startQuest(questId: string): void
|
||||
abandonQuest(questId: string): void
|
||||
advanceObjective(questId: string, objectiveId: string, amount?: number): void
|
||||
isComplete(questId: string): boolean
|
||||
isAvailable(questId: string): boolean
|
||||
getActive(): QuestInstance[]
|
||||
getCompleted(): QuestInstance[]
|
||||
tick(dt: number): void // for time-limit tracking
|
||||
}
|
||||
|
||||
interface QuestInstance {
|
||||
readonly questId: string
|
||||
readonly progress: Map<string, number>
|
||||
readonly startedAt: number
|
||||
readonly isComplete: boolean
|
||||
readonly isFailed: boolean
|
||||
}
|
||||
|
||||
// Event-driven advancement
|
||||
function createQuestEventHandler(log: QuestLog, mapping: QuestEventMapping): QuestEventHandler
|
||||
|
||||
type QuestEventMapping = Map<string, { questId: string; objectiveId: string; amount?: number }[]>
|
||||
|
||||
interface QuestEventHandler {
|
||||
onEvent(eventName: string, payload?: unknown): void
|
||||
}
|
||||
```
|
||||
|
||||
### Error codes
|
||||
|
||||
```ts
|
||||
type QuestErrorCode =
|
||||
| 'QUEST_NOT_FOUND'
|
||||
| 'QUEST_ALREADY_ACTIVE'
|
||||
| 'QUEST_PREREQUISITES_NOT_MET'
|
||||
| 'QUEST_OBJECTIVE_NOT_FOUND'
|
||||
| 'QUEST_ALREADY_COMPLETED'
|
||||
| 'QUEST_TIMED_OUT'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## New additions for genesis
|
||||
|
||||
1. **Prerequisite chain** — `isAvailable()` checks full prerequisite DAG using DFS.
|
||||
2. **Auto-chain** — on completion, if `chainNextId` is set and prerequisites met, quest log auto-starts next quest.
|
||||
3. **Time limit** — `tick()` checks elapsed time; exceeds limit → marks quest failed, fires `quest:failed`.
|
||||
4. **OTel** — `quest.completions_total` counter; `quest.active_count` gauge.
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
gaming/narrative/quest/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
constants/index.ts
|
||||
types/common.ts
|
||||
types/common.md
|
||||
core/
|
||||
quest-registry/
|
||||
quest-registry.ts
|
||||
quest-registry.md
|
||||
quest-registry.test.ts
|
||||
index.ts
|
||||
quest-log/
|
||||
quest-log.ts
|
||||
quest-log.md
|
||||
quest-log.types.ts
|
||||
quest-log.test.ts
|
||||
index.ts
|
||||
event-handler/
|
||||
event-handler.ts
|
||||
event-handler.md
|
||||
event-handler.types.ts
|
||||
event-handler.test.ts
|
||||
index.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Downstream**: `@suaveplan/progression` listens for `quest:completed` events to award XP. Prerequisites: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`.
|
||||
Reference in New Issue
Block a user