qnce-engine 1.2.0 โ 1.2.2
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 +713 -7
- package/dist/cli/audit.js +0 -0
- package/dist/cli/init.js +0 -0
- package/dist/cli/perf.d.ts.map +1 -1
- package/dist/cli/perf.js +2 -1
- package/dist/cli/perf.js.map +1 -1
- 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 +274 -3
- package/dist/engine/core.d.ts.map +1 -1
- package/dist/engine/core.js +1148 -9
- package/dist/engine/core.js.map +1 -1
- package/dist/engine/demo-story.d.ts.map +1 -1
- package/dist/engine/demo-story.js +99 -13
- 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 +12 -1
- 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.js +3 -3
- package/dist/narrative/branching/engine-simple.js.map +1 -1
- 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/models.d.ts.map +1 -1
- package/dist/performance/HotReloadDelta.d.ts +25 -8
- package/dist/performance/HotReloadDelta.d.ts.map +1 -1
- package/dist/performance/HotReloadDelta.js +10 -15
- package/dist/performance/HotReloadDelta.js.map +1 -1
- 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/examples/autosave-undo-demo.ts +306 -0
- package/examples/branching-demo-simple.ts +0 -0
- package/examples/branching-demo.ts +0 -0
- package/examples/persistence-demo.ts +84 -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 +24 -4
- package/docs/branching/PDM.md +0 -443
- package/docs/branching/RELEASE-v1.2.0.md +0 -169
package/README.md
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
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
|
-
> **๐
|
|
5
|
+
> **๐ Latest v1.2.2:** 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)
|
|
6
10
|
|
|
7
11
|
## Core Concepts
|
|
8
12
|
|
|
@@ -10,7 +14,13 @@
|
|
|
10
14
|
- **Collapse:** Player choices "collapse" the narrative to a specific path, updating state and flags
|
|
11
15
|
- **Entanglement:** Early decisions affect later outcomes, enabling complex, interconnected stories
|
|
12
16
|
|
|
13
|
-
## โจ
|
|
17
|
+
## โจ Current Features (v1.2.2)
|
|
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
|
|
14
24
|
|
|
15
25
|
### ๐ฟ Advanced Branching System
|
|
16
26
|
- **Multi-path narratives** with conditional logic and flag-based branching
|
|
@@ -18,6 +28,197 @@
|
|
|
18
28
|
- **Real-time branch insertion/removal** for live content updates
|
|
19
29
|
- **Comprehensive analytics** for narrative optimization
|
|
20
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
|
+
|
|
21
222
|
### โก Performance Infrastructure
|
|
22
223
|
- **๐โโ๏ธ Object Pooling:** 90%+ allocation reduction, eliminating GC pressure
|
|
23
224
|
- **๐งต Background Processing:** Non-blocking cache preloading and telemetry writes
|
|
@@ -96,6 +297,129 @@ const poolStats = engine.getPoolStats();
|
|
|
96
297
|
console.log(`Pool efficiency: ${poolStats.flow.hitRate}%`);
|
|
97
298
|
```
|
|
98
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.2 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
|
+
|
|
99
423
|
### Live Performance Monitoring
|
|
100
424
|
|
|
101
425
|
```bash
|
|
@@ -269,6 +593,7 @@ The main engine class for managing narrative state.
|
|
|
269
593
|
#### Methods
|
|
270
594
|
|
|
271
595
|
- `getCurrentNode()`: Get the current narrative node
|
|
596
|
+
- `goToNodeById(nodeId)`: Navigate directly to a node by its ID
|
|
272
597
|
- `getState()`: Get the complete engine state
|
|
273
598
|
- `getFlags()`: Get current narrative flags
|
|
274
599
|
- `getHistory()`: Get choice history
|
|
@@ -335,26 +660,267 @@ Creates:
|
|
|
335
660
|
- package.json with QNCE dependencies
|
|
336
661
|
- README with usage instructions
|
|
337
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
|
+
**Current in v1.2.2:** 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
|
+
|
|
338
724
|
## Integration Examples
|
|
339
725
|
|
|
340
|
-
### React
|
|
726
|
+
### React Hooks
|
|
727
|
+
|
|
728
|
+
QNCE provides comprehensive React hooks for seamless integration:
|
|
341
729
|
|
|
342
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';
|
|
343
799
|
import { createQNCEEngine } from 'qnce-engine';
|
|
344
|
-
|
|
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';
|
|
345
905
|
|
|
346
906
|
function useQNCE(storyData) {
|
|
347
907
|
const [engine] = useState(() => createQNCEEngine(storyData));
|
|
348
908
|
const [currentNode, setCurrentNode] = useState(engine.getCurrentNode());
|
|
349
909
|
const [flags, setFlags] = useState(engine.getFlags());
|
|
350
910
|
|
|
351
|
-
const selectChoice = (choice) => {
|
|
911
|
+
const selectChoice = useCallback((choice) => {
|
|
352
912
|
engine.selectChoice(choice);
|
|
353
913
|
setCurrentNode(engine.getCurrentNode());
|
|
354
914
|
setFlags(engine.getFlags());
|
|
355
|
-
};
|
|
915
|
+
}, [engine]);
|
|
356
916
|
|
|
357
|
-
|
|
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 };
|
|
358
924
|
}
|
|
359
925
|
```
|
|
360
926
|
|
|
@@ -424,6 +990,12 @@ The repository includes comprehensive examples demonstrating all features:
|
|
|
424
990
|
- **Features:** Complex narrative flows, conditional branching, analytics
|
|
425
991
|
- **Story:** "The Mysterious Library" - Interactive mystery with multiple paths
|
|
426
992
|
|
|
993
|
+
### ๐พ Autosave & Undo Demo (Available in v1.2.2)
|
|
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
|
+
|
|
427
999
|
### ๐งช Validation Scripts
|
|
428
1000
|
- **Real-world testing:** `scripts/validation-real-world.ts`
|
|
429
1001
|
- **Comprehensive testing:** `scripts/validation-comprehensive.ts`
|
|
@@ -433,6 +1005,13 @@ The repository includes comprehensive examples demonstrating all features:
|
|
|
433
1005
|
npm run build
|
|
434
1006
|
node dist/examples/branching-quickstart.js
|
|
435
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
|
+
|
|
436
1015
|
# Run validation tests
|
|
437
1016
|
npm run build
|
|
438
1017
|
node dist/scripts/validation-real-world.js
|
|
@@ -471,3 +1050,130 @@ MIT - See LICENSE file for details.
|
|
|
471
1050
|
---
|
|
472
1051
|
|
|
473
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
|
+
```
|