Files
Project-M/Docs/Vault/02_Game_Design/Data_Driven_Abilities.md
T
kronic a7405c3f38 Docs: reconcile vault roadmap + indexes to current state
Bring the living docs up to 2026-06-08 reality (Milestones/Backlog/Home/Systems_Index were stalled at 2026-06-06): add the 6 missing Milestones rows (animation, enemy animation, HUD Synty skin, world redo, world collision, Inventory+Equipment Phase 0/1); declutter Backlog to open-work-only (remove shipped [x] items whose context lives in their DRs/session logs, resolve items completed by later slices, surface Inventory/Equipment Phase 2-4 as next); refresh Home links (DR range -> DR-027, latest sessions); add a Systems_Index Items/Inventory/Equipment section + fix the stale 'StructureType 2-4 reserved for M7' note + a pointer to systems documented only in their DRs.

Reconcile DR-026's roadmap (mark Phase 0/1 done; the Phase-1 line now carries the DR-027 event-driven supersession note). Trim Data_Driven_Abilities resolved open-questions + fix cross-links. DRs + session logs (the historical record) untouched apart from that one DR-026 note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:33:46 -07:00

6.7 KiB
Raw Blame History

tags, status, updated, permalink
tags status updated permalink
design
abilities
data-driven
m3
built 2026-06-08 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 + 12 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<AbilityDatabaseBlob> 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<StatModifier> 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 12 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).

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).