qnce-engine 0.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +959 -5
- package/dist/cli/audit.js +6 -4
- package/dist/cli/audit.js.map +1 -1
- package/dist/cli/init.js +11 -9
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/perf.d.ts +30 -0
- package/dist/cli/perf.d.ts.map +1 -0
- package/dist/cli/perf.js +220 -0
- package/dist/cli/perf.js.map +1 -0
- package/dist/cli/play.d.ts +4 -0
- package/dist/cli/play.d.ts.map +1 -0
- package/dist/cli/play.js +259 -0
- package/dist/cli/play.js.map +1 -0
- package/dist/engine/condition.d.ts +69 -0
- package/dist/engine/condition.d.ts.map +1 -0
- package/dist/engine/condition.js +195 -0
- package/dist/engine/condition.js.map +1 -0
- package/dist/engine/core.d.ts +378 -11
- package/dist/engine/core.d.ts.map +1 -1
- package/dist/engine/core.js +1433 -13
- package/dist/engine/core.js.map +1 -1
- package/dist/engine/demo-story.d.ts.map +1 -1
- package/dist/engine/demo-story.js +103 -14
- package/dist/engine/demo-story.js.map +1 -1
- package/dist/engine/errors.d.ts +76 -0
- package/dist/engine/errors.d.ts.map +1 -0
- package/dist/engine/errors.js +178 -0
- package/dist/engine/errors.js.map +1 -0
- package/dist/engine/types.d.ts +445 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +9 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/engine/validation.d.ts +110 -0
- package/dist/engine/validation.d.ts.map +1 -0
- package/dist/engine/validation.js +261 -0
- package/dist/engine/validation.js.map +1 -0
- package/dist/examples/examples/autosave-undo-demo.js +248 -0
- package/dist/examples/examples/persistence-demo.js +63 -0
- package/dist/examples/src/engine/condition.js +194 -0
- package/dist/examples/src/engine/core.js +1382 -0
- package/dist/examples/src/engine/demo-story.js +200 -0
- package/dist/examples/src/engine/types.js +8 -0
- package/dist/examples/src/index.js +35 -0
- package/dist/examples/src/integrations/react.js +322 -0
- package/dist/examples/src/narrative/branching/engine-simple.js +348 -0
- package/dist/examples/src/narrative/branching/index.js +55 -0
- package/dist/examples/src/narrative/branching/models.js +5 -0
- package/dist/examples/src/performance/ObjectPool.js +296 -0
- package/dist/examples/src/performance/PerfReporter.js +280 -0
- package/dist/examples/src/performance/ThreadPool.js +347 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -3
- package/dist/index.js.map +1 -1
- package/dist/integrations/react.d.ts +200 -0
- package/dist/integrations/react.d.ts.map +1 -0
- package/dist/integrations/react.js +365 -0
- package/dist/integrations/react.js.map +1 -0
- package/dist/narrative/branching/engine-simple.d.ts +84 -0
- package/dist/narrative/branching/engine-simple.d.ts.map +1 -0
- package/dist/narrative/branching/engine-simple.js +349 -0
- package/dist/narrative/branching/engine-simple.js.map +1 -0
- package/dist/narrative/branching/engine.d.ts +1 -0
- package/dist/narrative/branching/engine.d.ts.map +1 -0
- package/dist/narrative/branching/engine.js +2 -0
- package/dist/narrative/branching/engine.js.map +1 -0
- package/dist/narrative/branching/index.d.ts +12 -0
- package/dist/narrative/branching/index.d.ts.map +1 -0
- package/dist/narrative/branching/index.js +56 -0
- package/dist/narrative/branching/index.js.map +1 -0
- package/dist/narrative/branching/models.d.ts +223 -0
- package/dist/narrative/branching/models.d.ts.map +1 -0
- package/dist/narrative/branching/models.js +6 -0
- package/dist/narrative/branching/models.js.map +1 -0
- package/dist/performance/HotReloadDelta.d.ts +124 -0
- package/dist/performance/HotReloadDelta.d.ts.map +1 -0
- package/dist/performance/HotReloadDelta.js +328 -0
- package/dist/performance/HotReloadDelta.js.map +1 -0
- package/dist/performance/ObjectPool.d.ts +150 -0
- package/dist/performance/ObjectPool.d.ts.map +1 -0
- package/dist/performance/ObjectPool.js +297 -0
- package/dist/performance/ObjectPool.js.map +1 -0
- package/dist/performance/PerfReporter.d.ts +123 -0
- package/dist/performance/PerfReporter.d.ts.map +1 -0
- package/dist/performance/PerfReporter.js +281 -0
- package/dist/performance/PerfReporter.js.map +1 -0
- package/dist/performance/ThreadPool.d.ts +107 -0
- package/dist/performance/ThreadPool.d.ts.map +1 -0
- package/dist/performance/ThreadPool.js +348 -0
- package/dist/performance/ThreadPool.js.map +1 -0
- package/dist/ui/__tests__/AutosaveIndicator.test.d.ts +2 -0
- package/dist/ui/__tests__/AutosaveIndicator.test.d.ts.map +1 -0
- package/dist/ui/__tests__/AutosaveIndicator.test.js +329 -0
- package/dist/ui/__tests__/AutosaveIndicator.test.js.map +1 -0
- package/dist/ui/__tests__/UndoRedoControls.test.d.ts +2 -0
- package/dist/ui/__tests__/UndoRedoControls.test.d.ts.map +1 -0
- package/dist/ui/__tests__/UndoRedoControls.test.js +245 -0
- package/dist/ui/__tests__/UndoRedoControls.test.js.map +1 -0
- package/dist/ui/__tests__/autosave-simple.test.d.ts +2 -0
- package/dist/ui/__tests__/autosave-simple.test.d.ts.map +1 -0
- package/dist/ui/__tests__/autosave-simple.test.js +29 -0
- package/dist/ui/__tests__/autosave-simple.test.js.map +1 -0
- package/dist/ui/__tests__/setup.d.ts +2 -0
- package/dist/ui/__tests__/setup.d.ts.map +1 -0
- package/dist/ui/__tests__/setup.js +40 -0
- package/dist/ui/__tests__/setup.js.map +1 -0
- package/dist/ui/__tests__/smoke-test.d.ts +2 -0
- package/dist/ui/__tests__/smoke-test.d.ts.map +1 -0
- package/dist/ui/__tests__/smoke-test.js +18 -0
- package/dist/ui/__tests__/smoke-test.js.map +1 -0
- package/dist/ui/__tests__/smoke-test.test.d.ts +2 -0
- package/dist/ui/__tests__/smoke-test.test.d.ts.map +1 -0
- package/dist/ui/__tests__/smoke-test.test.js +18 -0
- package/dist/ui/__tests__/smoke-test.test.js.map +1 -0
- package/dist/ui/__tests__/useKeyboardShortcuts.test.d.ts +2 -0
- package/dist/ui/__tests__/useKeyboardShortcuts.test.d.ts.map +1 -0
- package/dist/ui/__tests__/useKeyboardShortcuts.test.js +374 -0
- package/dist/ui/__tests__/useKeyboardShortcuts.test.js.map +1 -0
- package/dist/ui/components/AutosaveIndicator.d.ts +18 -0
- package/dist/ui/components/AutosaveIndicator.d.ts.map +1 -0
- package/dist/ui/components/AutosaveIndicator.js +175 -0
- package/dist/ui/components/AutosaveIndicator.js.map +1 -0
- package/dist/ui/components/UndoRedoControls.d.ts +16 -0
- package/dist/ui/components/UndoRedoControls.d.ts.map +1 -0
- package/dist/ui/components/UndoRedoControls.js +144 -0
- package/dist/ui/components/UndoRedoControls.js.map +1 -0
- package/dist/ui/hooks/useKeyboardShortcuts.d.ts +22 -0
- package/dist/ui/hooks/useKeyboardShortcuts.d.ts.map +1 -0
- package/dist/ui/hooks/useKeyboardShortcuts.js +162 -0
- package/dist/ui/hooks/useKeyboardShortcuts.js.map +1 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +14 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/types.d.ts +141 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +51 -0
- package/dist/ui/types.js.map +1 -0
- package/docs/PERFORMANCE.md +355 -0
- package/docs/branching/ERD.md +214 -0
- package/docs/branching/PDM.md +443 -0
- package/docs/branching/RELEASE-v1.2.0.md +169 -0
- package/examples/autosave-undo-demo.ts +306 -0
- package/examples/branching-advanced-demo.ts +339 -0
- package/examples/branching-demo-simple.ts +0 -0
- package/examples/branching-demo.ts +0 -0
- package/examples/branching-quickstart.ts +314 -0
- package/examples/persistence-demo.ts +84 -0
- package/examples/quickstart-demo.js +82 -0
- package/examples/tsconfig.json +13 -0
- package/examples/ui-components-demo.tsx +320 -0
- package/examples/validation-demo-story.json +177 -0
- package/examples/validation-demo.js +163 -0
- package/package.json +41 -8
package/README.md
CHANGED
|
@@ -2,20 +2,257 @@
|
|
|
2
2
|
|
|
3
3
|
**Quantum Narrative Convergence Engine** - A framework-agnostic TypeScript library for creating interactive narrative experiences with quantum-inspired mechanics.
|
|
4
4
|
|
|
5
|
+
> **🚀 NEW in v1.2.0:** Complete state persistence, advanced branching with AI integration, autosave & undo/redo system, conditional choice display, and comprehensive UI components with React integration.
|
|
6
|
+
|
|
7
|
+
[](https://badge.fury.io/js/qnce-engine)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
|
|
5
11
|
## Core Concepts
|
|
6
12
|
|
|
7
13
|
- **Superposition:** Multiple narrative outcomes exist simultaneously until a choice is made
|
|
8
14
|
- **Collapse:** Player choices "collapse" the narrative to a specific path, updating state and flags
|
|
9
15
|
- **Entanglement:** Early decisions affect later outcomes, enabling complex, interconnected stories
|
|
10
16
|
|
|
17
|
+
## ✨ What's New in v1.2.0
|
|
18
|
+
|
|
19
|
+
### 💾 State Persistence & Checkpoints
|
|
20
|
+
- **Complete save/load system** with data integrity validation
|
|
21
|
+
- **Lightweight checkpoints** for undo/redo functionality
|
|
22
|
+
- **Automatic state serialization** with metadata and versioning
|
|
23
|
+
- **Migration support** for upgrading saved states between versions
|
|
24
|
+
|
|
25
|
+
### 🌿 Advanced Branching System
|
|
26
|
+
- **Multi-path narratives** with conditional logic and flag-based branching
|
|
27
|
+
- **AI-driven content generation** for dynamic story expansion
|
|
28
|
+
- **Real-time branch insertion/removal** for live content updates
|
|
29
|
+
- **Comprehensive analytics** for narrative optimization
|
|
30
|
+
|
|
31
|
+
### 🔄 Autosave & Undo/Redo System
|
|
32
|
+
- **Intelligent state tracking** with automatic snapshots on key events
|
|
33
|
+
- **Sub-millisecond undo/redo operations** with configurable history depth
|
|
34
|
+
- **Autosave throttling** prevents excessive saves during rapid changes
|
|
35
|
+
- **Memory efficient** with automatic cleanup and history limits
|
|
36
|
+
|
|
37
|
+
### 🎯 Conditional Choice Display
|
|
38
|
+
- **Flag-based choice filtering** with complex logical expressions
|
|
39
|
+
- **Custom validation rules** for advanced choice availability
|
|
40
|
+
- **Real-time choice updates** based on dynamic game state
|
|
41
|
+
- **Intuitive condition syntax** supporting multiple data types
|
|
42
|
+
|
|
43
|
+
### 🖥️ UI Integration & React Components
|
|
44
|
+
- **Ready-to-use React components** for common UI patterns
|
|
45
|
+
- **Keyboard shortcuts** with customizable bindings
|
|
46
|
+
- **Accessibility features** with ARIA support and screen reader compatibility
|
|
47
|
+
- **Theming system** with customizable appearance
|
|
48
|
+
|
|
49
|
+
### ⚡ Enterprise Performance
|
|
50
|
+
- **Object pooling** reduces memory allocations by 90%+
|
|
51
|
+
- **Background processing** for non-blocking operations
|
|
52
|
+
- **Hot-reload capabilities** with <3.5ms story updates
|
|
53
|
+
- **Comprehensive monitoring** with built-in CLI tools
|
|
54
|
+
|
|
55
|
+
#### Basic Flag-Based Conditions
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { createQNCEEngine } from 'qnce-engine';
|
|
59
|
+
|
|
60
|
+
const storyData = {
|
|
61
|
+
currentNodeId: 'town-square',
|
|
62
|
+
nodes: {
|
|
63
|
+
'town-square': {
|
|
64
|
+
id: 'town-square',
|
|
65
|
+
text: 'You stand in the bustling town square.',
|
|
66
|
+
choices: [
|
|
67
|
+
{
|
|
68
|
+
id: 'enter-tavern',
|
|
69
|
+
text: 'Enter the tavern',
|
|
70
|
+
nextNodeId: 'tavern-inside'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'approach-guard',
|
|
74
|
+
text: 'Approach the suspicious guard',
|
|
75
|
+
nextNodeId: 'guard-encounter',
|
|
76
|
+
condition: 'flags.curiosity >= 3' // Only show if curious enough
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'use-disguise',
|
|
80
|
+
text: 'Use your disguise to blend in',
|
|
81
|
+
nextNodeId: 'disguised-approach',
|
|
82
|
+
condition: 'flags.hasDisguise && !flags.disguiseUsed'
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
flags: {}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const engine = createQNCEEngine(storyData);
|
|
91
|
+
|
|
92
|
+
// Set flags to test conditional choices
|
|
93
|
+
engine.setFlag('curiosity', 4);
|
|
94
|
+
engine.setFlag('hasDisguise', true);
|
|
95
|
+
|
|
96
|
+
// Get available choices - only those meeting conditions
|
|
97
|
+
const choices = engine.getAvailableChoices();
|
|
98
|
+
console.log(`Available choices: ${choices.length}`); // Shows 3 choices
|
|
99
|
+
|
|
100
|
+
// Change flags to see different options
|
|
101
|
+
engine.setFlag('curiosity', 1); // Below threshold
|
|
102
|
+
const reducedChoices = engine.getAvailableChoices();
|
|
103
|
+
console.log(`Available choices: ${reducedChoices.length}`); // Shows 2 choices
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### Complex Conditional Expressions
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const complexStory = {
|
|
110
|
+
currentNodeId: 'critical-moment',
|
|
111
|
+
nodes: {
|
|
112
|
+
'critical-moment': {
|
|
113
|
+
id: 'critical-moment',
|
|
114
|
+
text: 'The fate of the kingdom hangs in the balance.',
|
|
115
|
+
choices: [
|
|
116
|
+
{
|
|
117
|
+
id: 'diplomatic-solution',
|
|
118
|
+
text: 'Attempt diplomatic negotiation',
|
|
119
|
+
nextNodeId: 'peaceful-resolution',
|
|
120
|
+
condition: 'flags.charisma >= 5 && flags.hasAlliance'
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'magical-intervention',
|
|
124
|
+
text: 'Cast the ancient spell',
|
|
125
|
+
nextNodeId: 'magical-outcome',
|
|
126
|
+
condition: 'flags.magicPower > 3 && flags.spellComponents >= 2 && !flags.cursed'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'time-limited-escape',
|
|
130
|
+
text: 'Escape through the secret passage',
|
|
131
|
+
nextNodeId: 'secret-escape',
|
|
132
|
+
condition: 'context.timeElapsed < 300 && flags.knowsSecretPath'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'sacrifice-play',
|
|
136
|
+
text: 'Make the ultimate sacrifice',
|
|
137
|
+
nextNodeId: 'heroic-end',
|
|
138
|
+
condition: '(flags.loyalty >= 8 || flags.desperate) && flags.hasArtifact'
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
flags: {}
|
|
144
|
+
};
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Custom Condition Evaluators
|
|
148
|
+
|
|
149
|
+
For advanced scenarios, you can provide custom logic for condition evaluation:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// Set up custom evaluator for complex game logic
|
|
153
|
+
engine.setConditionEvaluator((expression, context) => {
|
|
154
|
+
// Custom logic for special conditions
|
|
155
|
+
if (expression === 'canUseSpecialAbility') {
|
|
156
|
+
return context.flags.level >= 10 &&
|
|
157
|
+
context.flags.mana > 50 &&
|
|
158
|
+
!context.flags.abilityOnCooldown;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (expression.startsWith('inventory:')) {
|
|
162
|
+
const itemName = expression.replace('inventory:', '');
|
|
163
|
+
return context.flags[`has_${itemName}`] === true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Fall back to default expression evaluation
|
|
167
|
+
return null;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Use custom conditions in story
|
|
171
|
+
const storyWithCustom = {
|
|
172
|
+
currentNodeId: 'boss-fight',
|
|
173
|
+
nodes: {
|
|
174
|
+
'boss-fight': {
|
|
175
|
+
id: 'boss-fight',
|
|
176
|
+
text: 'The dragon roars menacingly.',
|
|
177
|
+
choices: [
|
|
178
|
+
{
|
|
179
|
+
id: 'special-attack',
|
|
180
|
+
text: 'Use your special ability',
|
|
181
|
+
nextNodeId: 'special-victory',
|
|
182
|
+
condition: 'canUseSpecialAbility'
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: 'use-potion',
|
|
186
|
+
text: 'Drink healing potion',
|
|
187
|
+
nextNodeId: 'healed-state',
|
|
188
|
+
condition: 'inventory:healing_potion'
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Condition Validation & Debugging
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// Validate conditions during development
|
|
200
|
+
try {
|
|
201
|
+
engine.validateCondition('flags.strength >= 5 && flags.weapon');
|
|
202
|
+
console.log('Condition is valid');
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error('Invalid condition:', error.message);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Get flags referenced in a condition for debugging
|
|
208
|
+
const referencedFlags = engine.getConditionFlags('flags.curiosity >= 3 && flags.hasKey');
|
|
209
|
+
console.log('Referenced flags:', referencedFlags); // ['curiosity', 'hasKey']
|
|
210
|
+
|
|
211
|
+
// Clear custom evaluator
|
|
212
|
+
engine.clearConditionEvaluator();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Performance Considerations
|
|
216
|
+
|
|
217
|
+
- **Expression Caching:** Conditions are compiled once and cached for subsequent evaluations
|
|
218
|
+
- **Safe Evaluation:** All expressions are sanitized to prevent code injection
|
|
219
|
+
- **Minimal Overhead:** Choice filtering adds <1ms to `getAvailableChoices()` calls
|
|
220
|
+
- **Error Isolation:** Invalid conditions don't affect other choices in the same node
|
|
221
|
+
|
|
222
|
+
### ⚡ Performance Infrastructure
|
|
223
|
+
- **🏊♂️ Object Pooling:** 90%+ allocation reduction, eliminating GC pressure
|
|
224
|
+
- **🧵 Background Processing:** Non-blocking cache preloading and telemetry writes
|
|
225
|
+
- **🔥 Hot-Reload:** <3.5ms live story updates with delta patching
|
|
226
|
+
- **📊 Real-time Profiling:** Comprehensive event instrumentation and analysis
|
|
227
|
+
- **🖥️ Live Monitoring:** `qnce-perf` CLI dashboard with performance alerts
|
|
228
|
+
|
|
229
|
+
### Performance Dashboard
|
|
230
|
+
```bash
|
|
231
|
+
# Real-time performance monitoring
|
|
232
|
+
qnce-perf dashboard
|
|
233
|
+
|
|
234
|
+
# Live monitoring with updates
|
|
235
|
+
qnce-perf live 1000
|
|
236
|
+
|
|
237
|
+
# Export performance data
|
|
238
|
+
qnce-perf export > performance-report.json
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**[📚 Complete Performance Guide →](docs/PERFORMANCE.md)**
|
|
242
|
+
|
|
11
243
|
## Installation
|
|
12
244
|
|
|
13
245
|
```bash
|
|
14
246
|
npm install qnce-engine
|
|
247
|
+
|
|
248
|
+
# Global CLI installation for performance monitoring
|
|
249
|
+
npm install -g qnce-engine
|
|
15
250
|
```
|
|
16
251
|
|
|
17
252
|
## Quick Start
|
|
18
253
|
|
|
254
|
+
### Basic Usage
|
|
255
|
+
|
|
19
256
|
```typescript
|
|
20
257
|
import { createQNCEEngine, DEMO_STORY } from 'qnce-engine';
|
|
21
258
|
|
|
@@ -40,6 +277,313 @@ const flags = engine.getFlags();
|
|
|
40
277
|
console.log('Current flags:', flags);
|
|
41
278
|
```
|
|
42
279
|
|
|
280
|
+
### Performance Mode (Recommended for Production)
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { createQNCEEngine, DEMO_STORY } from 'qnce-engine';
|
|
284
|
+
|
|
285
|
+
// Enable performance optimizations
|
|
286
|
+
const engine = createQNCEEngine(DEMO_STORY, {}, true, {
|
|
287
|
+
maxWorkers: 4,
|
|
288
|
+
enableProfiling: true
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Background cache preloading happens automatically
|
|
292
|
+
// Object pooling reduces memory allocations by 90%+
|
|
293
|
+
// Performance events are collected for monitoring
|
|
294
|
+
|
|
295
|
+
// Get performance statistics
|
|
296
|
+
const poolStats = engine.getPoolStats();
|
|
297
|
+
console.log(`Pool efficiency: ${poolStats.flow.hitRate}%`);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### 💾 State Persistence & Checkpoints
|
|
301
|
+
|
|
302
|
+
QNCE Engine provides robust state persistence and checkpointing, allowing you to save and load the complete narrative state. This is useful for implementing save games, undo/redo functionality, and scenario replay.
|
|
303
|
+
|
|
304
|
+
**Key Features:**
|
|
305
|
+
- **Full State Serialization:** Save and load the entire engine state, including narrative position, flags, history, and branching context.
|
|
306
|
+
- **Lightweight Checkpoints:** Create fast, in-memory snapshots for undo operations or temporary state saves.
|
|
307
|
+
- **Data Integrity:** Optional checksum verification ensures that saved data is not corrupted.
|
|
308
|
+
- **Cross-Version Compatibility:** A migration system helps upgrade older save states to the latest version.
|
|
309
|
+
|
|
310
|
+
**Example Usage:**
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { createQNCEEngine, DEMO_STORY } from 'qnce-engine';
|
|
314
|
+
|
|
315
|
+
const engine = createQNCEEngine(DEMO_STORY);
|
|
316
|
+
|
|
317
|
+
// ...progress through the story...
|
|
318
|
+
const choices = engine.getAvailableChoices();
|
|
319
|
+
if (choices.length > 0) {
|
|
320
|
+
engine.selectChoice(choices[0]);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
// Save the current state to a JSON string
|
|
325
|
+
const savedState = await engine.saveState();
|
|
326
|
+
console.log('State saved!');
|
|
327
|
+
|
|
328
|
+
// ...later, or in a new session...
|
|
329
|
+
|
|
330
|
+
// Create a new engine instance
|
|
331
|
+
const newEngine = createQNCEEngine(DEMO_STORY);
|
|
332
|
+
|
|
333
|
+
// Load the state
|
|
334
|
+
await newEngine.loadState(JSON.parse(savedState));
|
|
335
|
+
|
|
336
|
+
console.log('State loaded successfully!');
|
|
337
|
+
console.log('Current Node:', newEngine.getCurrentNode().text);
|
|
338
|
+
|
|
339
|
+
// Create a lightweight checkpoint
|
|
340
|
+
const checkpoint = await engine.createCheckpoint('Before a risky choice');
|
|
341
|
+
|
|
342
|
+
// ...make a choice...
|
|
343
|
+
|
|
344
|
+
// Restore to the checkpoint
|
|
345
|
+
await engine.restoreFromCheckpoint(checkpoint.id);
|
|
346
|
+
console.log('Restored to checkpoint:', engine.getCurrentNode().text);
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### 🔄 Autosave & Undo/Redo System
|
|
350
|
+
|
|
351
|
+
QNCE Engine v1.2.0 introduces an advanced autosave and undo/redo system that automatically tracks state changes and provides instant rollback capabilities with sub-millisecond performance.
|
|
352
|
+
|
|
353
|
+
**Key Features:**
|
|
354
|
+
- **Automatic State Tracking:** Intelligently captures state snapshots on key events (choice selection, flag changes, state loading)
|
|
355
|
+
- **High-Performance Undo/Redo:** Sub-millisecond undo/redo operations with configurable history depth
|
|
356
|
+
- **Autosave Throttling:** Configurable throttling prevents excessive saves during rapid state changes
|
|
357
|
+
- **Memory Efficient:** Capped history with automatic cleanup of older entries
|
|
358
|
+
|
|
359
|
+
**Basic Usage:**
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import { createQNCEEngine, DEMO_STORY } from 'qnce-engine';
|
|
363
|
+
|
|
364
|
+
const engine = createQNCEEngine(DEMO_STORY);
|
|
365
|
+
|
|
366
|
+
// Autosave is enabled by default and will track state changes automatically
|
|
367
|
+
console.log('Can undo:', engine.canUndo()); // false initially
|
|
368
|
+
|
|
369
|
+
// Make some choices (autosave will track each change)
|
|
370
|
+
const choices = engine.getAvailableChoices();
|
|
371
|
+
engine.selectChoice(choices[0]);
|
|
372
|
+
|
|
373
|
+
console.log('Can undo:', engine.canUndo()); // true after making a choice
|
|
374
|
+
|
|
375
|
+
// Undo the last action
|
|
376
|
+
const undoResult = engine.undo();
|
|
377
|
+
if (undoResult.success) {
|
|
378
|
+
console.log('Undid:', undoResult.description);
|
|
379
|
+
console.log('Can redo:', engine.canRedo()); // true
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Redo the undone action
|
|
383
|
+
const redoResult = engine.redo();
|
|
384
|
+
if (redoResult.success) {
|
|
385
|
+
console.log('Redid:', redoResult.description);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Get history summary
|
|
389
|
+
const history = engine.getHistorySummary();
|
|
390
|
+
console.log(`History: ${history.undoCount} undo, ${history.redoCount} redo entries`);
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Advanced Configuration:**
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
// Configure undo/redo system
|
|
397
|
+
engine.configureUndoRedo({
|
|
398
|
+
maxUndoEntries: 100, // Maximum undo operations to remember
|
|
399
|
+
maxRedoEntries: 50, // Maximum redo operations to remember
|
|
400
|
+
enabled: true // Enable/disable undo/redo tracking
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Configure autosave behavior
|
|
404
|
+
engine.configureAutosave({
|
|
405
|
+
enabled: true, // Enable/disable autosave
|
|
406
|
+
throttleMs: 100, // Minimum time between saves (milliseconds)
|
|
407
|
+
events: ['choice', 'flag', 'load'] // Which events trigger autosave
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// Manual autosave trigger
|
|
411
|
+
await engine.autosave();
|
|
412
|
+
|
|
413
|
+
// Clear all undo/redo history
|
|
414
|
+
engine.clearHistory();
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Performance Guarantees:**
|
|
418
|
+
- Undo operations: <1ms for normal state
|
|
419
|
+
- Redo operations: <1ms for normal state
|
|
420
|
+
- Autosave overhead: <1ms per operation
|
|
421
|
+
- Memory efficient with configurable history limits
|
|
422
|
+
|
|
423
|
+
### Live Performance Monitoring
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
# Real-time performance dashboard
|
|
427
|
+
qnce-perf dashboard
|
|
428
|
+
|
|
429
|
+
# Live monitoring with updates every 2 seconds
|
|
430
|
+
qnce-perf live
|
|
431
|
+
|
|
432
|
+
# Export performance data
|
|
433
|
+
qnce-perf export > performance-report.json
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## 🌿 Advanced Branching & AI Integration
|
|
437
|
+
|
|
438
|
+
### Basic Branching
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import { createQNCEEngine, createBranchingEngine } from 'qnce-engine';
|
|
442
|
+
|
|
443
|
+
// Create core engine
|
|
444
|
+
const engine = createQNCEEngine(storyData);
|
|
445
|
+
|
|
446
|
+
// Enable advanced branching
|
|
447
|
+
const branchingEngine = engine.enableBranching(advancedStoryData);
|
|
448
|
+
|
|
449
|
+
// Evaluate available branches
|
|
450
|
+
const branches = await branchingEngine.evaluateAvailableBranches();
|
|
451
|
+
console.log(`Available paths: ${branches.length}`);
|
|
452
|
+
|
|
453
|
+
// Execute a narrative branch
|
|
454
|
+
await branchingEngine.executeBranch(branches[0].id);
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### AI-Driven Content Generation
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
// Set AI context for personalized content
|
|
461
|
+
branchingEngine.setAIContext({
|
|
462
|
+
playerProfile: {
|
|
463
|
+
playStyle: 'explorer',
|
|
464
|
+
preferences: { adventure: 0.8, mystery: 0.6 },
|
|
465
|
+
historicalChoices: ['brave-path', 'investigate-clue']
|
|
466
|
+
},
|
|
467
|
+
narrativeContext: {
|
|
468
|
+
currentTone: 'mysterious',
|
|
469
|
+
thematicElements: ['exploration', 'discovery'],
|
|
470
|
+
plotTension: 0.7
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Generate AI-enhanced branches
|
|
475
|
+
const aiBranches = await branchingEngine.generateAIBranches(3);
|
|
476
|
+
console.log('AI-generated options:', aiBranches.map(b => b.displayText));
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Dynamic Content Management
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
// Insert new branch at runtime
|
|
483
|
+
const dynamicBranch = {
|
|
484
|
+
type: 'insert',
|
|
485
|
+
branchId: 'special-event',
|
|
486
|
+
targetLocation: { chapterId: 'main', nodeId: 'crossroads' },
|
|
487
|
+
payload: {
|
|
488
|
+
name: 'Special Event',
|
|
489
|
+
branchOptions: [{
|
|
490
|
+
id: 'event-choice',
|
|
491
|
+
displayText: 'Investigate the mysterious sound',
|
|
492
|
+
flagEffects: { event_discovered: true }
|
|
493
|
+
}]
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
await branchingEngine.insertDynamicBranch(dynamicBranch);
|
|
498
|
+
|
|
499
|
+
// Remove branch when no longer needed
|
|
500
|
+
await branchingEngine.removeDynamicBranch('special-event');
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Analytics & Monitoring
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
// Get branching analytics
|
|
507
|
+
const analytics = branchingEngine.getBranchingAnalytics();
|
|
508
|
+
console.log(`Branches traversed: ${analytics.totalBranchesTraversed}`);
|
|
509
|
+
console.log(`Popular choices: ${analytics.mostPopularBranches}`);
|
|
510
|
+
|
|
511
|
+
// Export comprehensive data
|
|
512
|
+
const exportData = branchingEngine.exportBranchingData();
|
|
513
|
+
// Contains: story structure, session data, player behavior, performance metrics
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Live Performance Monitoring
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
# Real-time performance dashboard
|
|
520
|
+
qnce-perf dashboard
|
|
521
|
+
|
|
522
|
+
# Live monitoring with updates every 2 seconds
|
|
523
|
+
qnce-perf live
|
|
524
|
+
|
|
525
|
+
# Export performance data
|
|
526
|
+
qnce-perf export > performance-report.json
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
## 🚀 Performance Guide
|
|
530
|
+
|
|
531
|
+
QNCE v1.2.0-sprint2 includes advanced performance infrastructure for production applications.
|
|
532
|
+
|
|
533
|
+
### Performance Benchmarks
|
|
534
|
+
|
|
535
|
+
| Feature | Performance Gain | Impact |
|
|
536
|
+
|---------|-----------------|--------|
|
|
537
|
+
| Object Pooling | 90%+ allocation reduction | Eliminates GC hitches |
|
|
538
|
+
| Hot-Reload | 68% improvement (3.35ms) | Near-instant story updates |
|
|
539
|
+
| Background Processing | Non-blocking operations | Smooth user experience |
|
|
540
|
+
| Performance Monitoring | Real-time metrics | Production visibility |
|
|
541
|
+
|
|
542
|
+
### CLI Performance Dashboard
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
# Install CLI globally
|
|
546
|
+
npm install -g qnce-engine
|
|
547
|
+
|
|
548
|
+
# Real-time performance monitoring
|
|
549
|
+
qnce-perf live
|
|
550
|
+
|
|
551
|
+
# Performance dashboard output:
|
|
552
|
+
🚀 QNCE Performance Dashboard
|
|
553
|
+
=====================================
|
|
554
|
+
📊 Session Duration: 45.2s
|
|
555
|
+
🔢 Total Events: 1,247
|
|
556
|
+
|
|
557
|
+
💾 Cache Performance:
|
|
558
|
+
✅ Hit Rate: 92.3% (threshold: 80%)
|
|
559
|
+
✅ Avg Cache Time: 0.8ms (threshold: 50ms)
|
|
560
|
+
|
|
561
|
+
🔥 Hot-Reload Performance:
|
|
562
|
+
⚠️ Avg Time: 3.35ms (threshold: 2ms)
|
|
563
|
+
📊 Max Time: 4.1ms
|
|
564
|
+
🔄 Total Reloads: 12
|
|
565
|
+
|
|
566
|
+
🧵 ThreadPool Status:
|
|
567
|
+
📊 Completed Jobs: 445
|
|
568
|
+
⏳ Queued Jobs: 3
|
|
569
|
+
🏃 Active Workers: 2
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### Performance Mode Usage
|
|
573
|
+
|
|
574
|
+
```typescript
|
|
575
|
+
// Enable all performance optimizations
|
|
576
|
+
const engine = createQNCEEngine(storyData, {}, true, {
|
|
577
|
+
maxWorkers: 4, // Background processing
|
|
578
|
+
enableProfiling: true // Performance monitoring
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// Object pooling and background caching happen automatically
|
|
582
|
+
// Monitor performance in real-time with CLI dashboard
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
**📖 Complete Performance Guide:** [docs/PERFORMANCE_GUIDE.md](docs/PERFORMANCE_GUIDE.md)
|
|
586
|
+
|
|
43
587
|
## Core API
|
|
44
588
|
|
|
45
589
|
### QNCEEngine Class
|
|
@@ -49,6 +593,7 @@ The main engine class for managing narrative state.
|
|
|
49
593
|
#### Methods
|
|
50
594
|
|
|
51
595
|
- `getCurrentNode()`: Get the current narrative node
|
|
596
|
+
- `goToNodeById(nodeId)`: Navigate directly to a node by its ID
|
|
52
597
|
- `getState()`: Get the complete engine state
|
|
53
598
|
- `getFlags()`: Get current narrative flags
|
|
54
599
|
- `getHistory()`: Get choice history
|
|
@@ -115,26 +660,267 @@ Creates:
|
|
|
115
660
|
- package.json with QNCE dependencies
|
|
116
661
|
- README with usage instructions
|
|
117
662
|
|
|
663
|
+
### qnce-play
|
|
664
|
+
|
|
665
|
+
Interactive narrative sessions with full undo/redo support:
|
|
666
|
+
|
|
667
|
+
```bash
|
|
668
|
+
qnce-play story.json
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Features:
|
|
672
|
+
- Real-time narrative playthrough
|
|
673
|
+
- Instant undo/redo with `u` and `r` commands
|
|
674
|
+
- State inspection and debugging
|
|
675
|
+
- Performance monitoring
|
|
676
|
+
- Session save/load functionality
|
|
677
|
+
|
|
678
|
+
### qnce-perf
|
|
679
|
+
|
|
680
|
+
**NEW in v1.2.0:** Real-time performance monitoring and analytics:
|
|
681
|
+
|
|
682
|
+
```bash
|
|
683
|
+
# Launch interactive performance dashboard
|
|
684
|
+
qnce-perf dashboard
|
|
685
|
+
|
|
686
|
+
# Live monitoring with custom update interval
|
|
687
|
+
qnce-perf live [interval-ms]
|
|
688
|
+
|
|
689
|
+
# Export performance data to JSON
|
|
690
|
+
qnce-perf export [--format json|csv] [--output filename]
|
|
691
|
+
|
|
692
|
+
# Single performance snapshot
|
|
693
|
+
qnce-perf snapshot story.json
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
**Dashboard Features:**
|
|
697
|
+
- Real-time memory usage and allocation tracking
|
|
698
|
+
- Object pool efficiency monitoring
|
|
699
|
+
- Performance hotspot identification
|
|
700
|
+
- Live story update timing analysis
|
|
701
|
+
- Historical performance trend graphs
|
|
702
|
+
|
|
703
|
+
**Live Monitoring:**
|
|
704
|
+
```bash
|
|
705
|
+
# Monitor with 1-second updates
|
|
706
|
+
qnce-perf live 1000
|
|
707
|
+
|
|
708
|
+
# Default 500ms updates
|
|
709
|
+
qnce-perf live
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
**Export Options:**
|
|
713
|
+
```bash
|
|
714
|
+
# Export to JSON with full metrics
|
|
715
|
+
qnce-perf export --format json --output metrics.json
|
|
716
|
+
|
|
717
|
+
# Export to CSV for spreadsheet analysis
|
|
718
|
+
qnce-perf export --format csv --output performance.csv
|
|
719
|
+
|
|
720
|
+
# Stream to stdout
|
|
721
|
+
qnce-perf export
|
|
722
|
+
```
|
|
723
|
+
|
|
118
724
|
## Integration Examples
|
|
119
725
|
|
|
120
|
-
### React
|
|
726
|
+
### React Hooks
|
|
727
|
+
|
|
728
|
+
QNCE provides comprehensive React hooks for seamless integration:
|
|
121
729
|
|
|
122
730
|
```typescript
|
|
731
|
+
import { useQNCE, useUndoRedo, useAutosave } from 'qnce-engine/react';
|
|
732
|
+
import { DEMO_STORY } from 'qnce-engine';
|
|
733
|
+
|
|
734
|
+
function NarrativeComponent() {
|
|
735
|
+
// Core narrative hook
|
|
736
|
+
const { engine, currentNode, choices, flags, selectChoice, resetNarrative } = useQNCE(DEMO_STORY);
|
|
737
|
+
|
|
738
|
+
// Undo/redo functionality
|
|
739
|
+
const {
|
|
740
|
+
undo,
|
|
741
|
+
redo,
|
|
742
|
+
canUndo,
|
|
743
|
+
canRedo,
|
|
744
|
+
undoCount,
|
|
745
|
+
redoCount,
|
|
746
|
+
clearHistory
|
|
747
|
+
} = useUndoRedo(engine);
|
|
748
|
+
|
|
749
|
+
// Autosave status
|
|
750
|
+
const { isAutosaveEnabled, lastAutosave } = useAutosave(engine);
|
|
751
|
+
|
|
752
|
+
return (
|
|
753
|
+
<div>
|
|
754
|
+
<h2>Current Scene</h2>
|
|
755
|
+
<p>{currentNode.text}</p>
|
|
756
|
+
|
|
757
|
+
<h3>Choices</h3>
|
|
758
|
+
{choices.map((choice, index) => (
|
|
759
|
+
<button key={index} onClick={() => selectChoice(choice)}>
|
|
760
|
+
{choice.text}
|
|
761
|
+
</button>
|
|
762
|
+
))}
|
|
763
|
+
|
|
764
|
+
<div className="controls">
|
|
765
|
+
<button onClick={undo} disabled={!canUndo}>
|
|
766
|
+
Undo ({undoCount})
|
|
767
|
+
</button>
|
|
768
|
+
<button onClick={redo} disabled={!canRedo}>
|
|
769
|
+
Redo ({redoCount})
|
|
770
|
+
</button>
|
|
771
|
+
<button onClick={resetNarrative}>Reset</button>
|
|
772
|
+
<button onClick={clearHistory}>Clear History</button>
|
|
773
|
+
</div>
|
|
774
|
+
|
|
775
|
+
<div className="status">
|
|
776
|
+
<p>Autosave: {isAutosaveEnabled ? 'Enabled' : 'Disabled'}</p>
|
|
777
|
+
{lastAutosave && <p>Last saved: {lastAutosave.toLocaleTimeString()}</p>}
|
|
778
|
+
</div>
|
|
779
|
+
|
|
780
|
+
<details>
|
|
781
|
+
<summary>Debug Info</summary>
|
|
782
|
+
<pre>{JSON.stringify(flags, null, 2)}</pre>
|
|
783
|
+
</details>
|
|
784
|
+
</div>
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
### React UI Components
|
|
790
|
+
|
|
791
|
+
QNCE provides pre-built React components for common UI patterns:
|
|
792
|
+
|
|
793
|
+
#### UndoRedoControls Component
|
|
794
|
+
|
|
795
|
+
A complete undo/redo control panel with accessibility features:
|
|
796
|
+
|
|
797
|
+
```typescript
|
|
798
|
+
import { UndoRedoControls } from 'qnce-engine/ui';
|
|
123
799
|
import { createQNCEEngine } from 'qnce-engine';
|
|
124
|
-
|
|
800
|
+
|
|
801
|
+
function MyApp() {
|
|
802
|
+
const engine = createQNCEEngine(DEMO_STORY);
|
|
803
|
+
|
|
804
|
+
return (
|
|
805
|
+
<div>
|
|
806
|
+
{/* Narrative content */}
|
|
807
|
+
|
|
808
|
+
{/* Undo/Redo Controls */}
|
|
809
|
+
<UndoRedoControls
|
|
810
|
+
engine={engine}
|
|
811
|
+
size="md" // sm, md, lg
|
|
812
|
+
layout="horizontal" // horizontal, vertical
|
|
813
|
+
showLabels={true} // Show text labels
|
|
814
|
+
labels={{ // Custom labels
|
|
815
|
+
undo: "Go Back",
|
|
816
|
+
redo: "Go Forward"
|
|
817
|
+
}}
|
|
818
|
+
onUndo={(result) => console.log('Undo:', result)}
|
|
819
|
+
onRedo={(result) => console.log('Redo:', result)}
|
|
820
|
+
theme={{ // Custom theming
|
|
821
|
+
colors: {
|
|
822
|
+
primary: '#007bff',
|
|
823
|
+
disabled: '#6c757d'
|
|
824
|
+
},
|
|
825
|
+
borderRadius: { md: '8px' }
|
|
826
|
+
}}
|
|
827
|
+
/>
|
|
828
|
+
</div>
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
#### AutosaveIndicator Component
|
|
834
|
+
|
|
835
|
+
Visual indicator for autosave status with animations:
|
|
836
|
+
|
|
837
|
+
```typescript
|
|
838
|
+
import { AutosaveIndicator } from 'qnce-engine/ui';
|
|
839
|
+
|
|
840
|
+
function MyApp() {
|
|
841
|
+
const engine = createQNCEEngine(DEMO_STORY);
|
|
842
|
+
|
|
843
|
+
return (
|
|
844
|
+
<div>
|
|
845
|
+
{/* Narrative content */}
|
|
846
|
+
|
|
847
|
+
{/* Autosave Status */}
|
|
848
|
+
<AutosaveIndicator
|
|
849
|
+
engine={engine}
|
|
850
|
+
variant="detailed" // minimal, detailed, icon-only
|
|
851
|
+
position="top-right" // inline, top-right, bottom-left, etc.
|
|
852
|
+
showTimestamp={true} // Show last save time
|
|
853
|
+
autoHideDelay={3000} // Auto-hide after 3 seconds
|
|
854
|
+
messages={{ // Custom messages
|
|
855
|
+
idle: "Ready to save",
|
|
856
|
+
saving: "Saving...",
|
|
857
|
+
saved: "All changes saved",
|
|
858
|
+
error: "Save failed"
|
|
859
|
+
}}
|
|
860
|
+
theme={{ // Custom theming
|
|
861
|
+
colors: {
|
|
862
|
+
success: '#28a745',
|
|
863
|
+
error: '#dc3545',
|
|
864
|
+
warning: '#ffc107'
|
|
865
|
+
}
|
|
866
|
+
}}
|
|
867
|
+
/>
|
|
868
|
+
</div>
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
#### Keyboard Shortcuts
|
|
874
|
+
|
|
875
|
+
Built-in keyboard support with the `useKeyboardShortcuts` hook:
|
|
876
|
+
|
|
877
|
+
```typescript
|
|
878
|
+
import { useKeyboardShortcuts } from 'qnce-engine/ui';
|
|
879
|
+
|
|
880
|
+
function MyApp() {
|
|
881
|
+
const engine = createQNCEEngine(DEMO_STORY);
|
|
882
|
+
|
|
883
|
+
// Enable keyboard shortcuts
|
|
884
|
+
useKeyboardShortcuts(engine, {
|
|
885
|
+
undo: ['ctrl+z', 'cmd+z'], // Undo shortcuts
|
|
886
|
+
redo: ['ctrl+y', 'cmd+shift+z'], // Redo shortcuts
|
|
887
|
+
save: ['ctrl+s', 'cmd+s'], // Manual save
|
|
888
|
+
disabled: false // Enable/disable all shortcuts
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
return (
|
|
892
|
+
<div>
|
|
893
|
+
{/* Your narrative UI */}
|
|
894
|
+
<p>Press Ctrl+Z to undo, Ctrl+Y to redo, Ctrl+S to save</p>
|
|
895
|
+
</div>
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
### Basic React Hook (Legacy)
|
|
901
|
+
|
|
902
|
+
```typescript
|
|
903
|
+
import { createQNCEEngine } from 'qnce-engine';
|
|
904
|
+
import { useState, useCallback } from 'react';
|
|
125
905
|
|
|
126
906
|
function useQNCE(storyData) {
|
|
127
907
|
const [engine] = useState(() => createQNCEEngine(storyData));
|
|
128
908
|
const [currentNode, setCurrentNode] = useState(engine.getCurrentNode());
|
|
129
909
|
const [flags, setFlags] = useState(engine.getFlags());
|
|
130
910
|
|
|
131
|
-
const selectChoice = (choice) => {
|
|
911
|
+
const selectChoice = useCallback((choice) => {
|
|
132
912
|
engine.selectChoice(choice);
|
|
133
913
|
setCurrentNode(engine.getCurrentNode());
|
|
134
914
|
setFlags(engine.getFlags());
|
|
135
|
-
};
|
|
915
|
+
}, [engine]);
|
|
136
916
|
|
|
137
|
-
|
|
917
|
+
const goToNodeById = useCallback((nodeId) => {
|
|
918
|
+
engine.goToNodeById(nodeId);
|
|
919
|
+
setCurrentNode(engine.getCurrentNode());
|
|
920
|
+
setFlags(engine.getFlags());
|
|
921
|
+
}, [engine]);
|
|
922
|
+
|
|
923
|
+
return { currentNode, flags, selectChoice, goToNodeById };
|
|
138
924
|
}
|
|
139
925
|
```
|
|
140
926
|
|
|
@@ -190,6 +976,47 @@ async function playStory() {
|
|
|
190
976
|
}
|
|
191
977
|
```
|
|
192
978
|
|
|
979
|
+
## 📚 Examples & Demos
|
|
980
|
+
|
|
981
|
+
The repository includes comprehensive examples demonstrating all features:
|
|
982
|
+
|
|
983
|
+
### 🚀 Quickstart Example
|
|
984
|
+
- **File:** `examples/branching-quickstart.ts`
|
|
985
|
+
- **Features:** Basic branching, AI integration, dynamic operations
|
|
986
|
+
- **Run:** `npm run build && node dist/examples/branching-quickstart.js`
|
|
987
|
+
|
|
988
|
+
### 🎭 Advanced Demo
|
|
989
|
+
- **File:** `examples/branching-advanced-demo.ts`
|
|
990
|
+
- **Features:** Complex narrative flows, conditional branching, analytics
|
|
991
|
+
- **Story:** "The Mysterious Library" - Interactive mystery with multiple paths
|
|
992
|
+
|
|
993
|
+
### 💾 Autosave & Undo Demo (NEW in v1.2.0)
|
|
994
|
+
- **File:** `examples/autosave-undo-demo.ts`
|
|
995
|
+
- **Features:** Autosave, undo/redo, performance monitoring, state management
|
|
996
|
+
- **Run:** `npm run demo:autosave`
|
|
997
|
+
- **Performance:** Demonstrates <1ms undo/redo with real-time metrics
|
|
998
|
+
|
|
999
|
+
### 🧪 Validation Scripts
|
|
1000
|
+
- **Real-world testing:** `scripts/validation-real-world.ts`
|
|
1001
|
+
- **Comprehensive testing:** `scripts/validation-comprehensive.ts`
|
|
1002
|
+
|
|
1003
|
+
```bash
|
|
1004
|
+
# Run the quickstart example
|
|
1005
|
+
npm run build
|
|
1006
|
+
node dist/examples/branching-quickstart.js
|
|
1007
|
+
|
|
1008
|
+
# Run the new autosave demo
|
|
1009
|
+
npm run demo:autosave
|
|
1010
|
+
|
|
1011
|
+
# Try the interactive CLI tool
|
|
1012
|
+
npm run build
|
|
1013
|
+
qnce-play examples/demo-story.json
|
|
1014
|
+
|
|
1015
|
+
# Run validation tests
|
|
1016
|
+
npm run build
|
|
1017
|
+
node dist/scripts/validation-real-world.js
|
|
1018
|
+
```
|
|
1019
|
+
|
|
193
1020
|
## Development
|
|
194
1021
|
|
|
195
1022
|
```bash
|
|
@@ -223,3 +1050,130 @@ MIT - See LICENSE file for details.
|
|
|
223
1050
|
---
|
|
224
1051
|
|
|
225
1052
|
**QNCE Engine** - Empowering interactive narratives with quantum-inspired mechanics.
|
|
1053
|
+
|
|
1054
|
+
## 🛡️ Choice Validation System
|
|
1055
|
+
|
|
1056
|
+
Ensure only valid choices can be executed with comprehensive validation rules.
|
|
1057
|
+
|
|
1058
|
+
### Basic Validation
|
|
1059
|
+
|
|
1060
|
+
```typescript
|
|
1061
|
+
import { createQNCEEngine } from 'qnce-engine';
|
|
1062
|
+
|
|
1063
|
+
const engine = createQNCEEngine(storyData);
|
|
1064
|
+
|
|
1065
|
+
try {
|
|
1066
|
+
// makeChoice() automatically validates before executing
|
|
1067
|
+
engine.makeChoice(0);
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
if (error instanceof ChoiceValidationError) {
|
|
1070
|
+
console.error('Invalid choice:', error.message);
|
|
1071
|
+
console.log('Available choices:', error.availableChoices);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
### Advanced Choice Requirements
|
|
1077
|
+
|
|
1078
|
+
Define complex validation rules on your choices:
|
|
1079
|
+
|
|
1080
|
+
```typescript
|
|
1081
|
+
// Choice with flag requirements
|
|
1082
|
+
const flagBasedChoice = {
|
|
1083
|
+
text: 'Use the magic key',
|
|
1084
|
+
nextNodeId: 'unlock_door',
|
|
1085
|
+
flagRequirements: {
|
|
1086
|
+
hasKey: true,
|
|
1087
|
+
playerLevel: 5
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
// Choice with inventory requirements
|
|
1092
|
+
const inventoryChoice = {
|
|
1093
|
+
text: 'Buy expensive sword',
|
|
1094
|
+
nextNodeId: 'shop_success',
|
|
1095
|
+
inventoryRequirements: {
|
|
1096
|
+
gold: 1000,
|
|
1097
|
+
gems: 2
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
// Time-based availability
|
|
1102
|
+
const timedChoice = {
|
|
1103
|
+
text: 'Enter the tavern',
|
|
1104
|
+
nextNodeId: 'tavern',
|
|
1105
|
+
timeRequirements: {
|
|
1106
|
+
availableAfter: new Date('2025-01-01T18:00:00'),
|
|
1107
|
+
availableBefore: new Date('2025-01-01T24:00:00')
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// Disabled choice
|
|
1112
|
+
const disabledChoice = {
|
|
1113
|
+
text: 'Broken bridge',
|
|
1114
|
+
nextNodeId: 'fall',
|
|
1115
|
+
enabled: false
|
|
1116
|
+
};
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
### Custom Validation Rules
|
|
1120
|
+
|
|
1121
|
+
Create your own validation logic:
|
|
1122
|
+
|
|
1123
|
+
```typescript
|
|
1124
|
+
import {
|
|
1125
|
+
StandardValidationRules,
|
|
1126
|
+
createChoiceValidator
|
|
1127
|
+
} from 'qnce-engine';
|
|
1128
|
+
|
|
1129
|
+
const validator = createChoiceValidator();
|
|
1130
|
+
|
|
1131
|
+
// Add custom rule
|
|
1132
|
+
validator.addRule({
|
|
1133
|
+
name: 'custom-rule',
|
|
1134
|
+
priority: 10,
|
|
1135
|
+
validate: (choice, context) => {
|
|
1136
|
+
// Your custom logic
|
|
1137
|
+
if (choice.text.includes('danger') && !context.state.flags.brave) {
|
|
1138
|
+
return {
|
|
1139
|
+
isValid: false,
|
|
1140
|
+
reason: 'You must be brave to take this path!',
|
|
1141
|
+
failedConditions: ['requires-bravery']
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
return { isValid: true };
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
// Apply to engine
|
|
1149
|
+
engine.setChoiceValidator(validator);
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
### Validation Error Handling
|
|
1153
|
+
|
|
1154
|
+
```typescript
|
|
1155
|
+
import {
|
|
1156
|
+
ChoiceValidationError,
|
|
1157
|
+
isChoiceValidationError
|
|
1158
|
+
} from 'qnce-engine';
|
|
1159
|
+
|
|
1160
|
+
try {
|
|
1161
|
+
engine.makeChoice(2);
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
if (isChoiceValidationError(error)) {
|
|
1164
|
+
// Get user-friendly message
|
|
1165
|
+
const message = error.getUserFriendlyMessage();
|
|
1166
|
+
console.log(message);
|
|
1167
|
+
|
|
1168
|
+
// Get debug information
|
|
1169
|
+
const debugInfo = error.getDebugInfo();
|
|
1170
|
+
console.log('Failed conditions:', debugInfo.validationResult.failedConditions);
|
|
1171
|
+
|
|
1172
|
+
// Show alternatives
|
|
1173
|
+
console.log('Available choices:');
|
|
1174
|
+
error.availableChoices?.forEach((choice, i) => {
|
|
1175
|
+
console.log(`${i + 1}. ${choice.text}`);
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
```
|