169 lines
4.8 KiB
Markdown
169 lines
4.8 KiB
Markdown
# 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`.
|