Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization
npm install qnce-engineQuantum Narrative Convergence Engine - A framework-agnostic TypeScript library for creating interactive narrative experiences with quantum-inspired mechanics.
> π Latest v1.3.1 (Import & Persistence): New qnce-import CLI (Custom JSON, Twison with tagsβmeta.tags, experimental Ink), persistence adapters (Memory, LocalStorage, SessionStorage, File, IndexedDB), and qnce-play support for storage backends and non-interactive runs.



> New: Lightweight, privacy-safe telemetry (opt-in). CLI now supports --telemetry and --telemetry-report, with p50/p95 batch send latency. See the wiki: Analytics & Telemetry.
- Superposition: Multiple narrative outcomes exist simultaneously until a choice is made
- Collapse: Player choices "collapse" the narrative to a specific path, updating state and flags
- Entanglement: Early decisions affect later outcomes, enabling complex, interconnected stories
#### Basic Flag-Based Conditions
``typescript
import { createQNCEEngine } from 'qnce-engine';
const storyData = {
currentNodeId: 'town-square',
nodes: {
'town-square': {
id: 'town-square',
text: 'You stand in the bustling town square.',
choices: [
{
id: 'enter-tavern',
text: 'Enter the tavern',
nextNodeId: 'tavern-inside'
},
{
id: 'approach-guard',
text: 'Approach the suspicious guard',
nextNodeId: 'guard-encounter',
condition: 'flags.curiosity >= 3' // Only show if curious enough
},
{
id: 'use-disguise',
text: 'Use your disguise to blend in',
nextNodeId: 'disguised-approach',
condition: 'flags.hasDisguise && !flags.disguiseUsed'
}
]
}
},
flags: {}
};
const engine = createQNCEEngine(storyData);
// Set flags to test conditional choices
engine.setFlag('curiosity', 4);
engine.setFlag('hasDisguise', true);
// Get available choices - only those meeting conditions
const choices = engine.getAvailableChoices();
console.log(Available choices: ${choices.length}); // Shows 3 choices
// Change flags to see different options
engine.setFlag('curiosity', 1); // Below threshold
const reducedChoices = engine.getAvailableChoices();
console.log(Available choices: ${reducedChoices.length}); // Shows 2 choices`
#### Complex Conditional Expressions
`typescript`
const complexStory = {
currentNodeId: 'critical-moment',
nodes: {
'critical-moment': {
id: 'critical-moment',
text: 'The fate of the kingdom hangs in the balance.',
choices: [
{
id: 'diplomatic-solution',
text: 'Attempt diplomatic negotiation',
nextNodeId: 'peaceful-resolution',
condition: 'flags.charisma >= 5 && flags.hasAlliance'
},
{
id: 'magical-intervention',
text: 'Cast the ancient spell',
nextNodeId: 'magical-outcome',
condition: 'flags.magicPower > 3 && flags.spellComponents >= 2 && !flags.cursed'
},
{
id: 'time-limited-escape',
text: 'Escape through the secret passage',
id: 'sacrifice-play',
text: 'Make the ultimate sacrifice',
nextNodeId: 'heroic-end',
condition: '(flags.loyalty >= 8 || flags.desperate) && flags.hasArtifact'
}
]
}
},
flags: {}
};
#### Custom Condition Evaluators
For advanced scenarios, you can provide custom logic for condition evaluation:
`typescripthas_${itemName}
// Set up custom evaluator for complex game logic
engine.setConditionEvaluator((expression, context) => {
// Custom logic for special conditions
if (expression === 'canUseSpecialAbility') {
return context.flags.level >= 10 &&
context.flags.mana > 50 &&
!context.flags.abilityOnCooldown;
}
if (expression.startsWith('inventory:')) {
const itemName = expression.replace('inventory:', '');
return context.flags[] === true;
}
// Fall back to default expression evaluation
return null;
});
// Use custom conditions in story
const storyWithCustom = {
currentNodeId: 'boss-fight',
nodes: {
'boss-fight': {
id: 'boss-fight',
text: 'The dragon roars menacingly.',
choices: [
{
id: 'special-attack',
text: 'Use your special ability',
nextNodeId: 'special-victory',
condition: 'canUseSpecialAbility'
},
{
id: 'use-potion',
text: 'Drink healing potion',
nextNodeId: 'healed-state',
condition: 'inventory:healing_potion'
}
]
}
}
};
`
#### Condition Validation & Debugging
`typescript
// Validate conditions during development
try {
engine.validateCondition('flags.strength >= 5 && flags.weapon');
console.log('Condition is valid');
} catch (error) {
console.error('Invalid condition:', error.message);
}
// Get flags referenced in a condition for debugging
const referencedFlags = engine.getConditionFlags('flags.curiosity >= 3 && flags.hasKey');
console.log('Referenced flags:', referencedFlags); // ['curiosity', 'hasKey']
// Clear custom evaluator
engine.clearConditionEvaluator();
`
#### Performance Considerations
- Expression Caching: Conditions are compiled once and cached for subsequent evaluations
- Safe Evaluation: All expressions are sanitized to prevent code injection
- Minimal Overhead: Choice filtering adds <1ms to getAvailableChoices() calls
- Error Isolation: Invalid conditions don't affect other choices in the same node
CLI dashboard with performance alerts$3
`bash
Real-time performance monitoring
qnce-perf dashboardLive monitoring with updates
qnce-perf live 1000Export performance data
qnce-perf export > performance-report.json
`π Complete Performance Guide β
Installation
`bash
npm install qnce-engineGlobal CLI installation for performance monitoring
npm install -g qnce-engine
`Quick Start
$3
`typescript
import { createQNCEEngine, DEMO_STORY } from 'qnce-engine';// Create engine instance with demo story
const engine = createQNCEEngine(DEMO_STORY);
// Get current narrative state
const currentNode = engine.getCurrentNode();
console.log(currentNode.text);
// Get available choices
const choices = engine.getAvailableChoices();
console.log(choices);
// Make a choice
if (choices.length > 0) {
engine.selectChoice(choices[0]);
}
// Check narrative flags
const flags = engine.getFlags();
console.log('Current flags:', flags);
`$3
`typescript
import { createQNCEEngine, DEMO_STORY } from 'qnce-engine';// Enable performance optimizations
const engine = createQNCEEngine(DEMO_STORY, {}, true, {
maxWorkers: 4,
enableProfiling: true
});
// Background cache preloading happens automatically
// Object pooling reduces memory allocations by 90%+
// Performance events are collected for monitoring
// Get performance statistics
const poolStats = engine.getPoolStats();
console.log(
Pool efficiency: ${poolStats.flow.hitRate}%);
`$3
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.
Key Features:
- Full State Serialization: Save and load the entire engine state, including narrative position, flags, history, and branching context.
- Lightweight Checkpoints: Create fast, in-memory snapshots for undo operations or temporary state saves.
- Data Integrity: Optional checksum verification ensures that saved data is not corrupted.
- Cross-Version Compatibility: A migration system helps upgrade older save states to the latest version.
Example Usage:
`typescript
import { createQNCEEngine, DEMO_STORY } from 'qnce-engine';const engine = createQNCEEngine(DEMO_STORY);
// ...progress through the story...
const choices = engine.getAvailableChoices();
if (choices.length > 0) {
engine.selectChoice(choices[0]);
}
// Save the current state to a JSON string
const savedState = await engine.saveState();
console.log('State saved!');
// ...later, or in a new session...
// Create a new engine instance
const newEngine = createQNCEEngine(DEMO_STORY);
// Load the state
await newEngine.loadState(JSON.parse(savedState));
console.log('State loaded successfully!');
console.log('Current Node:', newEngine.getCurrentNode().text);
// Create a lightweight checkpoint
const checkpoint = await engine.createCheckpoint('Before a risky choice');
// ...make a choice...
// Restore to the checkpoint
await engine.restoreFromCheckpoint(checkpoint.id);
console.log('Restored to checkpoint:', engine.getCurrentNode().text);
`$3
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.
Key Features:
- Automatic State Tracking: Intelligently captures state snapshots on key events (choice selection, flag changes, state loading)
- High-Performance Undo/Redo: Sub-millisecond undo/redo operations with configurable history depth
- Autosave Throttling: Configurable throttling prevents excessive saves during rapid state changes
- Memory Efficient: Capped history with automatic cleanup of older entries
Basic Usage:
`typescript
import { createQNCEEngine, DEMO_STORY } from 'qnce-engine';const engine = createQNCEEngine(DEMO_STORY);
// Autosave is enabled by default and will track state changes automatically
console.log('Can undo:', engine.canUndo()); // false initially
// Make some choices (autosave will track each change)
const choices = engine.getAvailableChoices();
engine.selectChoice(choices[0]);
console.log('Can undo:', engine.canUndo()); // true after making a choice
// Undo the last action
const undoResult = engine.undo();
if (undoResult.success) {
console.log('Undid:', undoResult.description);
console.log('Can redo:', engine.canRedo()); // true
}
// Redo the undone action
const redoResult = engine.redo();
if (redoResult.success) {
console.log('Redid:', redoResult.description);
}
// Get history summary
const history = engine.getHistorySummary();
console.log(
History: ${history.undoCount} undo, ${history.redoCount} redo entries);
`Advanced Configuration:
`typescript
// Configure undo/redo system
engine.configureUndoRedo({
maxUndoEntries: 100, // Maximum undo operations to remember
maxRedoEntries: 50, // Maximum redo operations to remember
enabled: true // Enable/disable undo/redo tracking
});// Configure autosave behavior
engine.configureAutosave({
enabled: true, // Enable/disable autosave
throttleMs: 100, // Minimum time between saves (milliseconds)
events: ['choice', 'flag', 'load'] // Which events trigger autosave
});
// Manual autosave trigger
await engine.autosave();
// Clear all undo/redo history
engine.clearHistory();
`Performance Guarantees:
- Undo operations: <1ms for normal state
- Redo operations: <1ms for normal state
- Autosave overhead: <1ms per operation
- Memory efficient with configurable history limits
$3
`bash
Real-time performance dashboard
qnce-perf dashboardLive monitoring with updates every 2 seconds
qnce-perf liveExport performance data
qnce-perf export > performance-report.json
`πΏ Advanced Branching & AI Integration
$3
`typescript
import { createQNCEEngine, createBranchingEngine } from 'qnce-engine';// Create core engine
const engine = createQNCEEngine(storyData);
// Enable advanced branching
const branchingEngine = engine.enableBranching(advancedStoryData);
// Evaluate available branches
const branches = await branchingEngine.evaluateAvailableBranches();
console.log(
Available paths: ${branches.length});// Execute a narrative branch
await branchingEngine.executeBranch(branches[0].id);
`$3
`typescript
// Set AI context for personalized content
branchingEngine.setAIContext({
playerProfile: {
playStyle: 'explorer',
preferences: { adventure: 0.8, mystery: 0.6 },
historicalChoices: ['brave-path', 'investigate-clue']
},
narrativeContext: {
currentTone: 'mysterious',
thematicElements: ['exploration', 'discovery'],
plotTension: 0.7
}
});// Generate AI-enhanced branches
const aiBranches = await branchingEngine.generateAIBranches(3);
console.log('AI-generated options:', aiBranches.map(b => b.displayText));
`$3
`typescript
// Insert new branch at runtime
const dynamicBranch = {
type: 'insert',
branchId: 'special-event',
targetLocation: { chapterId: 'main', nodeId: 'crossroads' },
payload: {
name: 'Special Event',
branchOptions: [{
id: 'event-choice',
displayText: 'Investigate the mysterious sound',
flagEffects: { event_discovered: true }
}]
}
};await branchingEngine.insertDynamicBranch(dynamicBranch);
// Remove branch when no longer needed
await branchingEngine.removeDynamicBranch('special-event');
`$3
`typescript
// Get branching analytics
const analytics = branchingEngine.getBranchingAnalytics();
console.log(Branches traversed: ${analytics.totalBranchesTraversed});
console.log(Popular choices: ${analytics.mostPopularBranches});// Export comprehensive data
const exportData = branchingEngine.exportBranchingData();
// Contains: story structure, session data, player behavior, performance metrics
`$3
`bash
Real-time performance dashboard
qnce-perf dashboardLive monitoring with updates every 2 seconds
qnce-perf liveExport performance data
qnce-perf export > performance-report.json
`π Performance Guide
QNCE v1.2.0-sprint2 includes advanced performance infrastructure for production applications.
$3
| Feature | Performance Gain | Impact |
|---------|-----------------|--------|
| Object Pooling | 90%+ allocation reduction | Eliminates GC hitches |
| Hot-Reload | 68% improvement (3.35ms) | Near-instant story updates |
| Background Processing | Non-blocking operations | Smooth user experience |
| Performance Monitoring | Real-time metrics | Production visibility |
$3
`bash
Install CLI globally
npm install -g qnce-engineReal-time performance monitoring
qnce-perf livePerformance dashboard output:
π QNCE Performance Dashboard
=====================================
π Session Duration: 45.2s
π’ Total Events: 1,247πΎ Cache Performance:
β
Hit Rate: 92.3% (threshold: 80%)
β
Avg Cache Time: 0.8ms (threshold: 50ms)
π₯ Hot-Reload Performance:
β οΈ Avg Time: 3.35ms (threshold: 2ms)
π Max Time: 4.1ms
π Total Reloads: 12
π§΅ ThreadPool Status:
π Completed Jobs: 445
β³ Queued Jobs: 3
π Active Workers: 2
`$3
`typescript
// Enable all performance optimizations
const engine = createQNCEEngine(storyData, {}, true, {
maxWorkers: 4, // Background processing
enableProfiling: true // Performance monitoring
});// Object pooling and background caching happen automatically
// Monitor performance in real-time with CLI dashboard
`π Complete Performance Guide: docs/PERFORMANCE_GUIDE.md
Core API
$3
The main engine class for managing narrative state.
#### Methods
-
getCurrentNode(): Get the current narrative node
- goToNodeById(nodeId): Navigate directly to a node by its ID
- getState(): Get the complete engine state
- getFlags(): Get current narrative flags
- getHistory(): Get choice history
- selectChoice(choice): Make a narrative choice
- resetNarrative(): Reset to initial state
- loadState(state): Load a saved state
- checkFlag(name, value?): Check flag conditions
- getAvailableChoices(): Get filtered available choices$3
-
createQNCEEngine(storyData, initialState?): Create a new engine instance
- loadStoryData(jsonData): Load and validate story data from JSONStory Format
Stories are defined using JSON with the following structure:
`json
{
"initialNodeId": "start",
"nodes": [
{
"id": "start",
"text": "You stand at a crossroads...",
"choices": [
{
"text": "Go left",
"nextNodeId": "left_path",
"flagEffects": { "direction": "left" }
}
]
}
]
}
`CLI Tools
$3
Normalize external story formats into QNCE StoryData with schema + semantic validation.
Supported formats:
- Custom JSON: strict/lenient validation with JSON Schema
- Twison/Twine JSON: passages, links, robust start detection; tags mapped to
node.meta.tags
- Ink JSON: minimal mapping (developer preview). Use --experimental-ink for extended best-effort mappingUsage examples:
`bash
Autodetect format and write normalized JSON to stdout
qnce-import path/to/story.json > story.normalized.jsonForce a format and fail on schema/semantic issues
qnce-import --format twison --strict input.json -o normalized.jsonAdd an ID prefix when merging multiple sources
qnce-import --id-prefix libA_ a.json > a.norm.jsonRead from stdin, write to file
cat story.json | qnce-import --format custom -o out.json
`Exit codes: 0 success, 1 validation failure, 2 unexpected error.
$3
Validate your story structure:
`bash
qnce-audit story.json
`Features:
- Checks for missing node references
- Identifies unreachable nodes
- Finds dead ends
- Validates story structure
$3
Scaffold a new QNCE project:
`bash
qnce-init my-story
`Creates:
- Basic story template
- package.json with QNCE dependencies
- README with usage instructions
$3
Interactive narrative sessions with full undo/redo support:
`bash
qnce-play story.json
`Features:
- Real-time narrative playthrough
- Instant undo/redo with
u and r commands
- State inspection and debugging
- Performance monitoring
- Session save/load functionality
- Persistence backends via --storage (memory | local | session | file | indexeddb)
- Non-interactive runs for scripting/CI with JSON summary outputExamples:
`bash
Basic interactive play
qnce-play story.jsonUse file storage (directory configurable)
qnce-play story.json --storage file --storage-dir .qnce --save-key session1Resume a saved session
qnce-play story.json --storage file --storage-dir .qnce --load-key session1Scriptable non-interactive run (emits a JSON summary)
qnce-play story.json --non-interactive --storage memory
`$3
Current in v1.2.2: Real-time performance monitoring and analytics:
`bash
Launch interactive performance dashboard
qnce-perf dashboardLive monitoring with custom update interval
qnce-perf live [interval-ms]Export performance data to JSON
qnce-perf export [--format json|csv] [--output filename]Single performance snapshot
qnce-perf snapshot story.json
`Dashboard Features:
- Real-time memory usage and allocation tracking
- Object pool efficiency monitoring
- Performance hotspot identification
- Live story update timing analysis
- Historical performance trend graphs
Live Monitoring:
`bash
Monitor with 1-second updates
qnce-perf live 1000Default 500ms updates
qnce-perf live
`Export Options:
`bash
Export to JSON with full metrics
qnce-perf export --format json --output metrics.jsonExport to CSV for spreadsheet analysis
qnce-perf export --format csv --output performance.csvStream to stdout
qnce-perf export
`Integration Examples
$3
QNCE provides comprehensive React hooks for seamless integration:
`typescript
import { useQNCE, useUndoRedo, useAutosave } from 'qnce-engine/react';
import { DEMO_STORY } from 'qnce-engine';function NarrativeComponent() {
// Core narrative hook
const { engine, currentNode, choices, flags, selectChoice, resetNarrative } = useQNCE(DEMO_STORY);
// Undo/redo functionality
const {
undo,
redo,
canUndo,
canRedo,
undoCount,
redoCount,
clearHistory
} = useUndoRedo(engine);
// Autosave status
const { isAutosaveEnabled, lastAutosave } = useAutosave(engine);
return (
Current Scene
{currentNode.text}
Choices
{choices.map((choice, index) => (
))}
Autosave: {isAutosaveEnabled ? 'Enabled' : 'Disabled'}
{lastAutosave && Last saved: {lastAutosave.toLocaleTimeString()}
}
Debug Info
{JSON.stringify(flags, null, 2)}
);
}
`$3
QNCE provides pre-built React components for common UI patterns:
#### UndoRedoControls Component
A complete undo/redo control panel with accessibility features:
`typescript
import { UndoRedoControls } from 'qnce-engine/ui';
import { createQNCEEngine } from 'qnce-engine';function MyApp() {
const engine = createQNCEEngine(DEMO_STORY);
return (
{/ Narrative content /}
{/ Undo/Redo Controls /}
engine={engine}
size="md" // sm, md, lg
layout="horizontal" // horizontal, vertical
showLabels={true} // Show text labels
labels={{ // Custom labels
undo: "Go Back",
redo: "Go Forward"
}}
onUndo={(result) => console.log('Undo:', result)}
onRedo={(result) => console.log('Redo:', result)}
theme={{ // Custom theming
colors: {
primary: '#007bff',
disabled: '#6c757d'
},
borderRadius: { md: '8px' }
}}
/>
);
}
`#### AutosaveIndicator Component
Visual indicator for autosave status with animations:
`typescript
import { AutosaveIndicator } from 'qnce-engine/ui';function MyApp() {
const engine = createQNCEEngine(DEMO_STORY);
return (
{/ Narrative content /}
{/ Autosave Status /}
engine={engine}
variant="detailed" // minimal, detailed, icon-only
position="top-right" // inline, top-right, bottom-left, etc.
showTimestamp={true} // Show last save time
autoHideDelay={3000} // Auto-hide after 3 seconds
messages={{ // Custom messages
idle: "Ready to save",
saving: "Saving...",
saved: "All changes saved",
error: "Save failed"
}}
theme={{ // Custom theming
colors: {
success: '#28a745',
error: '#dc3545',
warning: '#ffc107'
}
}}
/>
);
}
`#### Keyboard Shortcuts
Built-in keyboard support with the
useKeyboardShortcuts hook:`typescript
import { useKeyboardShortcuts } from 'qnce-engine/ui';function MyApp() {
const engine = createQNCEEngine(DEMO_STORY);
// Enable keyboard shortcuts
useKeyboardShortcuts(engine, {
undo: ['ctrl+z', 'cmd+z'], // Undo shortcuts
redo: ['ctrl+y', 'cmd+shift+z'], // Redo shortcuts
save: ['ctrl+s', 'cmd+s'], // Manual save
disabled: false // Enable/disable all shortcuts
});
return (
{/ Your narrative UI /}
Press Ctrl+Z to undo, Ctrl+Y to redo, Ctrl+S to save
);
}
`$3
`typescript
import { createQNCEEngine } from 'qnce-engine';
import { useState, useCallback } from 'react';function useQNCE(storyData) {
const [engine] = useState(() => createQNCEEngine(storyData));
const [currentNode, setCurrentNode] = useState(engine.getCurrentNode());
const [flags, setFlags] = useState(engine.getFlags());
const selectChoice = useCallback((choice) => {
engine.selectChoice(choice);
setCurrentNode(engine.getCurrentNode());
setFlags(engine.getFlags());
}, [engine]);
const goToNodeById = useCallback((nodeId) => {
engine.goToNodeById(nodeId);
setCurrentNode(engine.getCurrentNode());
setFlags(engine.getFlags());
}, [engine]);
return { currentNode, flags, selectChoice, goToNodeById };
}
`$3
`typescript
import { createQNCEEngine } from 'qnce-engine';
import { ref, reactive } from 'vue';export function useQNCE(storyData) {
const engine = createQNCEEngine(storyData);
const currentNode = ref(engine.getCurrentNode());
const flags = reactive(engine.getFlags());
const selectChoice = (choice) => {
engine.selectChoice(choice);
currentNode.value = engine.getCurrentNode();
Object.assign(flags, engine.getFlags());
};
return { currentNode, flags, selectChoice };
}
`$3
`typescript
import { createQNCEEngine, loadStoryData } from 'qnce-engine';
import { readFileSync } from 'fs';
import * as readline from 'readline';const storyData = loadStoryData(JSON.parse(readFileSync('story.json', 'utf-8')));
const engine = createQNCEEngine(storyData);
async function playStory() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
while (true) {
const node = engine.getCurrentNode();
console.log('\n' + node.text);
if (node.choices.length === 0) break;
node.choices.forEach((choice, i) => {
console.log(
${i + 1}. ${choice.text});
});
// Get user input and make choice...
}
}
`π Examples & Demos
The repository includes comprehensive examples demonstrating all features:
$3
- File: examples/branching-quickstart.ts
- Features: Basic branching, AI integration, dynamic operations
- Run: npm run build && node dist/examples/branching-quickstart.js$3
- File: examples/branching-advanced-demo.ts
- Features: Complex narrative flows, conditional branching, analytics
- Story: "The Mysterious Library" - Interactive mystery with multiple paths$3
- File: examples/autosave-undo-demo.ts
- Features: Autosave, undo/redo, performance monitoring, state management
- Run: npm run demo:autosave
- Performance: Demonstrates <1ms undo/redo with real-time metrics$3
- Real-world testing: scripts/validation-real-world.ts
- Comprehensive testing: scripts/validation-comprehensive.ts`bash
Run the quickstart example
npm run build
node dist/examples/branching-quickstart.jsRun the new autosave demo
npm run demo:autosaveTry the interactive CLI tool
npm run build
qnce-play examples/demo-story.jsonRun validation tests
npm run build
node dist/scripts/validation-real-world.js
`Development
`bash
Clone and setup
git clone https://github.com/ByteSower/qnce-engine.git
cd qnce-engine
npm installBuild
npm run buildWatch mode
npm run build:watchLint
npm run lint
`License
MIT - See LICENSE file for details.
Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request
---
QNCE Engine - Empowering interactive narratives with quantum-inspired mechanics.
π‘οΈ Choice Validation System
Ensure only valid choices can be executed with comprehensive validation rules.
$3
`typescript
import { createQNCEEngine } from 'qnce-engine';const engine = createQNCEEngine(storyData);
try {
// makeChoice() automatically validates before executing
engine.makeChoice(0);
} catch (error) {
if (error instanceof ChoiceValidationError) {
console.error('Invalid choice:', error.message);
console.log('Available choices:', error.availableChoices);
}
}
`$3
Define complex validation rules on your choices:
`typescript
// Choice with flag requirements
const flagBasedChoice = {
text: 'Use the magic key',
nextNodeId: 'unlock_door',
flagRequirements: {
hasKey: true,
playerLevel: 5
}
};// Choice with inventory requirements
const inventoryChoice = {
text: 'Buy expensive sword',
nextNodeId: 'shop_success',
inventoryRequirements: {
gold: 1000,
gems: 2
}
};
// Time-based availability
const timedChoice = {
text: 'Enter the tavern',
nextNodeId: 'tavern',
timeRequirements: {
availableAfter: new Date('2025-01-01T18:00:00'),
availableBefore: new Date('2025-01-01T24:00:00')
}
};
// Disabled choice
const disabledChoice = {
text: 'Broken bridge',
nextNodeId: 'fall',
enabled: false
};
`$3
Create your own validation logic:
`typescript
import {
StandardValidationRules,
createChoiceValidator
} from 'qnce-engine';const validator = createChoiceValidator();
// Add custom rule
validator.addRule({
name: 'custom-rule',
priority: 10,
validate: (choice, context) => {
// Your custom logic
if (choice.text.includes('danger') && !context.state.flags.brave) {
return {
isValid: false,
reason: 'You must be brave to take this path!',
failedConditions: ['requires-bravery']
};
}
return { isValid: true };
}
});
// Apply to engine
engine.setChoiceValidator(validator);
`$3
`typescript
import {
ChoiceValidationError,
isChoiceValidationError
} from 'qnce-engine';try {
engine.makeChoice(2);
} catch (error) {
if (isChoiceValidationError(error)) {
// Get user-friendly message
const message = error.getUserFriendlyMessage();
console.log(message);
// Get debug information
const debugInfo = error.getDebugInfo();
console.log('Failed conditions:', debugInfo.validationResult.failedConditions);
// Show alternatives
console.log('Available choices:');
error.availableChoices?.forEach((choice, i) => {
console.log(
${i + 1}. ${choice.text});
});
}
}
``