Support live attribute restoration mechanics
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { connect } from '@tursodatabase/database';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { conditionalRuleDraftFromRows } from './conditional-effects.js';
|
||||
import {
|
||||
conditionalRuleDraftFromRows,
|
||||
loadConditionalRuleDraft,
|
||||
saveConditionalRuleInTransaction
|
||||
} from './conditional-effects.js';
|
||||
|
||||
const rule = {
|
||||
id: 'rule-1',
|
||||
@@ -72,5 +77,157 @@ describe('conditional mechanic persistence adapter', () => {
|
||||
I: { value: 3 },
|
||||
II: { value: 5 }
|
||||
});
|
||||
expect(draft?.clauses[0].outcomes[0]).not.toHaveProperty('delivery');
|
||||
expect(draft?.clauses[0].outcomes[0]).not.toHaveProperty('durationSeconds');
|
||||
});
|
||||
|
||||
it('loads continuous delivery metadata and normalizes legacy instant variants', () => {
|
||||
const continuous = conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{
|
||||
...outcome,
|
||||
application_kind: 'restore_health',
|
||||
stat_attribute_id: null,
|
||||
resource_kind: 'health',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 3,
|
||||
variant_key: 'I'
|
||||
},
|
||||
{
|
||||
...outcome,
|
||||
application_kind: 'restore_health',
|
||||
stat_attribute_id: null,
|
||||
resource_kind: 'health',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 3,
|
||||
variant_key: 'II'
|
||||
}
|
||||
],
|
||||
['I', 'II']
|
||||
);
|
||||
expect(continuous?.clauses[0].outcomes[0]).toMatchObject({
|
||||
delivery: 'continuous',
|
||||
durationSeconds: 3
|
||||
});
|
||||
|
||||
expect(() => conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{ ...outcome, variant_key: 'I' },
|
||||
{ ...outcome, variant_key: 'II', delivery_mode: 'instant' }
|
||||
],
|
||||
['I', 'II']
|
||||
)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects divergent continuous-delivery durations across variants', () => {
|
||||
expect(() => conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{
|
||||
...outcome,
|
||||
variant_key: 'I',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 3
|
||||
},
|
||||
{
|
||||
...outcome,
|
||||
variant_key: 'II',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 4
|
||||
}
|
||||
],
|
||||
['I', 'II']
|
||||
)).toThrow(/inconsistent delivery_duration_seconds metadata/);
|
||||
});
|
||||
|
||||
it('round-trips continuous delivery through normalized rows', async () => {
|
||||
const database = await connect(':memory:');
|
||||
await database.exec(`CREATE TABLE conditional_effect_rules (
|
||||
id TEXT PRIMARY KEY, source_type TEXT, source_id TEXT, configured INTEGER,
|
||||
script_version INTEGER, simulation_readiness TEXT, scenario_inputs_json TEXT,
|
||||
created_at TEXT, updated_at TEXT, UNIQUE(source_type,source_id)
|
||||
)`);
|
||||
await database.exec(`CREATE TABLE conditional_effect_state_pools (
|
||||
id TEXT PRIMARY KEY, rule_id TEXT, state_key TEXT, label TEXT, state_kind TEXT,
|
||||
state_scope TEXT, initial_value REAL, minimum_value REAL, maximum_value REAL,
|
||||
best_case_value REAL, duration_seconds REAL, expiry_policy TEXT, refresh_policy TEXT,
|
||||
parameters_json TEXT, sort_order INTEGER, created_at TEXT, updated_at TEXT
|
||||
)`);
|
||||
await database.exec(`CREATE TABLE conditional_effect_clauses (
|
||||
id TEXT PRIMARY KEY, rule_id TEXT, clause_key TEXT, label TEXT, event_kind TEXT,
|
||||
recipe_key TEXT, recipe_parameters_json TEXT, conditions_json TEXT, reducer_json TEXT,
|
||||
weight_expression_json TEXT, emission_kind TEXT, state_pool_id TEXT, mutations_json TEXT,
|
||||
cooldown_seconds REAL, output_branch TEXT, sort_order INTEGER, active INTEGER,
|
||||
created_at TEXT, updated_at TEXT
|
||||
)`);
|
||||
await database.exec(`CREATE TABLE conditional_effect_outcomes (
|
||||
id TEXT PRIMARY KEY, rule_id TEXT, clause_id TEXT, state_pool_id TEXT, effect_key TEXT,
|
||||
variant_key TEXT, outcome_key TEXT, application_kind TEXT, effect_scope TEXT,
|
||||
stat_attribute_id TEXT, value REAL, value_expression_json TEXT, value_basis TEXT,
|
||||
value_scaling TEXT, unit TEXT, damage_basis TEXT, application_phase TEXT,
|
||||
combat_timing TEXT, resource_kind TEXT, delivery_mode TEXT,
|
||||
delivery_duration_seconds REAL, min_state_value REAL, max_state_value REAL,
|
||||
description_slot INTEGER, sort_order INTEGER, created_at TEXT, updated_at TEXT
|
||||
)`);
|
||||
|
||||
await saveConditionalRuleInTransaction(database, 'talent', 'predatory-aura', {
|
||||
version: 1,
|
||||
simulationReadiness: 'ready',
|
||||
scenarioInputs: [],
|
||||
pools: [],
|
||||
clauses: [{
|
||||
key: 'weapon-kill',
|
||||
label: 'Weapon kill',
|
||||
event: 'enemy_killed',
|
||||
if: { kind: 'true' },
|
||||
reducer: { kind: 'none' },
|
||||
mutations: [],
|
||||
outcomes: [{
|
||||
key: 'restore-health',
|
||||
label: 'Restore Health',
|
||||
phase: 'after_event',
|
||||
kind: 'restoration',
|
||||
scope: 'self',
|
||||
resource: 'health',
|
||||
basis: 'flat',
|
||||
delivery: 'continuous',
|
||||
durationSeconds: 3,
|
||||
variants: {
|
||||
I: { value: 240 },
|
||||
II: { value: 336 }
|
||||
}
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
const saved = await (await database.prepare(
|
||||
'SELECT delivery_mode,delivery_duration_seconds FROM conditional_effect_outcomes ORDER BY variant_key'
|
||||
)).all();
|
||||
expect(saved).toEqual([
|
||||
{ delivery_mode: 'continuous', delivery_duration_seconds: 3 },
|
||||
{ delivery_mode: 'continuous', delivery_duration_seconds: 3 }
|
||||
]);
|
||||
const loaded = await loadConditionalRuleDraft(
|
||||
database,
|
||||
'talent',
|
||||
'predatory-aura',
|
||||
['I', 'II']
|
||||
);
|
||||
expect(loaded?.clauses[0].outcomes[0]).toMatchObject({
|
||||
delivery: 'continuous',
|
||||
durationSeconds: 3,
|
||||
variants: {
|
||||
I: { value: 240 },
|
||||
II: { value: 336 }
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user