Namespace Core.Systems
Classes
- AdjacencySystem
ENGINE LAYER: Province adjacency system
Stores which provinces are adjacent (share a border) for movement validation. Populated from FastAdjacencyScanner during map initialization.
Architecture:
- Dictionary{ushort, HashSet{ushort}}: provinceID → set of neighbor IDs
- Bidirectional: if A is adjacent to B, then B is adjacent to A
- Read-only after initialization
Usage:
- Initialize with SetAdjacencies(adjacencyData)
- Query with IsAdjacent(province1, province2)
- Get all neighbors with GetNeighbors(provinceID)
Performance:
- IsAdjacent: O(1) HashSet lookup
- GetNeighbors: O(1) dictionary lookup
- Memory: ~6 neighbors × 13,350 provinces × 2 bytes = ~160 KB
- CountrySystem
Single source of truth for all country/nation data Manages CountryHotData (8-byte) array for frequently accessed data Lazy-loads CountryColdData for detailed information Performance: Structure of Arrays, hot/cold separation, zero allocations
- GameSystem
ENGINE LAYER - Base class for all game systems with standardized lifecycle
Responsibilities:
- Define standard system lifecycle (Initialize, Shutdown, Save, Load)
- Enforce dependency validation before initialization
- Prevent re-initialization and initialization of missing dependencies
- Provide consistent logging for system operations
Architecture:
- Pure mechanism, no game-specific knowledge
- Systems declare dependencies explicitly via GetDependencies()
- Initialization order determined by dependency graph
- All state changes go through standard lifecycle methods
Usage (Game Layer): public class EconomySystem : GameSystem { public override string SystemName => "Economy";
protected override IEnumerable{GameSystem} GetDependencies() { yield return timeManager; yield return provinceSystem; } protected override void OnInitialize() { // Dependencies guaranteed to be initialized here timeManager.OnMonthlyTick += CollectTaxes; }}
Benefits:
- No load order bugs (dependencies validated)
- No circular dependency crashes (detected at startup)
- Easy testing (mock dependencies)
- Save/load support (standard serialization hooks)
- Self-documenting (dependencies explicit)
- PathCache
ENGINE: LRU cache for pathfinding results.
Caches computed paths to avoid redundant A* searches. Uses simple LRU eviction when cache is full.
Cache key: (start, goal) pair Cache invalidation: Manual clear or frame-based expiry
Thread safety: NOT thread-safe (single-threaded pathfinding assumed)
- ProvinceSystem
Single source of truth for all province data Owns the 8-byte ProvinceState array for deterministic simulation Manages province ownership, development, terrain, and flags Performance: Structure of Arrays design, Burst-compatible, zero allocations
- StandardCalendar
ENGINE: Default Earth calendar with real month lengths.
Features:
- Real month lengths (31/30/28 days)
- 365 days per year (no leap years for determinism)
- Standard AD/BC era formatting
- English month names (GAME layer can override with localized names)
For simplified 360-day calendar, use SimplifiedCalendar instead.
- SystemRegistry
ENGINE LAYER - Manages game system registration and initialization order
Responsibilities:
- Register all game systems
- Determine initialization order via topological sort
- Initialize systems in dependency order
- Detect circular dependencies
- Provide access to registered systems
Architecture:
- Pure mechanism, no game-specific knowledge
- Systems register themselves (or are registered by initializer)
- Dependency graph built from GameSystem.GetDependencies()
- Initialization order computed automatically
Usage: var registry = new SystemRegistry(); registry.Register(timeManager); registry.Register(economySystem); registry.InitializeAll(); // Initializes in dependency order
Benefits:
- No manual initialization order management
- Circular dependency detection at startup
- Missing dependency errors before runtime crashes
- Easy to add new systems (just register)
- TerrainMovementCostCalculator
ENGINE LAYER: Movement cost calculator based on terrain types.
Provides MECHANISM only - looks up terrain costs from registry. Does NOT decide what's passable (that's GAME layer policy).
Usage: // ENGINE: Just terrain costs, everything passable var calculator = new TerrainMovementCostCalculator(provinceSystem, terrainRegistry);
// GAME: Wrap with game-specific traversability rules var gameCalculator = new LandUnitCostCalculator(calculator); // blocks water var navalCalculator = new NavalUnitCostCalculator(calculator); // blocks land
GAME layer implements IMovementCostCalculator with:
- Unit type checks (land/naval/amphibious)
- Ownership penalties (enemy territory)
- Supply/attrition considerations
- TimeManager
Deterministic time manager for multiplayer-ready simulation Uses fixed-point math and tick-based progression to ensure identical behavior across all clients
CRITICAL ARCHITECTURE REQUIREMENTS:
- Fixed-point accumulator (NO float drift)
- Real Earth calendar (365 days, no leap years for determinism)
- Tick counter for command synchronization
- Exact fraction speed multipliers
- NO Time.time dependencies (non-deterministic)
- ICalendar abstraction for custom calendars
See: Assets/Docs/Engine/time-system-architecture.md
- UniformCostCalculator
Default cost calculator - all provinces cost 1. Use when no terrain or ownership modifiers needed.
Structs
- AdjacencyStats
Queryable adjacency statistics struct.
- BurstPathfindingJob
Burst-compiled A* pathfinding job. Uses binary min-heap for O(log n) priority queue operations.
- CountrySystemInitializedEvent
Country-related events for EventBus Extracted from CountrySystem.cs for better organization
- GameLoadedEvent
Event emitted when a game is successfully loaded from a save file UI systems should listen to this and refresh their displays
- GameSavedEvent
Event emitted when a game is successfully saved to a file
- GameTime
Represents a point in game time (deterministic calendar). Supports real Earth calendar (365 days, variable month lengths). All operations are deterministic for multiplayer compatibility.
- NativeAdjacencyData
Read-only native adjacency data for Burst jobs. Use this struct in IJob implementations for parallel graph algorithms.
- NativeProvinceData
Read-only native province data for Burst jobs. Use this struct in IJob implementations for parallel algorithms.
- PathContext
Context passed to cost calculator for unit-specific pathfinding.
- PathOptions
ENGINE: Options for pathfinding requests. Allows customization of cost calculation, forbidden zones, and preferences.
- PathResult
ENGINE: Result of a pathfinding request. Contains path and metadata about the search.
- ProvinceSystemInitializedEvent
Province-related events for EventBus Extracted from ProvinceSystem.cs for better organization
Interfaces
- ICalendar
ENGINE: Calendar abstraction for custom date systems.
PATTERN: Engine-Game Separation (Pattern 1)
- ENGINE provides: ICalendar interface + StandardCalendar default
- GAME provides: Custom calendars (Roman AUC, Fantasy 13-month, etc.)
Use Cases:
- Historical games: AD/BC eras, real month names
- Roman games: AUC (Ab Urbe Condita) dating
- Fantasy games: Custom month names, different year lengths
All implementations must be deterministic for multiplayer compatibility.
- IMovementCostCalculator
ENGINE: Interface for calculating movement costs between provinces.
Allows GAME layer to provide custom cost calculations based on:
- Terrain type (mountains cost more than plains)
- Province ownership (enemy territory costs more)
- Unit type (cavalry faster on plains, slower in forests)
- Weather, supply, fortifications, etc.
Default implementation: UniformCostCalculator (all costs = 1)
Enums
- PathContextFlags
Flags for special pathfinding behavior.
- PathStatus
Status codes for pathfinding results.
Delegates
- MovementValidator
ENGINE LAYER: Pathfinding system for multi-province unit movement
Uses A* algorithm to find shortest path between provinces. Burst-compiled for high performance with large maps.
Features:
- IMovementCostCalculator for terrain/ownership-based costs
- PathOptions for forbidden zones and avoid preferences
- LRU path caching for repeated requests
- Burst path when using uniform costs (fast)
- Managed path with custom costs (flexible)
Performance:
- Burst: O(E log V) with binary heap, ~0.1ms typical
- Pre-allocated collections, zero gameplay allocations
- LRU cache reduces redundant searches