feat: support extra health state mechanics
Validate and deploy production / validate (push) Successful in 21s
Validate and deploy staging / validate (push) Successful in 23s
Validate and deploy production / deploy (push) Successful in 44s
Validate and deploy staging / deploy (push) Successful in 30s

This commit is contained in:
jb
2026-07-31 23:55:49 -04:00
parent 9ac2fe47cf
commit 1998e87309
16 changed files with 575 additions and 47 deletions
@@ -5,6 +5,7 @@ import {
parseConditionalMechanicDraft,
readCurrentHealthPercentBand,
readEventFieldComparison,
registeredEventFields,
readStatePoolThreshold,
statePoolThresholdPredicate,
type ConditionalMechanicDraftV1
@@ -126,6 +127,54 @@ describe('conditional mechanic authoring payload', () => {
})).toBeNull();
});
it('exposes typed Extra Health fields and parses their manual editor state', () => {
expect(registeredEventFields('extra_health_changed')).toEqual([
{ path: 'values.hasExtraHealth', label: 'Has Extra Health', type: 'boolean', required: true },
{ path: 'values.currentExtraHealth', label: 'Current Extra Health', type: 'number', minimum: 0 }
]);
const value = draft();
value.clauses[0].event = 'extra_health_changed';
value.clauses[0].recipeKey = undefined;
value.clauses[0].reducer = { kind: 'none' };
value.clauses[0].if = eventFieldComparisonPredicate({
path: 'values.hasExtraHealth',
operator: 'eq',
value: true
});
const result = parseConditionalMechanicDraft(value, {
variantKeys: ['I', 'II'],
allowedAttributeIds: new Set(['weapon-damage'])
});
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.programs.I.clauses[0]).toMatchObject({
when: 'extra_health_changed',
if: value.clauses[0].if
});
});
it('rejects an unregistered Extra Health event field deterministically', () => {
const value = draft();
value.clauses[0].event = 'extra_health_changed';
value.clauses[0].recipeKey = undefined;
value.clauses[0].reducer = { kind: 'none' };
value.clauses[0].if = eventFieldComparisonPredicate({
path: 'values.extraHealthActive',
operator: 'eq',
value: true
});
const result = parseConditionalMechanicDraft(value, {
variantKeys: ['I', 'II'],
allowedAttributeIds: new Set(['weapon-damage'])
});
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.errors).toEqual(expect.arrayContaining([
expect.stringContaining('is not defined for Extra Health changed')
]));
}
});
it('round-trips state-pool thresholds and preserves unsupported targeted pools as advanced', () => {
const comparison = { pool: 'stacks', operator: 'gte' as const, value: 10 };
const predicate = statePoolThresholdPredicate(comparison);