import { describe, expect, it } from 'vitest'; import { MECHANIC_EVENT_REGISTRY, evaluateMechanicProgram, formatMechanicProgram, validateMechanicProgram, type MechanicEvent, type MechanicProgram } from './mechanic.js'; const custom = (encounterSeconds = 30, inputs: Record = {}) => ({ mode: 'custom' as const, encounterSeconds, inputs }); describe('structured mechanic runtime', () => { it('advertises the authoritative weapon-hit target and headshot fields', () => { expect(MECHANIC_EVENT_REGISTRY.weapon_hit).toMatchObject({ description: expect.stringContaining('headshot boolean'), fields: [ { path: 'targetId', label: 'Target ID', type: 'string' }, { path: 'values.headshot', label: 'Headshot', type: 'boolean' } ] }); }); it('models Superior Firepower as same-target sliding hits and one shared expiring stack pool', () => { const superiorFirepower: MechanicProgram = { id: 'superior-firepower', name: 'Superior Firepower', pools: [{ key: 'relentless', label: 'Relentless', scope: 'agent', initial: 0, min: 0, max: 10, expiresAfterSeconds: 10 }], clauses: [{ id: 'build-relentless', label: 'Build Relentless', when: 'weapon_hit', if: { kind: 'exists', value: { source: 'event', path: 'targetId' } }, reducer: { kind: 'sliding_count', threshold: 9, windowSeconds: 3, groupBy: { source: 'event', path: 'targetId' } }, mutations: [{ kind: 'add', pool: 'relentless', value: 1 }], outcomes: [ { id: 'relentless-weapon-damage', label: 'Weapon Damage', phase: 'while_active', kind: 'attribute_modifier', basis: 'flat', scope: 'equipped_weapon', attributeId: 'weapon-damage', value: 3, statePool: 'relentless', perStack: true }, { id: 'relentless-critical-damage', label: 'Critical Damage', phase: 'while_active', kind: 'attribute_modifier', basis: 'flat', scope: 'equipped_weapon', attributeId: 'critical-damage', value: 1, statePool: 'relentless', perStack: true } ] }] }; const events: MechanicEvent[] = Array.from({ length: 18 }, (_, index) => ({ kind: 'weapon_hit', at: index * 0.2, targetId: 'enemy-a' })); const result = evaluateMechanicProgram(superiorFirepower, events, custom(15)); expect(result.status).toBe('OK'); expect(result.emissions[0].at).toBeCloseTo(1.6); expect(result.emissions[1].at).toBeCloseTo(3.4); expect(result.finalState).toContainEqual(expect.objectContaining({ pool: 'relentless', value: 0, expiresAt: null })); expect(result.outcomes.every((outcome) => outcome.attributeId !== null)).toBe(true); expect(result.outcomes.map((outcome) => outcome.value)).toEqual([3, 1, 6, 2]); expect(result.script).toContain('COUNT 9 MATCHES WITHIN 3s PER EVENT.targetId'); expect(result.script).toContain('ADD 1 TO relentless'); const maximum = evaluateMechanicProgram(superiorFirepower, [], { mode: 'maximum', encounterSeconds: 15 }); expect(maximum.finalState[0].value).toBe(10); expect(maximum.outcomes.map((outcome) => outcome.value)).toEqual([30, 10]); }); it('supports every-N-shot reducers without a bespoke action family', () => { const program: MechanicProgram = { id: 'every-three-shots', name: 'Every Three Shots', pools: [{ key: 'charges', label: 'Charges', scope: 'agent', initial: 0, max: 10 }], clauses: [{ id: 'charge', when: 'shot_fired', reducer: { kind: 'modulo', every: 3 }, mutations: [{ kind: 'add', pool: 'charges', value: 1 }] }] }; const events = Array.from({ length: 7 }, (_, index): MechanicEvent => ({ kind: 'shot_fired', at: index })); const result = evaluateMechanicProgram(program, events, custom(10)); expect(result.emissions.map((emission) => emission.at)).toEqual([2, 5]); expect(result.finalState[0].value).toBe(2); }); it('supports duration and periodic reducers over deterministic timeline snapshots', () => { const program: MechanicProgram = { id: 'timed-movement', name: 'Timed Movement', pools: [ { key: 'settled', label: 'Settled', scope: 'agent', initial: 0, max: 1 }, { key: 'tempo', label: 'Tempo', scope: 'agent', initial: 0, max: 10 } ], clauses: [ { id: 'immobile-duration', when: 'movement_snapshot', if: { kind: 'compare', left: { source: 'event', path: 'values.state' }, operator: 'eq', right: 'immobile' }, reducer: { kind: 'duration', seconds: 3 }, mutations: [{ kind: 'set', pool: 'settled', value: 1 }] }, { id: 'moving-periodic', when: 'movement_snapshot', if: { kind: 'compare', left: { source: 'event', path: 'values.state' }, operator: 'eq', right: 'moving' }, reducer: { kind: 'periodic', intervalSeconds: 2 }, mutations: [{ kind: 'add', pool: 'tempo', value: 1 }] } ] }; const result = evaluateMechanicProgram(program, [ { kind: 'movement_snapshot', at: 0, values: { state: 'immobile' } }, { kind: 'movement_snapshot', at: 2, values: { state: 'immobile' } }, { kind: 'movement_snapshot', at: 3, values: { state: 'immobile' } }, { kind: 'movement_snapshot', at: 5, values: { state: 'moving' } }, { kind: 'movement_snapshot', at: 7, values: { state: 'moving' } }, { kind: 'movement_snapshot', at: 9, values: { state: 'moving' } } ], custom(12)); expect(result.emissions.map(({ clauseId, at }) => ({ clauseId, at }))).toEqual([ { clauseId: 'immobile-duration', at: 3 }, { clauseId: 'moving-periodic', at: 7 }, { clauseId: 'moving-periodic', at: 9 } ]); expect(result.finalState).toEqual([ expect.objectContaining({ pool: 'settled', value: 1 }), expect.objectContaining({ pool: 'tempo', value: 2 }) ]); }); it('emits outcomes in the declared event and state-transition phases', () => { const program: MechanicProgram = { id: 'phases', name: 'Outcome Phases', pools: [{ key: 'active', label: 'Active', scope: 'agent', initial: 0, max: 1 }], clauses: [ { id: 'activate', when: 'weapon_hit', reducer: { kind: 'none' }, mutations: [{ kind: 'set', pool: 'active', value: 1 }], outcomes: [ { id: 'triggering-hit', label: 'Triggering Hit', phase: 'on_event', kind: 'marker', basis: 'flat', scope: 'equipped_weapon', value: 1 }, { id: 'entered', label: 'Entered', phase: 'on_enter', kind: 'marker', basis: 'flat', scope: 'self', statePool: 'active', value: 1 }, { id: 'active-modifier', label: 'Active Modifier', phase: 'while_active', kind: 'attribute_modifier', basis: 'flat', scope: 'equipped_weapon', attributeId: 'weapon-damage', statePool: 'active', value: 10 } ] }, { id: 'deactivate', when: 'firing_stopped', reducer: { kind: 'none' }, mutations: [{ kind: 'clear', pool: 'active' }], outcomes: [{ id: 'exited', label: 'Exited', phase: 'on_exit', kind: 'marker', basis: 'flat', scope: 'self', statePool: 'active', value: 1 }] } ] }; const result = evaluateMechanicProgram(program, [ { kind: 'weapon_hit', at: 1, targetId: 'enemy-a' }, { kind: 'firing_stopped', at: 2 } ], custom()); expect(result.outcomes.map((outcome) => `${outcome.outcomeId}:${outcome.phase}`)).toEqual([ 'triggering-hit:on_event', 'entered:on_enter', 'active-modifier:while_active', 'exited:on_exit' ]); }); it('emits on-exit outcomes at pool expiry and applies their resource side effects', () => { const program: MechanicProgram = { id: 'expiry-exit', name: 'Expiry Exit', pools: [{ key: 'active', label: 'Active', scope: 'agent', initial: 0, max: 1, expiresAfterSeconds: 2 }], clauses: [{ id: 'activate', when: 'weapon_hit', reducer: { kind: 'none' }, mutations: [{ kind: 'set', pool: 'active', value: 1 }], outcomes: [{ id: 'expiry-health-cost', label: 'Expiry Health Cost', phase: 'on_exit', kind: 'resource_cost', scope: 'self', resource: 'health', basis: 'max_health_percent', statePool: 'active', value: 10 }] }] }; const result = evaluateMechanicProgram( program, [{ kind: 'weapon_hit', at: 1, targetId: 'enemy-a' }], custom(10), { maxHealth: 100, currentHealth: 100 } ); expect(result.outcomes).toMatchObject([{ outcomeId: 'expiry-health-cost', phase: 'on_exit', at: 3, appliedValue: 10, combatBefore: { currentHealth: 100 }, combatAfter: { currentHealth: 90 } }]); expect(result.finalCombat?.currentHealth).toBe(90); expect(result.finalState).toContainEqual(expect.objectContaining({ pool: 'active', value: 0, expiresAt: null })); }); it('uses a custom percentage as a weighted estimate and resolves 100% in maximum mode', () => { const program: MechanicProgram = { id: 'full-health-hit', name: 'Full Health Hit', pools: [{ key: 'estimated-procs', label: 'Estimated Procs', scope: 'agent', initial: 0 }], scenarioInputs: [{ key: 'fullHealthHitPercent', label: 'Hits received at full Health', kind: 'slider', defaultValue: 25, bestCaseValue: 100, min: 0, max: 100 }], clauses: [{ id: 'estimate-full-health-hits', when: 'agent_hit', reducer: { kind: 'none' }, weight: { op: 'divide', left: { ref: { source: 'scenario', key: 'fullHealthHitPercent' } }, right: 100 }, mutations: [{ kind: 'add', pool: 'estimated-procs', value: 1 }] }] }; const events: MechanicEvent[] = [0, 5, 10, 15].map((at) => ({ kind: 'agent_hit', at })); const estimate = evaluateMechanicProgram(program, events, custom(20, { fullHealthHitPercent: 25 })); expect(estimate.estimatedActivationCount).toBe(1); expect(estimate.emissions.every((emission) => emission.weight === 0.25)).toBe(true); expect(estimate.emissions.map((emission) => emission.mutations[0].after)).toEqual([ 0.25, 0.5, 0.75, 1 ]); expect(estimate.finalState[0].value).toBe(1); const maximum = evaluateMechanicProgram(program, events, { mode: 'maximum', encounterSeconds: 20 }); expect(maximum.estimatedActivationCount).toBe(4); expect(maximum.scenarioInputs.fullHealthHitPercent).toBe(100); expect(maximum.finalState[0].value).toBe(4); }); it('derives proximity stacks directly from a builder-controlled snapshot', () => { const program: MechanicProgram = { id: 'nearby-allies', name: 'Nearby Allies', pools: [{ key: 'nearby-ally-stacks', label: 'Nearby Ally Stacks', scope: 'agent', initial: 0, min: 0, max: 3, bestCaseValue: 3 }], clauses: [{ id: 'derive-nearby-allies', when: 'proximity_snapshot', reducer: { kind: 'derived_scalar', value: { ref: { source: 'event', path: 'values.alliesWithinRadius' } } }, mutations: [{ kind: 'derive', pool: 'nearby-ally-stacks', value: { ref: { source: 'reducer' } } }] }] }; const result = evaluateMechanicProgram(program, [{ kind: 'proximity_snapshot', at: 0, values: { alliesWithinRadius: 2 } }], custom()); expect(result.finalState[0].value).toBe(2); const maximum = evaluateMechanicProgram(program, [], { mode: 'maximum', encounterSeconds: 30 }); expect(maximum.finalState[0].value).toBe(3); }); it('shares one movement charge pool across generation and consumption clauses', () => { const program: MechanicProgram = { id: 'mobile-charge', name: 'Mobile Charge', pools: [{ key: 'mobile-charge', label: 'Mobile Charge', scope: 'agent', initial: 0, min: 0, max: 20 }], clauses: [ { id: 'gain-moving', when: 'movement_snapshot', if: { kind: 'compare', left: { source: 'event', path: 'values.state' }, operator: 'eq', right: 'moving' }, reducer: { kind: 'none' }, mutations: [{ kind: 'add', pool: 'mobile-charge', value: 1 }] }, { id: 'gain-sprinting', when: 'movement_snapshot', if: { kind: 'compare', left: { source: 'event', path: 'values.state' }, operator: 'eq', right: 'sprinting' }, reducer: { kind: 'none' }, mutations: [{ kind: 'add', pool: 'mobile-charge', value: 2 }] }, { id: 'spend-on-shot', when: 'shot_fired', if: { kind: 'compare', left: { source: 'pool', pool: 'mobile-charge' }, operator: 'gt', right: 0 }, reducer: { kind: 'none' }, mutations: [{ kind: 'consume', pool: 'mobile-charge', value: 1 }] }, { id: 'spend-on-skill', when: 'skill_deployed', reducer: { kind: 'none' }, mutations: [{ kind: 'consume', pool: 'mobile-charge', value: 10 }] } ] }; const result = evaluateMechanicProgram(program, [ { kind: 'movement_snapshot', at: 0, values: { state: 'moving' } }, { kind: 'movement_snapshot', at: 1, values: { state: 'sprinting' } }, { kind: 'movement_snapshot', at: 2, values: { state: 'sprinting' } }, { kind: 'shot_fired', at: 3 }, { kind: 'skill_deployed', at: 4 } ], custom()); expect(result.finalState[0].value).toBe(0); expect(result.emissions.map((emission) => emission.clauseId)).toEqual([ 'gain-moving', 'gain-sprinting', 'gain-sprinting', 'spend-on-shot', 'spend-on-skill' ]); }); it('transfers a target-scoped mark to the target named by the death event', () => { const program: MechanicProgram = { id: 'transferring-mark', name: 'Transferring Mark', pools: [{ key: 'mark', label: 'Mark', scope: 'target', initial: 0, min: 0, max: 1 }], clauses: [ { id: 'apply-mark', when: 'weapon_hit', reducer: { kind: 'none' }, mutations: [{ kind: 'set', pool: 'mark', entity: { kind: 'event_target' }, value: 1 }] }, { id: 'transfer-mark', when: 'enemy_killed', if: { kind: 'compare', left: { source: 'pool', pool: 'mark', entity: { kind: 'event_target' } }, operator: 'gt', right: 0 }, reducer: { kind: 'none' }, mutations: [{ kind: 'transfer', pool: 'mark', from: { kind: 'event_target' }, to: { kind: 'event_field', path: 'values.transferTargetId' }, value: 'all' }] } ] }; const result = evaluateMechanicProgram(program, [ { kind: 'weapon_hit', at: 1, targetId: 'enemy-a' }, { kind: 'enemy_killed', at: 2, targetId: 'enemy-a', values: { transferTargetId: 'enemy-b' } } ], custom()); expect(result.finalState).toEqual([ expect.objectContaining({ entityId: 'enemy-a', value: 0 }), expect.objectContaining({ entityId: 'enemy-b', value: 1 }) ]); }); it('caps and refreshes target-scoped stacks independently', () => { const program: MechanicProgram = { id: 'target-trauma', name: 'Target Trauma', pools: [{ key: 'trauma', label: 'Trauma', scope: 'target', initial: 0, min: 0, max: 10, expiresAfterSeconds: 1 }], clauses: [ { id: 'body-stack', when: 'weapon_hit', if: { kind: 'compare', left: { source: 'event', path: 'values.headshot' }, operator: 'eq', right: false }, reducer: { kind: 'none' }, mutations: [{ kind: 'add', pool: 'trauma', entity: { kind: 'event_target' }, value: 1 }] }, { id: 'head-stack', when: 'weapon_hit', if: { kind: 'compare', left: { source: 'event', path: 'values.headshot' }, operator: 'eq', right: true }, reducer: { kind: 'none' }, mutations: [{ kind: 'add', pool: 'trauma', entity: { kind: 'event_target' }, value: 2 }] } ] }; const result = evaluateMechanicProgram(program, [ { kind: 'weapon_hit', at: 0, targetId: 'enemy-a', values: { headshot: false } }, { kind: 'weapon_hit', at: 0.25, targetId: 'enemy-b', values: { headshot: false } }, { kind: 'weapon_hit', at: 0.5, targetId: 'enemy-a', values: { headshot: true } }, ...Array.from({ length: 5 }, (_, index): MechanicEvent => ({ kind: 'weapon_hit', at: 0.55 + index * 0.05, targetId: 'enemy-a', values: { headshot: true } })) ], custom(0.9)); expect(result.status).toBe('OK'); expect(result.finalState).toEqual([ expect.objectContaining({ pool: 'trauma', entityId: 'enemy-a', value: 10, expiresAt: 1.75 }), expect.objectContaining({ pool: 'trauma', entityId: 'enemy-b', value: 1, expiresAt: 1.25 }) ]); }); it('uses the prior target stack count for live-Attribute damage on pool exit', () => { const skillDamageId = 'skill-damage'; const program: MechanicProgram = { id: 'precise-trauma', name: 'Precise Trauma', pools: [{ key: 'trauma', label: 'Trauma', scope: 'target', initial: 0, min: 0, max: 10, expiresAfterSeconds: 1 }], clauses: [{ id: 'apply-trauma', when: 'weapon_hit', reducer: { kind: 'none' }, mutations: [{ kind: 'add', pool: 'trauma', entity: { kind: 'event_target' }, value: 2 }], outcomes: [{ id: 'trauma-damage', label: 'Trauma Damage', phase: 'on_exit', kind: 'direct_damage', scope: 'target', basis: 'flat', value: { ref: { source: 'attribute', attributeId: skillDamageId } }, statePool: 'trauma', perStack: true, minStacks: 2 }] }] }; const result = evaluateMechanicProgram( program, [{ kind: 'weapon_hit', at: 0, targetId: 'enemy-a', values: { headshot: true } }], custom(2), { maxHealth: 100, currentHealth: 100, attributes: { [skillDamageId]: 75 } } ); expect(result.status).toBe('OK'); expect(result.outcomes).toMatchObject([{ outcomeId: 'trauma-damage', targetId: 'enemy-a', at: 1, phase: 'on_exit', value: 150, resolvedValue: 150, appliedValue: 150, stateValue: 2 }]); expect(result.finalState).toContainEqual(expect.objectContaining({ pool: 'trauma', entityId: 'enemy-a', value: 0, expiresAt: null })); }); it('models scaled OS Protocol restoration, an unscaled Health cost, and post-cost bonus damage', () => { const upgradeScale = { op: 'add' as const, left: 1, right: { op: 'divide' as const, left: { ref: { source: 'equipment' as const, path: 'protocolTalentUpgrades' } }, right: 5 } }; const program: MechanicProgram = { id: 'os-protocol-health-engine', name: 'OS Protocol Health Engine', scenarioInputs: [{ key: 'restorationIntervalSeconds', label: 'Restoration interval', description: 'The game cadence is not yet verified; Custom Scenario must supply it.', kind: 'number', readiness: 'requires_assumption', min: 0.01 }], pools: [{ key: 'nearby-enemies', label: 'Nearby Enemies', scope: 'agent', initial: 0, min: 0, max: 20 }], clauses: [ { id: 'sample-nearby-enemies', when: 'proximity_snapshot', reducer: { kind: 'derived_scalar', value: { ref: { source: 'event', path: 'values.enemiesWithinRadius' } } }, mutations: [{ kind: 'derive', pool: 'nearby-enemies', value: { ref: { source: 'reducer' } } }] }, { id: 'restore-nearby-enemies', when: 'tick', reducer: { kind: 'periodic', intervalSeconds: { ref: { source: 'scenario', key: 'restorationIntervalSeconds' } } }, mutations: [{ kind: 'none' }], outcomes: [{ id: 'nearby-restoration', label: 'Nearby Enemy Restoration', phase: 'after_event', kind: 'restoration', scope: 'self', resource: 'health', basis: 'max_health_percent', value: { op: 'multiply', left: { op: 'clamp', value: { op: 'multiply', left: { ref: { source: 'pool', pool: 'nearby-enemies' } }, right: 0.5 }, min: 0, max: 2.5 }, right: upgradeScale } }] }, { id: 'shot-health-exchange', when: 'shot_fired', reducer: { kind: 'none' }, mutations: [{ kind: 'none' }], outcomes: [ { id: 'shot-health-cost', label: 'Shot Health Cost', phase: 'on_event', kind: 'resource_cost', scope: 'self', resource: 'health', basis: 'max_health_percent', value: 2 }, { id: 'shot-health-damage', label: 'Current Health Bonus Damage', phase: 'on_event', kind: 'direct_damage', scope: 'target', basis: 'current_health_percent', combatTiming: 'current', value: { op: 'multiply', left: 0.6, right: upgradeScale } } ] } ] }; const events: MechanicEvent[] = [ { kind: 'tick', at: 0 }, { kind: 'shot_fired', at: 1 }, { kind: 'proximity_snapshot', at: 2, values: { enemiesWithinRadius: 9 } }, { kind: 'tick', at: 3 } ]; const result = evaluateMechanicProgram( program, events, custom(10, { restorationIntervalSeconds: 3 }), { maxHealth: 10_000, currentHealth: 10_000, weaponBaseDamage: 500 }, { protocolTalentUpgrades: 5 } ); expect(result.status).toBe('OK'); const cost = result.outcomes.find((outcome) => outcome.outcomeId === 'shot-health-cost'); const damage = result.outcomes.find((outcome) => outcome.outcomeId === 'shot-health-damage'); const restoration = result.outcomes.find((outcome) => outcome.outcomeId === 'nearby-restoration'); expect(cost).toMatchObject({ value: 2, resolvedValue: 200, appliedValue: 200 }); expect(damage).toMatchObject({ value: 1.2, resolvedValue: 117.6, appliedValue: 117.6 }); expect(restoration).toMatchObject({ value: 5, resolvedValue: 500, appliedValue: 200 }); expect(result.finalCombat?.currentHealth).toBe(10_000); expect(result.equipment?.protocolTalentUpgrades).toBe(5); const preCostProgram = structuredClone(program); const preCostDamage = preCostProgram.clauses[2].outcomes?.find( (outcome) => outcome.id === 'shot-health-damage' ); if (preCostDamage) preCostDamage.combatTiming = 'pre_event'; const preCost = evaluateMechanicProgram( preCostProgram, [{ kind: 'shot_fired', at: 1 }], custom(10, { restorationIntervalSeconds: 3 }), { maxHealth: 10_000, currentHealth: 10_000 }, { protocolTalentUpgrades: 5 } ); expect(preCost.outcomes.find((outcome) => outcome.outcomeId === 'shot-health-damage')) .toMatchObject({ resolvedValue: 120 }); const unresolved = evaluateMechanicProgram( program, [], { mode: 'maximum', encounterSeconds: 10 }, { maxHealth: 10_000, currentHealth: 10_000 }, { protocolTalentUpgrades: 5 } ); expect(unresolved.readiness).toEqual({ status: 'REQUIRES_ASSUMPTIONS', missingInputs: ['restorationIntervalSeconds'] }); }); it('counts distinct values in a rolling window, resets consecutive runs, and applies named resources', () => { const program: MechanicProgram = { id: 'runtime-gaps', name: 'Runtime gaps', clauses: [ { id: 'distinct-targets', recipeKey: 'distinct-enemies-within-window', when: 'weapon_hit', if: { kind: 'exists', value: { source: 'event', path: 'targetId' } }, reducer: { kind: 'distinct_count', threshold: 3, windowSeconds: 2, distinctBy: { source: 'event', path: 'targetId' } }, mutations: [{ kind: 'none' }], outcomes: [{ id: 'distinct-marker', label: 'Distinct target threshold', phase: 'after_event', kind: 'marker', scope: 'all_skills', basis: 'flat', value: 1 }] }, { id: 'consecutive-targets', when: 'shot_fired', if: { kind: 'exists', value: { source: 'event', path: 'targetId' } }, reducer: { kind: 'consecutive_count', threshold: 3, groupBy: { source: 'event', path: 'targetId' } }, mutations: [{ kind: 'none' }], outcomes: [{ id: 'consecutive-marker', label: 'Consecutive target threshold', phase: 'after_event', kind: 'marker', scope: 'assigned_ability', basis: 'flat', value: 1 }] }, { id: 'resources', when: 'tick', reducer: { kind: 'none' }, mutations: [{ kind: 'none' }], outcomes: [ { id: 'health-cost', label: 'Health cost', phase: 'on_event', kind: 'resource_cost', scope: 'self', resource: 'health', basis: 'flat', value: 10 }, { id: 'armor-restore', label: 'Armor restore', phase: 'on_event', kind: 'restoration', scope: 'self_and_allies', resource: 'armor', basis: 'flat', value: 20 }, { id: 'ammo-restore', label: 'Ammo restore', phase: 'on_event', kind: 'restoration', scope: 'equipped_weapon', resource: 'ammo', basis: 'flat', value: 5 } ] } ] }; const result = evaluateMechanicProgram(program, [ { kind: 'weapon_hit', at: 0, targetId: 'alpha' }, { kind: 'weapon_hit', at: 0.2, targetId: 'alpha' }, { kind: 'weapon_hit', at: 0.4, targetId: 'bravo' }, { kind: 'weapon_hit', at: 0.6, targetId: 'charlie' }, { kind: 'shot_fired', at: 1, targetId: 'alpha' }, { kind: 'shot_fired', at: 1.1, targetId: 'alpha' }, { kind: 'shot_fired', at: 1.2 }, { kind: 'shot_fired', at: 1.3, targetId: 'bravo' }, { kind: 'shot_fired', at: 1.4, targetId: 'bravo' }, { kind: 'shot_fired', at: 1.5, targetId: 'bravo' }, { kind: 'tick', at: 2 } ], custom(), { maxHealth: 100, currentHealth: 50, maxArmor: 50, currentArmor: 30, maxAmmo: 10, currentAmmo: 7 }); expect(result.status).toBe('OK'); expect(result.emissions.filter(({ clauseId }) => clauseId === 'distinct-targets')).toMatchObject([{ at: 0.6, reducerValue: 3 }]); expect(result.emissions.filter(({ clauseId }) => clauseId === 'consecutive-targets')).toMatchObject([{ at: 1.5, reducerValue: 3 }]); expect(result.outcomes.filter(({ clauseId }) => clauseId === 'resources')).toMatchObject([ { outcomeId: 'health-cost', resource: 'health', appliedValue: 10 }, { outcomeId: 'armor-restore', resource: 'armor', appliedValue: 20 }, { outcomeId: 'ammo-restore', resource: 'ammo', appliedValue: 3 } ]); expect(result.finalCombat).toMatchObject({ currentHealth: 40, currentArmor: 50, currentAmmo: 10 }); expect(result.script).toContain('COUNT 3 DISTINCT EVENT.targetId WITHIN 2s'); expect(result.script).toContain('COUNT 3 CONSECUTIVE MATCHES PER EVENT.targetId'); expect(result.script).toContain('[RECIPE distinct-enemies-within-window]'); }); it('resolves live Attribute percentages and delivers a restoration total continuously', () => { const firepowerMultiplierId = '82a95611-283b-4fd8-a6ed-3567a316f36e'; const program: MechanicProgram = { id: 'predatory-aura', name: 'Predatory Aura', clauses: [{ id: 'restore-on-kill', when: 'enemy_killed', reducer: { kind: 'none' }, mutations: [], outcomes: [{ id: 'health-restoration', label: 'Health restoration', phase: 'after_event', kind: 'restoration', scope: 'self', resource: 'health', basis: 'flat', delivery: 'continuous', durationSeconds: 3, value: { op: 'multiply', left: 240, right: { op: 'divide', left: { ref: { source: 'attribute', attributeId: firepowerMultiplierId } }, right: 100 } } }] }] }; const result = evaluateMechanicProgram( program, [{ kind: 'enemy_killed', at: 0 }], custom(3), { maxHealth: 1_000, currentHealth: 500, attributes: { [firepowerMultiplierId]: 100 } } ); expect(result.status).toBe('OK'); expect(result.outcomes).toHaveLength(1); expect(result.outcomes[0]).toMatchObject({ at: 3, delivery: 'continuous', deliveryStart: 0, deliveryEnd: 3, totalResolvedValue: 240, resolvedValue: 240, appliedValue: 240 }); expect(result.finalCombat?.currentHealth).toBe(740); expect(result.script).toContain( 'ATTRIBUTE[82a95611-283b-4fd8-a6ed-3567a316f36e] / 100' ); expect(result.script).toContain('DELIVER TOTAL CONTINUOUSLY OVER 3s'); const missingAttribute = evaluateMechanicProgram( program, [{ kind: 'enemy_killed', at: 0 }], custom(3), { maxHealth: 1_000, currentHealth: 500 } ); expect(missingAttribute.status).toBe('INVALID'); expect(missingAttribute.errors).toContain( `This mechanic requires authoritative Attribute ${firepowerMultiplierId}.` ); }); it('integrates overlapping restoration streams at event boundaries without banking overheal', () => { const program: MechanicProgram = { id: 'continuous-restoration', name: 'Continuous Restoration', clauses: [{ id: 'restore', when: 'enemy_killed', reducer: { kind: 'none' }, mutations: [], outcomes: [{ id: 'restore-health', label: 'Restore Health', phase: 'after_event', kind: 'restoration', scope: 'self', resource: 'health', basis: 'flat', value: 240, delivery: 'continuous', durationSeconds: 3 }] }, { id: 'damage-between-streams', when: 'agent_hit', reducer: { kind: 'none' }, mutations: [], outcomes: [{ id: 'health-cost', label: 'Incoming damage proxy', phase: 'on_event', kind: 'resource_cost', scope: 'self', resource: 'health', basis: 'flat', value: 100 }] }] }; const result = evaluateMechanicProgram( program, [ { kind: 'enemy_killed', at: 0 }, { kind: 'agent_hit', at: 1 }, { kind: 'enemy_killed', at: 1 } ], custom(4), { maxHealth: 1_000, currentHealth: 500 } ); const continuous = result.outcomes.filter((outcome) => outcome.delivery === 'continuous'); expect(continuous[0]).toMatchObject({ at: 1, resolvedValue: 80, appliedValue: 80 }); expect(continuous.reduce((total, outcome) => total + outcome.resolvedValue, 0)).toBeCloseTo(480); expect(continuous.reduce((total, outcome) => total + outcome.appliedValue, 0)).toBeCloseTo(480); expect(result.finalCombat?.currentHealth).toBe(880); const capped = evaluateMechanicProgram( program, [{ kind: 'enemy_killed', at: 0 }], custom(3), { maxHealth: 1_000, currentHealth: 950 } ); expect(capped.outcomes.reduce((total, outcome) => total + outcome.appliedValue, 0)).toBe(50); expect(capped.finalCombat?.currentHealth).toBe(1_000); }); }); describe('mechanic validation and pseudo-script', () => { it('enforces clause cooldowns without counting events received during the cooldown', () => { const program: MechanicProgram = { id: 'cooldown', name: 'Cooldown', clauses: [{ id: 'shot-proc', when: 'shot_fired', reducer: { kind: 'none' }, cooldownSeconds: 2, mutations: [{ kind: 'none' }], outcomes: [{ id: 'proc', label: 'Proc', phase: 'after_event', kind: 'marker', scope: 'self', basis: 'flat', value: 1 }] }] }; const result = evaluateMechanicProgram(program, [ { kind: 'shot_fired', at: 0 }, { kind: 'shot_fired', at: 1 }, { kind: 'shot_fired', at: 2 }, { kind: 'shot_fired', at: 3 } ], custom(4)); expect(result.emissions.map(({ at }) => at)).toEqual([0, 2]); expect(result.outcomes.map(({ at }) => at)).toEqual([0, 2]); expect(result.script).toContain('COOLDOWN 2s'); }); it('rejects invalid references, reducers, and target transfer scopes', () => { const invalid: MechanicProgram = { id: 'invalid', name: 'Invalid', pools: [{ key: 'agent-state', label: 'Agent State', scope: 'agent', initial: 0 }], clauses: [{ id: 'bad', recipeKey: '', when: 'shot_fired', reducer: { kind: 'modulo', every: 0 }, mutations: [{ kind: 'transfer', pool: 'agent-state', from: { kind: 'agent' }, to: { kind: 'event_target' } }], outcomes: [{ id: 'missing-attribute', label: 'Missing', phase: 'after_event', kind: 'attribute_modifier', basis: 'flat', scope: 'self', value: { ref: { source: 'scenario', key: 'missing-input' } } }, { id: 'missing-resource', label: 'Missing resource', phase: 'after_event', kind: 'restoration', scope: 'self', basis: 'flat', value: 1 }] }] }; const result = validateMechanicProgram(invalid); expect(result.ok).toBe(false); if (!result.ok) { expect(result.errors).toContain('Clause 1 modulo interval must be a positive integer.'); expect(result.errors).toContain('Clause 1 recipe key cannot be empty.'); expect(result.errors).toContain('Clause 1 mutation 1 can only transfer a target-scoped pool.'); expect(result.errors).toContain('Clause 1 outcome 1 requires an Attribute ID.'); expect(result.errors).toContain('Clause 1 outcome 1 value references unknown scenario input "missing-input".'); expect(result.errors).toContain('Clause 1 outcome 2 requires a resource.'); } }); it('rejects invalid continuous-delivery combinations', () => { const base = { id: 'delivery-validation', name: 'Delivery Validation', clauses: [{ id: 'invalid-delivery', when: 'enemy_killed' as const, reducer: { kind: 'none' as const }, mutations: [], outcomes: [{ id: 'damage-over-time', label: 'Damage', phase: 'after_event' as const, kind: 'direct_damage' as const, scope: 'target' as const, basis: 'flat' as const, value: 10, delivery: 'continuous' as const, durationSeconds: 3 }] }] }; const result = validateMechanicProgram(base); expect(result.ok).toBe(false); if (!result.ok) { expect(result.errors).toContain( 'Clause 1 outcome 1 can only use continuous delivery for restoration.' ); } }); it('formats nested conditions and state operations deterministically', () => { const program: MechanicProgram = { id: 'format', name: 'Prepared', pools: [{ key: 'prepared', label: 'Prepared', scope: 'agent', initial: 0, max: 10 }], clauses: [{ id: 'gain', label: 'Gain Prepared', when: 'agent_hit', if: { kind: 'all', predicates: [ { kind: 'compare', left: { source: 'event', path: 'values.healthPercentBefore' }, operator: 'eq', right: 100 }, { kind: 'not', predicate: { kind: 'exists', value: { source: 'event', path: 'values.blocked' } } } ] }, reducer: { kind: 'none' }, mutations: [{ kind: 'add', pool: 'prepared', value: 1 }] }] }; expect(formatMechanicProgram(program)).toBe([ 'MECHANIC PREPARED', 'STATE prepared [AGENT] 0..10', '', 'WHEN AGENT HIT // Gain Prepared', 'IF (EVENT.values.healthPercentBefore == 100) AND (NOT (EXISTS EVENT.values.blocked))', 'ADD 1 TO prepared' ].join('\n')); }); });