Support target-scoped trauma mechanics
This commit is contained in:
@@ -54,6 +54,41 @@ const draft = (): ConditionalMechanicDraftV1 => ({
|
||||
});
|
||||
|
||||
describe('conditional mechanic authoring payload', () => {
|
||||
it('accepts an empty captured-only program for deferred modeling', () => {
|
||||
const partial: ConditionalMechanicDraftV1 = {
|
||||
version: 1,
|
||||
simulationReadiness: 'captured_only',
|
||||
scenarioInputs: [],
|
||||
pools: [],
|
||||
clauses: []
|
||||
};
|
||||
const result = parseConditionalMechanicDraft(partial, {
|
||||
variantKeys: ['I', 'II']
|
||||
});
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(result.value.draft).toEqual(partial);
|
||||
expect(result.value.programs.I.clauses).toEqual([]);
|
||||
expect(result.value.programs.II.clauses).toEqual([]);
|
||||
});
|
||||
|
||||
it('requires a branch after a partial program is marked calculation-ready', () => {
|
||||
const partial: ConditionalMechanicDraftV1 = {
|
||||
version: 1,
|
||||
simulationReadiness: 'ready',
|
||||
scenarioInputs: [],
|
||||
pools: [],
|
||||
clauses: []
|
||||
};
|
||||
const result = parseConditionalMechanicDraft(partial, {
|
||||
variantKeys: ['I', 'II']
|
||||
});
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.errors).toContain('A simulation-ready mechanic requires at least one clause.');
|
||||
}
|
||||
});
|
||||
|
||||
it('builds one validated runtime program per exact source variant', () => {
|
||||
const result = parseConditionalMechanicDraft(draft(), {
|
||||
variantKeys: ['I', 'II'],
|
||||
@@ -263,6 +298,98 @@ describe('conditional mechanic authoring payload', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('preserves target entity mutations and state-scaled exit outcomes', () => {
|
||||
const targeted: ConditionalMechanicDraftV1 = {
|
||||
version: 1,
|
||||
simulationReadiness: 'ready',
|
||||
scenarioInputs: [],
|
||||
pools: [{
|
||||
key: 'marked-target',
|
||||
label: 'Marked Target',
|
||||
stateKind: 'target_mark',
|
||||
scope: 'target',
|
||||
initial: 0,
|
||||
min: 0,
|
||||
max: 1
|
||||
}],
|
||||
clauses: [{
|
||||
key: 'detonate-mark',
|
||||
event: 'enemy_killed',
|
||||
reducer: { kind: 'none' },
|
||||
mutations: [{
|
||||
kind: 'set',
|
||||
pool: 'marked-target',
|
||||
entity: { kind: 'event_target' },
|
||||
value: 1
|
||||
}],
|
||||
outcomes: [{
|
||||
key: 'target-damage',
|
||||
label: 'Target Damage',
|
||||
phase: 'on_exit',
|
||||
kind: 'direct_damage',
|
||||
scope: 'target',
|
||||
basis: 'flat',
|
||||
statePool: 'marked-target',
|
||||
perStack: true,
|
||||
variants: {
|
||||
base: {
|
||||
value: 240,
|
||||
expression: {
|
||||
op: 'multiply',
|
||||
left: 240,
|
||||
right: {
|
||||
op: 'divide',
|
||||
left: {
|
||||
ref: {
|
||||
source: 'attribute',
|
||||
attributeId: 'firepower-multiplier'
|
||||
}
|
||||
},
|
||||
right: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}]
|
||||
};
|
||||
const result = parseConditionalMechanicDraft(targeted, {
|
||||
variantKeys: ['base'],
|
||||
allowedAttributeIds: new Set(['firepower-multiplier'])
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(result.value.draft).toMatchObject(targeted);
|
||||
expect(result.value.programs.base).toMatchObject({
|
||||
pools: [{ key: 'marked-target', scope: 'target' }],
|
||||
clauses: [{
|
||||
mutations: [{
|
||||
kind: 'set',
|
||||
pool: 'marked-target',
|
||||
entity: { kind: 'event_target' },
|
||||
value: 1
|
||||
}],
|
||||
outcomes: [{
|
||||
phase: 'on_exit',
|
||||
kind: 'direct_damage',
|
||||
statePool: 'marked-target',
|
||||
perStack: true,
|
||||
value: {
|
||||
right: {
|
||||
left: {
|
||||
ref: {
|
||||
source: 'attribute',
|
||||
attributeId: 'firepower-multiplier'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}]
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects outcome fields that are absent from the canonical capability manifest', () => {
|
||||
const unsupported = draft();
|
||||
(unsupported.clauses[0].outcomes[0] as unknown as Record<string, unknown>).cadence =
|
||||
|
||||
Reference in New Issue
Block a user