Files
shd.build/src/lib/calculations/mechanic.test.ts
T

3543 lines
98 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
MECHANIC_EVENT_REGISTRY,
evaluateMechanicProgram,
formatMechanicProgram,
validateMechanicProgram,
type MechanicEvent,
type MechanicEventKind,
type MechanicProgram,
type NumericExpression,
} from './mechanic.js';
const custom = (
encounterSeconds = 30,
inputs: Record<string, number | boolean> = {},
) => ({
mode: 'custom' as const,
encounterSeconds,
inputs,
});
const closeAndPersonalProgram = (
resolvedVariantValue: number,
): MechanicProgram => ({
id: 'close-and-personal-engineering',
name: 'Close and Personal Engineering',
pools: [
{
key: 'eligible-allies',
label: 'Eligible Allies',
scope: 'agent',
initial: 0,
min: 0,
max: 20,
},
],
clauses: [
{
id: 'sample-eligible-allies',
when: 'proximity_snapshot',
reducer: {
kind: 'derived_scalar',
value: {
ref: {
source: 'event',
path: 'values.alliesWithin30MetersWithAgentGrantedBuff',
},
},
},
mutations: [
{
kind: 'derive',
pool: 'eligible-allies',
value: { ref: { source: 'reducer' } },
},
],
},
{
id: 'resolve-engineering',
when: 'tick',
reducer: { kind: 'none' },
mutations: [{ kind: 'none' }],
outcomes: [
{
id: 'engineering',
label: 'Engineering',
phase: 'on_event',
kind: 'attribute_modifier',
scope: 'self',
basis: 'flat',
attributeId: 'engineering',
value: {
op: 'multiply',
left: {
op: 'multiply',
left: { ref: { source: 'pool', pool: 'eligible-allies' } },
right: resolvedVariantValue,
},
right: {
op: 'add',
left: 1,
right: {
op: 'divide',
left: {
ref: {
source: 'equipment',
path: 'protocolTalentUpgrades',
},
},
right: 5,
},
},
},
},
],
},
],
});
describe('deterministic mechanic probabilities and assigned abilities', () => {
it('rolls outcome probability independently with a reproducible seed and preserves a refreshed window', () => {
const program: MechanicProgram = {
id: 'ammo-refill',
name: 'Ammo Refill',
pools: [
{
key: 'tactical-link-window',
label: 'Tactical Link window',
scope: 'agent',
initial: 0,
min: 0,
max: 1,
expiresAfterSeconds: 6,
},
],
clauses: [
{
id: 'start-window',
when: 'signature_ability_deployed',
reducer: { kind: 'none' },
mutations: [{ kind: 'set', pool: 'tactical-link-window', value: 1 }],
},
{
id: 'refresh-window',
when: 'enemy_killed',
reducer: { kind: 'none' },
mutations: [{ kind: 'set', pool: 'tactical-link-window', value: 1 }],
},
{
id: 'recover-ammo',
when: 'shot_fired',
if: {
kind: 'compare',
left: { source: 'pool', pool: 'tactical-link-window' },
operator: 'eq',
right: 1,
},
reducer: { kind: 'none' },
mutations: [],
outcomes: [
{
id: 'recover-one-ammo',
label: 'Recover one ammo',
phase: 'after_event',
kind: 'restoration',
scope: 'self',
basis: 'flat',
resource: 'ammo',
value: 1,
probabilityPercent: 29,
},
],
},
],
};
const events = [
{
kind: 'signature_ability_deployed' as const,
at: 0,
values: { assignedAbilityDurationSeconds: 6 },
},
{ kind: 'shot_fired' as const, at: 1 },
{ kind: 'shot_fired' as const, at: 2 },
{ kind: 'enemy_killed' as const, at: 5 },
{ kind: 'shot_fired' as const, at: 6 },
{ kind: 'shot_fired' as const, at: 11 },
];
const first = evaluateMechanicProgram(
program,
events,
{ mode: 'custom', encounterSeconds: 12, randomSeed: 1 },
{ maxHealth: 100, currentHealth: 100, maxAmmo: 20, currentAmmo: 5 },
);
const second = evaluateMechanicProgram(
program,
events,
{ mode: 'custom', encounterSeconds: 12, randomSeed: 1 },
{ maxHealth: 100, currentHealth: 100, maxAmmo: 20, currentAmmo: 5 },
);
expect(first.status).toBe('OK');
expect(first.randomSeed).toBe(1);
expect(first.outcomes.map((outcome) => outcome.at)).toEqual([2]);
expect(first.outcomes[0]).toMatchObject({
probabilityPercent: 29,
probabilityRoll: expect.any(Number),
appliedValue: 1,
});
expect(first.finalCombat?.currentAmmo).toBe(6);
expect(second.outcomes).toEqual(first.outcomes);
});
it('gates a proc before mutations, extends a live assigned ability, and starts cooldown on the eighth stack', () => {
const program: MechanicProgram = {
id: 'tactical-loop',
name: 'Tactical Loop',
pools: [
{
key: 'extension-stacks',
label: 'Extension stacks',
scope: 'agent',
initial: 7,
min: 0,
max: 8,
},
{
key: 'extension-cooldown',
label: 'Extension cooldown',
scope: 'agent',
initial: 0,
min: 0,
max: 1,
expiresAfterSeconds: 15,
},
],
clauses: [
{
id: 'extend-tactical-link',
when: 'skill_hit',
if: {
kind: 'compare',
left: { source: 'pool', pool: 'extension-cooldown' },
operator: 'eq',
right: 0,
},
requiresAssignedAbilityActive: true,
procChancePercent: 10,
reducer: { kind: 'none' },
mutations: [
{ kind: 'add', pool: 'extension-stacks', value: 1 },
{
kind: 'extend_duration',
target: 'assigned_ability',
seconds: 0.3,
},
{
kind: 'start_cooldown_on_max',
stackPool: 'extension-stacks',
cooldownPool: 'extension-cooldown',
},
],
},
],
};
const success = evaluateMechanicProgram(
program,
[
{
kind: 'signature_ability_deployed',
at: 0,
values: { assignedAbilityDurationSeconds: 6 },
},
{ kind: 'skill_hit', at: 5.9 },
{ kind: 'skill_hit', at: 6.1 },
],
{ mode: 'custom', encounterSeconds: 7, randomSeed: 7 },
);
const failed = evaluateMechanicProgram(
program,
[
{
kind: 'signature_ability_deployed',
at: 0,
values: { assignedAbilityDurationSeconds: 6 },
},
{ kind: 'skill_hit', at: 5.9 },
],
{ mode: 'custom', encounterSeconds: 7, randomSeed: 1 },
);
expect(success.status).toBe('OK');
expect(success.emissions).toHaveLength(1);
const extension = success.emissions[0].mutations.find(
(mutation) => mutation.kind === 'extend_duration',
);
expect(extension).toMatchObject({ kind: 'extend_duration' });
expect(extension?.before).toBeCloseTo(0.1);
expect(extension?.after).toBeCloseTo(0.4);
expect(success.emissions[0].mutations).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: 'start_cooldown_on_max',
pool: 'extension-cooldown',
after: 1,
}),
]),
);
expect(success.finalState).toEqual(
expect.arrayContaining([
expect.objectContaining({
pool: 'extension-stacks',
value: 8,
}),
expect.objectContaining({
pool: 'extension-cooldown',
value: 1,
expiresAt: 20.9,
}),
]),
);
expect(failed.emissions).toEqual([]);
});
it('does not extend an assigned ability at its exact expiry and rejects invalid chance data', () => {
const boundaryProgram: MechanicProgram = {
id: 'exact-expiry',
name: 'Exact expiry',
clauses: [
{
id: 'extend',
when: 'skill_hit',
requiresAssignedAbilityActive: true,
procChancePercent: 100,
reducer: { kind: 'none' },
mutations: [
{
kind: 'extend_duration',
target: 'assigned_ability',
seconds: 0.3,
},
],
},
],
};
expect(
evaluateMechanicProgram(
boundaryProgram,
[
{
kind: 'signature_ability_deployed',
at: 0,
values: { assignedAbilityDurationSeconds: 1 },
},
{ kind: 'skill_hit', at: 1 },
],
{ mode: 'custom', encounterSeconds: 2, randomSeed: 7 },
).emissions,
).toEqual([]);
const invalid = structuredClone(boundaryProgram);
invalid.clauses[0].procChancePercent = 101;
expect(validateMechanicProgram(invalid)).toMatchObject({
ok: false,
errors: [
expect.stringContaining('proc chance must be from 0 through 100'),
],
});
const invalidOutcome = structuredClone(boundaryProgram);
invalidOutcome.clauses[0].outcomes = [
{
id: 'bad-chance',
label: 'Bad chance',
phase: 'after_event',
kind: 'marker',
scope: 'self',
basis: 'flat',
value: 1,
probabilityPercent: -1,
},
];
expect(validateMechanicProgram(invalidOutcome)).toMatchObject({
ok: false,
errors: [
expect.stringContaining('probability must be from 0 through 100'),
],
});
});
});
describe('structured mechanic runtime', () => {
it('advertises the authoritative weapon-hit target and headshot fields', () => {
expect(MECHANIC_EVENT_REGISTRY.weapon_hit).toMatchObject({
description: expect.stringContaining('previous target'),
fields: [
{ path: 'targetId', label: 'Target ID', type: 'string' },
{
path: 'values.previousTargetId',
label: 'Previous target ID',
type: 'string',
},
{ path: 'values.headshot', label: 'Headshot', type: 'boolean' },
{ path: 'values.critical', label: 'Critical hit', type: 'boolean' },
{
path: 'values.aiming',
label: 'Aiming down sights',
type: 'boolean',
},
{
path: 'values.behindCover',
label: 'Agent behind cover',
type: 'boolean',
},
{
path: 'values.distanceMeters',
label: 'Target distance',
type: 'number',
},
],
});
});
it('advertises the optional non-negative integer proximity count without making snapshots strict', () => {
const definition = MECHANIC_EVENT_REGISTRY.proximity_snapshot;
const countField = definition.fields?.find(
({ path }) => path === 'values.alliesWithin30MetersWithAgentGrantedBuff',
);
expect(definition.strictFields).not.toBe(true);
expect(countField).toEqual({
path: 'values.alliesWithin30MetersWithAgentGrantedBuff',
label: 'Allies within 30 meters with an Agent-granted buff',
type: 'number',
minimum: 0,
integer: true,
});
expect(countField?.required).not.toBe(true);
});
it('advertises imported support and equipment-use trigger events', () => {
expect(MECHANIC_EVENT_REGISTRY.ally_downed).toMatchObject({
key: 'ally_downed',
label: 'Ally downed',
});
expect(MECHANIC_EVENT_REGISTRY.agent_downed).toMatchObject({
key: 'agent_downed',
label: 'Agent downed',
});
expect(MECHANIC_EVENT_REGISTRY.ally_rescued).toMatchObject({
key: 'ally_rescued',
label: 'Ally rescued',
});
expect(MECHANIC_EVENT_REGISTRY.grenade_thrown).toMatchObject({
key: 'grenade_thrown',
label: 'Grenade thrown',
});
expect(MECHANIC_EVENT_REGISTRY.medical_kit_used).toMatchObject({
key: 'medical_kit_used',
label: 'Medical Kit used',
});
expect(MECHANIC_EVENT_REGISTRY.signature_ability_deployed).toMatchObject({
key: 'signature_ability_deployed',
label: 'Signature Ability deployed',
});
expect(MECHANIC_EVENT_REGISTRY.skill_multishot_triggered).toMatchObject({
key: 'skill_multishot_triggered',
label: 'Skill Multi-Shot triggered',
});
});
it('advertises the authoritative Extra Health state contract', () => {
expect(MECHANIC_EVENT_REGISTRY.extra_health_changed).toEqual({
key: 'extra_health_changed',
label: 'Extra Health changed',
description: expect.stringContaining('transition to zero'),
strictFields: true,
fields: [
{
path: 'values.hasExtraHealth',
label: 'Has Extra Health',
type: 'boolean',
required: true,
},
{
path: 'values.currentExtraHealth',
label: 'Current Extra Health',
type: 'number',
minimum: 0,
},
],
});
});
it('gains one capped stack per second with Extra Health and clears all stacks on depletion', () => {
const skillIntensityId = 'skill-intensity';
const program: MechanicProgram = {
id: 'electrical-protection',
name: 'Electrical Protection',
pools: [
{
key: 'extra-health-active',
label: 'Extra Health active',
scope: 'agent',
initial: 0,
min: 0,
max: 1,
},
{
key: 'stacks',
label: 'Electrical Protection stacks',
scope: 'agent',
initial: 0,
min: 0,
max: 10,
bestCaseValue: 10,
},
],
clauses: [
{
id: 'extra-health-present',
when: 'extra_health_changed',
if: {
kind: 'compare',
left: { source: 'event', path: 'values.hasExtraHealth' },
operator: 'eq',
right: true,
},
reducer: { kind: 'none' },
mutations: [{ kind: 'set', pool: 'extra-health-active', value: 1 }],
},
{
id: 'extra-health-depleted',
when: 'extra_health_changed',
if: {
kind: 'compare',
left: { source: 'event', path: 'values.hasExtraHealth' },
operator: 'eq',
right: false,
},
reducer: { kind: 'none' },
mutations: [
{ kind: 'clear', pool: 'extra-health-active' },
{ kind: 'clear', pool: 'stacks' },
],
},
{
id: 'gain-stack',
when: 'tick',
if: {
kind: 'compare',
left: { source: 'pool', pool: 'extra-health-active' },
operator: 'eq',
right: 1,
},
reducer: { kind: 'periodic', intervalSeconds: 1 },
mutations: [{ kind: 'add', pool: 'stacks', value: 1 }],
outcomes: [
{
id: 'skill-intensity',
label: 'Skill Intensity',
phase: 'while_active',
kind: 'attribute_modifier',
scope: 'all_skills',
basis: 'flat',
attributeId: skillIntensityId,
value: 1.8,
statePool: 'stacks',
perStack: true,
},
],
},
],
};
const ticks = Array.from({ length: 13 }, (_, index): MechanicEvent => ({
kind: 'tick',
at: index,
}));
const capped = evaluateMechanicProgram(
program,
[
{
kind: 'extra_health_changed',
at: 0,
values: { hasExtraHealth: true, currentExtraHealth: 500 },
},
...ticks,
],
custom(12),
{
currentHealth: 1000,
maxHealth: 1000,
attributes: { [skillIntensityId]: 0 },
},
);
expect(capped.status).toBe('OK');
expect(capped.finalState).toEqual(
expect.arrayContaining([
expect.objectContaining({ pool: 'stacks', value: 10 }),
]),
);
expect(capped.outcomes.at(-1)).toMatchObject({ value: 18, stateValue: 10 });
const depleted = evaluateMechanicProgram(
program,
[
{
kind: 'extra_health_changed',
at: 0,
values: { hasExtraHealth: true, currentExtraHealth: 500 },
},
{ kind: 'tick', at: 0 },
{ kind: 'tick', at: 1 },
{
kind: 'extra_health_changed',
at: 1.5,
values: { hasExtraHealth: true, currentExtraHealth: 100 },
},
{ kind: 'tick', at: 2 },
{
kind: 'extra_health_changed',
at: 2.5,
values: { hasExtraHealth: false, currentExtraHealth: 0 },
},
{ kind: 'tick', at: 3 },
],
custom(3),
{
currentHealth: 1000,
maxHealth: 1000,
attributes: { [skillIntensityId]: 0 },
},
);
expect(depleted.status).toBe('OK');
expect(depleted.finalState).toEqual(
expect.arrayContaining([
expect.objectContaining({ pool: 'extra-health-active', value: 0 }),
expect.objectContaining({ pool: 'stacks', value: 0 }),
]),
);
expect(
depleted.emissions.filter(
(emission) => emission.clauseId === 'gain-stack',
),
).toHaveLength(2);
});
it('rejects malformed Extra Health state fields and inconsistent amounts', () => {
const program: MechanicProgram = {
id: 'extra-health-state',
name: 'Extra Health state',
clauses: [
{
id: 'state-change',
when: 'extra_health_changed',
if: {
kind: 'compare',
left: { source: 'event', path: 'values.hasExtraHealth' },
operator: 'eq',
right: true,
},
reducer: { kind: 'none' },
mutations: [{ kind: 'none' }],
},
],
};
for (const event of [
{ kind: 'extra_health_changed', at: 0, values: {} },
{
kind: 'extra_health_changed',
at: 0,
values: { hasExtraHealth: true, currentExtraHealth: -1 },
},
{
kind: 'extra_health_changed',
at: 0,
values: { hasExtraHealth: false, currentExtraHealth: 1 },
},
] as MechanicEvent[]) {
expect(evaluateMechanicProgram(program, [event], custom(1)).status).toBe(
'INVALID',
);
}
const unknownField = structuredClone(program);
unknownField.clauses[0].if = {
kind: 'compare',
left: { source: 'event', path: 'values.extraHealthActive' },
operator: 'eq',
right: true,
};
expect(validateMechanicProgram(unknownField)).toMatchObject({
ok: false,
errors: [
expect.stringContaining('is not defined for Extra Health changed'),
],
});
});
it('matches kills and assists only while the assigned ability is active', () => {
const weaponCriticalHitChance = 'weapon-critical-hit-chance';
const activeOutcome = {
id: 'tactical-link-critical-chance',
label: 'Weapon Critical Hit Chance',
phase: 'while_active' as const,
kind: 'attribute_modifier' as const,
scope: 'self' as const,
basis: 'flat' as const,
value: 10.2,
attributeId: weaponCriticalHitChance,
statePool: 'tactical-link-kill-bonus',
};
const activePredicate = {
kind: 'compare' as const,
left: {
source: 'event' as const,
path: 'values.assignedAbilityActive',
},
operator: 'eq' as const,
right: true,
};
const program: MechanicProgram = {
id: 'tactical-link-kill-bonus',
name: 'Tactical Link Kill Bonus',
pools: [
{
key: 'tactical-link-kill-bonus',
label: 'Tactical Link kill bonus',
scope: 'agent',
initial: 0,
min: 0,
max: 1,
expiresAfterSeconds: 10,
},
],
clauses: [
{
id: 'kill-during-tactical-link',
when: 'enemy_killed',
if: activePredicate,
reducer: { kind: 'none' },
mutations: [
{ kind: 'set', pool: 'tactical-link-kill-bonus', value: 1 },
],
outcomes: [activeOutcome],
},
{
id: 'assist-during-tactical-link',
when: 'assist',
if: activePredicate,
reducer: { kind: 'none' },
mutations: [
{ kind: 'set', pool: 'tactical-link-kill-bonus', value: 1 },
],
outcomes: [{ ...activeOutcome }],
},
],
};
expect(validateMechanicProgram(program)).toMatchObject({ ok: true });
const result = evaluateMechanicProgram(
program,
[
{
kind: 'enemy_killed',
at: 0,
values: { assignedAbilityActive: true },
},
{
kind: 'assist',
at: 4,
values: { assignedAbilityActive: false },
},
{ kind: 'enemy_killed', at: 6 },
{
kind: 'assist',
at: 10,
values: { assignedAbilityActive: true },
},
],
custom(19),
{
maxHealth: 1_000,
currentHealth: 1_000,
attributes: { [weaponCriticalHitChance]: 0 },
},
);
expect(result.status).toBe('OK');
expect(result.outcomes.map((outcome) => outcome.at)).toEqual([0, 10]);
expect(result.finalState).toContainEqual(
expect.objectContaining({
pool: 'tactical-link-kill-bonus',
value: 1,
expiresAt: 20,
}),
);
});
it('rejects invalid assigned-ability activity predicates and event values', () => {
const program: MechanicProgram = {
id: 'assigned-ability-activity',
name: 'Assigned ability activity',
clauses: [
{
id: 'kill',
when: 'enemy_killed',
if: {
kind: 'compare',
left: {
source: 'event',
path: 'values.assignedAbilityActive',
},
operator: 'eq',
right: true,
},
reducer: { kind: 'none' },
mutations: [],
},
],
};
const invalidPredicate = structuredClone(program);
invalidPredicate.clauses[0].if = {
kind: 'compare',
left: { source: 'event', path: 'values.assignedAbilityActive' },
operator: 'eq',
right: 'yes',
};
expect(validateMechanicProgram(invalidPredicate)).toMatchObject({
ok: false,
errors: [
expect.stringContaining('Assigned ability active with a boolean value'),
],
});
expect(
evaluateMechanicProgram(
program,
[
{
kind: 'enemy_killed',
at: 0,
values: { assignedAbilityActive: 'yes' },
} as unknown as MechanicEvent,
],
custom(1),
),
).toMatchObject({
status: 'INVALID',
errors: [
expect.stringContaining('Assigned ability active must be a boolean'),
],
});
});
it('executes imported talent trigger families as first-class events', () => {
const eventKinds: MechanicEventKind[] = [
'ally_downed',
'agent_downed',
'ally_rescued',
'grenade_thrown',
'medical_kit_used',
'signature_ability_deployed',
'skill_multishot_triggered',
];
const program: MechanicProgram = {
id: 'imported-talent-events',
name: 'Imported talent events',
clauses: eventKinds.map((when) => ({
id: when,
when,
reducer: { kind: 'none' },
mutations: [{ kind: 'none' }],
outcomes: [
{
id: `${when}-marker`,
label: `${when} marker`,
phase: 'after_event',
kind: 'marker',
scope: 'self',
basis: 'flat',
value: 1,
},
],
})),
};
expect(validateMechanicProgram(program)).toMatchObject({ ok: true });
const events: MechanicEvent[] = eventKinds.map((kind, index) => ({
kind,
at: index + 1,
}));
expect(
evaluateMechanicProgram(program, events, custom()).outcomes.map(
(outcome) => outcome.outcomeId,
),
).toEqual(eventKinds.map((kind) => `${kind}-marker`));
});
it('derives the previous weapon-hit target for target-switch mechanics', () => {
const program: MechanicProgram = {
id: 'target-switch-stacks',
name: 'Target Switch Stacks',
pools: [
{
key: 'stacks',
label: 'Stacks',
scope: 'agent',
initial: 0,
min: 0,
max: 100,
expiresAfterSeconds: 5,
},
],
clauses: [
{
id: 'halve-on-switch',
when: 'weapon_hit',
if: {
kind: 'all',
predicates: [
{
kind: 'exists',
value: { source: 'event', path: 'values.previousTargetId' },
},
{
kind: 'compare',
left: { source: 'event', path: 'values.previousTargetId' },
operator: 'ne',
right: { source: 'event', path: 'targetId' },
},
],
},
reducer: { kind: 'none' },
mutations: [
{
kind: 'set',
pool: 'stacks',
value: {
op: 'multiply',
left: { ref: { source: 'pool', pool: 'stacks' } },
right: 0.5,
},
},
],
},
{
id: 'build-stacks',
when: 'weapon_hit',
if: { kind: 'exists', value: { source: 'event', path: 'targetId' } },
reducer: { kind: 'none' },
mutations: [{ kind: 'add', pool: 'stacks', value: 1 }],
},
],
};
const result = evaluateMechanicProgram(
program,
[
{ kind: 'weapon_hit', at: 0, targetId: 'alpha' },
{ kind: 'weapon_hit', at: 1, targetId: 'alpha' },
{ kind: 'weapon_hit', at: 2, targetId: 'bravo' },
],
custom(3),
);
expect(result.status).toBe('OK');
expect(result.finalState).toContainEqual(
expect.objectContaining({
pool: 'stacks',
value: 2,
}),
);
expect(result.emissions.map((emission) => emission.clauseId)).toEqual([
'build-stacks',
'build-stacks',
'halve-on-switch',
'build-stacks',
]);
});
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('preserves an explicit zero clause weight', () => {
const result = evaluateMechanicProgram(
{
id: 'zero-weight',
name: 'Zero weight',
clauses: [
{
id: 'zero-weight-clause',
when: 'agent_hit',
reducer: { kind: 'none' },
weight: 0,
mutations: [],
},
],
},
[{ kind: 'agent_hit', at: 0 }],
custom(1),
);
expect(result.emissions).toHaveLength(1);
expect(result.emissions[0].weight).toBe(0);
expect(result.estimatedActivationCount).toBe(0);
});
it.each([0, 0.5])(
'keeps a weighted reload delay intact at weight %s',
(weight) => {
const result = evaluateMechanicProgram(
{
id: 'weighted-reload',
name: 'Weighted reload',
clauses: [
{
id: 'weighted-reload-clause',
when: 'weapon_hit',
reducer: { kind: 'none' },
weight,
mutations: [],
outcomes: [
{
id: 'reload',
label: 'Reload',
phase: 'on_event',
kind: 'weapon_reload',
scope: 'equipped_weapon',
value: 5,
basis: 'flat',
},
],
},
],
},
[{ kind: 'weapon_hit', at: 1 }],
custom(10),
);
expect(result.outcomes).toHaveLength(1);
expect(result.outcomes[0]).toMatchObject({
at: 6,
weight,
resolvedValue: 5,
appliedValue: 5,
});
},
);
it('preserves a zero weight for maximum-mode passive outcomes', () => {
const result = evaluateMechanicProgram(
{
id: 'zero-weight-maximum',
name: 'Zero weight maximum',
clauses: [
{
id: 'zero-weight-maximum-clause',
label: 'Maximum passive',
when: 'weapon_hit',
reducer: { kind: 'none' },
weight: 0,
mutations: [],
outcomes: [
{
id: 'marker',
label: 'Passive marker',
phase: 'while_active',
kind: 'marker',
scope: 'self',
value: 1,
basis: 'flat',
},
],
},
],
},
[],
{ mode: 'maximum', encounterSeconds: 10 },
);
expect(result.outcomes).toHaveLength(1);
expect(result.outcomes[0]).toMatchObject({ weight: 0, appliedValue: 0 });
});
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.status).toBe('OK');
expect(result.finalState[0].value).toBe(2);
const maximum = evaluateMechanicProgram(program, [], {
mode: 'maximum',
encounterSeconds: 30,
});
expect(maximum.finalState[0].value).toBe(3);
});
it.each([
{ count: 0, upgrades: 0, multiplier: 0 },
{ count: 3, upgrades: 0, multiplier: 3 },
{ count: 3, upgrades: 5, multiplier: 6 },
])(
'resolves Close-and-Personal-style Engineering for count $count and $upgrades OS upgrades',
({ count, upgrades, multiplier }) => {
const resolvedVariantValue = 7.25;
const result = evaluateMechanicProgram(
closeAndPersonalProgram(resolvedVariantValue),
[
{
kind: 'proximity_snapshot',
at: 0,
values: {
alliesWithin30MetersWithAgentGrantedBuff: count,
},
},
{ kind: 'tick', at: 1 },
],
custom(),
undefined,
{ protocolTalentUpgrades: upgrades },
);
expect(result.status).toBe('OK');
expect(result.finalState).toContainEqual(
expect.objectContaining({ pool: 'eligible-allies', value: count }),
);
expect(
result.outcomes.find(({ outcomeId }) => outcomeId === 'engineering'),
).toMatchObject({
value: resolvedVariantValue * multiplier,
resolvedValue: resolvedVariantValue * multiplier,
});
},
);
it.each([
{
count: '2',
error:
'Allies within 30 meters with an Agent-granted buff must be a number.',
},
{
count: -1,
error:
'Allies within 30 meters with an Agent-granted buff must be 0 or greater.',
},
{
count: 1.5,
error:
'Allies within 30 meters with an Agent-granted buff must be an integer.',
},
])('rejects invalid proximity ally count $count', ({ count, error }) => {
const result = evaluateMechanicProgram(
closeAndPersonalProgram(7.25),
[
{
kind: 'proximity_snapshot',
at: 0,
values: {
alliesWithin30MetersWithAgentGrantedBuff: count,
},
},
],
custom(),
undefined,
{ protocolTalentUpgrades: 0 },
);
expect(result.status).toBe('INVALID');
expect(result.errors).toContain(`Event 1 (Proximity snapshot) ${error}`);
});
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('shares an identical timed modifier outcome across kill and assist refresh clauses', () => {
const weaponDamage = {
id: 'pummel-weapon-damage',
label: 'Weapon Damage',
phase: 'while_active' as const,
kind: 'attribute_modifier' as const,
scope: 'equipped_weapon' as const,
basis: 'flat' as const,
value: 10,
attributeId: 'weapon-damage',
statePool: 'pummel-active',
};
const program: MechanicProgram = {
id: 'pummel-refresh',
name: 'Pummel Refresh',
pools: [
{
key: 'pummel-active',
label: 'Pummel active',
scope: 'agent',
initial: 0,
min: 0,
max: 1,
expiresAfterSeconds: 12,
},
],
clauses: [
{
id: 'activate-on-kill',
when: 'enemy_killed',
reducer: { kind: 'none' },
mutations: [{ kind: 'set', pool: 'pummel-active', value: 1 }],
outcomes: [weaponDamage],
},
{
id: 'refresh-on-assist',
when: 'assist',
reducer: { kind: 'none' },
mutations: [{ kind: 'set', pool: 'pummel-active', value: 1 }],
outcomes: [{ ...weaponDamage }],
},
],
};
expect(validateMechanicProgram(program)).toMatchObject({ ok: true });
const result = evaluateMechanicProgram(
program,
[
{ kind: 'enemy_killed', at: 1 },
{ kind: 'assist', at: 10 },
],
custom(20),
);
expect(result.status).toBe('OK');
expect(
result.outcomes.map(({ outcomeId, at, appliedValue }) => ({
outcomeId,
at,
appliedValue,
})),
).toEqual([
{ outcomeId: 'pummel-weapon-damage', at: 1, appliedValue: 10 },
{ outcomeId: 'pummel-weapon-damage', at: 10, appliedValue: 10 },
]);
expect(result.finalState).toContainEqual(
expect.objectContaining({
pool: 'pummel-active',
value: 1,
expiresAt: 22,
}),
);
});
it('rejects a shared outcome ID when its mechanical definitions conflict', () => {
const outcome = {
id: 'shared-damage',
label: 'Weapon Damage',
phase: 'on_event' as const,
kind: 'attribute_modifier' as const,
scope: 'equipped_weapon' as const,
basis: 'flat' as const,
value: 10,
attributeId: 'weapon-damage',
};
const program: MechanicProgram = {
id: 'conflicting-shared-outcome',
name: 'Conflicting Shared Outcome',
clauses: [
{
id: 'kill',
when: 'enemy_killed',
reducer: { kind: 'none' },
mutations: [{ kind: 'none' }],
outcomes: [outcome],
},
{
id: 'assist',
when: 'assist',
reducer: { kind: 'none' },
mutations: [{ kind: 'none' }],
outcomes: [{ ...outcome, value: 14 }],
},
],
};
expect(validateMechanicProgram(program)).toEqual({
ok: false,
errors: ['Outcome "shared-damage" has conflicting definitions.'],
});
});
it('applies distance bonuses in full five-meter steps and caps the result', () => {
const distanceBonus = (
base: number,
increment: number,
maximum: number,
): NumericExpression => ({
op: 'min',
left: maximum,
right: {
op: 'add',
left: base,
right: {
op: 'multiply',
left: increment,
right: {
op: 'floor',
value: {
op: 'divide',
left: {
op: 'max',
left: {
ref: { source: 'event', path: 'values.distanceMeters' },
},
right: 0,
},
right: 5,
},
},
},
},
});
const program: MechanicProgram = {
id: 'long-distance-marking',
name: 'Long-Distance Marking',
clauses: [
{
id: 'headshot',
when: 'weapon_hit',
reducer: { kind: 'none' },
mutations: [],
outcomes: [
{
id: 'skill-damage',
label: 'Skill Damage',
phase: 'after_event',
kind: 'attribute_modifier',
scope: 'self',
attributeId: 'skill-damage',
basis: 'flat',
value: distanceBonus(7.5, 1.5, 18),
},
],
},
],
};
expect(validateMechanicProgram(program)).toMatchObject({ ok: true });
const result = evaluateMechanicProgram(
program,
[-1, 0, 4.99, 5, 9.99, 10, 34.99, 35, 100].map((distanceMeters, at) => ({
kind: 'weapon_hit' as const,
at,
values: { distanceMeters },
})),
custom(10),
);
expect(result.status).toBe('OK');
expect(result.outcomes.map((outcome) => outcome.resolvedValue)).toEqual([
7.5, 7.5, 7.5, 9, 9, 10.5, 16.5, 18, 18,
]);
expect(result.script).toContain(
'MIN(18, (7.5 + (1.5 * FLOOR((MAX(EVENT.values.distanceMeters, 0) / 5)))))',
);
});
it('supports an immediate equipped-weapon magazine refill', () => {
const program: MechanicProgram = {
id: 'equipped-magazine',
name: 'Equipped Magazine',
clauses: [
{
id: 'refill-equipped-weapon',
when: 'enemy_killed',
reducer: { kind: 'none' },
mutations: [{ kind: 'none' }],
outcomes: [
{
id: 'equipped-weapon-reload',
label: 'Refill equipped weapon',
phase: 'after_event',
kind: 'weapon_reload',
scope: 'equipped_weapon',
basis: 'flat',
value: 0,
},
],
},
],
};
const result = evaluateMechanicProgram(
program,
[{ kind: 'enemy_killed', at: 1 }],
custom(10),
);
expect(result.status).toBe('OK');
expect(result.outcomes).toEqual([
expect.objectContaining({
kind: 'weapon_reload',
scope: 'equipped_weapon',
at: 1,
value: 0,
}),
]);
expect(result.script).toContain('RELOAD EQUIPPED WEAPON AFTER 0s');
});
it('schedules an inactive-weapon reload and refreshes a pending delay', () => {
const program: MechanicProgram = {
id: 'backup-magazine',
name: 'Backup Magazine',
clauses: [
{
id: 'reload-inactive-weapon',
when: 'weapon_swapped',
reducer: { kind: 'none' },
mutations: [{ kind: 'none' }],
outcomes: [
{
id: 'inactive-weapon-reload',
label: 'Reload inactive weapon',
phase: 'after_event',
kind: 'weapon_reload',
scope: 'inactive_weapon',
basis: 'flat',
value: 4,
},
],
},
],
};
const refreshed = evaluateMechanicProgram(
program,
[
{ kind: 'weapon_swapped', at: 1 },
{ kind: 'weapon_swapped', at: 3 },
],
custom(10),
);
expect(refreshed.status).toBe('OK');
expect(refreshed.emissions.map(({ at }) => at)).toEqual([1, 3]);
expect(refreshed.outcomes).toEqual([
expect.objectContaining({
kind: 'weapon_reload',
scope: 'inactive_weapon',
at: 7,
value: 4,
}),
]);
expect(refreshed.script).toContain('RELOAD INACTIVE WEAPON AFTER 4s');
const beyondEncounter = evaluateMechanicProgram(
program,
[{ kind: 'weapon_swapped', at: 8 }],
custom(10),
);
expect(beyondEncounter.outcomes).toEqual([]);
});
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('caps Signature Ability Charge at 1000 and scales grants by charge efficiency', () => {
const efficiencyAttributeId = 'signature-charge-efficiency';
const program: MechanicProgram = {
id: 'signature-charge',
name: 'Signature Charge',
clauses: [
{
id: 'grant-charge',
when: 'weapon_hit',
reducer: { kind: 'none' },
mutations: [],
outcomes: [
{
id: 'charge',
label: 'Signature Ability Charge',
phase: 'after_event',
kind: 'restoration',
scope: 'self',
resource: 'signature_ability_charge',
basis: 'flat',
value: {
op: 'multiply',
left: 70,
right: {
op: 'add',
left: 1,
right: {
op: 'divide',
left: {
ref: {
source: 'attribute',
attributeId: efficiencyAttributeId,
},
},
right: 100,
},
},
},
},
],
},
],
};
const result = evaluateMechanicProgram(
program,
[
{ kind: 'weapon_hit', at: 1 },
{ kind: 'weapon_hit', at: 2 },
],
custom(),
{
maxHealth: 100,
currentHealth: 100,
currentSignatureAbilityCharge: 900,
attributes: { [efficiencyAttributeId]: 50 },
},
);
expect(result.status).toBe('OK');
expect(result.outcomes.map((outcome) => outcome.appliedValue)).toEqual([
100, 0,
]);
expect(result.finalCombat?.currentSignatureAbilityCharge).toBe(1000);
expect(result.finalCombat?.maxSignatureAbilityCharge).toBeUndefined();
});
it('evaluates mutually exclusive current Health percentage bands at strict boundaries', () => {
const band = (
id: string,
minimum: number | undefined,
maximum: number,
value: number,
): MechanicProgram['clauses'][number] => ({
id,
when: 'tick',
if:
minimum === undefined
? {
kind: 'compare',
left: {
source: 'combat',
path: 'currentHealthPercent',
timing: 'current',
},
operator: 'lt',
right: maximum,
}
: {
kind: 'all',
predicates: [
{
kind: 'compare',
left: {
source: 'combat',
path: 'currentHealthPercent',
timing: 'current',
},
operator: 'gte',
right: minimum,
},
{
kind: 'compare',
left: {
source: 'combat',
path: 'currentHealthPercent',
timing: 'current',
},
operator: 'lt',
right: maximum,
},
],
},
reducer: { kind: 'none' },
mutations: [],
outcomes: [
{
id: `${id}-damage-reduction`,
label: 'Damage Reduction',
phase: 'while_active',
kind: 'attribute_modifier',
scope: 'self',
basis: 'flat',
attributeId: 'damage-reduction',
value,
},
],
});
const spirited: MechanicProgram = {
id: 'spirited',
name: 'Spirited',
clauses: [
band('critical', undefined, 25, 15),
band('damaged', 25, 50, 12),
band('wounded', 50, 75, 9),
],
};
const evaluateAt = (
healthPercent: number,
mode: 'custom' | 'maximum' = 'custom',
) =>
evaluateMechanicProgram(
spirited,
[{ kind: 'tick', at: 0 }],
mode === 'maximum'
? { mode: 'maximum', encounterSeconds: 1 }
: custom(1),
{ maxHealth: 1_000, currentHealth: healthPercent * 10 },
);
const valuesAt = (
healthPercent: number,
mode: 'custom' | 'maximum' = 'custom',
) =>
evaluateAt(healthPercent, mode).outcomes.map(
(outcome) => outcome.appliedValue,
);
expect(valuesAt(0)).toEqual([15]);
expect(valuesAt(24.99)).toEqual([15]);
expect(valuesAt(25)).toEqual([12]);
expect(valuesAt(49.99)).toEqual([12]);
expect(valuesAt(50)).toEqual([9]);
expect(valuesAt(74.99)).toEqual([9]);
expect(valuesAt(75)).toEqual([]);
expect(valuesAt(100)).toEqual([]);
expect(valuesAt(24, 'maximum')).toEqual([15]);
});
it('re-evaluates current Health percentage after deterministic combat-state updates', () => {
const program: MechanicProgram = {
id: 'live-health',
name: 'Live Health',
clauses: [
{
id: 'health-cost',
when: 'tick',
reducer: { kind: 'none' },
mutations: [],
outcomes: [
{
id: 'cost',
label: 'Health cost',
phase: 'on_event',
kind: 'resource_cost',
scope: 'self',
resource: 'health',
basis: 'flat',
value: 30,
},
],
},
{
id: 'below-seventy-five',
when: 'tick',
if: {
kind: 'compare',
left: {
source: 'combat',
path: 'currentHealthPercent',
timing: 'current',
},
operator: 'lt',
right: 75,
},
reducer: { kind: 'none' },
mutations: [],
outcomes: [
{
id: 'reduction',
label: 'Damage Reduction',
phase: 'while_active',
kind: 'attribute_modifier',
scope: 'self',
attributeId: 'damage-reduction',
basis: 'flat',
value: 9,
},
],
},
],
};
const result = evaluateMechanicProgram(
program,
[{ kind: 'tick', at: 0 }],
custom(1),
{ maxHealth: 100, currentHealth: 100 },
);
expect(result.finalCombat?.currentHealth).toBe(70);
expect(result.outcomes.map((outcome) => outcome.outcomeId)).toEqual([
'cost',
'reduction',
]);
});
it('evaluates pre-event timing for the derived current Health percentage', () => {
const program: MechanicProgram = {
id: 'pre-event-health-percent',
name: 'Pre-event Health Percent',
clauses: [
{
id: 'full-health-before-event',
when: 'tick',
if: {
kind: 'compare',
left: {
source: 'combat',
path: 'currentHealthPercent',
timing: 'pre_event',
},
operator: 'gte',
right: 100,
},
reducer: { kind: 'none' },
mutations: [],
outcomes: [
{
id: 'matched',
label: 'Matched',
phase: 'on_event',
kind: 'marker',
scope: 'self',
basis: 'flat',
value: 1,
},
],
},
],
};
expect(validateMechanicProgram(program)).toMatchObject({ ok: true });
expect(
evaluateMechanicProgram(program, [{ kind: 'tick', at: 0 }], custom(1), {
maxHealth: 100,
currentHealth: 100,
}).outcomes.map((outcome) => outcome.outcomeId),
).toEqual(['matched']);
});
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'),
);
});
it('preserves radius targeting on ally modifiers and enemy markers', () => {
const program: MechanicProgram = {
id: 'radius-targeting',
name: 'Radius Targeting',
clauses: [
{
id: 'kill',
when: 'enemy_killed',
reducer: { kind: 'none' },
mutations: [{ kind: 'none' }],
outcomes: [
{
id: 'healing',
label: 'Incoming Healing',
phase: 'on_event',
kind: 'attribute_modifier',
scope: 'allies',
attributeId: 'incoming-healing',
basis: 'flat',
value: 7,
targeting: {
kind: 'within_radius',
population: 'allies',
radiusMeters: 40,
},
},
{
id: 'taunt',
label: 'Taunt',
phase: 'on_event',
kind: 'marker',
scope: 'target',
basis: 'flat',
value: 1,
targeting: {
kind: 'within_radius',
population: 'enemies',
radiusMeters: 20,
},
},
],
},
],
};
const result = evaluateMechanicProgram(
program,
[{ kind: 'enemy_killed', at: 1 }],
custom(5),
);
expect(result.status).toBe('OK');
expect(result.outcomes.map((outcome) => outcome.targeting)).toEqual([
{ kind: 'within_radius', population: 'allies', radiusMeters: 40 },
{ kind: 'within_radius', population: 'enemies', radiusMeters: 20 },
]);
expect(result.script).toContain('TO ALLIES WITHIN 40m');
expect(result.script).toContain('TO ENEMIES WITHIN 20m');
});
it('applies aimed critical-hit area damage once to each valid enemy at or inside the radius', () => {
const program: MechanicProgram = {
id: 'aimed-critical-explosion',
name: 'Aimed Critical Explosion',
clauses: [
{
id: 'aimed-critical-hit',
when: 'weapon_hit',
if: {
kind: 'all',
predicates: [
{
kind: 'compare',
left: { source: 'event', path: 'values.critical' },
operator: 'eq',
right: true,
},
{
kind: 'compare',
left: { source: 'event', path: 'values.aiming' },
operator: 'eq',
right: true,
},
],
},
reducer: { kind: 'none' },
mutations: [],
outcomes: [
{
id: 'explosion',
label: 'Explosion',
phase: 'after_event',
kind: 'direct_damage',
scope: 'target',
basis: 'flat',
value: {
op: 'multiply',
left: 20,
right: {
ref: { source: 'attribute', attributeId: 'firepower' },
},
},
targeting: {
kind: 'event_target_area',
center: 'event_target',
validTargets: 'enemies',
radiusMeters: 5,
},
probabilityPercent: 100,
},
],
},
],
};
const result = evaluateMechanicProgram(
program,
[
{
kind: 'weapon_hit',
at: 1,
targetId: 'enemy-b',
values: { critical: true, aiming: true },
areaTargetSample: {
center: 'event_target',
validTargets: [
{
entityId: 'enemy-c',
population: 'enemy',
distanceMeters: 5.001,
},
{ entityId: 'enemy-b', population: 'enemy', distanceMeters: 0 },
{ entityId: 'ally-a', population: 'ally', distanceMeters: 2 },
{ entityId: 'enemy-a', population: 'enemy', distanceMeters: 5 },
],
},
},
],
custom(5),
{ maxHealth: 1000, currentHealth: 1000, attributes: { firepower: 100 } },
);
expect(result.status).toBe('OK');
expect(result.outcomes.map((outcome) => outcome.targetId)).toEqual([
'enemy-a',
'enemy-b',
]);
expect(result.outcomes.map((outcome) => outcome.appliedValue)).toEqual([
2000, 2000,
]);
expect(
new Set(result.outcomes.map((outcome) => outcome.probabilityRoll)).size,
).toBe(1);
expect(result.outcomes[0].probabilityRoll).not.toBeNull();
expect(result.script).toContain(
'EXPLOSION CENTERED ON EVENT TARGET; APPLY TO VALID ENEMIES WITHIN 5m',
);
const maximum = evaluateMechanicProgram(
program,
[
{
kind: 'weapon_hit',
at: 1,
targetId: 'enemy-b',
values: { critical: true, aiming: true },
areaTargetSample: {
center: 'event_target',
validTargets: [
{ entityId: 'enemy-b', population: 'enemy', distanceMeters: 0 },
{ entityId: 'enemy-a', population: 'enemy', distanceMeters: 5 },
],
},
},
],
{ mode: 'maximum', encounterSeconds: 5 },
{ maxHealth: 1000, currentHealth: 1000, attributes: { firepower: 100 } },
);
expect(maximum.outcomes.map((outcome) => outcome.targetId)).toEqual([
'enemy-a',
'enemy-b',
]);
expect(
maximum.outcomes.every((outcome) => outcome.probabilityRoll === 0),
).toBe(true);
const skillProgram: MechanicProgram = {
...program,
id: 'skill-hit-explosion',
clauses: program.clauses.map((clause) => ({
...clause,
when: 'skill_hit',
if: undefined,
})),
};
expect(validateMechanicProgram(skillProgram)).toMatchObject({ ok: true });
expect(
evaluateMechanicProgram(
skillProgram,
[
{
kind: 'skill_hit',
at: 1,
targetId: 'enemy-b',
areaTargetSample: {
center: 'event_target',
validTargets: [
{ entityId: 'enemy-b', population: 'enemy', distanceMeters: 0 },
{ entityId: 'enemy-a', population: 'enemy', distanceMeters: 5 },
],
},
},
],
custom(5),
{
maxHealth: 1000,
currentHealth: 1000,
attributes: { firepower: 100 },
},
).outcomes.map((outcome) => outcome.targetId),
).toEqual(['enemy-a', 'enemy-b']);
});
it('requires authoritative area evidence only when an area outcome activates', () => {
const program: MechanicProgram = {
id: 'area-evidence',
name: 'Area Evidence',
clauses: [
{
id: 'critical-hit',
when: 'weapon_hit',
if: {
kind: 'compare',
left: { source: 'event', path: 'values.critical' },
operator: 'eq',
right: true,
},
reducer: { kind: 'none' },
mutations: [],
outcomes: [
{
id: 'explosion',
label: 'Explosion',
phase: 'after_event',
kind: 'direct_damage',
scope: 'target',
basis: 'flat',
value: 1,
targeting: {
kind: 'event_target_area',
center: 'event_target',
validTargets: 'enemies',
radiusMeters: 5,
},
},
],
},
],
};
expect(
evaluateMechanicProgram(
program,
[
{
kind: 'weapon_hit',
at: 1,
targetId: 'enemy',
values: { critical: false },
},
],
custom(5),
),
).toMatchObject({ status: 'OK', outcomes: [] });
expect(
evaluateMechanicProgram(
program,
[
{
kind: 'weapon_hit',
at: 1,
targetId: 'enemy',
values: { critical: true },
},
],
custom(5),
),
).toMatchObject({
status: 'INVALID',
errors: [
expect.stringContaining('authoritative event-target area sample'),
],
});
});
it('rejects malformed and duplicate area-target samples', () => {
const result = evaluateMechanicProgram(
{ id: 'sample-validation', name: 'Sample Validation', clauses: [] },
[
{
kind: 'weapon_hit',
at: 1,
targetId: 'enemy',
areaTargetSample: {
center: 'event_target',
validTargets: [
{ entityId: 'enemy', population: 'enemy', distanceMeters: 0 },
{ entityId: 'enemy', population: 'enemy', distanceMeters: 1 },
],
},
},
],
custom(5),
);
expect(result).toMatchObject({
status: 'INVALID',
errors: [expect.stringContaining('duplicates entity "enemy"')],
});
const oversizedTargets = Array.from({ length: 257 }, (_, index) => ({
entityId: index === 0 ? 'enemy' : `enemy-${index}`,
population: 'enemy' as const,
distanceMeters: index === 0 ? 0 : 1,
}));
expect(
evaluateMechanicProgram(
{ id: 'sample-bound', name: 'Sample Bound', clauses: [] },
[
{
kind: 'weapon_hit',
at: 1,
targetId: 'enemy',
areaTargetSample: {
center: 'event_target',
validTargets: oversizedTargets,
},
},
],
custom(5),
),
).toMatchObject({
status: 'INVALID',
errors: [expect.stringContaining('cannot contain more than 256')],
});
});
});