Namespace Core.Diplomacy
Classes
- BreakAllianceCommand
ENGINE LAYER - Command to break alliance between two countries
- BreakNonAggressionPactCommand
ENGINE LAYER - Command to break non-aggression pact
- DeclareWarCommand
ENGINE LAYER - Command to declare war between two countries
Validation:
- Both countries exist
- Not already at war
- Not the same country
Execution:
- Set war state in DiplomacySystem
- Add "Declared War" opinion modifier
- Emit DiplomacyWarDeclaredEvent
- DiplomacyColdData
ENGINE LAYER - Cold data for diplomatic relations (rarely accessed)
Architecture:
- Separate from hot RelationData struct
- Only loaded when needed (UI, calculations, save/load)
- Modifiers and history stored here
- Managed heap (Dictionary) storage acceptable for cold data
Memory: Variable (~200 bytes + modifiers)
Storage Pattern: Dictionary{(ushort, ushort), DiplomacyColdData} coldData;
- Same key as RelationData
- Only created when modifiers exist
- DiplomacyKeyHelper
ENGINE LAYER - Helper for packing/unpacking country pairs into relationship keys. PATTERN: Static utility class (no state). Ensures consistent key generation (always smaller ID first). Prevents duplicate relationships (A to B and B to A use same key). KEY FORMAT: ulong 64 bits, country1 always less than country2 (normalized order).
- DiplomacyModifierProcessor
ENGINE LAYER - Processes opinion modifier decay with Burst compilation
RESPONSIBILITY:
- Decay opinion modifiers (DecayOpinionModifiers with Burst job)
- Rebuild modifier cache after compaction
PATTERN: Stateless processor (receives data references from DiplomacySystem)
- Does NOT own NativeCollections (passed as parameters)
- Uses Burst-compiled parallel job for performance
- Sequential compaction for determinism
PERFORMANCE:
- 610,750 modifiers processed in 3ms (87% improvement from Burst)
- Parallel marking (IJobParallelFor) + sequential compaction (deterministic)
- O(1) cache rebuild for GetOpinion performance
ARCHITECTURE:
- Step 1: Burst job marks decayed modifiers (parallel, read-only)
- Step 2: Sequential compaction (deterministic ordering)
- Step 3: Rebuild modifierCache for O(1) GetOpinion lookups
- DiplomacyRelationManager
ENGINE LAYER - Manages opinion calculations and modifiers for diplomatic relations
RESPONSIBILITY:
- Opinion queries (GetOpinion, GetBaseOpinion)
- Opinion filtering (GetCountriesWithOpinionAbove/Below)
- Opinion modifier management (Add/Remove)
PATTERN: Stateless manager (receives data references from DiplomacySystem)
- Does NOT own NativeCollections (passed as parameters)
- Pure methods for opinion calculations
- Zero allocations in hot paths
PERFORMANCE:
- GetOpinion: O(1) cache lookup + O(m) modifier scan (m = modifiers per relationship, ~10)
- GetBaseOpinion: O(1) dictionary lookup
- AddOpinionModifier: O(1) append to flat array
- DiplomacySaveLoadHandler
ENGINE LAYER - Handles diplomacy save/load serialization
RESPONSIBILITY:
- Save diplomatic state to binary format
- Load diplomatic state and rebuild indices
- Rebuild derived data (activeWars, warsByCountry, modifierCache)
PATTERN: Stateless handler (receives data references from DiplomacySystem)
- Does NOT own NativeCollections (passed as parameters)
- Binary serialization for efficiency
- Rebuilds indices after load (Pattern 14: Hybrid Save/Load)
FORMAT:
- relationCount: int32
- For each relationship:
- country1, country2: ushort × 2
- baseOpinion: int64 (FixedPoint64.RawValue)
- atWar: bool
- treatyFlags: byte
- modifierCount: int32
- For each modifier:
- modifierTypeID: ushort
- value: int64 (FixedPoint64.RawValue)
- appliedTick: int32
- decayRate: int32
- DiplomacySystem
ENGINE LAYER - Facade for diplomatic relations between countries
PATTERN 6: Facade Pattern
- Owns all NativeCollections (data ownership)
- Delegates operations to specialized managers (stateless processors)
- Provides unified API for external systems
ARCHITECTURE:
- DiplomacyRelationManager: Opinion calculations and modifiers
- DiplomacyWarManager: War state and declarations
- DiplomacyTreatyManager: Treaties (Alliance, NAP, Guarantee, Military Access)
- DiplomacyModifierProcessor: Burst-optimized modifier decay
- DiplomacySaveLoadHandler: Serialization/deserialization
Performance Targets (Paradox Scale):
- 1000 countries, 30k active relationships
- GetOpinion() <0.1ms (O(1) cache + O(m) modifiers)
- IsAtWar() <0.01ms (HashSet O(1))
- DecayOpinionModifiers() <5ms for 610k modifiers (Burst parallel)
Pattern Compliance:
- Pattern 6: Facade (delegates to specialized managers)
- Pattern 8: Sparse Collections (store active only)
- Pattern 4: Hot/Cold Separation (RelationData hot, modifiers cold)
- Pattern 5: Fixed-Point Determinism (FixedPoint64 opinions)
- Pattern 17: Single Source of Truth (owns all diplomatic state)
- DiplomacyTreatyManager
ENGINE LAYER - Manages treaties between countries
RESPONSIBILITY:
- Treaty queries (AreAllied, HasNonAggressionPact, IsGuaranteeing, HasMilitaryAccess)
- Treaty state changes (Form/Break Alliance, NAP, Guarantee, Military Access)
- Treaty relationship queries (GetAllies, GetAlliesRecursive, GetGuaranteeing, GetGuaranteedBy)
PATTERN: Stateless manager (receives data references from DiplomacySystem)
- Does NOT own NativeCollections (passed as parameters)
- Uses RelationData.treatyFlags bitfield for treaty storage
- Directional treaties (Guarantee, Military Access) use separate bits for each direction
PERFORMANCE:
- Treaty checks: O(1) dictionary lookup + bitfield check
- GetAllies: O(n) where n = total relationships
- GetAlliesRecursive: O(n) BFS traversal
- DiplomacyWarManager
ENGINE LAYER - Manages war state and war-related queries
RESPONSIBILITY:
- War state queries (IsAtWar, GetEnemies, GetAllWars)
- War declarations and peace treaties
- War index management (warsByCountry for fast lookups)
PATTERN: Stateless manager (receives data references from DiplomacySystem)
- Does NOT own NativeCollections (passed as parameters)
- Manages activeWars HashSet and warsByCountry index
- Emits events through GameState.EventBus
PERFORMANCE:
- IsAtWar: O(1) HashSet lookup
- GetEnemies: O(k) where k = enemies for this country
- DeclareWar: O(1) + event emission
- FormAllianceCommand
ENGINE LAYER - Command to form alliance between two countries
Validation:
- Both countries exist
- Not already allied
- Not at war
Execution:
- Set Alliance bit in DiplomacySystem
- Emit AllianceFormedEvent
- FormNonAggressionPactCommand
ENGINE LAYER - Command to form non-aggression pact
- GrantMilitaryAccessCommand
ENGINE LAYER - Command to grant military access (directional)
- GuaranteeIndependenceCommand
ENGINE LAYER - Command to guarantee another country's independence (directional)
- ImproveRelationsCommand
ENGINE LAYER - Command to improve relations between two countries
Architecture:
- ENGINE: Generic mechanism using ushort resourceId (resource-agnostic)
- GAME: Policy layer sets which resource (gold) via factory: (ushort)ResourceType.Gold
Validation:
- Both countries exist
- Not at war
- Source country has enough of the specified resource (if cost > 0)
Execution:
- Deduct resource cost (if specified)
- Add "Improved Relations" opinion modifier
- Emit DiplomacyOpinionChangedEvent
- MakePeaceCommand
ENGINE LAYER - Command to make peace between two countries
Validation:
- Both countries exist
- Currently at war
Execution:
- Remove war state in DiplomacySystem
- Add "Made Peace" opinion modifier
- Emit DiplomacyPeaceMadeEvent
- RevokeGuaranteeCommand
ENGINE LAYER - Command to revoke guarantee of independence
- RevokeMilitaryAccessCommand
ENGINE LAYER - Command to revoke military access
Structs
- AllianceBrokenEvent
Emitted when an alliance is broken GAME layer uses this to add opinion penalties, prestige loss
- AllianceFormedEvent
Emitted when an alliance is formed between two countries GAME layer uses this to add opinion bonuses, UI notifications
- DecayModifiersJob
Burst-compiled job for marking decayed opinion modifiers
ARCHITECTURE: Flat storage with relationship keys
- All modifiers stored in single NativeList{ModifierWithKey}
- Job marks decayed modifiers (parallel READ-ONLY operation)
- Main thread compacts array SEQUENTIALLY (deterministic order preserved)
DETERMINISM GUARANTEE:
- Job is READ-ONLY (no race conditions, no order dependency)
- Compaction is SEQUENTIAL on main thread
- Insertion order preserved (append-only)
- Result identical across all game clients
Performance Target: <5ms for 610k modifiers (parallel SIMD processing)
- DiplomacyColdDataNative
ENGINE LAYER - Cold data for diplomatic relations using NativeCollections
Architecture:
- Struct (not class) for Burst compatibility
- NativeList for modifiers (dynamic growth, zero GC)
- Stored in NativeList{DiplomacyColdDataNative} in DiplomacySystem
Memory: ~24 bytes + modifiers
- DiplomacyOpinionChangedEvent
Emitted when opinion changes between two countries AI uses this to: detect improving/worsening relations, adjust diplomacy UI uses this to: update relations panel, show opinion trends
- DiplomacyPeaceMadeEvent
Emitted when two countries make peace AI uses this to: recalculate threat levels, consider new targets UI uses this to: show notifications, update war list
- DiplomacyStats
Statistics for debugging and validation
- DiplomacySystemInitializedEvent
Emitted when DiplomacySystem initializes Used by UI and AI to know when diplomatic queries are available
- DiplomacyWarDeclaredEvent
ENGINE LAYER - Diplomatic event definitions
Architecture:
- Structs for zero-allocation EventBus
- Emitted by DiplomacySystem on state changes
- GAME layer subscribes to trigger UI updates, notifications, AI reactions
Pattern 3: Event-Driven Architecture (Zero-Allocation)
- Events are structs (no boxing)
- EventBus uses EventQueue{T} wrapper
- Frame-coherent processing
- GuaranteeGrantedEvent
Emitted when a country guarantees another's independence (directional)
- GuaranteeRevokedEvent
Emitted when a guarantee is revoked
- MilitaryAccessGrantedEvent
Emitted when military access is granted (directional)
- MilitaryAccessRevokedEvent
Emitted when military access is revoked
- ModifierEntry
Single modifier entry in the flattened modifier array Stores both the modifier data AND which relationship it belongs to
This enables Burst-compiled parallel processing of ALL modifiers at once instead of iterating relationships sequentially
- ModifierRange
Tracks the range of modifiers for a single relationship in the flat modifier array
DETERMINISM GUARANTEE:
- Modifiers for each relationship stored in STABLE order (insertion order preserved)
- Compaction is SEQUENTIAL on main thread (deterministic)
- Parallel Burst job ONLY marks decayed (read-only, no order dependency)
Architecture: allModifiers[startIndex ... startIndex+count-1] = this relationship's modifiers
- ModifierWithKey
Opinion modifier tagged with its relationship key
ARCHITECTURE:
- Enables flat storage without range tracking
- allModifiers = NativeList{ModifierWithKey} (all modifiers from all relationships)
- GetOpinion filters by relationshipKey
- Burst job processes entire array in parallel
DETERMINISM:
- Insertion order preserved (append-only)
- Decay marks modifiers for removal (parallel read-only)
- Compaction rebuilds array sequentially (deterministic)
Memory: ~32 bytes per modifier (8 bytes key + 24 bytes modifier)
- NonAggressionPactBrokenEvent
Emitted when a non-aggression pact is broken GAME layer uses this to add opinion penalties
- NonAggressionPactFormedEvent
Emitted when a non-aggression pact is formed
- OpinionModifier
ENGINE LAYER - Individual opinion modifier affecting relations between countries
Architecture:
- Fixed-size struct for deterministic serialization
- Decays linearly over time (value * (1 - elapsed/decayRate))
- Fully decayed modifiers removed automatically
Memory: ~20 bytes per modifier (aligned)
Example Usage: var modifier = new OpinionModifier { modifierTypeID = OpinionModifierTypes.DeclaredWar, value = FixedPoint64.FromInt(-50), appliedTick = currentTick, decayRate = 3600 // 10 years (360 ticks/year) };
// Calculate current value after 5 years: FixedPoint64 current = modifier.CalculateCurrentValue(currentTick); // Returns -25 (50% decay after half the decay period)
- RelationData
ENGINE LAYER - Hot data for diplomatic relations between two countries
Architecture:
- Fixed-size struct for cache efficiency
- Only stores essential hot data (opinion, war state)
- Cold data (modifiers, history) stored separately
- Stored in sparse Dictionary (only active relationships)
Memory: 16 bytes per relationship
Storage Pattern: Dictionary{(ushort, ushort), RelationData} relations;
- Key: (country1, country2) sorted pair
- Value: this struct
- Only store relationships that exist (sparse)
Example: 1000 countries × 30% interaction = ~30k relationships × 16 bytes = ~480KB
Enums
- TreatyFlags
Treaty type bitfield flags Used in RelationData.treatyFlags to track active treaties