import { RuntimeFault, isRecord } from './types.js'; export class ActionExecutor { constructor(engine, { diagnostics } = {}) { this.engine = engine; this.diagnostics = diagnostics; this.invocationOrdinal = 0; } execute(actions, context = {}) { if (!Array.isArray(actions)) throw new RuntimeFault('ERR_SCHEMA_VALIDATION', 'Actions must be an array.'); const stream = this.engine.rng.stream('scenario', `actions:${++this.invocationOrdinal}`); const results = []; for (let index = 0; index < actions.length; index += 1) { const action = actions[index]; try { results.push(this.executeOne(action, stream, context, `$.actions[${index}]`)); } catch (error) { const fault = error instanceof RuntimeFault ? error : new RuntimeFault('ERR_ACTION_FAILURE', error.message); this.diagnostics?.error(fault.code, fault.message, { exhibitId: this.engine.document.meta.id, section: 'actions', objectId: action?.id ?? null, property: fault.path }); results.push({ status: 'failed', error: fault }); if (action?.critical !== false) throw fault; } } return results; } executeOne(action, stream, context, path) { if (!isRecord(action) || typeof action.type !== 'string') throw new RuntimeFault('ERR_SCHEMA_VALIDATION', 'Action requires a type.', path); if (action.when !== undefined && !this.engine.evaluateCondition(action.when, stream, `${path}.when`)) return { status: 'skipped', reason: 'condition' }; if (action.chance !== undefined) { if (!Number.isFinite(action.chance) || action.chance < 0 || action.chance > 1) throw new RuntimeFault('ERR_OUT_OF_BOUNDS', 'Action chance must be from 0 through 1.', `${path}.chance`); if (stream.nextFloat() >= action.chance) return { status: 'skipped', reason: 'chance' }; } if (action.type === 'set') { const value = this.engine.evaluateValue(action.value, stream, `${path}.value`); this.engine.setState(action.target, value, action.transition); return { status: 'executed', type: 'set', target: action.target, value }; } if (action.type === 'override') { const value = this.engine.evaluateValue(action.value, stream, `${path}.value`); const instanceId = this.engine.overrides.add(action, value, { owner: context.owner, inheritedPriority: context.priority }); this.engine.resolveAll(); return { status: 'executed', type: 'override', target: action.target, value, instanceId }; } throw new RuntimeFault('ERR_UNSUPPORTED_TARGET', `Action '${action.type}' belongs to a later subsystem phase.`, `${path}.type`); } }