Table of Contents

Universal Modifier System

Status: Production Standard


Core Problem

Grand strategy games have many sources of bonuses (buildings, tech, events, government, terrain) that must stack predictably. Without a unified system, bonus application becomes inconsistent, tooltips are impossible, and removal creates orphaned modifiers.


Core Principle: Universal Accumulation

All modifiers follow the industry-standard formula:

Final = (Base + Additive) × (1 + Multiplicative)

Why this formula:

  • Additive bonuses stack linearly (+5, +10 = +15)
  • Multiplicative bonuses stack additively THEN multiply (50% + 50% = 100%, not 125%)
  • Prevents exponential scaling that breaks game balance
  • Designer-friendly (percentages add intuitively)

Industry pattern: EU4, CK3, Stellaris, Victoria 3 all use this formula.


Scope Inheritance

Modifiers exist at three scopes that inherit downward:

Global Scope: Applies to all entities (tech breakthroughs, world events, golden ages)

Country Scope: Applies to all provinces owned by that country (government type, national policies, country events)

Province Scope: Local only (buildings, terrain, governors)

Key insight: When you add a modifier to country scope, ALL provinces in that country inherit it automatically. No iteration required.

Example:

  • Research tech → add +10% to country scope → all 50 provinces get +10%
  • Build farm → add +50% to province scope → only that province gets +50%
  • Province total: inherits country (+10%) + local (+50%) = +60%

Source Tracking

Every modifier knows its origin:

  • Source type: Building, Technology, Event, Government, Character, etc.
  • Source ID: Which specific building/tech/event
  • Temporary flag: Permanent vs expires after N ticks

This enables:

Tooltips: Display exactly where each bonus comes from

Production: +80%
  Farm building:  +50% (local)
  Government:     +20% (country)
  Tech:           +10% (country)

Clean removal: Destroy building → remove all modifiers from that source (no orphans)

Expiration: Temporary modifiers auto-expire without manual tracking


Design Constraints

Fixed-size storage: Maximum modifier types defined at compile time (not runtime)

Why fixed-size:

  • Zero allocations during gameplay (multiplayer requirement)
  • Deterministic memory layout (same on all platforms)
  • O(1) lookup (array index by ID)
  • Cache-friendly (contiguous memory)

Dirty flag caching: Modifiers change rarely but are queried frequently. Cache the accumulated total and only rebuild when a modifier is added/removed.


Engine-Game Separation

ENGINE provides mechanism:

  • Scope containers and inheritance
  • Accumulation formula application
  • Source tracking and removal
  • Dirty flag optimization
  • Temporary modifier expiration

GAME defines policy:

  • Which modifier types exist (production, tax, manpower, etc.)
  • Which systems apply modifiers (buildings, tech, events)
  • String ID ↔ numeric ID mapping for data files

Key Trade-offs

Aspect Decision Rationale
Storage Fixed-size arrays Zero allocations, deterministic, O(1) lookup
Accumulation Add-then-multiply Prevents exponential scaling
Inheritance Scope chain Country changes affect all provinces without iteration
Tracking Source-based Clean removal, accurate tooltips

Anti-Patterns

Don't multiply modifiers separately: Each modifier multiplied individually causes exponential scaling (50% × 50% = 125% instead of 100%)

Don't store modifiers in multiple places: Province object AND country object AND modifier system = desync risk. Single source of truth only.

Don't forget removal: Destroying a building without removing its modifiers leaves orphaned bonuses. Always remove by source.

Don't allocate during modifier application: Hot path called thousands of times per frame. Zero allocations required.


  • Pattern 2 (Command Pattern): Modifier changes flow through commands for networking
  • Pattern 5 (Fixed-Point Determinism): Modifier values stored as deterministic types
  • Pattern 10 (Frame-Coherent Caching): Dirty flag optimization
  • Pattern 17 (Single Source of Truth): One modifier system owns all modifiers

Modifiers stack predictably. Sources are tracked. Removal is clean.