Keep AI mechanic capabilities in sync

This commit is contained in:
JB
2026-07-27 21:43:48 -04:00
parent 568e46cd05
commit 37918bd411
5 changed files with 207 additions and 50 deletions
@@ -1,7 +1,9 @@
import { describe, expect, it } from 'vitest';
import type { AiMechanicAuthoringRequest } from '$lib/ai/mechanic-authoring';
import { parseConditionalMechanicDraft } from '$lib/calculations/mechanic-editor';
import {
AI_MECHANIC_AUTHORING_INSTRUCTIONS,
AI_MECHANIC_PROMPT_VERSION,
buildAiMechanicAuthoringInput
} from './mechanic-authoring-prompt';
@@ -58,4 +60,100 @@ describe('AI mechanic authoring prompt', () => {
]));
expect(AI_MECHANIC_AUTHORING_INSTRUCTIONS).toContain('Never invent an Attribute ID');
});
it('advertises continuous restoration as a supported outcome capability', () => {
const input = JSON.parse(buildAiMechanicAuthoringInput({
request,
attributes: [{
id: '1bce573c-80f7-4487-b1d3-c4dc598b5244',
name: 'Firepower Multiplier',
description: null,
unit: '%',
type: null,
category: null,
calculationType: null
}],
events: [{ key: 'enemy_killed', label: 'Enemy killed' }],
actions: [],
recipeKeys: []
}));
expect(AI_MECHANIC_PROMPT_VERSION).toBe(4);
expect(input.canonicalCatalog.grammar.outcome.optional).toEqual(
expect.arrayContaining(['delivery', 'durationSeconds'])
);
expect(input.canonicalCatalog.grammar.outcome.delivery).toEqual([
'instant',
'continuous'
]);
expect(input.canonicalCatalog.grammar.continuousRestorationOutcomeShape)
.toMatchObject({
kind: 'restoration',
resource: 'health',
delivery: 'continuous',
durationSeconds: 3,
variants: {
'replace-with-each-required-variant-key': {
value: 240,
expression: {
left: 240,
right: {
left: {
ref: {
source: 'attribute'
}
},
right: 100
}
}
}
}
});
expect(input.examplesAndRules).toEqual(expect.arrayContaining([
expect.stringContaining('Continuous Health restoration is supported'),
expect.stringContaining('not a per-second value'),
expect.stringContaining('Do not replace continuous delivery with a periodic reducer')
]));
expect(AI_MECHANIC_AUTHORING_INSTRUCTIONS).toContain(
'Prompt contract version: 4'
);
const advertised = structuredClone(
input.canonicalCatalog.grammar.continuousRestorationOutcomeShape
);
const tierI = JSON.parse(JSON.stringify(
advertised.variants['replace-with-each-required-variant-key']
).replaceAll(
'replace-with-an-exact-catalog-attribute-id',
'1bce573c-80f7-4487-b1d3-c4dc598b5244'
));
const tierII = structuredClone(tierI);
tierII.value = 336;
tierII.expression.left = 336;
advertised.variants = { I: tierI, II: tierII };
const parsed = parseConditionalMechanicDraft({
version: 1,
simulationReadiness: 'ready',
scenarioInputs: [],
pools: [],
clauses: [{
key: 'predator-aura',
label: 'Predator Aura',
event: 'enemy_killed',
if: {
kind: 'compare',
left: { source: 'event', path: 'values.distanceMeters' },
operator: 'lte',
right: 20
},
reducer: { kind: 'none' },
mutations: [],
outcomes: [advertised]
}]
}, {
variantKeys: ['I', 'II'],
allowedAttributeIds: new Set(['1bce573c-80f7-4487-b1d3-c4dc598b5244'])
});
expect(parsed.ok).toBe(true);
});
});