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>
6.7 KiB
tags, status, updated, permalink
| tags | status | updated | permalink | ||||
|---|---|---|---|---|---|---|---|
|
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
AbilityPrefabElementbuffer (not the blob — blobs don't remap entity refs); recompute runs every predicted tick (not the dirty-flag option — rollback-correctness);StatModifier.Target/Opreplicate as rawbyte;MaxHealthsingle-sourced from the character SO;PlayerAimSystemleft 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
Bakerconverts them into DOTS-native immutableBlobAssetReferenceruntime 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
AbilityDefinition/CharacterStatsDefinitionScriptableObjects (designer-facing; live underAssets/_Project/Abilities/).- A baker bakes the SO set into a singleton "definition database": a
BlobAssetReference<AbilityDatabaseBlob>holding an array of ability/stat definitions indexed by a stableAbilityId(andCharacterId). 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. - 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
StatRecomputeSystemcomputes effective-stat components (e.g.EffectiveAbilityStats,EffectiveMoveStats) frombase (blob) + modifiersusing 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:
AbilityFireSystemuses effective damage/cooldown/range;PlayerMoveSystemuses 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).StatRecomputeSystemis a pure function → predicted + server results match. No wall-clock; timed buffs (a later extension) expire onNetworkTick.
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).