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
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// QNCE Choice Validation System - Sprint 3.2
|
|
3
|
+
// Ensures only valid choices can be executed, providing robust error handling
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.DefaultChoiceValidator = exports.StandardValidationRules = void 0;
|
|
6
|
+
exports.createChoiceValidator = createChoiceValidator;
|
|
7
|
+
exports.createValidationContext = createValidationContext;
|
|
8
|
+
/**
|
|
9
|
+
* Built-in validation rules for common scenarios
|
|
10
|
+
*/
|
|
11
|
+
class StandardValidationRules {
|
|
12
|
+
/**
|
|
13
|
+
* Validates that choice exists in the current node's choices
|
|
14
|
+
*/
|
|
15
|
+
static CHOICE_EXISTS = {
|
|
16
|
+
name: 'choice-exists',
|
|
17
|
+
priority: 1,
|
|
18
|
+
validate(choice, context) {
|
|
19
|
+
const exists = context.currentNode.choices.some(c => c.text === choice.text && c.nextNodeId === choice.nextNodeId);
|
|
20
|
+
return {
|
|
21
|
+
isValid: exists,
|
|
22
|
+
reason: exists ? undefined : `Choice "${choice.text}" is not available from current node`,
|
|
23
|
+
suggestedChoices: exists ? undefined : context.currentNode.choices
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Validates flag-based conditions for choice availability
|
|
29
|
+
*/
|
|
30
|
+
static FLAG_CONDITIONS = {
|
|
31
|
+
name: 'flag-conditions',
|
|
32
|
+
priority: 2,
|
|
33
|
+
validate(choice, context) {
|
|
34
|
+
// Check if choice has flag requirements
|
|
35
|
+
if (choice.flagRequirements) {
|
|
36
|
+
const missingFlags = [];
|
|
37
|
+
const conflictingFlags = [];
|
|
38
|
+
for (const [flagName, requiredValue] of Object.entries(choice.flagRequirements)) {
|
|
39
|
+
const currentValue = context.state.flags[flagName];
|
|
40
|
+
if (requiredValue === true && !currentValue) {
|
|
41
|
+
missingFlags.push(flagName);
|
|
42
|
+
}
|
|
43
|
+
else if (requiredValue === false && currentValue) {
|
|
44
|
+
conflictingFlags.push(flagName);
|
|
45
|
+
}
|
|
46
|
+
else if (typeof requiredValue !== 'boolean' && currentValue !== requiredValue) {
|
|
47
|
+
missingFlags.push(`${flagName}=${requiredValue}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (missingFlags.length > 0 || conflictingFlags.length > 0) {
|
|
51
|
+
const reasons = [];
|
|
52
|
+
if (missingFlags.length > 0) {
|
|
53
|
+
reasons.push(`Missing required flags: ${missingFlags.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
if (conflictingFlags.length > 0) {
|
|
56
|
+
reasons.push(`Conflicting flags: ${conflictingFlags.join(', ')}`);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
isValid: false,
|
|
60
|
+
reason: `Flag conditions not met: ${reasons.join('; ')}`,
|
|
61
|
+
failedConditions: [...missingFlags, ...conflictingFlags],
|
|
62
|
+
metadata: {
|
|
63
|
+
missingFlags,
|
|
64
|
+
conflictingFlags,
|
|
65
|
+
currentFlags: context.state.flags
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
isValid: true,
|
|
72
|
+
reason: undefined
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Validates choice is not disabled or marked as unavailable
|
|
78
|
+
*/
|
|
79
|
+
static CHOICE_ENABLED = {
|
|
80
|
+
name: 'choice-enabled',
|
|
81
|
+
priority: 3,
|
|
82
|
+
validate(choice, _context) {
|
|
83
|
+
if (choice.enabled === false) {
|
|
84
|
+
return {
|
|
85
|
+
isValid: false,
|
|
86
|
+
reason: `Choice "${choice.text}" is currently disabled`,
|
|
87
|
+
failedConditions: ['choice-disabled']
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
isValid: true,
|
|
92
|
+
reason: undefined
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Validates time-based requirements for choice availability
|
|
98
|
+
*/
|
|
99
|
+
static TIME_CONDITIONS = {
|
|
100
|
+
name: 'time-conditions',
|
|
101
|
+
priority: 4,
|
|
102
|
+
validate(choice, context) {
|
|
103
|
+
if (choice.timeRequirements) {
|
|
104
|
+
const now = new Date();
|
|
105
|
+
const currentTime = context.timestamp || now.getTime();
|
|
106
|
+
const failures = [];
|
|
107
|
+
if (choice.timeRequirements.availableAfter) {
|
|
108
|
+
const afterTime = new Date(choice.timeRequirements.availableAfter).getTime();
|
|
109
|
+
if (currentTime < afterTime) {
|
|
110
|
+
failures.push(`not available until ${choice.timeRequirements.availableAfter}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (choice.timeRequirements.availableBefore) {
|
|
114
|
+
const beforeTime = new Date(choice.timeRequirements.availableBefore).getTime();
|
|
115
|
+
if (currentTime > beforeTime) {
|
|
116
|
+
failures.push(`no longer available after ${choice.timeRequirements.availableBefore}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (choice.timeRequirements.minTime && currentTime < choice.timeRequirements.minTime) {
|
|
120
|
+
failures.push(`minimum time not reached`);
|
|
121
|
+
}
|
|
122
|
+
if (choice.timeRequirements.maxTime && currentTime > choice.timeRequirements.maxTime) {
|
|
123
|
+
failures.push(`maximum time exceeded`);
|
|
124
|
+
}
|
|
125
|
+
if (failures.length > 0) {
|
|
126
|
+
return {
|
|
127
|
+
isValid: false,
|
|
128
|
+
reason: `Time conditions not met: ${failures.join(', ')}`,
|
|
129
|
+
failedConditions: failures,
|
|
130
|
+
metadata: {
|
|
131
|
+
currentTime,
|
|
132
|
+
timeRequirements: choice.timeRequirements
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
isValid: true,
|
|
139
|
+
reason: undefined
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Validates inventory-based requirements for choice availability
|
|
145
|
+
*/
|
|
146
|
+
static INVENTORY_CONDITIONS = {
|
|
147
|
+
name: 'inventory-conditions',
|
|
148
|
+
priority: 5,
|
|
149
|
+
validate(choice, context) {
|
|
150
|
+
if (choice.inventoryRequirements) {
|
|
151
|
+
const inventory = context.state.flags.inventory || {};
|
|
152
|
+
const missingItems = [];
|
|
153
|
+
for (const [itemName, requiredQuantity] of Object.entries(choice.inventoryRequirements)) {
|
|
154
|
+
const currentQuantity = inventory[itemName] || 0;
|
|
155
|
+
if (currentQuantity < requiredQuantity) {
|
|
156
|
+
missingItems.push(`${itemName} (need ${requiredQuantity}, have ${currentQuantity})`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (missingItems.length > 0) {
|
|
160
|
+
return {
|
|
161
|
+
isValid: false,
|
|
162
|
+
reason: `Insufficient inventory: ${missingItems.join(', ')}`,
|
|
163
|
+
failedConditions: missingItems,
|
|
164
|
+
metadata: {
|
|
165
|
+
currentInventory: inventory,
|
|
166
|
+
requirements: choice.inventoryRequirements
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
isValid: true,
|
|
173
|
+
reason: undefined
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
exports.StandardValidationRules = StandardValidationRules;
|
|
179
|
+
/**
|
|
180
|
+
* Default implementation of ChoiceValidator
|
|
181
|
+
* Uses a rule-based system for extensible validation logic
|
|
182
|
+
*/
|
|
183
|
+
class DefaultChoiceValidator {
|
|
184
|
+
rules = [];
|
|
185
|
+
constructor() {
|
|
186
|
+
// Add standard validation rules by default
|
|
187
|
+
this.addRule(StandardValidationRules.CHOICE_EXISTS);
|
|
188
|
+
this.addRule(StandardValidationRules.FLAG_CONDITIONS);
|
|
189
|
+
this.addRule(StandardValidationRules.CHOICE_ENABLED);
|
|
190
|
+
this.addRule(StandardValidationRules.TIME_CONDITIONS);
|
|
191
|
+
this.addRule(StandardValidationRules.INVENTORY_CONDITIONS);
|
|
192
|
+
}
|
|
193
|
+
validate(choice, context) {
|
|
194
|
+
// Run all validation rules in priority order
|
|
195
|
+
const sortedRules = this.getRules();
|
|
196
|
+
for (const rule of sortedRules) {
|
|
197
|
+
const result = rule.validate(choice, context);
|
|
198
|
+
// If any rule fails, return that failure
|
|
199
|
+
if (!result.isValid) {
|
|
200
|
+
return {
|
|
201
|
+
...result,
|
|
202
|
+
metadata: {
|
|
203
|
+
...result.metadata,
|
|
204
|
+
failedRule: rule.name,
|
|
205
|
+
rulesPassed: sortedRules.indexOf(rule)
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// All rules passed
|
|
211
|
+
return {
|
|
212
|
+
isValid: true,
|
|
213
|
+
metadata: {
|
|
214
|
+
rulesChecked: sortedRules.length,
|
|
215
|
+
allRulesPassed: true
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
getAvailableChoices(context) {
|
|
220
|
+
// Use the availableChoices from context (which may have been pre-filtered by conditions)
|
|
221
|
+
// instead of all choices from the node
|
|
222
|
+
return context.availableChoices.filter(choice => {
|
|
223
|
+
const result = this.validate(choice, context);
|
|
224
|
+
return result.isValid;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
addRule(rule) {
|
|
228
|
+
// Remove any existing rule with the same name
|
|
229
|
+
this.removeRule(rule.name);
|
|
230
|
+
// Add the new rule
|
|
231
|
+
this.rules.push(rule);
|
|
232
|
+
// Sort by priority
|
|
233
|
+
this.rules.sort((a, b) => a.priority - b.priority);
|
|
234
|
+
}
|
|
235
|
+
removeRule(ruleName) {
|
|
236
|
+
this.rules = this.rules.filter(rule => rule.name !== ruleName);
|
|
237
|
+
}
|
|
238
|
+
getRules() {
|
|
239
|
+
return [...this.rules].sort((a, b) => a.priority - b.priority);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
exports.DefaultChoiceValidator = DefaultChoiceValidator;
|
|
243
|
+
/**
|
|
244
|
+
* Factory function to create a choice validator with default rules
|
|
245
|
+
*/
|
|
246
|
+
function createChoiceValidator() {
|
|
247
|
+
return new DefaultChoiceValidator();
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Utility function to create validation context from engine state
|
|
251
|
+
*/
|
|
252
|
+
function createValidationContext(currentNode, state, availableChoices, metadata) {
|
|
253
|
+
return {
|
|
254
|
+
currentNode,
|
|
255
|
+
state,
|
|
256
|
+
availableChoices,
|
|
257
|
+
timestamp: performance.now(),
|
|
258
|
+
metadata
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/engine/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* QNCE Autosave & Undo/Redo Demo
|
|
4
|
+
*
|
|
5
|
+
* Demonstrates the Sprint 3.5 autosave and undo/redo functionality
|
|
6
|
+
* with interactive examples and performance metrics.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
const core_js_1 = require("../src/engine/core.js");
|
|
10
|
+
const demo_story_js_1 = require("../src/engine/demo-story.js");
|
|
11
|
+
console.log('🔄 QNCE Autosave & Undo/Redo Demo - Sprint 3.5');
|
|
12
|
+
console.log('='.repeat(50));
|
|
13
|
+
async function demonstrateUndoRedo() {
|
|
14
|
+
console.log('\n📝 Demonstrating Undo/Redo Functionality');
|
|
15
|
+
console.log('-'.repeat(40));
|
|
16
|
+
const engine = (0, core_js_1.createQNCEEngine)(demo_story_js_1.DEMO_STORY);
|
|
17
|
+
// Configure undo/redo with detailed logging
|
|
18
|
+
engine.configureUndoRedo({
|
|
19
|
+
enabled: true,
|
|
20
|
+
maxUndoEntries: 10,
|
|
21
|
+
maxRedoEntries: 5
|
|
22
|
+
});
|
|
23
|
+
console.log('Initial state:', engine.getCurrentNode().text.substring(0, 50) + '...');
|
|
24
|
+
console.log('Can undo:', engine.canUndo());
|
|
25
|
+
console.log('Can redo:', engine.canRedo());
|
|
26
|
+
// Make some choices
|
|
27
|
+
console.log('\n🎮 Making choices...');
|
|
28
|
+
const choices1 = engine.getAvailableChoices();
|
|
29
|
+
if (choices1.length > 0) {
|
|
30
|
+
console.log(`Selecting: "${choices1[0].text}"`);
|
|
31
|
+
engine.selectChoice(choices1[0]);
|
|
32
|
+
console.log('New state:', engine.getCurrentNode().text.substring(0, 50) + '...');
|
|
33
|
+
console.log('Undo count:', engine.getUndoCount());
|
|
34
|
+
}
|
|
35
|
+
const choices2 = engine.getAvailableChoices();
|
|
36
|
+
if (choices2.length > 0) {
|
|
37
|
+
console.log(`Selecting: "${choices2[0].text}"`);
|
|
38
|
+
engine.selectChoice(choices2[0]);
|
|
39
|
+
console.log('New state:', engine.getCurrentNode().text.substring(0, 50) + '...');
|
|
40
|
+
console.log('Undo count:', engine.getUndoCount());
|
|
41
|
+
}
|
|
42
|
+
// Demonstrate undo
|
|
43
|
+
console.log('\n⏪ Testing undo...');
|
|
44
|
+
const undoResult1 = engine.undo();
|
|
45
|
+
if (undoResult1.success) {
|
|
46
|
+
console.log('✅ Undo successful');
|
|
47
|
+
console.log('Restored to:', engine.getCurrentNode().text.substring(0, 50) + '...');
|
|
48
|
+
console.log('Can redo:', engine.canRedo());
|
|
49
|
+
}
|
|
50
|
+
const undoResult2 = engine.undo();
|
|
51
|
+
if (undoResult2.success) {
|
|
52
|
+
console.log('✅ Second undo successful');
|
|
53
|
+
console.log('Restored to:', engine.getCurrentNode().text.substring(0, 50) + '...');
|
|
54
|
+
console.log('Undo count:', engine.getUndoCount());
|
|
55
|
+
console.log('Redo count:', engine.getRedoCount());
|
|
56
|
+
}
|
|
57
|
+
// Demonstrate redo
|
|
58
|
+
console.log('\n⏩ Testing redo...');
|
|
59
|
+
const redoResult = engine.redo();
|
|
60
|
+
if (redoResult.success) {
|
|
61
|
+
console.log('✅ Redo successful');
|
|
62
|
+
console.log('Restored to:', engine.getCurrentNode().text.substring(0, 50) + '...');
|
|
63
|
+
console.log('Final state - Undo:', engine.getUndoCount(), 'Redo:', engine.getRedoCount());
|
|
64
|
+
}
|
|
65
|
+
// Show history summary
|
|
66
|
+
const history = engine.getHistorySummary();
|
|
67
|
+
console.log('\n📊 History Summary:');
|
|
68
|
+
console.log(`- Undo entries: ${history.undoEntries.length}`);
|
|
69
|
+
console.log(`- Redo entries: ${history.redoEntries.length}`);
|
|
70
|
+
return engine;
|
|
71
|
+
}
|
|
72
|
+
async function demonstrateAutosave() {
|
|
73
|
+
console.log('\n💾 Demonstrating Autosave Functionality');
|
|
74
|
+
console.log('-'.repeat(40));
|
|
75
|
+
const engine = (0, core_js_1.createQNCEEngine)(demo_story_js_1.DEMO_STORY);
|
|
76
|
+
// Configure autosave with custom settings
|
|
77
|
+
engine.configureAutosave({
|
|
78
|
+
enabled: true,
|
|
79
|
+
triggers: ['choice', 'flag-change'],
|
|
80
|
+
throttleMs: 50, // Very fast for demo
|
|
81
|
+
maxEntries: 5,
|
|
82
|
+
includeMetadata: true
|
|
83
|
+
});
|
|
84
|
+
console.log('Autosave configured with 50ms throttle');
|
|
85
|
+
// Track autosave events
|
|
86
|
+
let autosaveCount = 0;
|
|
87
|
+
const originalMethod = engine.manualAutosave.bind(engine);
|
|
88
|
+
engine.manualAutosave = async (metadata) => {
|
|
89
|
+
autosaveCount++;
|
|
90
|
+
console.log(`🔄 Autosave #${autosaveCount} triggered`);
|
|
91
|
+
return originalMethod(metadata);
|
|
92
|
+
};
|
|
93
|
+
// Make choices to trigger autosave
|
|
94
|
+
console.log('\n🎮 Making choices (autosave should trigger)...');
|
|
95
|
+
for (let i = 0; i < 3; i++) {
|
|
96
|
+
const choices = engine.getAvailableChoices();
|
|
97
|
+
if (choices.length > 0) {
|
|
98
|
+
console.log(`Choice ${i + 1}: "${choices[0].text}"`);
|
|
99
|
+
engine.selectChoice(choices[0]);
|
|
100
|
+
// Small delay to observe throttling
|
|
101
|
+
await new Promise(resolve => setTimeout(resolve, 25));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Set some flags to trigger more autosaves
|
|
105
|
+
console.log('\n🏁 Setting flags (autosave should trigger)...');
|
|
106
|
+
engine.setFlag('demo_flag_1', true);
|
|
107
|
+
await new Promise(resolve => setTimeout(resolve, 60));
|
|
108
|
+
engine.setFlag('demo_flag_2', 'test_value');
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, 60));
|
|
110
|
+
// Manual autosave
|
|
111
|
+
console.log('\n💾 Triggering manual autosave...');
|
|
112
|
+
await engine.manualAutosave({ demo: 'manual_save' });
|
|
113
|
+
console.log(`\n📊 Total autosaves triggered: ${autosaveCount}`);
|
|
114
|
+
return engine;
|
|
115
|
+
}
|
|
116
|
+
async function demonstratePerformance() {
|
|
117
|
+
console.log('\n⚡ Performance Testing');
|
|
118
|
+
console.log('-'.repeat(40));
|
|
119
|
+
const engine = (0, core_js_1.createQNCEEngine)(demo_story_js_1.DEMO_STORY);
|
|
120
|
+
// Configure for performance testing
|
|
121
|
+
engine.configureUndoRedo({
|
|
122
|
+
enabled: true,
|
|
123
|
+
maxUndoEntries: 100,
|
|
124
|
+
maxRedoEntries: 50
|
|
125
|
+
});
|
|
126
|
+
// Build up some history
|
|
127
|
+
console.log('Building up history for performance test...');
|
|
128
|
+
for (let i = 0; i < 20; i++) {
|
|
129
|
+
const choices = engine.getAvailableChoices();
|
|
130
|
+
if (choices.length > 0) {
|
|
131
|
+
engine.selectChoice(choices[0]);
|
|
132
|
+
}
|
|
133
|
+
// Set some flags
|
|
134
|
+
engine.setFlag(`test_flag_${i}`, i * 2);
|
|
135
|
+
}
|
|
136
|
+
console.log(`History built: ${engine.getUndoCount()} undo entries`);
|
|
137
|
+
// Performance test undo operations
|
|
138
|
+
console.log('\n⏪ Testing undo performance...');
|
|
139
|
+
const undoTimes = [];
|
|
140
|
+
for (let i = 0; i < 10; i++) {
|
|
141
|
+
const startTime = performance.now();
|
|
142
|
+
const result = engine.undo();
|
|
143
|
+
const endTime = performance.now();
|
|
144
|
+
if (result.success) {
|
|
145
|
+
undoTimes.push(endTime - startTime);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const avgUndoTime = undoTimes.reduce((a, b) => a + b, 0) / undoTimes.length;
|
|
149
|
+
const maxUndoTime = Math.max(...undoTimes);
|
|
150
|
+
console.log(`Average undo time: ${avgUndoTime.toFixed(3)}ms`);
|
|
151
|
+
console.log(`Maximum undo time: ${maxUndoTime.toFixed(3)}ms`);
|
|
152
|
+
console.log(`Target: <1ms ${avgUndoTime < 1 ? '✅' : '❌'}`);
|
|
153
|
+
// Performance test redo operations
|
|
154
|
+
console.log('\n⏩ Testing redo performance...');
|
|
155
|
+
const redoTimes = [];
|
|
156
|
+
for (let i = 0; i < Math.min(10, engine.getRedoCount()); i++) {
|
|
157
|
+
const startTime = performance.now();
|
|
158
|
+
const result = engine.redo();
|
|
159
|
+
const endTime = performance.now();
|
|
160
|
+
if (result.success) {
|
|
161
|
+
redoTimes.push(endTime - startTime);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (redoTimes.length > 0) {
|
|
165
|
+
const avgRedoTime = redoTimes.reduce((a, b) => a + b, 0) / redoTimes.length;
|
|
166
|
+
const maxRedoTime = Math.max(...redoTimes);
|
|
167
|
+
console.log(`Average redo time: ${avgRedoTime.toFixed(3)}ms`);
|
|
168
|
+
console.log(`Maximum redo time: ${maxRedoTime.toFixed(3)}ms`);
|
|
169
|
+
console.log(`Target: <1ms ${avgRedoTime < 1 ? '✅' : '❌'}`);
|
|
170
|
+
}
|
|
171
|
+
// Test autosave performance
|
|
172
|
+
console.log('\n💾 Testing autosave performance...');
|
|
173
|
+
const autosaveTimes = [];
|
|
174
|
+
for (let i = 0; i < 5; i++) {
|
|
175
|
+
const startTime = performance.now();
|
|
176
|
+
await engine.manualAutosave({ test: `performance_${i}` });
|
|
177
|
+
const endTime = performance.now();
|
|
178
|
+
autosaveTimes.push(endTime - startTime);
|
|
179
|
+
}
|
|
180
|
+
const avgAutosaveTime = autosaveTimes.reduce((a, b) => a + b, 0) / autosaveTimes.length;
|
|
181
|
+
const maxAutosaveTime = Math.max(...autosaveTimes);
|
|
182
|
+
console.log(`Average autosave time: ${avgAutosaveTime.toFixed(3)}ms`);
|
|
183
|
+
console.log(`Maximum autosave time: ${maxAutosaveTime.toFixed(3)}ms`);
|
|
184
|
+
console.log(`Target: <1ms ${avgAutosaveTime < 1 ? '✅' : '❌'}`);
|
|
185
|
+
}
|
|
186
|
+
async function demonstrateIntegration() {
|
|
187
|
+
console.log('\n🔗 Integration with Existing Features');
|
|
188
|
+
console.log('-'.repeat(40));
|
|
189
|
+
const engine = (0, core_js_1.createQNCEEngine)(demo_story_js_1.DEMO_STORY);
|
|
190
|
+
// Enable all features
|
|
191
|
+
engine.configureUndoRedo({ enabled: true, maxUndoEntries: 50, maxRedoEntries: 25 });
|
|
192
|
+
engine.configureAutosave({
|
|
193
|
+
enabled: true,
|
|
194
|
+
triggers: ['choice', 'flag-change', 'state-load'],
|
|
195
|
+
throttleMs: 100,
|
|
196
|
+
maxEntries: 10,
|
|
197
|
+
includeMetadata: true
|
|
198
|
+
});
|
|
199
|
+
console.log('✅ Undo/redo and autosave configured');
|
|
200
|
+
// Test with state persistence
|
|
201
|
+
console.log('\n💾 Testing state save/load with undo/redo...');
|
|
202
|
+
// Make some changes
|
|
203
|
+
const choices = engine.getAvailableChoices();
|
|
204
|
+
if (choices.length > 0) {
|
|
205
|
+
engine.selectChoice(choices[0]);
|
|
206
|
+
}
|
|
207
|
+
engine.setFlag('integration_test', true);
|
|
208
|
+
console.log(`State before save - Undo count: ${engine.getUndoCount()}`);
|
|
209
|
+
// Save state
|
|
210
|
+
const savedState = await engine.saveState();
|
|
211
|
+
console.log('✅ State saved successfully');
|
|
212
|
+
// Make more changes
|
|
213
|
+
const choices2 = engine.getAvailableChoices();
|
|
214
|
+
if (choices2.length > 0) {
|
|
215
|
+
engine.selectChoice(choices2[0]);
|
|
216
|
+
}
|
|
217
|
+
engine.setFlag('after_save', 'test');
|
|
218
|
+
console.log(`State after more changes - Undo count: ${engine.getUndoCount()}`);
|
|
219
|
+
// Load previous state
|
|
220
|
+
await engine.loadState(savedState);
|
|
221
|
+
console.log('✅ State loaded successfully');
|
|
222
|
+
console.log(`State after load - Undo count: ${engine.getUndoCount()}`);
|
|
223
|
+
console.log('Flags:', Object.keys(engine.getState().flags));
|
|
224
|
+
// Test undo after load
|
|
225
|
+
const undoAfterLoad = engine.undo();
|
|
226
|
+
console.log(`Undo after load: ${undoAfterLoad.success ? '✅ Success' : '❌ Failed'}`);
|
|
227
|
+
}
|
|
228
|
+
async function main() {
|
|
229
|
+
try {
|
|
230
|
+
await demonstrateUndoRedo();
|
|
231
|
+
await demonstrateAutosave();
|
|
232
|
+
await demonstratePerformance();
|
|
233
|
+
await demonstrateIntegration();
|
|
234
|
+
console.log('\n🎉 Demo completed successfully!');
|
|
235
|
+
console.log('\nSprint 3.5 Features Demonstrated:');
|
|
236
|
+
console.log('✅ Undo/Redo with configurable history limits');
|
|
237
|
+
console.log('✅ Autosave with throttling and event triggers');
|
|
238
|
+
console.log('✅ Sub-millisecond performance for operations');
|
|
239
|
+
console.log('✅ Integration with existing state persistence');
|
|
240
|
+
console.log('✅ Memory-efficient history management');
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
console.error('❌ Demo failed:', error);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Run the demo
|
|
248
|
+
main();
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const src_1 = require("../src");
|
|
4
|
+
/**
|
|
5
|
+
* Demonstrates the state persistence and checkpoint features of the QNCE Engine.
|
|
6
|
+
* This example covers:
|
|
7
|
+
* 1. Saving the complete narrative state.
|
|
8
|
+
* 2. Loading that state into a new engine instance.
|
|
9
|
+
* 3. Creating lightweight checkpoints for undo/redo functionality.
|
|
10
|
+
* 4. Restoring the state from a checkpoint.
|
|
11
|
+
*/
|
|
12
|
+
async function runPersistenceDemo() {
|
|
13
|
+
console.log('🚀 Starting QNCE Engine Persistence Demo 🚀\n');
|
|
14
|
+
// 1. Initialize the engine and advance the story
|
|
15
|
+
const engine = (0, src_1.createQNCEEngine)(src_1.DEMO_STORY);
|
|
16
|
+
console.log('Initial Node:', engine.getCurrentNode().text);
|
|
17
|
+
let choices = engine.getAvailableChoices();
|
|
18
|
+
engine.selectChoice(choices[1]); // Choose "Examine the shimmering portal."
|
|
19
|
+
console.log('\nChoice Made. Current Node:', engine.getCurrentNode().text);
|
|
20
|
+
choices = engine.getAvailableChoices();
|
|
21
|
+
engine.selectChoice(choices[0]); // Choose "Step through the portal."
|
|
22
|
+
console.log('Choice Made. Current Node:', engine.getCurrentNode().text);
|
|
23
|
+
console.log('Current Flags:', engine.getFlags());
|
|
24
|
+
// 2. Create a checkpoint before making another choice
|
|
25
|
+
console.log('\n💾 Creating a checkpoint...');
|
|
26
|
+
const checkpoint = await engine.createCheckpoint('Before the final choice');
|
|
27
|
+
console.log(`Checkpoint '${checkpoint.name}' created with ID: ${checkpoint.id}`);
|
|
28
|
+
// 3. Save the complete engine state to a string
|
|
29
|
+
console.log('\n💾 Saving engine state to JSON...');
|
|
30
|
+
const savedState = await engine.saveState({ prettyPrint: true });
|
|
31
|
+
const savedStateJSON = JSON.stringify(savedState);
|
|
32
|
+
console.log('State saved successfully! Size:', savedStateJSON.length, 'bytes');
|
|
33
|
+
// In a real application, you would store this JSON string in localStorage,
|
|
34
|
+
// a file, or a remote database.
|
|
35
|
+
// 4. Continue the story
|
|
36
|
+
choices = engine.getAvailableChoices();
|
|
37
|
+
engine.selectChoice(choices[0]);
|
|
38
|
+
console.log('\nMade one more choice. Current Node:', engine.getCurrentNode().text);
|
|
39
|
+
console.log('Final Flags:', engine.getFlags());
|
|
40
|
+
// 5. Restore from the checkpoint to "undo" the last choice
|
|
41
|
+
console.log(`\n🔄 Restoring from checkpoint '${checkpoint.name}'...`);
|
|
42
|
+
await engine.restoreFromCheckpoint(checkpoint.id);
|
|
43
|
+
console.log('State restored from checkpoint!');
|
|
44
|
+
console.log('Current Node after restore:', engine.getCurrentNode().text);
|
|
45
|
+
console.log('Flags after restore:', engine.getFlags());
|
|
46
|
+
// 6. Create a new engine instance and load the saved state
|
|
47
|
+
console.log('\n🔄 Creating a new engine and loading the saved state...');
|
|
48
|
+
const newEngine = (0, src_1.createQNCEEngine)(src_1.DEMO_STORY);
|
|
49
|
+
// The state can be loaded from a JSON string or a parsed object
|
|
50
|
+
await newEngine.loadState(savedState);
|
|
51
|
+
console.log('State loaded into new engine instance!');
|
|
52
|
+
// 7. Verify that the state was restored correctly
|
|
53
|
+
console.log('\n🔍 Verifying restored state...');
|
|
54
|
+
console.log('Restored Node:', newEngine.getCurrentNode().text);
|
|
55
|
+
console.log('Restored Flags:', newEngine.getFlags());
|
|
56
|
+
console.log('History length:', newEngine.getHistory().length, '| Expected:', 2);
|
|
57
|
+
const areFlagsEqual = JSON.stringify(engine.getFlags()) === JSON.stringify(newEngine.getFlags());
|
|
58
|
+
console.log('Verification successful:', areFlagsEqual);
|
|
59
|
+
console.log('\n✅ Persistence Demo Completed Successfully! ✅');
|
|
60
|
+
}
|
|
61
|
+
runPersistenceDemo().catch(error => {
|
|
62
|
+
console.error('An error occurred during the persistence demo:', error);
|
|
63
|
+
});
|