qnce-engine 1.2.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 +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 +23 -3
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// QNCE Branching API - Simplified Runtime Implementation
|
|
3
|
+
// Sprint #3 - Advanced Narrative & AI Integration
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.default = exports.QNCEBranchingEngine = void 0;
|
|
6
|
+
exports.createBranchingEngine = createBranchingEngine;
|
|
7
|
+
/**
|
|
8
|
+
* QNCE Branching Engine - Core API for dynamic narrative branching
|
|
9
|
+
* Simplified implementation focusing on core functionality
|
|
10
|
+
*/
|
|
11
|
+
class QNCEBranchingEngine {
|
|
12
|
+
story;
|
|
13
|
+
context;
|
|
14
|
+
aiContext;
|
|
15
|
+
constructor(story, initialState) {
|
|
16
|
+
this.story = story;
|
|
17
|
+
this.context = this.createBranchContext(story, initialState);
|
|
18
|
+
}
|
|
19
|
+
// ================================
|
|
20
|
+
// Core Branching Operations
|
|
21
|
+
// ================================
|
|
22
|
+
/**
|
|
23
|
+
* Evaluate available branches from current position
|
|
24
|
+
*/
|
|
25
|
+
async evaluateAvailableBranches() {
|
|
26
|
+
const currentNode = this.getCurrentNode();
|
|
27
|
+
const availableBranches = this.findBranchPointsForNode(this.context.currentFlow.id, currentNode.id);
|
|
28
|
+
const validOptions = [];
|
|
29
|
+
for (const branchPoint of availableBranches) {
|
|
30
|
+
// Evaluate branch point conditions
|
|
31
|
+
if (await this.evaluateConditions(branchPoint.conditions || [])) {
|
|
32
|
+
// Evaluate individual option conditions
|
|
33
|
+
for (const option of branchPoint.branchOptions) {
|
|
34
|
+
if (await this.evaluateConditions(option.conditions || [])) {
|
|
35
|
+
validOptions.push(option);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return validOptions;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Execute a branch transition
|
|
44
|
+
*/
|
|
45
|
+
async executeBranch(optionId) {
|
|
46
|
+
const option = await this.findBranchOption(optionId);
|
|
47
|
+
if (!option) {
|
|
48
|
+
throw new Error(`Branch option not found: ${optionId}`);
|
|
49
|
+
}
|
|
50
|
+
// Apply flag effects
|
|
51
|
+
if (option.flagEffects) {
|
|
52
|
+
Object.assign(this.context.activeState.flags, option.flagEffects);
|
|
53
|
+
}
|
|
54
|
+
// Execute flow transition
|
|
55
|
+
const targetFlow = this.findFlow(option.targetFlowId);
|
|
56
|
+
if (!targetFlow) {
|
|
57
|
+
throw new Error(`Target flow not found: ${option.targetFlowId}`);
|
|
58
|
+
}
|
|
59
|
+
const success = await this.transitionToFlow(targetFlow, option.targetNodeId);
|
|
60
|
+
if (success) {
|
|
61
|
+
// Record branch execution in history
|
|
62
|
+
this.recordBranchHistory(optionId, option);
|
|
63
|
+
// Update analytics
|
|
64
|
+
this.updateBranchAnalytics(optionId);
|
|
65
|
+
}
|
|
66
|
+
return success;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Dynamic branch insertion at runtime
|
|
70
|
+
*/
|
|
71
|
+
async insertDynamicBranch(operation) {
|
|
72
|
+
// Validate operation conditions
|
|
73
|
+
if (operation.conditions && !await this.evaluateConditions(operation.conditions)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
// Find target location
|
|
77
|
+
const chapter = this.findChapter(operation.targetLocation.chapterId);
|
|
78
|
+
if (!chapter) {
|
|
79
|
+
throw new Error(`Chapter not found: ${operation.targetLocation.chapterId}`);
|
|
80
|
+
}
|
|
81
|
+
// Create new branch point
|
|
82
|
+
const branchPoint = {
|
|
83
|
+
id: operation.branchId,
|
|
84
|
+
name: operation.payload?.name || `Dynamic Branch ${operation.branchId}`,
|
|
85
|
+
sourceFlowId: operation.targetLocation.flowId,
|
|
86
|
+
sourceNodeId: operation.targetLocation.nodeId,
|
|
87
|
+
branchType: operation.payload?.branchType || 'conditional',
|
|
88
|
+
branchOptions: operation.payload?.branchOptions || [],
|
|
89
|
+
conditions: operation.conditions,
|
|
90
|
+
metadata: {
|
|
91
|
+
usageCount: 0,
|
|
92
|
+
avgTraversalTime: 0,
|
|
93
|
+
playerPreference: 0,
|
|
94
|
+
lastUsed: new Date()
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
// Insert into chapter
|
|
98
|
+
chapter.branches.push(branchPoint);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Remove dynamic branch
|
|
103
|
+
*/
|
|
104
|
+
async removeDynamicBranch(branchId) {
|
|
105
|
+
for (const chapter of this.story.chapters) {
|
|
106
|
+
const index = chapter.branches.findIndex(b => b.id === branchId);
|
|
107
|
+
if (index !== -1) {
|
|
108
|
+
chapter.branches.splice(index, 1);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// ================================
|
|
115
|
+
// AI Integration Methods
|
|
116
|
+
// ================================
|
|
117
|
+
/**
|
|
118
|
+
* Set AI context for enhanced branching decisions
|
|
119
|
+
*/
|
|
120
|
+
setAIContext(aiContext) {
|
|
121
|
+
this.aiContext = aiContext;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Generate AI-driven branch options
|
|
125
|
+
*/
|
|
126
|
+
async generateAIBranches(maxOptions = 3) {
|
|
127
|
+
if (!this.aiContext) {
|
|
128
|
+
throw new Error('AI context not set. Call setAIContext() first.');
|
|
129
|
+
}
|
|
130
|
+
const generatedOptions = [];
|
|
131
|
+
const playerProfile = this.aiContext.playerProfile;
|
|
132
|
+
// Example: Generate options based on player style
|
|
133
|
+
if (playerProfile.playStyle === 'explorer') {
|
|
134
|
+
generatedOptions.push({
|
|
135
|
+
id: `ai-explore-${Date.now()}`,
|
|
136
|
+
targetFlowId: this.context.currentFlow.id,
|
|
137
|
+
displayText: "Investigate the mysterious artifact",
|
|
138
|
+
weight: 0.8
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (playerProfile.playStyle === 'socializer') {
|
|
142
|
+
generatedOptions.push({
|
|
143
|
+
id: `ai-social-${Date.now()}`,
|
|
144
|
+
targetFlowId: this.context.currentFlow.id,
|
|
145
|
+
displayText: "Ask your companion about their past",
|
|
146
|
+
weight: 0.7
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return generatedOptions.slice(0, maxOptions);
|
|
150
|
+
}
|
|
151
|
+
// ================================
|
|
152
|
+
// Analytics & Monitoring
|
|
153
|
+
// ================================
|
|
154
|
+
/**
|
|
155
|
+
* Get current branching analytics
|
|
156
|
+
*/
|
|
157
|
+
getBranchingAnalytics() {
|
|
158
|
+
return {
|
|
159
|
+
...this.context.analytics,
|
|
160
|
+
currentChapter: this.context.currentChapter.id,
|
|
161
|
+
currentFlow: this.context.currentFlow.id,
|
|
162
|
+
historyLength: this.context.branchHistory.length,
|
|
163
|
+
pendingBranches: this.context.pendingBranches.length
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Export branching data for external analysis
|
|
168
|
+
*/
|
|
169
|
+
exportBranchingData() {
|
|
170
|
+
return {
|
|
171
|
+
story: {
|
|
172
|
+
id: this.story.id,
|
|
173
|
+
title: this.story.title,
|
|
174
|
+
version: this.story.version
|
|
175
|
+
},
|
|
176
|
+
session: {
|
|
177
|
+
startTime: this.context.analytics.sessionStartTime,
|
|
178
|
+
currentState: this.context.activeState,
|
|
179
|
+
branchHistory: this.context.branchHistory,
|
|
180
|
+
analytics: this.context.analytics
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// ================================
|
|
185
|
+
// Private Helper Methods
|
|
186
|
+
// ================================
|
|
187
|
+
createBranchContext(story, initialState) {
|
|
188
|
+
const initialChapter = story.chapters[0];
|
|
189
|
+
const initialFlow = initialChapter.flows[0];
|
|
190
|
+
return {
|
|
191
|
+
currentStory: story,
|
|
192
|
+
currentChapter: initialChapter,
|
|
193
|
+
currentFlow: initialFlow,
|
|
194
|
+
activeState: initialState,
|
|
195
|
+
branchHistory: [],
|
|
196
|
+
pendingBranches: [],
|
|
197
|
+
analytics: {
|
|
198
|
+
totalBranchesTraversed: 0,
|
|
199
|
+
avgBranchDecisionTime: 0,
|
|
200
|
+
mostPopularBranches: [],
|
|
201
|
+
abandonmentPoints: [],
|
|
202
|
+
completionRate: 0,
|
|
203
|
+
sessionStartTime: new Date()
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
getCurrentNode() {
|
|
208
|
+
const currentNodeId = this.context.activeState.currentNodeId;
|
|
209
|
+
const node = this.context.currentFlow.nodes.find(n => n.id === currentNodeId);
|
|
210
|
+
if (!node) {
|
|
211
|
+
throw new Error(`Current node not found: ${currentNodeId}`);
|
|
212
|
+
}
|
|
213
|
+
return node;
|
|
214
|
+
}
|
|
215
|
+
findBranchPointsForNode(flowId, nodeId) {
|
|
216
|
+
return this.context.currentChapter.branches.filter(b => b.sourceFlowId === flowId && b.sourceNodeId === nodeId);
|
|
217
|
+
}
|
|
218
|
+
async evaluateConditions(conditions) {
|
|
219
|
+
for (const condition of conditions) {
|
|
220
|
+
if (!await this.evaluateCondition(condition)) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
async evaluateCondition(condition) {
|
|
227
|
+
const { operator, key, value, evaluator } = condition;
|
|
228
|
+
// Custom evaluator takes precedence
|
|
229
|
+
if (evaluator) {
|
|
230
|
+
return evaluator(this.context.activeState, this.context);
|
|
231
|
+
}
|
|
232
|
+
// Standard condition evaluation
|
|
233
|
+
const stateValue = this.context.activeState.flags[key];
|
|
234
|
+
switch (operator) {
|
|
235
|
+
case 'equals':
|
|
236
|
+
return stateValue === value;
|
|
237
|
+
case 'not_equals':
|
|
238
|
+
return stateValue !== value;
|
|
239
|
+
case 'greater':
|
|
240
|
+
return Number(stateValue) > Number(value);
|
|
241
|
+
case 'less':
|
|
242
|
+
return Number(stateValue) < Number(value);
|
|
243
|
+
case 'contains':
|
|
244
|
+
return Array.isArray(stateValue) && stateValue.includes(value);
|
|
245
|
+
case 'exists':
|
|
246
|
+
return stateValue !== undefined;
|
|
247
|
+
default:
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async findBranchOption(optionId) {
|
|
252
|
+
for (const chapter of this.story.chapters) {
|
|
253
|
+
for (const branchPoint of chapter.branches) {
|
|
254
|
+
const option = branchPoint.branchOptions.find(o => o.id === optionId);
|
|
255
|
+
if (option) {
|
|
256
|
+
return option;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
findFlow(flowId) {
|
|
263
|
+
for (const chapter of this.story.chapters) {
|
|
264
|
+
const flow = chapter.flows.find(f => f.id === flowId);
|
|
265
|
+
if (flow) {
|
|
266
|
+
return flow;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
findChapter(chapterId) {
|
|
272
|
+
return this.story.chapters.find(c => c.id === chapterId) || null;
|
|
273
|
+
}
|
|
274
|
+
async transitionToFlow(targetFlow, targetNodeId) {
|
|
275
|
+
// Find entry point
|
|
276
|
+
let entryNodeId;
|
|
277
|
+
if (targetNodeId) {
|
|
278
|
+
entryNodeId = targetNodeId;
|
|
279
|
+
}
|
|
280
|
+
else if (targetFlow.entryPoints.length > 0) {
|
|
281
|
+
// Use highest priority entry point
|
|
282
|
+
const entryPoint = targetFlow.entryPoints.sort((a, b) => b.priority - a.priority)[0];
|
|
283
|
+
entryNodeId = entryPoint.nodeId;
|
|
284
|
+
}
|
|
285
|
+
else if (targetFlow.nodes.length > 0) {
|
|
286
|
+
// Use first node as fallback
|
|
287
|
+
entryNodeId = targetFlow.nodes[0].id;
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
// Update context
|
|
293
|
+
this.context.currentFlow = targetFlow;
|
|
294
|
+
this.context.activeState.currentNodeId = entryNodeId;
|
|
295
|
+
this.context.activeState.history.push(entryNodeId);
|
|
296
|
+
// Update chapter if necessary
|
|
297
|
+
const targetChapter = this.story.chapters.find(c => c.flows.some(f => f.id === targetFlow.id));
|
|
298
|
+
if (targetChapter) {
|
|
299
|
+
this.context.currentChapter = targetChapter;
|
|
300
|
+
}
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
recordBranchHistory(optionId, option) {
|
|
304
|
+
const historyEntry = {
|
|
305
|
+
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
306
|
+
branchPointId: 'unknown', // Would need to track this
|
|
307
|
+
chosenOptionId: optionId,
|
|
308
|
+
timestamp: new Date(),
|
|
309
|
+
executionTime: 0, // Would measure actual execution time
|
|
310
|
+
context: {
|
|
311
|
+
currentNodeId: this.context.activeState.currentNodeId,
|
|
312
|
+
flags: { ...this.context.activeState.flags }
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
this.context.branchHistory.push(historyEntry);
|
|
316
|
+
// Limit history size for memory management
|
|
317
|
+
if (this.context.branchHistory.length > 1000) {
|
|
318
|
+
this.context.branchHistory.splice(0, 100);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
updateBranchAnalytics(optionId) {
|
|
322
|
+
this.context.analytics.totalBranchesTraversed++;
|
|
323
|
+
// Update popular branches
|
|
324
|
+
const popular = this.context.analytics.mostPopularBranches;
|
|
325
|
+
const index = popular.indexOf(optionId);
|
|
326
|
+
if (index !== -1) {
|
|
327
|
+
// Move to front
|
|
328
|
+
popular.splice(index, 1);
|
|
329
|
+
popular.unshift(optionId);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
// Add to front
|
|
333
|
+
popular.unshift(optionId);
|
|
334
|
+
// Limit to top 10
|
|
335
|
+
if (popular.length > 10) {
|
|
336
|
+
popular.pop();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
exports.QNCEBranchingEngine = QNCEBranchingEngine;
|
|
342
|
+
exports.default = QNCEBranchingEngine;
|
|
343
|
+
/**
|
|
344
|
+
* Factory function for creating branching engines
|
|
345
|
+
*/
|
|
346
|
+
function createBranchingEngine(story, initialState) {
|
|
347
|
+
return new QNCEBranchingEngine(story, initialState);
|
|
348
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// QNCE Branching System - Main Export
|
|
3
|
+
// Sprint #3 - Advanced Narrative & AI Integration
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
16
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.example = exports.createBranchingEngine = exports.QNCEBranchingEngine = void 0;
|
|
20
|
+
// Core branching engine
|
|
21
|
+
var engine_simple_1 = require("./engine-simple");
|
|
22
|
+
Object.defineProperty(exports, "QNCEBranchingEngine", { enumerable: true, get: function () { return engine_simple_1.QNCEBranchingEngine; } });
|
|
23
|
+
Object.defineProperty(exports, "createBranchingEngine", { enumerable: true, get: function () { return engine_simple_1.createBranchingEngine; } });
|
|
24
|
+
// Complete type system
|
|
25
|
+
__exportStar(require("./models"), exports);
|
|
26
|
+
// Usage example for documentation
|
|
27
|
+
exports.example = {
|
|
28
|
+
// Example story structure showing branching capabilities
|
|
29
|
+
storyStructure: {
|
|
30
|
+
story: 'QNCEStory with chapters and branching config',
|
|
31
|
+
chapters: 'Logical groupings of flows and branch points',
|
|
32
|
+
flows: 'Sequences of narrative nodes with entry/exit points',
|
|
33
|
+
branches: 'Dynamic decision points with conditions and options'
|
|
34
|
+
},
|
|
35
|
+
// Example API usage
|
|
36
|
+
usage: `
|
|
37
|
+
import { createBranchingEngine, QNCEStory } from 'qnce-engine/branching';
|
|
38
|
+
|
|
39
|
+
// Create branching engine
|
|
40
|
+
const engine = createBranchingEngine(story, initialState);
|
|
41
|
+
|
|
42
|
+
// Evaluate available branches
|
|
43
|
+
const options = await engine.evaluateAvailableBranches();
|
|
44
|
+
|
|
45
|
+
// Execute player choice
|
|
46
|
+
await engine.executeBranch(selectedOptionId);
|
|
47
|
+
|
|
48
|
+
// Dynamic content insertion
|
|
49
|
+
await engine.insertDynamicBranch(branchOperation);
|
|
50
|
+
|
|
51
|
+
// AI-driven content generation
|
|
52
|
+
engine.setAIContext(aiContext);
|
|
53
|
+
const aiBranches = await engine.generateAIBranches();
|
|
54
|
+
`
|
|
55
|
+
};
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// S2-T1: Object Pooling for Narrative Objects
|
|
3
|
+
// Generic object pool to eliminate runtime allocations and reduce GC pressure
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.poolManager = exports.PoolManager = exports.PooledAsset = exports.PooledNode = exports.PooledFlow = exports.ObjectPool = void 0;
|
|
6
|
+
/**
|
|
7
|
+
* Generic Object Pool for QNCE narrative objects
|
|
8
|
+
* Reduces GC pressure by reusing objects instead of creating new ones
|
|
9
|
+
*/
|
|
10
|
+
class ObjectPool {
|
|
11
|
+
pool = [];
|
|
12
|
+
createFn;
|
|
13
|
+
maxSize;
|
|
14
|
+
created = 0;
|
|
15
|
+
borrowed = 0;
|
|
16
|
+
returned = 0;
|
|
17
|
+
constructor(createFn, initialSize = 10, maxSize = 100) {
|
|
18
|
+
this.createFn = createFn;
|
|
19
|
+
this.maxSize = maxSize;
|
|
20
|
+
// Pre-populate pool
|
|
21
|
+
for (let i = 0; i < initialSize; i++) {
|
|
22
|
+
const obj = this.createFn();
|
|
23
|
+
obj.setInUse(false);
|
|
24
|
+
this.pool.push(obj);
|
|
25
|
+
this.created++;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Borrow an object from the pool
|
|
30
|
+
*/
|
|
31
|
+
borrow() {
|
|
32
|
+
let obj;
|
|
33
|
+
if (this.pool.length > 0) {
|
|
34
|
+
obj = this.pool.pop();
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Pool exhausted, create new object
|
|
38
|
+
obj = this.createFn();
|
|
39
|
+
this.created++;
|
|
40
|
+
}
|
|
41
|
+
obj.reset();
|
|
42
|
+
obj.setInUse(true);
|
|
43
|
+
this.borrowed++;
|
|
44
|
+
return obj;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Return an object to the pool
|
|
48
|
+
*/
|
|
49
|
+
return(obj) {
|
|
50
|
+
if (!obj.isInUse()) {
|
|
51
|
+
console.warn('Attempting to return object that is not in use');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
obj.setInUse(false);
|
|
55
|
+
this.returned++;
|
|
56
|
+
// Only return to pool if under max size
|
|
57
|
+
if (this.pool.length < this.maxSize) {
|
|
58
|
+
this.pool.push(obj);
|
|
59
|
+
}
|
|
60
|
+
// Otherwise let GC handle it (controlled disposal)
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get pool statistics for monitoring
|
|
64
|
+
*/
|
|
65
|
+
getStats() {
|
|
66
|
+
return {
|
|
67
|
+
poolSize: this.pool.length,
|
|
68
|
+
maxSize: this.maxSize,
|
|
69
|
+
created: this.created,
|
|
70
|
+
borrowed: this.borrowed,
|
|
71
|
+
returned: this.returned,
|
|
72
|
+
inUse: this.borrowed - this.returned,
|
|
73
|
+
hitRate: this.borrowed > 0 ? ((this.borrowed - this.created) / this.borrowed) * 100 : 0
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Clear the pool (useful for testing)
|
|
78
|
+
*/
|
|
79
|
+
clear() {
|
|
80
|
+
this.pool.forEach(obj => obj.setInUse(false));
|
|
81
|
+
this.pool.length = 0;
|
|
82
|
+
this.created = 0;
|
|
83
|
+
this.borrowed = 0;
|
|
84
|
+
this.returned = 0;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resize the pool
|
|
88
|
+
*/
|
|
89
|
+
resize(newSize) {
|
|
90
|
+
if (newSize < this.pool.length) {
|
|
91
|
+
// Shrink pool
|
|
92
|
+
this.pool.splice(newSize);
|
|
93
|
+
}
|
|
94
|
+
else if (newSize > this.pool.length) {
|
|
95
|
+
// Grow pool
|
|
96
|
+
const toAdd = newSize - this.pool.length;
|
|
97
|
+
for (let i = 0; i < toAdd && this.pool.length < this.maxSize; i++) {
|
|
98
|
+
const obj = this.createFn();
|
|
99
|
+
obj.setInUse(false);
|
|
100
|
+
this.pool.push(obj);
|
|
101
|
+
this.created++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.ObjectPool = ObjectPool;
|
|
107
|
+
/**
|
|
108
|
+
* Pooled Flow object for narrative state management
|
|
109
|
+
*/
|
|
110
|
+
class PooledFlow {
|
|
111
|
+
_inUse = false;
|
|
112
|
+
nodeId = '';
|
|
113
|
+
timestamp = 0;
|
|
114
|
+
metadata = {};
|
|
115
|
+
transitions = [];
|
|
116
|
+
constructor() {
|
|
117
|
+
this.reset();
|
|
118
|
+
}
|
|
119
|
+
reset() {
|
|
120
|
+
this.nodeId = '';
|
|
121
|
+
this.timestamp = 0;
|
|
122
|
+
this.metadata = {};
|
|
123
|
+
this.transitions.length = 0;
|
|
124
|
+
}
|
|
125
|
+
isInUse() {
|
|
126
|
+
return this._inUse;
|
|
127
|
+
}
|
|
128
|
+
setInUse(inUse) {
|
|
129
|
+
this._inUse = inUse;
|
|
130
|
+
}
|
|
131
|
+
// Flow-specific methods
|
|
132
|
+
initialize(nodeId, metadata) {
|
|
133
|
+
this.nodeId = nodeId;
|
|
134
|
+
this.timestamp = performance.now();
|
|
135
|
+
if (metadata) {
|
|
136
|
+
this.metadata = { ...metadata };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
addTransition(fromNodeId, toNodeId) {
|
|
140
|
+
this.transitions.push(`${fromNodeId}->${toNodeId}`);
|
|
141
|
+
}
|
|
142
|
+
getDuration() {
|
|
143
|
+
return performance.now() - this.timestamp;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.PooledFlow = PooledFlow;
|
|
147
|
+
/**
|
|
148
|
+
* Pooled Node object for narrative content
|
|
149
|
+
*/
|
|
150
|
+
class PooledNode {
|
|
151
|
+
_inUse = false;
|
|
152
|
+
id = '';
|
|
153
|
+
text = '';
|
|
154
|
+
choices = [];
|
|
155
|
+
flags = {};
|
|
156
|
+
lastAccessed = 0;
|
|
157
|
+
constructor() {
|
|
158
|
+
this.reset();
|
|
159
|
+
}
|
|
160
|
+
reset() {
|
|
161
|
+
this.id = '';
|
|
162
|
+
this.text = '';
|
|
163
|
+
this.choices.length = 0;
|
|
164
|
+
this.flags = {};
|
|
165
|
+
this.lastAccessed = 0;
|
|
166
|
+
}
|
|
167
|
+
isInUse() {
|
|
168
|
+
return this._inUse;
|
|
169
|
+
}
|
|
170
|
+
setInUse(inUse) {
|
|
171
|
+
this._inUse = inUse;
|
|
172
|
+
}
|
|
173
|
+
// Node-specific methods
|
|
174
|
+
initialize(id, text, choices = []) {
|
|
175
|
+
this.id = id;
|
|
176
|
+
this.text = text;
|
|
177
|
+
this.choices = [...choices];
|
|
178
|
+
this.lastAccessed = performance.now();
|
|
179
|
+
}
|
|
180
|
+
touch() {
|
|
181
|
+
this.lastAccessed = performance.now();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
exports.PooledNode = PooledNode;
|
|
185
|
+
/**
|
|
186
|
+
* Pooled Asset object for narrative resources
|
|
187
|
+
*/
|
|
188
|
+
class PooledAsset {
|
|
189
|
+
_inUse = false;
|
|
190
|
+
id = '';
|
|
191
|
+
type = '';
|
|
192
|
+
data = null;
|
|
193
|
+
size = 0;
|
|
194
|
+
loaded = false;
|
|
195
|
+
constructor() {
|
|
196
|
+
this.reset();
|
|
197
|
+
}
|
|
198
|
+
reset() {
|
|
199
|
+
this.id = '';
|
|
200
|
+
this.type = '';
|
|
201
|
+
this.data = null;
|
|
202
|
+
this.size = 0;
|
|
203
|
+
this.loaded = false;
|
|
204
|
+
}
|
|
205
|
+
isInUse() {
|
|
206
|
+
return this._inUse;
|
|
207
|
+
}
|
|
208
|
+
setInUse(inUse) {
|
|
209
|
+
this._inUse = inUse;
|
|
210
|
+
}
|
|
211
|
+
// Asset-specific methods
|
|
212
|
+
initialize(id, type, data) {
|
|
213
|
+
this.id = id;
|
|
214
|
+
this.type = type;
|
|
215
|
+
this.data = data;
|
|
216
|
+
this.size = this.calculateSize(data);
|
|
217
|
+
this.loaded = true;
|
|
218
|
+
}
|
|
219
|
+
calculateSize(data) {
|
|
220
|
+
if (typeof data === 'string') {
|
|
221
|
+
return data.length * 2; // Approximate UTF-16 size
|
|
222
|
+
}
|
|
223
|
+
if (data && typeof data === 'object') {
|
|
224
|
+
return JSON.stringify(data).length * 2;
|
|
225
|
+
}
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
exports.PooledAsset = PooledAsset;
|
|
230
|
+
/**
|
|
231
|
+
* Pool Manager - Centralized management of all object pools
|
|
232
|
+
*/
|
|
233
|
+
class PoolManager {
|
|
234
|
+
static instance;
|
|
235
|
+
flowPool;
|
|
236
|
+
nodePool;
|
|
237
|
+
assetPool;
|
|
238
|
+
constructor() {
|
|
239
|
+
// Initialize pools with different sizes based on expected usage
|
|
240
|
+
this.flowPool = new ObjectPool(() => new PooledFlow(), 5, 50);
|
|
241
|
+
this.nodePool = new ObjectPool(() => new PooledNode(), 20, 200);
|
|
242
|
+
this.assetPool = new ObjectPool(() => new PooledAsset(), 10, 100);
|
|
243
|
+
}
|
|
244
|
+
static getInstance() {
|
|
245
|
+
if (!PoolManager.instance) {
|
|
246
|
+
PoolManager.instance = new PoolManager();
|
|
247
|
+
}
|
|
248
|
+
return PoolManager.instance;
|
|
249
|
+
}
|
|
250
|
+
// Flow pool methods
|
|
251
|
+
borrowFlow() {
|
|
252
|
+
return this.flowPool.borrow();
|
|
253
|
+
}
|
|
254
|
+
returnFlow(flow) {
|
|
255
|
+
this.flowPool.return(flow);
|
|
256
|
+
}
|
|
257
|
+
// Node pool methods
|
|
258
|
+
borrowNode() {
|
|
259
|
+
return this.nodePool.borrow();
|
|
260
|
+
}
|
|
261
|
+
returnNode(node) {
|
|
262
|
+
this.nodePool.return(node);
|
|
263
|
+
}
|
|
264
|
+
// Asset pool methods
|
|
265
|
+
borrowAsset() {
|
|
266
|
+
return this.assetPool.borrow();
|
|
267
|
+
}
|
|
268
|
+
returnAsset(asset) {
|
|
269
|
+
this.assetPool.return(asset);
|
|
270
|
+
}
|
|
271
|
+
// Statistics and monitoring
|
|
272
|
+
getAllStats() {
|
|
273
|
+
return {
|
|
274
|
+
flows: this.flowPool.getStats(),
|
|
275
|
+
nodes: this.nodePool.getStats(),
|
|
276
|
+
assets: this.assetPool.getStats()
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
// Performance monitoring
|
|
280
|
+
getGCPressureReduction() {
|
|
281
|
+
const stats = this.getAllStats();
|
|
282
|
+
const totalHitRate = (stats.flows.hitRate +
|
|
283
|
+
stats.nodes.hitRate +
|
|
284
|
+
stats.assets.hitRate) / 3;
|
|
285
|
+
return totalHitRate;
|
|
286
|
+
}
|
|
287
|
+
// Cleanup for testing
|
|
288
|
+
clearAllPools() {
|
|
289
|
+
this.flowPool.clear();
|
|
290
|
+
this.nodePool.clear();
|
|
291
|
+
this.assetPool.clear();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
exports.PoolManager = PoolManager;
|
|
295
|
+
// Export singleton instance
|
|
296
|
+
exports.poolManager = PoolManager.getInstance();
|