--- tags: - design - abilities - data-driven - m3 status: built updated: 2026-06-08 permalink: gamevault/02-game-design/data-driven-abilities --- # M3 — Data-Driven Abilities & Modifiers (design) > **Status: ✅ BUILT 2026-05-31** — runtime-validated on 6.4.7. Final architecture + build deviations in [[DR-004_M3_DataDriven_Abilities_Modifiers]]; session [[2026-05-31_M3_Data_Driven_Abilities]]. Build-time refinements vs this design: prefab refs went to a companion `AbilityPrefabElement` buffer (not the blob — blobs don't remap entity refs); recompute runs **every predicted tick** (not the dirty-flag option — rollback-correctness); `StatModifier.Target`/`Op` replicate as raw `byte`; `MaxHealth` single-sourced from the character SO; `PlayerAimSystem` left as-is. The design narrative below is preserved as the original intent. > Goal: make the combat/ability system **scalable** by moving ability **and** character stats out of hard-baked components into **authored definitions**, with **runtime modifiers** (upgrades/buffs) mutating effective values. ## Why M2 hard-codes combat values in baked components (`AbilityStats`, `Projectile` Speed/Damage/Range, `PlayerMoveStats`, `Health.Max`). Adding abilities or tuning balance means editing code/prefabs. For an ARPG with many abilities + an upgrade/modifier meta-game, definitions must be **data** a designer edits, and values must be **mutable at runtime** by upgrades/buffs. Locked by [[Pillars]]: server-authoritative + deterministic, so modifiers must be prediction-correct. ## Decisions (operator-chosen 2026-05-31) - **Scope = pattern slice.** Establish the definition + modifier *pattern* and prove it by refactoring the **current projectile ability** + **1–2 sample abilities** onto it. Not building a full multi-archetype/loadout system yet (that comes later, on this foundation). - **Authoring = ScriptableObjects → baked to blob assets.** Designers edit SO assets in the inspector; a `Baker` converts them into DOTS-native immutable `BlobAssetReference` runtime data (Burst-fast, shared, zero per-instance cost). - **Modifiers = flat + percent stacks.** ARPG-standard additive (+X) and percentage (+X%) modifier stacks compute *effective* stats from the base definition; upgrades add/remove modifiers. **Server-authoritative + prediction-correct.** - **Stat scope = abilities + character stats.** One framework covers ability stats (damage/cooldown/range/projectile speed/auto-target) **and** character stats (max health, move speed, turn rate), so an upgrade can buff either. ## Architecture (proposed — DOTS-idiomatic) **Authoring → runtime pipeline** 1. `AbilityDefinition` / `CharacterStatsDefinition` **ScriptableObjects** (designer-facing; live under `Assets/_Project/Abilities/`). 2. A baker bakes the SO set into a **singleton "definition database"**: a `BlobAssetReference` holding an array of ability/stat definitions indexed by a stable **`AbilityId`** (and `CharacterId`). Entity-prefab references (e.g. the projectile ghost) are resolved at bake into the blob; **managed/UI assets (icon, description, VFX/SFX prefabs) stay off the blob** (blobs are unmanaged) — looked up separately by id for presentation. 3. Gameplay entities carry a light `AbilityRef { AbilityId }` / `CharacterStatsRef { CharacterId }` instead of inlined values; systems read the base values from the blob. **Base → modifiers → effective stats** - `StatModifier` (buffer element): `{ StatTarget (enum: Damage, CooldownTicks, Range, ProjectileSpeed, MoveSpeed, MaxHealth, …), Op (Flat | PercentAdd | PercentMult), float Value, ModifierSource }`. - Per-entity `DynamicBuffer` holds active modifiers (from upgrades, gear, buffs). - A deterministic `StatRecomputeSystem` computes **effective-stat components** (e.g. `EffectiveAbilityStats`, `EffectiveMoveStats`) from `base (blob) + modifiers` using the standard order: `effective = (base + Σ flat) × (1 + Σ percentAdd) × Π (1 + percentMult)`. Recompute on modifier-set change (dirty) rather than every tick. - Gameplay systems read the **effective** components: `AbilityFireSystem` uses effective damage/cooldown/range; `PlayerMoveSystem` uses effective move speed; health uses effective max. **Netcode determinism** (the important constraint) - **Definitions** are static config — baked identically into both worlds' blobs; **not replicated**. - **Modifiers** affect server-authoritative damage, so the predicted client must compute the *same* effective stats: the active-modifier state is **replicated** (a ghost buffer of `StatModifier`, or derived deterministically from a replicated upgrade-level/loadout component). `StatRecomputeSystem` is a pure function → predicted + server results match. No wall-clock; timed buffs (a later extension) expire on `NetworkTick`. ## Proposed definition fields (refine at build time) **AbilityDefinition:** `AbilityId`; `DisplayName` (FixedString); *[authoring/UI only]* `Description`, `Icon`; `Archetype` (enum — Projectile now; Hitscan/MeleeCone/AoE/Buff later); `Damage`; `DamageType`/`Element` (enum, for resistances later); `CooldownTicks`; `Range`; `ProjectileSpeed`; `AutoTargetRange`, `AutoTargetConeDegrees`; `ProjectilePrefab` (entity-prefab ref, baked); *[UI]* `MuzzleVfx`/`HitVfx`/`Sfx`; `Tags` (FixedList — for modifier targeting, e.g. "all Fire abilities +10%"). *Future:* `ResourceCost`, `CastTime`, `Charges`. **CharacterStatsDefinition:** `CharacterId`; `MaxHealth`; `MoveSpeed`; `TurnRate`; (extensible). ## Refactor target (the pattern slice) Move M2's hard-baked values onto the data model: `AbilityStats` + `Projectile`(Damage/Speed/Range) ← ability definition by id; `PlayerMoveStats` + `Health.Max` ← character-stats definition; gameplay reads route through the effective-stat components. Add 1–2 sample abilities (e.g. a faster low-damage shot + a slow heavy shot) purely as data to prove no code changes are needed per ability. ## Open questions (defer to build) - *Resolved at build (see [[DR-004_M3_DataDriven_Abilities_Modifiers]]): ghost-buffer modifier replication; recompute **every predicted tick** (not dirty-flag — rollback-correctness); sample abilities + a real pickup grant + a debug-injection source all shipped.* - **Still open** — UI/icon/description pipeline (managed lookup keyed by `ItemId`/`AbilityId`, off the blob); see [[Backlog]]. - **Still open** — Tag/element taxonomy (kept minimal until needed). ## Related [[DR-003_M2_Combat_Netcode_Architecture]] (the stats this refactors) · [[DR-004_M3_DataDriven_Abilities_Modifiers]] (build-time decisions) · [[DR-026_Inventory_Equipment_Progression_Foundation]] + [[DR-027_Equipment_Slots_Phase1]] (the inventory/equipment layer reuses this modifier stack + the `AbilityRef` swap) · [[Systems_Index]] · [[Pillars]] (server-authoritative + deterministic).