Cookbook
January 18, 2026 · View on GitHub
Quick recipes for common tasks. Each recipe shows the minimal code needed. See feature guides for deeper explanations.
Province & Country Data
Get province owner
var state = gameState.Provinces.GetProvinceState(provinceId);
ushort ownerId = state.ownerID;
Get all provinces owned by a country
using var provinces = Query.Provinces(gameState)
.OwnedBy(countryId)
.Execute(Allocator.Temp);
foreach (ushort provinceId in provinces)
{
// Process province
}
Get province count for a country
int count = gameState.ProvinceQueries.GetCountryProvinceCount(countryId);
Get country tag (e.g., "FRA", "ENG")
string tag = gameState.CountryQueries.GetTag(countryId);
Get province name
string name = LocalizationManager.Get($"PROV{provinceId}");
Check if provinces are adjacent
bool adjacent = gameState.Adjacencies.IsAdjacent(provinceA, provinceB);
Get neighboring provinces
using var neighbors = gameState.Adjacencies.GetNeighbors(provinceId, Allocator.Temp);
foreach (ushort neighborId in neighbors)
{
// Process neighbor
}
Commands
Create and execute a command
var cmd = new ChangeOwnerCommand
{
ProvinceID = provinceId,
NewOwnerID = targetCountryId
};
bool success = gameState.TryExecuteCommand(cmd);
Create a custom command (using SimpleCommand)
[Command("my_command", "Does something")]
public class MyCommand : SimpleCommand
{
[Arg("target", "Target province")]
public ushort TargetId { get; set; }
[Arg("amount", "Amount to apply")]
public int Amount { get; set; }
public override bool Validate(GameState gameState)
{
return TargetId > 0 && Amount > 0;
}
public override void Execute(GameState gameState)
{
// Modify state here
}
}
Command with validation builder
public override bool Validate(GameState gameState)
{
return Validate.For(gameState)
.Province(ProvinceId)
.ProvinceOwnedBy(ProvinceId, IssuingCountryId)
.Result();
}
Events
Subscribe to an event
gameState.EventBus.Subscribe<ProvinceOwnerChangedEvent>(OnOwnerChanged);
void OnOwnerChanged(ProvinceOwnerChangedEvent evt)
{
Debug.Log($"Province {evt.ProvinceId} now owned by {evt.NewOwner}");
}
Emit an event
gameState.EventBus.Emit(new MyCustomEvent
{
Data = someValue
});
Define a custom event
public struct MyCustomEvent : IGameEvent
{
public int Data;
public float TimeStamp { get; set; }
}
Unsubscribe from an event
IDisposable subscription = gameState.EventBus.Subscribe<MyEvent>(handler);
// Later, to unsubscribe:
subscription.Dispose();
Economy & Resources
Add/remove gold from a country (GAME layer)
// This is GAME layer - implement in your EconomySystem
economySystem.AddGold(countryId, FixedPoint64.FromInt(100));
economySystem.TrySpendGold(countryId, 50); // Returns false if insufficient
Calculate with FixedPoint64 (deterministic)
// Always use FixedPoint64 for simulation math
FixedPoint64 baseIncome = FixedPoint64.FromInt(provinceCount);
FixedPoint64 modifier = FixedPoint64.FromFraction(3, 2); // 1.5x
FixedPoint64 totalIncome = baseIncome * modifier;
int asInt = totalIncome.ToInt();
Buildings (GAME Layer)
Building systems are GAME-specific. Here's the pattern:
// In your BuildingSystem
public bool CanBuild(ushort provinceId, byte buildingTypeId, ushort countryId)
{
var state = gameState.Provinces.GetProvinceState(provinceId);
return state.ownerID == countryId;
}
// Execute via command
var cmd = new BuildCommand
{
ProvinceId = provinceId,
BuildingTypeId = buildingTypeId,
IssuingCountryId = countryId
};
gameState.TryExecuteCommand(cmd);
Units (ENGINE Layer)
Create a unit
var cmd = new CreateUnitCommand
{
ProvinceId = provinceId,
CountryId = countryId,
UnitTypeId = unitTypeId,
InitialCount = 1000
};
gameState.TryExecuteCommand(cmd);
Move a unit
var cmd = new MoveUnitCommand
{
UnitId = unitId,
TargetProvinceId = targetProvinceId
};
gameState.TryExecuteCommand(cmd);
Get units in a province
using var units = Query.Units(unitSystem)
.InProvince(provinceId)
.Execute(Allocator.Temp);
Get all units owned by a country
using var units = Query.Units(unitSystem)
.OwnedBy(countryId)
.Execute(Allocator.Temp);
Time System
Subscribe to monthly tick
gameState.EventBus.Subscribe<MonthlyTickEvent>(OnMonthlyTick);
void OnMonthlyTick(MonthlyTickEvent evt)
{
// Called once per game month
}
Get current date
int year = gameState.Time.CurrentYear;
int month = gameState.Time.CurrentMonth;
int day = gameState.Time.CurrentDay;
Pause/resume time
gameState.Time.PauseTime();
gameState.Time.StartTime(); // Resume
gameState.Time.TogglePause();
Set game speed
gameState.Time.SetSpeed(3); // 3x speed
Map Modes
Create a custom map mode handler
public class MyMapMode : BaseMapModeHandler
{
public override MapMode Mode => MapMode.Economic;
public override string Name => "My Mode";
public override int ShaderModeID => 8;
public override UpdateFrequency GetUpdateFrequency() => UpdateFrequency.Monthly;
public override void OnActivate(Material mapMaterial, MapModeDataTextures dataTextures)
{
DisableAllMapModeKeywords(mapMaterial);
EnableMapModeKeyword(mapMaterial, "MAP_MODE_ECONOMIC");
SetShaderMode(mapMaterial, ShaderModeID);
}
public override void OnDeactivate(Material mapMaterial) { }
public override void UpdateTextures(MapModeDataTextures dataTextures,
ProvinceQueries provinceQueries, CountryQueries countryQueries,
ProvinceMapping provinceMapping, object gameProvinceSystem = null)
{
// Update textures based on your data
}
public override string GetProvinceTooltip(ushort provinceId,
ProvinceQueries provinceQueries, CountryQueries countryQueries)
{
return $"Province {provinceId}";
}
}
// Register during initialization
mapModeManager.RegisterHandler(MapMode.Economic, new MyMapMode());
Switch map mode
mapModeManager.SetMapMode(MapMode.Political);
UI Patterns
Block map clicks when over UI
private bool IsPointerOverUI()
{
return EventSystem.current != null &&
EventSystem.current.IsPointerOverGameObject();
}
void Update()
{
if (Input.GetMouseButtonDown(0) && !IsPointerOverUI())
{
HandleMapClick();
}
}
Update UI when game state changes
void Initialize()
{
gameState.EventBus.Subscribe<GoldChangedEvent>(OnGoldChanged);
}
void OnGoldChanged(GoldChangedEvent evt)
{
if (evt.CountryId == playerCountryId)
goldLabel.text = $"Gold: {evt.NewValue}";
}
Scroll to bottom after adding content (UI Toolkit)
contentLabel.text += newText;
scrollView.schedule.Execute(() =>
{
float maxScroll = scrollView.contentContainer.layout.height -
scrollView.contentViewport.layout.height;
scrollView.scrollOffset = new Vector2(0, Mathf.Max(0, maxScroll));
}).ExecuteLater(10);
Diplomacy
Declare war
var cmd = new DeclareWarCommand
{
AttackerID = myCountryId,
DefenderID = targetCountryId
};
gameState.TryExecuteCommand(cmd);
Check if countries are at war
bool atWar = gameState.Diplomacy.IsAtWar(countryA, countryB);
Get opinion between countries
FixedPoint64 opinion = gameState.Diplomacy.GetOpinion(fromCountry, toCountry, currentTick);
int opinionInt = opinion.ToInt();
Save/Load (GAME Layer)
Save/load is typically GAME-specific. Here's the pattern:
// In your SaveManager
public void SaveGame(string saveName)
{
var data = new SaveData
{
CurrentTick = gameState.Time.CurrentTick,
// ... serialize your game state
};
// Write to file
}
public void LoadGame(string saveName)
{
// Read from file
// Restore game state
}
Debugging
Log with subsystem (goes to Logs/ folder)
ArchonLogger.Log("Something happened", "game_systems");
ArchonLogger.LogWarning("Something suspicious", "game_systems");
ArchonLogger.LogError("Something broke", "game_systems");
Check console for errors
Read Logs/game_initialization.log, Logs/core_simulation.log, etc.
Verify province data at runtime
var state = gameState.Provinces.GetProvinceState(provinceId);
ArchonLogger.Log($"Province {provinceId}: owner={state.ownerID}, terrain={state.terrainType}", "debug");
Common Patterns
Iterate with proper disposal
// Always use 'using' with NativeCollections
using var results = Query.Provinces(gameState)
.OwnedBy(countryId)
.Execute(Allocator.Temp);
foreach (var id in results)
{
// Process
}
// Automatically disposed
Cache expensive calculations per frame
private int cachedFrame = -1;
private int cachedValue;
public int GetValue()
{
if (Time.frameCount != cachedFrame)
{
cachedValue = ExpensiveCalculation();
cachedFrame = Time.frameCount;
}
return cachedValue;
}
Initialize system after ENGINE ready
IEnumerator Start()
{
while (ArchonEngine.Instance == null || !ArchonEngine.Instance.IsInitialized)
yield return null;
var gameState = ArchonEngine.Instance.GameState;
// Now safe to use gameState
}
Deterministic math (multiplayer-safe)
// ❌ WRONG - Float is non-deterministic
float result = value * 1.5f;
// ✅ CORRECT - FixedPoint64 is deterministic
FixedPoint64 result = value * FixedPoint64.FromFraction(3, 2);
Validation with error message
public override bool Validate(GameState gameState)
{
bool isValid = Validate.For(gameState)
.Country(CountryId)
.Province(ProvinceId)
.ProvinceOwnedBy(ProvinceId, CountryId)
.Result(out string reason);
if (!isValid)
ArchonLogger.LogWarning($"Validation failed: {reason}", "game_commands");
return isValid;
}