qnce-engine
Version:
Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization
349 lines • 12.5 kB
JavaScript
"use strict";
// QNCE Branching API - Simplified Runtime Implementation
// Sprint #3 - Advanced Narrative & AI Integration
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = exports.QNCEBranchingEngine = void 0;
exports.createBranchingEngine = createBranchingEngine;
/**
* QNCE Branching Engine - Core API for dynamic narrative branching
* Simplified implementation focusing on core functionality
*/
class QNCEBranchingEngine {
story;
context;
aiContext;
constructor(story, initialState) {
this.story = story;
this.context = this.createBranchContext(story, initialState);
}
// ================================
// Core Branching Operations
// ================================
/**
* Evaluate available branches from current position
*/
async evaluateAvailableBranches() {
const currentNode = this.getCurrentNode();
const availableBranches = this.findBranchPointsForNode(this.context.currentFlow.id, currentNode.id);
const validOptions = [];
for (const branchPoint of availableBranches) {
// Evaluate branch point conditions
if (await this.evaluateConditions(branchPoint.conditions || [])) {
// Evaluate individual option conditions
for (const option of branchPoint.branchOptions) {
if (await this.evaluateConditions(option.conditions || [])) {
validOptions.push(option);
}
}
}
}
return validOptions;
}
/**
* Execute a branch transition
*/
async executeBranch(optionId) {
const option = await this.findBranchOption(optionId);
if (!option) {
throw new Error(`Branch option not found: ${optionId}`);
}
// Apply flag effects
if (option.flagEffects) {
Object.assign(this.context.activeState.flags, option.flagEffects);
}
// Execute flow transition
const targetFlow = this.findFlow(option.targetFlowId);
if (!targetFlow) {
throw new Error(`Target flow not found: ${option.targetFlowId}`);
}
const success = await this.transitionToFlow(targetFlow, option.targetNodeId);
if (success) {
// Record branch execution in history
this.recordBranchHistory(optionId);
// Update analytics
this.updateBranchAnalytics(optionId);
}
return success;
}
/**
* Dynamic branch insertion at runtime
*/
async insertDynamicBranch(operation) {
// Validate operation conditions
if (operation.conditions && !await this.evaluateConditions(operation.conditions)) {
return false;
}
// Find target location
const chapter = this.findChapter(operation.targetLocation.chapterId);
if (!chapter) {
throw new Error(`Chapter not found: ${operation.targetLocation.chapterId}`);
}
// Create new branch point
const branchPoint = {
id: operation.branchId,
name: operation.payload?.name || `Dynamic Branch ${operation.branchId}`,
sourceFlowId: operation.targetLocation.flowId,
sourceNodeId: operation.targetLocation.nodeId,
branchType: operation.payload?.branchType || 'conditional',
branchOptions: operation.payload?.branchOptions || [],
conditions: operation.conditions,
metadata: {
usageCount: 0,
avgTraversalTime: 0,
playerPreference: 0,
lastUsed: new Date()
}
};
// Insert into chapter
chapter.branches.push(branchPoint);
return true;
}
/**
* Remove dynamic branch
*/
async removeDynamicBranch(branchId) {
for (const chapter of this.story.chapters) {
const index = chapter.branches.findIndex(b => b.id === branchId);
if (index !== -1) {
chapter.branches.splice(index, 1);
return true;
}
}
return false;
}
// ================================
// AI Integration Methods
// ================================
/**
* Set AI context for enhanced branching decisions
*/
setAIContext(aiContext) {
this.aiContext = aiContext;
}
/**
* Generate AI-driven branch options
*/
async generateAIBranches(maxOptions = 3) {
if (!this.aiContext) {
throw new Error('AI context not set. Call setAIContext() first.');
}
const generatedOptions = [];
const playerProfile = this.aiContext.playerProfile;
// Example: Generate options based on player style
if (playerProfile.playStyle === 'explorer') {
generatedOptions.push({
id: `ai-explore-${Date.now()}`,
targetFlowId: this.context.currentFlow.id,
displayText: "Investigate the mysterious artifact",
weight: 0.8
});
}
if (playerProfile.playStyle === 'socializer') {
generatedOptions.push({
id: `ai-social-${Date.now()}`,
targetFlowId: this.context.currentFlow.id,
displayText: "Ask your companion about their past",
weight: 0.7
});
}
return generatedOptions.slice(0, maxOptions);
}
// ================================
// Analytics & Monitoring
// ================================
/**
* Get current branching analytics
*/
getBranchingAnalytics() {
return {
...this.context.analytics,
currentChapter: this.context.currentChapter.id,
currentFlow: this.context.currentFlow.id,
historyLength: this.context.branchHistory.length,
pendingBranches: this.context.pendingBranches.length
};
}
/**
* Export branching data for external analysis
*/
exportBranchingData() {
return {
story: {
id: this.story.id,
title: this.story.title,
version: this.story.version
},
session: {
startTime: this.context.analytics.sessionStartTime,
currentState: this.context.activeState,
branchHistory: this.context.branchHistory,
analytics: this.context.analytics
}
};
}
// ================================
// Private Helper Methods
// ================================
createBranchContext(story, initialState) {
const initialChapter = story.chapters[0];
const initialFlow = initialChapter.flows[0];
return {
currentStory: story,
currentChapter: initialChapter,
currentFlow: initialFlow,
activeState: initialState,
branchHistory: [],
pendingBranches: [],
analytics: {
totalBranchesTraversed: 0,
avgBranchDecisionTime: 0,
mostPopularBranches: [],
abandonmentPoints: [],
completionRate: 0,
sessionStartTime: new Date()
}
};
}
getCurrentNode() {
const currentNodeId = this.context.activeState.currentNodeId;
const node = this.context.currentFlow.nodes.find(n => n.id === currentNodeId);
if (!node) {
throw new Error(`Current node not found: ${currentNodeId}`);
}
return node;
}
findBranchPointsForNode(flowId, nodeId) {
return this.context.currentChapter.branches.filter(b => b.sourceFlowId === flowId && b.sourceNodeId === nodeId);
}
async evaluateConditions(conditions) {
for (const condition of conditions) {
if (!await this.evaluateCondition(condition)) {
return false;
}
}
return true;
}
async evaluateCondition(condition) {
const { operator, key, value, evaluator } = condition;
// Custom evaluator takes precedence
if (evaluator) {
return evaluator(this.context.activeState, this.context);
}
// Standard condition evaluation
const stateValue = this.context.activeState.flags[key];
switch (operator) {
case 'equals':
return stateValue === value;
case 'not_equals':
return stateValue !== value;
case 'greater':
return Number(stateValue) > Number(value);
case 'less':
return Number(stateValue) < Number(value);
case 'contains':
return Array.isArray(stateValue) && stateValue.includes(value);
case 'exists':
return stateValue !== undefined;
default:
return false;
}
}
async findBranchOption(optionId) {
for (const chapter of this.story.chapters) {
for (const branchPoint of chapter.branches) {
const option = branchPoint.branchOptions.find(o => o.id === optionId);
if (option) {
return option;
}
}
}
return null;
}
findFlow(flowId) {
for (const chapter of this.story.chapters) {
const flow = chapter.flows.find(f => f.id === flowId);
if (flow) {
return flow;
}
}
return null;
}
findChapter(chapterId) {
return this.story.chapters.find(c => c.id === chapterId) || null;
}
async transitionToFlow(targetFlow, targetNodeId) {
// Find entry point
let entryNodeId;
if (targetNodeId) {
entryNodeId = targetNodeId;
}
else if (targetFlow.entryPoints.length > 0) {
// Use highest priority entry point
const entryPoint = targetFlow.entryPoints.sort((a, b) => b.priority - a.priority)[0];
entryNodeId = entryPoint.nodeId;
}
else if (targetFlow.nodes.length > 0) {
// Use first node as fallback
entryNodeId = targetFlow.nodes[0].id;
}
else {
return false;
}
// Update context
this.context.currentFlow = targetFlow;
this.context.activeState.currentNodeId = entryNodeId;
this.context.activeState.history.push(entryNodeId);
// Update chapter if necessary
const targetChapter = this.story.chapters.find(c => c.flows.some(f => f.id === targetFlow.id));
if (targetChapter) {
this.context.currentChapter = targetChapter;
}
return true;
}
recordBranchHistory(optionId) {
const historyEntry = {
id: `history-${Date.now()}`,
branchPointId: 'unknown', // Would need to track this
chosenOptionId: optionId,
timestamp: new Date(),
executionTime: 0, // Would measure actual execution time
context: {
currentNodeId: this.context.activeState.currentNodeId,
flags: { ...this.context.activeState.flags }
}
};
this.context.branchHistory.push(historyEntry);
// Limit history size for memory management
if (this.context.branchHistory.length > 1000) {
this.context.branchHistory.splice(0, 100);
}
}
updateBranchAnalytics(optionId) {
this.context.analytics.totalBranchesTraversed++;
// Update popular branches
const popular = this.context.analytics.mostPopularBranches;
const index = popular.indexOf(optionId);
if (index !== -1) {
// Move to front
popular.splice(index, 1);
popular.unshift(optionId);
}
else {
// Add to front
popular.unshift(optionId);
// Limit to top 10
if (popular.length > 10) {
popular.pop();
}
}
}
}
exports.QNCEBranchingEngine = QNCEBranchingEngine;
exports.default = QNCEBranchingEngine;
/**
* Factory function for creating branching engines
*/
function createBranchingEngine(story, initialState) {
return new QNCEBranchingEngine(story, initialState);
}
//# sourceMappingURL=engine-simple.js.map