UNPKG

qnce-engine

Version:

Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization

261 lines 10.1 kB
"use strict"; // QNCE Choice Validation System - Sprint 3.2 // Ensures only valid choices can be executed, providing robust error handling Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultChoiceValidator = exports.StandardValidationRules = void 0; exports.createChoiceValidator = createChoiceValidator; exports.createValidationContext = createValidationContext; /** * Built-in validation rules for common scenarios */ class StandardValidationRules { /** * Validates that choice exists in the current node's choices */ static CHOICE_EXISTS = { name: 'choice-exists', priority: 1, validate(choice, context) { const exists = context.currentNode.choices.some(c => c.text === choice.text && c.nextNodeId === choice.nextNodeId); return { isValid: exists, reason: exists ? undefined : `Choice "${choice.text}" is not available from current node`, suggestedChoices: exists ? undefined : context.currentNode.choices }; } }; /** * Validates flag-based conditions for choice availability */ static FLAG_CONDITIONS = { name: 'flag-conditions', priority: 2, validate(choice, context) { // Check if choice has flag requirements if (choice.flagRequirements) { const missingFlags = []; const conflictingFlags = []; for (const [flagName, requiredValue] of Object.entries(choice.flagRequirements)) { const currentValue = context.state.flags[flagName]; if (requiredValue === true && !currentValue) { missingFlags.push(flagName); } else if (requiredValue === false && currentValue) { conflictingFlags.push(flagName); } else if (typeof requiredValue !== 'boolean' && currentValue !== requiredValue) { missingFlags.push(`${flagName}=${requiredValue}`); } } if (missingFlags.length > 0 || conflictingFlags.length > 0) { const reasons = []; if (missingFlags.length > 0) { reasons.push(`Missing required flags: ${missingFlags.join(', ')}`); } if (conflictingFlags.length > 0) { reasons.push(`Conflicting flags: ${conflictingFlags.join(', ')}`); } return { isValid: false, reason: `Flag conditions not met: ${reasons.join('; ')}`, failedConditions: [...missingFlags, ...conflictingFlags], metadata: { missingFlags, conflictingFlags, currentFlags: context.state.flags } }; } } return { isValid: true, reason: undefined }; } }; /** * Validates choice is not disabled or marked as unavailable */ static CHOICE_ENABLED = { name: 'choice-enabled', priority: 3, validate(choice, _context) { if (choice.enabled === false) { return { isValid: false, reason: `Choice "${choice.text}" is currently disabled`, failedConditions: ['choice-disabled'] }; } return { isValid: true, reason: undefined }; } }; /** * Validates time-based requirements for choice availability */ static TIME_CONDITIONS = { name: 'time-conditions', priority: 4, validate(choice, context) { if (choice.timeRequirements) { const now = new Date(); const currentTime = context.timestamp || now.getTime(); const failures = []; if (choice.timeRequirements.availableAfter) { const afterTime = new Date(choice.timeRequirements.availableAfter).getTime(); if (currentTime < afterTime) { failures.push(`not available until ${choice.timeRequirements.availableAfter}`); } } if (choice.timeRequirements.availableBefore) { const beforeTime = new Date(choice.timeRequirements.availableBefore).getTime(); if (currentTime > beforeTime) { failures.push(`no longer available after ${choice.timeRequirements.availableBefore}`); } } if (choice.timeRequirements.minTime && currentTime < choice.timeRequirements.minTime) { failures.push(`minimum time not reached`); } if (choice.timeRequirements.maxTime && currentTime > choice.timeRequirements.maxTime) { failures.push(`maximum time exceeded`); } if (failures.length > 0) { return { isValid: false, reason: `Time conditions not met: ${failures.join(', ')}`, failedConditions: failures, metadata: { currentTime, timeRequirements: choice.timeRequirements } }; } } return { isValid: true, reason: undefined }; } }; /** * Validates inventory-based requirements for choice availability */ static INVENTORY_CONDITIONS = { name: 'inventory-conditions', priority: 5, validate(choice, context) { if (choice.inventoryRequirements) { const inventory = context.state.flags.inventory || {}; const missingItems = []; for (const [itemName, requiredQuantity] of Object.entries(choice.inventoryRequirements)) { const currentQuantity = inventory[itemName] || 0; if (currentQuantity < requiredQuantity) { missingItems.push(`${itemName} (need ${requiredQuantity}, have ${currentQuantity})`); } } if (missingItems.length > 0) { return { isValid: false, reason: `Insufficient inventory: ${missingItems.join(', ')}`, failedConditions: missingItems, metadata: { currentInventory: inventory, requirements: choice.inventoryRequirements } }; } } return { isValid: true, reason: undefined }; } }; } exports.StandardValidationRules = StandardValidationRules; /** * Default implementation of ChoiceValidator * Uses a rule-based system for extensible validation logic */ class DefaultChoiceValidator { rules = []; constructor() { // Add standard validation rules by default this.addRule(StandardValidationRules.CHOICE_EXISTS); this.addRule(StandardValidationRules.FLAG_CONDITIONS); this.addRule(StandardValidationRules.CHOICE_ENABLED); this.addRule(StandardValidationRules.TIME_CONDITIONS); this.addRule(StandardValidationRules.INVENTORY_CONDITIONS); } validate(choice, context) { // Run all validation rules in priority order const sortedRules = this.getRules(); for (const rule of sortedRules) { const result = rule.validate(choice, context); // If any rule fails, return that failure if (!result.isValid) { return { ...result, metadata: { ...result.metadata, failedRule: rule.name, rulesPassed: sortedRules.indexOf(rule) } }; } } // All rules passed return { isValid: true, metadata: { rulesChecked: sortedRules.length, allRulesPassed: true } }; } getAvailableChoices(context) { // Use the availableChoices from context (which may have been pre-filtered by conditions) // instead of all choices from the node return context.availableChoices.filter(choice => { const result = this.validate(choice, context); return result.isValid; }); } addRule(rule) { // Remove any existing rule with the same name this.removeRule(rule.name); // Add the new rule this.rules.push(rule); // Sort by priority this.rules.sort((a, b) => a.priority - b.priority); } removeRule(ruleName) { this.rules = this.rules.filter(rule => rule.name !== ruleName); } getRules() { return [...this.rules].sort((a, b) => a.priority - b.priority); } } exports.DefaultChoiceValidator = DefaultChoiceValidator; /** * Factory function to create a choice validator with default rules */ function createChoiceValidator() { return new DefaultChoiceValidator(); } /** * Utility function to create validation context from engine state */ function createValidationContext(currentNode, state, availableChoices, metadata) { return { currentNode, state, availableChoices, timestamp: performance.now(), metadata }; } //# sourceMappingURL=validation.js.map