feat: add source-owned combat mechanic programs

This commit is contained in:
JB
2026-07-27 13:35:00 -04:00
parent 5a67b252aa
commit 4eb754e7c8
62 changed files with 14815 additions and 905 deletions
+151 -28
View File
@@ -369,30 +369,123 @@ CREATE TABLE IF NOT EXISTS talent_tiers (
updated_at TEXT NOT NULL,
UNIQUE(talent_id, tier)
);
CREATE TABLE IF NOT EXISTS talent_triggers (
CREATE TABLE IF NOT EXISTS conditional_effect_rules (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL,
supports_uptime INTEGER NOT NULL DEFAULT 0 CHECK(supports_uptime IN (0, 1)),
min_uptime REAL,
max_uptime REAL,
calculation_script TEXT,
active INTEGER NOT NULL DEFAULT 1,
source_type TEXT NOT NULL CHECK(source_type IN (
'talent','os_protocol_talent','skill_chip_talent','specialization_tree_rank'
)),
source_id TEXT NOT NULL,
configured INTEGER NOT NULL DEFAULT 0 CHECK(configured IN (0,1)),
script_version INTEGER NOT NULL DEFAULT 1 CHECK(script_version>0),
simulation_readiness TEXT NOT NULL DEFAULT 'ready' CHECK(simulation_readiness IN (
'ready','requires_assumptions','captured_only'
)),
scenario_inputs_json TEXT NOT NULL DEFAULT '[]',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(source_type,source_id)
);
CREATE TABLE IF NOT EXISTS conditional_effect_state_pools (
id TEXT PRIMARY KEY,
rule_id TEXT NOT NULL REFERENCES conditional_effect_rules(id) ON DELETE CASCADE,
state_key TEXT NOT NULL,
label TEXT NOT NULL,
state_kind TEXT NOT NULL DEFAULT 'stack' CHECK(state_kind IN (
'flag','count','stack','duration','scalar','target_mark','resource'
)),
state_scope TEXT NOT NULL DEFAULT 'agent' CHECK(state_scope IN ('agent','target')),
initial_value REAL NOT NULL DEFAULT 0,
minimum_value REAL,
maximum_value REAL NOT NULL DEFAULT 1 CHECK(maximum_value>0),
best_case_value REAL,
duration_seconds REAL CHECK(duration_seconds IS NULL OR duration_seconds>0),
expiry_policy TEXT NOT NULL DEFAULT 'clear_all' CHECK(expiry_policy IN ('clear_all','decrement_one','none')),
refresh_policy TEXT NOT NULL DEFAULT 'refresh' CHECK(refresh_policy IN ('refresh','extend','ignore')),
parameters_json TEXT NOT NULL DEFAULT '{}',
sort_order INTEGER NOT NULL CHECK(sort_order>=1),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(rule_id,state_key),
UNIQUE(rule_id,sort_order),
CHECK(minimum_value IS NULL OR maximum_value>=minimum_value),
CHECK(best_case_value IS NULL OR minimum_value IS NULL OR best_case_value>=minimum_value),
CHECK(best_case_value IS NULL OR best_case_value<=maximum_value)
);
CREATE INDEX IF NOT EXISTS conditional_effect_state_pools_rule_idx
ON conditional_effect_state_pools(rule_id,sort_order);
CREATE TABLE IF NOT EXISTS conditional_effect_clauses (
id TEXT PRIMARY KEY,
rule_id TEXT NOT NULL REFERENCES conditional_effect_rules(id) ON DELETE CASCADE,
clause_key TEXT NOT NULL,
label TEXT NOT NULL,
event_kind TEXT NOT NULL,
recipe_key TEXT,
recipe_parameters_json TEXT NOT NULL DEFAULT '{}',
conditions_json TEXT NOT NULL DEFAULT '{"kind":"true"}',
reducer_json TEXT NOT NULL DEFAULT '{"kind":"none"}',
weight_expression_json TEXT,
emission_kind TEXT NOT NULL DEFAULT 'each_match' CHECK(emission_kind IN (
'each_match','threshold_crossed','while_true','interval_elapsed','value_changed'
)),
state_pool_id TEXT REFERENCES conditional_effect_state_pools(id) ON DELETE SET NULL,
mutations_json TEXT NOT NULL DEFAULT '[]',
cooldown_seconds REAL CHECK(cooldown_seconds IS NULL OR cooldown_seconds>0),
output_branch TEXT NOT NULL DEFAULT 'active',
sort_order INTEGER NOT NULL CHECK(sort_order>=1),
active INTEGER NOT NULL DEFAULT 1 CHECK(active IN (0,1)),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(rule_id,clause_key),
UNIQUE(rule_id,sort_order)
);
CREATE INDEX IF NOT EXISTS conditional_effect_clauses_event_idx
ON conditional_effect_clauses(event_kind,rule_id);
CREATE TABLE IF NOT EXISTS conditional_effect_outcomes (
id TEXT PRIMARY KEY,
rule_id TEXT NOT NULL REFERENCES conditional_effect_rules(id) ON DELETE CASCADE,
clause_id TEXT REFERENCES conditional_effect_clauses(id) ON DELETE CASCADE,
state_pool_id TEXT REFERENCES conditional_effect_state_pools(id) ON DELETE SET NULL,
effect_key TEXT NOT NULL,
variant_key TEXT NOT NULL DEFAULT 'base',
outcome_key TEXT NOT NULL DEFAULT 'active',
application_kind TEXT NOT NULL DEFAULT 'active_modifier' CHECK(application_kind IN (
'active_modifier','direct_damage','restore_health','restore_armor','restore_ammo','resource_cost','marker'
)),
effect_scope TEXT NOT NULL DEFAULT 'self' CHECK(effect_scope IN (
'self','equipped_weapon','target','allies','self_and_allies','assigned_ability','all_skills'
)),
stat_attribute_id TEXT REFERENCES stat_attributes(id),
value REAL NOT NULL,
value_expression_json TEXT,
value_basis TEXT NOT NULL DEFAULT 'flat' CHECK(value_basis IN (
'flat','max_health_percent','current_health_percent','weapon_base_percent'
)),
value_scaling TEXT NOT NULL DEFAULT 'per_state_unit' CHECK(value_scaling IN ('flat','per_state_unit')),
unit TEXT,
damage_basis TEXT CHECK(damage_basis IS NULL OR damage_basis IN ('flat','weapon_base_percent')),
application_phase TEXT NOT NULL DEFAULT 'while_active' CHECK(application_phase IN (
'while_active','on_event','after_event','on_enter','on_exit'
)),
combat_timing TEXT NOT NULL DEFAULT 'current' CHECK(combat_timing IN ('pre_event','current')),
resource_kind TEXT CHECK(resource_kind IS NULL OR resource_kind IN ('health','armor','ammo')),
min_state_value REAL,
max_state_value REAL,
description_slot INTEGER CHECK(description_slot IS NULL OR description_slot>=1),
sort_order INTEGER NOT NULL CHECK(sort_order>=1),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(rule_id,variant_key,outcome_key,sort_order),
CHECK(min_state_value IS NULL OR min_state_value>=0),
CHECK(max_state_value IS NULL OR max_state_value>=0),
CHECK(min_state_value IS NULL OR max_state_value IS NULL OR max_state_value>=min_state_value),
CHECK(
(supports_uptime = 0 AND min_uptime IS NULL AND max_uptime IS NULL)
OR
(supports_uptime = 1 AND min_uptime IS NOT NULL AND max_uptime IS NOT NULL
AND min_uptime >= 0 AND max_uptime <= 100 AND min_uptime <= max_uptime)
(application_kind='active_modifier' AND stat_attribute_id IS NOT NULL AND damage_basis IS NULL)
OR (application_kind='direct_damage' AND damage_basis IS NOT NULL)
OR application_kind IN ('restore_health','restore_armor','restore_ammo','resource_cost','marker')
)
);
CREATE UNIQUE INDEX IF NOT EXISTS talent_triggers_name_nocase_idx ON talent_triggers(name COLLATE NOCASE);
CREATE TABLE IF NOT EXISTS talent_tier_triggers (
talent_tier_id TEXT NOT NULL REFERENCES talent_tiers(id) ON DELETE CASCADE,
trigger_id TEXT NOT NULL REFERENCES talent_triggers(id),
PRIMARY KEY(talent_tier_id, trigger_id)
);
CREATE INDEX IF NOT EXISTS conditional_effect_outcomes_rule_idx
ON conditional_effect_outcomes(rule_id,variant_key,outcome_key,sort_order);
CREATE TABLE IF NOT EXISTS talent_tier_effects (
id TEXT PRIMARY KEY,
talent_tier_id TEXT NOT NULL REFERENCES talent_tiers(id) ON DELETE CASCADE,
@@ -604,11 +697,6 @@ CREATE TABLE IF NOT EXISTS os_protocol_talents (
updated_at TEXT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS os_protocol_talents_name_nocase_idx ON os_protocol_talents(name COLLATE NOCASE);
CREATE TABLE IF NOT EXISTS os_protocol_talent_triggers (
talent_id TEXT NOT NULL REFERENCES os_protocol_talents(id) ON DELETE CASCADE,
trigger_id TEXT NOT NULL REFERENCES talent_triggers(id),
PRIMARY KEY(talent_id, trigger_id)
);
CREATE TABLE IF NOT EXISTS os_protocol_talent_effects (
id TEXT PRIMARY KEY,
talent_id TEXT NOT NULL REFERENCES os_protocol_talents(id) ON DELETE CASCADE,
@@ -655,11 +743,6 @@ CREATE TABLE IF NOT EXISTS skill_chip_talents (
updated_at TEXT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS skill_chip_talents_name_nocase_idx ON skill_chip_talents(name COLLATE NOCASE);
CREATE TABLE IF NOT EXISTS skill_chip_talent_triggers (
talent_id TEXT NOT NULL REFERENCES skill_chip_talents(id) ON DELETE CASCADE,
trigger_id TEXT NOT NULL REFERENCES talent_triggers(id),
PRIMARY KEY(talent_id, trigger_id)
);
CREATE TABLE IF NOT EXISTS skill_chip_talent_effects (
id TEXT PRIMARY KEY,
talent_id TEXT NOT NULL REFERENCES skill_chip_talents(id) ON DELETE CASCADE,
@@ -857,6 +940,26 @@ CREATE TABLE IF NOT EXISTS specialization_abilities (
updated_at TEXT NOT NULL,
UNIQUE(specialization_id,name)
);
CREATE TABLE IF NOT EXISTS specialization_ability_attacks (
ability_id TEXT PRIMARY KEY REFERENCES specialization_abilities(id) ON DELETE CASCADE,
base_damage REAL NOT NULL CHECK(base_damage>=0),
damage_type TEXT NOT NULL CHECK(damage_type IN ('Blast','Pierce','Shredding')),
delivery TEXT NOT NULL CHECK(delivery IN ('direct','periodic')),
hits_per_activation INTEGER NOT NULL DEFAULT 1 CHECK(hits_per_activation>0),
tick_interval_seconds REAL CHECK(tick_interval_seconds IS NULL OR tick_interval_seconds>0),
damage_duration_seconds REAL CHECK(damage_duration_seconds IS NULL OR damage_duration_seconds>0),
cast_seconds REAL NOT NULL DEFAULT 0 CHECK(cast_seconds>=0),
target_cap INTEGER NOT NULL DEFAULT 1 CHECK(target_cap>0),
charge_count INTEGER NOT NULL DEFAULT 1 CHECK(charge_count>0),
charge_policy TEXT NOT NULL DEFAULT 'none' CHECK(charge_policy IN ('none','independent','full_set')),
updated_at TEXT NOT NULL,
CHECK(
(delivery='direct' AND tick_interval_seconds IS NULL AND damage_duration_seconds IS NULL)
OR
(delivery='periodic' AND tick_interval_seconds IS NOT NULL AND damage_duration_seconds IS NOT NULL)
),
CHECK(charge_policy<>'none' OR charge_count=1)
);
CREATE INDEX IF NOT EXISTS specialization_abilities_specialization_idx ON specialization_abilities(specialization_id,name);
CREATE TABLE IF NOT EXISTS specialization_ability_effects (
id TEXT PRIMARY KEY,
@@ -1010,6 +1113,26 @@ CREATE TABLE IF NOT EXISTS attribute_calculation_profiles (
CHECK(hard_cap IS NULL OR hard_cap>=0),
CHECK(soft_cap IS NULL OR hard_cap IS NULL OR hard_cap>=soft_cap)
);
CREATE TABLE IF NOT EXISTS skill_attribute_calculation_profiles (
id TEXT PRIMARY KEY,
revision_id TEXT NOT NULL REFERENCES calculation_revisions(id) ON DELETE CASCADE,
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
stage_key TEXT NOT NULL CHECK(stage_key IN (
'flat_damage','engineering','skill_damage','critical_chance','critical_damage',
'multishot_chance','multishot_damage','final_damage',
'cooldown_recovery','duration'
)),
operator TEXT NOT NULL CHECK(operator IN ('add','multiply')),
weight REAL NOT NULL DEFAULT 1,
soft_cap REAL,
hard_cap REAL,
curve_type TEXT NOT NULL DEFAULT 'none' CHECK(curve_type IN ('none','power','smoothstep')),
curve_parameter REAL,
UNIQUE(revision_id,stat_attribute_id,stage_key),
CHECK(soft_cap IS NULL OR soft_cap>=0),
CHECK(hard_cap IS NULL OR hard_cap>=0),
CHECK(soft_cap IS NULL OR hard_cap IS NULL OR hard_cap>=soft_cap)
);
CREATE TABLE IF NOT EXISTS calculation_calibration_cases (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,