Support target-scoped trauma mechanics

This commit is contained in:
JB
2026-07-27 22:20:52 -04:00
parent 7e78d75f85
commit 0b3be3587b
20 changed files with 1032 additions and 93 deletions
@@ -78,7 +78,7 @@ describe('AI mechanic authoring prompt', () => {
recipeKeys: []
}));
expect(AI_MECHANIC_PROMPT_VERSION).toBe(4);
expect(AI_MECHANIC_PROMPT_VERSION).toBe(5);
expect(input.canonicalCatalog.grammar.outcome.optional).toEqual(
expect.arrayContaining(['delivery', 'durationSeconds'])
);
@@ -115,7 +115,7 @@ describe('AI mechanic authoring prompt', () => {
expect.stringContaining('Do not replace continuous delivery with a periodic reducer')
]));
expect(AI_MECHANIC_AUTHORING_INSTRUCTIONS).toContain(
'Prompt contract version: 4'
'Prompt contract version: 5'
);
const advertised = structuredClone(
@@ -156,4 +156,89 @@ describe('AI mechanic authoring prompt', () => {
});
expect(parsed.ok).toBe(true);
});
it('advertises Precise Trauma through target expiry without inventing target tick iteration', () => {
const skillDamageId = 'skill-damage-catalog-id';
const input = JSON.parse(buildAiMechanicAuthoringInput({
request: {
...request,
context: {
...request.context,
sourceName: 'Precise Trauma',
sourceDescription:
'Hits apply a debuff for 1 second, up to 10 stacks. Body shots add 1 stack and headshots add 2 stacks.'
}
},
attributes: [{
id: skillDamageId,
name: 'Skill Damage',
description: null,
unit: null,
type: null,
category: null,
calculationType: null
}],
events: [{ key: 'weapon_hit', label: 'Weapon hit' }],
actions: [
{ key: 'body_shot', label: 'Body shot' },
{ key: 'headshot', label: 'Headshot' }
],
recipeKeys: ['body_shot', 'headshot']
}));
const advertised = structuredClone(input.canonicalCatalog.grammar.preciseTraumaShape);
const outcome = advertised.clauses[0].outcomes[0];
const baseVariant = JSON.parse(JSON.stringify(
outcome.variants['replace-with-each-required-variant-key']
).replaceAll(
'replace-with-the-exact-skill-damage-catalog-id',
skillDamageId
));
outcome.variants = {
I: structuredClone(baseVariant),
II: structuredClone(baseVariant)
};
expect(advertised.pools[0]).toMatchObject({
scope: 'target',
max: 10,
expiresAfterSeconds: 1
});
expect(advertised.clauses[0].mutations[0]).toEqual({
kind: 'add',
pool: 'trauma',
entity: { kind: 'event_target' },
value: 1
});
expect(advertised.clauses[1].mutations[0]).toMatchObject({ value: 2 });
expect(outcome).toMatchObject({
phase: 'on_exit',
kind: 'direct_damage',
scope: 'target',
statePool: 'trauma',
perStack: true,
variants: {
I: {
expression: {
ref: {
source: 'attribute',
attributeId: skillDamageId
}
}
}
}
});
expect(input.examplesAndRules).toEqual(expect.arrayContaining([
expect.stringContaining('uses the entity previous state'),
expect.stringContaining('without inventing generic repeated target ticks')
]));
expect(JSON.stringify(input.canonicalCatalog.grammar.preciseTraumaShape))
.not.toContain('entitiesFromPool');
const parsed = parseConditionalMechanicDraft(advertised, {
variantKeys: ['I', 'II'],
allowedAttributeIds: new Set([skillDamageId]),
allowedRecipeKeys: new Set(['body_shot', 'headshot'])
});
expect(parsed.ok).toBe(true);
});
});