using SpaceGame.Api.Industry.Planning;
using SpaceGame.Api.Stations.Simulation;
using static SpaceGame.Api.Shared.Runtime.ShipBehaviorKinds;
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;

namespace SpaceGame.Api.Factions.AI;

internal sealed class CommanderPlanningService
{
    private const float FactionCommanderReplanInterval = 8f;
    private const float FleetCommanderReplanInterval = 4f;
    private const float StationCommanderReplanInterval = 5f;
    private const float ShipCommanderReplanInterval = 3f;
    private const int MaxDecisionLogEntries = 40;
    private const int MaxOutcomeEntries = 32;
    private const int MaxAiOrdersPerShip = 2;
    private const string MilitaryShipCategory = "military";
    private const string MiningShipCategory = "mining";
    private const string TransportShipCategory = "transport";
    private const string ConstructionShipCategory = "construction";

    internal void UpdateCommanders(SimulationWorld world, IPlayerStateStore playerStateStore, float deltaSeconds, ICollection<SimulationEventRecord> events)
    {
        EnsureHierarchy(world);

        foreach (var commander in world.Commanders)
        {
            if (!commander.IsAlive)
            {
                continue;
            }

            if (commander.ReplanTimer > 0f)
            {
                commander.ReplanTimer = MathF.Max(0f, commander.ReplanTimer - deltaSeconds);
            }
        }

        foreach (var commander in world.Commanders.Where(candidate => candidate.IsAlive && candidate.Kind == CommanderKind.Faction).ToList())
        {
            if (PlayerFactionService.IsPlayerFaction(playerStateStore, commander.FactionId))
            {
                continue;
            }

            if (commander.ReplanTimer > 0f && !commander.NeedsReplan)
            {
                continue;
            }

            UpdateFactionCommander(world, commander, events);
        }

        foreach (var commander in world.Commanders.Where(candidate => candidate.IsAlive && candidate.Kind == CommanderKind.Fleet).ToList())
        {
            if (PlayerFactionService.IsPlayerFaction(playerStateStore, commander.FactionId))
            {
                continue;
            }

            if (commander.ReplanTimer > 0f && !commander.NeedsReplan)
            {
                continue;
            }

            UpdateFleetCommander(world, commander);
        }

        foreach (var commander in world.Commanders.Where(candidate => candidate.IsAlive && candidate.Kind == CommanderKind.Station).ToList())
        {
            if (PlayerFactionService.IsPlayerFaction(playerStateStore, commander.FactionId))
            {
                continue;
            }

            if (commander.ReplanTimer > 0f && !commander.NeedsReplan)
            {
                continue;
            }

            UpdateStationCommander(world, commander);
        }

        foreach (var commander in world.Commanders.Where(candidate => candidate.IsAlive && candidate.Kind == CommanderKind.Ship).ToList())
        {
            if (PlayerFactionService.IsPlayerFaction(playerStateStore, commander.FactionId))
            {
                continue;
            }

            if (commander.ReplanTimer > 0f && !commander.NeedsReplan)
            {
                continue;
            }

            UpdateShipCommander(world, commander, events);
        }
    }

    internal static CommanderRuntime? FindFactionCommander(SimulationWorld world, string factionId) =>
      world.Commanders.FirstOrDefault(commander =>
        commander.Kind == CommanderKind.Faction &&
        commander.FactionId == factionId);

    internal static FactionRuntime? FindFaction(SimulationWorld world, string factionId) =>
      world.Factions.FirstOrDefault(faction => string.Equals(faction.Id, factionId, StringComparison.Ordinal));

    internal static FactionStrategicStateRuntime? FindFactionStrategicState(SimulationWorld world, string factionId) =>
      FindFaction(world, factionId)?.StrategicState;

    internal static FactionEconomicAssessmentRuntime? FindFactionEconomicAssessment(SimulationWorld world, string factionId) =>
      FindFactionStrategicState(world, factionId)?.EconomicAssessment;

    internal static FactionThreatAssessmentRuntime? FindFactionThreatAssessment(SimulationWorld world, string factionId) =>
      FindFactionStrategicState(world, factionId)?.ThreatAssessment;

    private static void EnsureHierarchy(SimulationWorld world)
    {
        var commandersById = world.Commanders.ToDictionary(commander => commander.Id, StringComparer.Ordinal);
        var factionCommanders = world.Commanders
          .Where(commander => commander.Kind == CommanderKind.Faction)
          .ToDictionary(commander => commander.FactionId, StringComparer.Ordinal);

        foreach (var faction in world.Factions)
        {
            EnsureFactionStateDefaults(world, faction);

            if (!factionCommanders.TryGetValue(faction.Id, out var commander))
            {
                commander = new CommanderRuntime
                {
                    Id = $"commander-faction-{faction.Id}",
                    Kind = CommanderKind.Faction,
                    FactionId = faction.Id,
                    ControlledEntityId = faction.Id,
                    PolicySetId = faction.DefaultPolicySetId,
                    Doctrine = "strategic-control",
                    Skills = new CommanderSkillProfileRuntime { Leadership = 5, Coordination = 4, Strategy = 5 },
                };
                world.Commanders.Add(commander);
                commandersById[commander.Id] = commander;
                factionCommanders[faction.Id] = commander;
            }
        }

        foreach (var commander in world.Commanders)
        {
            commander.SubordinateCommanderIds.Clear();
        }

        var stationCommanders = new Dictionary<string, CommanderRuntime>(StringComparer.Ordinal);
        foreach (var station in world.Stations)
        {
            if (!factionCommanders.TryGetValue(station.FactionId, out var parentCommander))
            {
                continue;
            }

            var commander = world.Commanders.FirstOrDefault(candidate =>
              candidate.Kind == CommanderKind.Station &&
              string.Equals(candidate.ControlledEntityId, station.Id, StringComparison.Ordinal));
            if (commander is null)
            {
                commander = new CommanderRuntime
                {
                    Id = $"commander-station-{station.Id}",
                    Kind = CommanderKind.Station,
                    FactionId = station.FactionId,
                    ControlledEntityId = station.Id,
                    Doctrine = "station-control",
                    Skills = new CommanderSkillProfileRuntime
                    {
                        Leadership = 3,
                        Coordination = Math.Clamp(3 + (station.Modules.Count / 8), 3, 5),
                        Strategy = 3,
                    },
                };
                world.Commanders.Add(commander);
            }

            commander.ParentCommanderId = parentCommander.Id;
            commander.PolicySetId = parentCommander.PolicySetId;
            station.CommanderId = commander.Id;
            station.PolicySetId ??= parentCommander.PolicySetId;
            stationCommanders[station.Id] = commander;
        }

        foreach (var ship in world.Ships)
        {
            if (!factionCommanders.TryGetValue(ship.FactionId, out var factionCommander))
            {
                continue;
            }

            var commander = world.Commanders.FirstOrDefault(candidate =>
              candidate.Kind == CommanderKind.Ship &&
              string.Equals(candidate.ControlledEntityId, ship.Id, StringComparison.Ordinal));
            if (commander is null)
            {
                commander = new CommanderRuntime
                {
                    Id = $"commander-ship-{ship.Id}",
                    Kind = CommanderKind.Ship,
                    FactionId = ship.FactionId,
                    ControlledEntityId = ship.Id,
                    Doctrine = "ship-control",
                    Skills = new CommanderSkillProfileRuntime
                    {
                        Leadership = Math.Clamp((ship.Skills.Navigation + ship.Skills.Combat + 1) / 2, 2, 5),
                        Coordination = Math.Clamp((ship.Skills.Trade + ship.Skills.Mining + 1) / 2, 2, 5),
                        Strategy = Math.Clamp((ship.Skills.Combat + ship.Skills.Construction + 1) / 2, 2, 5),
                    },
                };
                world.Commanders.Add(commander);
            }

            var parentCommander = ResolveShipParentCommander(world, ship, factionCommander, stationCommanders);
            commander.ParentCommanderId = parentCommander.Id;
            commander.PolicySetId = parentCommander.PolicySetId;
            ship.CommanderId = commander.Id;
            ship.PolicySetId ??= parentCommander.PolicySetId;
        }

        foreach (var commander in world.Commanders)
        {
            if (commander.ParentCommanderId is not null && commandersById.TryGetValue(commander.ParentCommanderId, out var parent))
            {
                parent.SubordinateCommanderIds.Add(commander.Id);
            }
        }

        foreach (var faction in world.Factions)
        {
            faction.CommanderIds.Clear();
        }

        foreach (var commander in world.Commanders)
        {
            if (world.Factions.FirstOrDefault(faction => faction.Id == commander.FactionId) is { } faction)
            {
                faction.CommanderIds.Add(commander.Id);
            }
        }
    }

    private static void EnsureFactionStateDefaults(SimulationWorld world, FactionRuntime faction)
    {
        faction.Doctrine.StrategicPosture = string.IsNullOrWhiteSpace(faction.Doctrine.StrategicPosture) ? "balanced" : faction.Doctrine.StrategicPosture;
        faction.Doctrine.ExpansionPosture = string.IsNullOrWhiteSpace(faction.Doctrine.ExpansionPosture) ? "measured" : faction.Doctrine.ExpansionPosture;
        faction.Doctrine.MilitaryPosture = string.IsNullOrWhiteSpace(faction.Doctrine.MilitaryPosture) ? "defensive" : faction.Doctrine.MilitaryPosture;
        faction.Doctrine.EconomicPosture = string.IsNullOrWhiteSpace(faction.Doctrine.EconomicPosture) ? "self-sufficient" : faction.Doctrine.EconomicPosture;
        faction.Doctrine.DesiredControlledSystems = Math.Max(2, Math.Min(world.Systems.Count, faction.Doctrine.DesiredControlledSystems <= 0 ? 3 : faction.Doctrine.DesiredControlledSystems));
        faction.Doctrine.DesiredMilitaryPerFront = Math.Max(1, faction.Doctrine.DesiredMilitaryPerFront);
        faction.Doctrine.DesiredMinersPerSystem = Math.Max(1, faction.Doctrine.DesiredMinersPerSystem);
        faction.Doctrine.DesiredTransportsPerSystem = Math.Max(1, faction.Doctrine.DesiredTransportsPerSystem);
        faction.Doctrine.DesiredConstructors = Math.Max(1, faction.Doctrine.DesiredConstructors);
        faction.Doctrine.ReserveCreditsRatio = ClampRatio(faction.Doctrine.ReserveCreditsRatio, 0.2f);
        faction.Doctrine.ExpansionBudgetRatio = ClampRatio(faction.Doctrine.ExpansionBudgetRatio, 0.25f);
        faction.Doctrine.WarBudgetRatio = ClampRatio(faction.Doctrine.WarBudgetRatio, 0.35f);
        faction.Doctrine.ReserveMilitaryRatio = ClampRatio(faction.Doctrine.ReserveMilitaryRatio, 0.2f);
        faction.Doctrine.OffensiveReadinessThreshold = ClampRatio(faction.Doctrine.OffensiveReadinessThreshold, 0.62f);
        faction.Doctrine.SupplySecurityBias = ClampRatio(faction.Doctrine.SupplySecurityBias, 0.55f);
        faction.Doctrine.FailureAversion = ClampRatio(faction.Doctrine.FailureAversion, 0.45f);
        faction.Doctrine.ReinforcementLeadPerFront = Math.Max(1, faction.Doctrine.ReinforcementLeadPerFront);
    }

    private static float ClampRatio(float value, float fallback) =>
      value is >= 0f and <= 1f ? value : fallback;

    private static CommanderRuntime ResolveShipParentCommander(
      SimulationWorld world,
      ShipRuntime ship,
      CommanderRuntime factionCommander,
      IReadOnlyDictionary<string, CommanderRuntime> stationCommanders)
    {
        if (IsMilitaryShip(ship.Definition))
        {
            return factionCommander;
        }

        var stationCommander = world.Stations
          .Where(station => string.Equals(station.FactionId, ship.FactionId, StringComparison.Ordinal))
          .OrderByDescending(station => station.SystemId == ship.SystemId ? 1 : 0)
          .ThenBy(station => station.Position.DistanceTo(ship.Position))
          .Select(station => station.CommanderId is not null && stationCommanders.TryGetValue(station.Id, out var commander)
            ? commander
            : null)
          .FirstOrDefault(candidate => candidate is not null);

        return stationCommander ?? factionCommander;
    }

    private static void UpdateFactionCommander(SimulationWorld world, CommanderRuntime commander, ICollection<SimulationEventRecord> events)
    {
        var faction = FindFaction(world, commander.FactionId);
        if (faction is null)
        {
            commander.IsAlive = false;
            return;
        }

        commander.ReplanTimer = FactionCommanderReplanInterval;
        commander.NeedsReplan = false;
        commander.PlanningCycle += 1;

        EnsureFactionStateDefaults(world, faction);

        var nowUtc = DateTimeOffset.UtcNow;
        var previousTheaters = faction.StrategicState.Theaters.ToDictionary(theater => theater.Id, StringComparer.Ordinal);
        var previousCampaigns = faction.StrategicState.Campaigns.ToDictionary(campaign => campaign.Id, StringComparer.Ordinal);
        var previousObjectives = faction.StrategicState.Objectives.ToDictionary(objective => objective.Id, StringComparer.Ordinal);
        var previousPrograms = faction.StrategicState.ProductionPrograms.ToDictionary(program => program.Id, StringComparer.Ordinal);

        var economy = FactionEconomyAnalyzer.Build(world, faction.Id);
        var expansionProject = ResolveExpansionProject(world, faction);
        var threatAssessment = BuildThreatAssessment(world, faction, commander, nowUtc);
        var economicAssessment = BuildEconomicAssessment(world, faction, commander, economy, expansionProject, threatAssessment, nowUtc);
        UpdateDoctrine(world, faction, threatAssessment, economicAssessment, expansionProject);
        UpdateBudget(faction, threatAssessment, economicAssessment);
        UpdateMemory(world, faction, threatAssessment, economicAssessment, nowUtc);

        if (expansionProject is not null
            && economicAssessment.ConstructorShipCount > 0
            && faction.StrategicState.Budget.ExpansionCredits > 0f)
        {
            FactionIndustryPlanner.EnsureExpansionSite(world, faction.Id, expansionProject);
            expansionProject = FactionIndustryPlanner.GetActiveExpansionProject(world, faction.Id) ?? expansionProject;
            economicAssessment.PrimaryExpansionSiteId = expansionProject.SiteId;
            economicAssessment.PrimaryExpansionSystemId = expansionProject.SystemId;
        }

        var theaters = BuildTheaters(world, faction, threatAssessment, economicAssessment, expansionProject, nowUtc);
        var campaigns = BuildCampaigns(world, faction, theaters, threatAssessment, economicAssessment, expansionProject, previousCampaigns, nowUtc);
        var theatersById = theaters.ToDictionary(theater => theater.Id, StringComparer.Ordinal);
        foreach (var campaign in campaigns)
        {
            if (campaign.TheaterId is not null && theatersById.TryGetValue(campaign.TheaterId, out var theater))
            {
                theater.CampaignIds.Add(campaign.Id);
            }
        }

        var objectives = BuildObjectives(world, faction, theaters, campaigns, economicAssessment, threatAssessment, expansionProject, previousObjectives, nowUtc);
        var reservations = BuildReservations(world, faction, objectives, nowUtc);
        var programs = BuildProductionPrograms(faction, theaters, campaigns, economicAssessment, threatAssessment, expansionProject, previousPrograms);

        ReconcileCampaignLifecycle(world, faction, previousCampaigns, campaigns, economicAssessment, threatAssessment, nowUtc);
        ReconcileObjectiveLifecycle(faction, previousObjectives, objectives, nowUtc);
        ReconcileTheaterLifecycle(faction, previousTheaters, theaters, nowUtc);
        ReconcileProgramLifecycle(faction, previousPrograms, programs, nowUtc);

        faction.Memory.LastPlanCycle = commander.PlanningCycle;
        faction.Memory.UpdatedAtUtc = nowUtc;
        faction.StrategicState.PlanCycle = commander.PlanningCycle;
        faction.StrategicState.UpdatedAtUtc = nowUtc;
        faction.StrategicState.Status = ResolveStrategicStatus(theaters, campaigns, economicAssessment, threatAssessment);
        faction.StrategicState.EconomicAssessment = economicAssessment;
        faction.StrategicState.ThreatAssessment = threatAssessment;
        faction.StrategicState.Theaters.Clear();
        faction.StrategicState.Theaters.AddRange(theaters);
        faction.StrategicState.Campaigns.Clear();
        faction.StrategicState.Campaigns.AddRange(campaigns);
        faction.StrategicState.Objectives.Clear();
        faction.StrategicState.Objectives.AddRange(objectives);
        faction.StrategicState.Reservations.Clear();
        faction.StrategicState.Reservations.AddRange(reservations);
        faction.StrategicState.ProductionPrograms.Clear();
        faction.StrategicState.ProductionPrograms.AddRange(programs);

        ApplyDelegation(world, faction, commander, events, nowUtc);
    }

    private static void UpdateStationCommander(SimulationWorld world, CommanderRuntime commander)
    {
        commander.ReplanTimer = StationCommanderReplanInterval;
        commander.NeedsReplan = false;
        commander.PlanningCycle += 1;
        commander.ActiveObjectiveIds.Clear();

        var station = world.Stations.FirstOrDefault(candidate => candidate.Id == commander.ControlledEntityId);
        if (station is null)
        {
            commander.IsAlive = false;
            commander.Assignment = null;
            return;
        }

        var faction = FindFaction(world, commander.FactionId);
        if (faction is null)
        {
            commander.Assignment = null;
            return;
        }

        var objective = faction.StrategicState.Objectives
          .Where(candidate => string.Equals(candidate.CommanderId, commander.Id, StringComparison.Ordinal))
          .OrderByDescending(candidate => candidate.Priority)
          .ThenBy(candidate => candidate.Id, StringComparer.Ordinal)
          .FirstOrDefault();
        if (objective is not null)
        {
            commander.ActiveObjectiveIds.Add(objective.Id);
            commander.Assignment = ToAssignment(objective);
            return;
        }

        var activeSite = world.ConstructionSites
          .Where(site =>
            site.FactionId == station.FactionId &&
            site.State is ConstructionSiteStateKinds.Active or ConstructionSiteStateKinds.Planned &&
            (string.Equals(site.StationId, station.Id, StringComparison.Ordinal) || string.Equals(site.SystemId, station.SystemId, StringComparison.Ordinal)))
          .OrderByDescending(site => string.Equals(site.StationId, station.Id, StringComparison.Ordinal) ? 1 : 0)
          .ThenBy(site => site.Id, StringComparer.Ordinal)
          .FirstOrDefault();
        var strategicAssignment = BuildStationFocusAssignment(world, faction, station, activeSite);
        commander.Assignment = strategicAssignment;
    }

    private static void UpdateShipCommander(SimulationWorld world, CommanderRuntime commander, ICollection<SimulationEventRecord> events)
    {
        commander.ReplanTimer = ShipCommanderReplanInterval;
        commander.NeedsReplan = false;
        commander.PlanningCycle += 1;
        commander.ActiveObjectiveIds.Clear();

        var ship = world.Ships.FirstOrDefault(candidate => candidate.Id == commander.ControlledEntityId);
        if (ship is null)
        {
            commander.IsAlive = false;
            commander.Assignment = null;
            return;
        }

        var faction = FindFaction(world, commander.FactionId);
        if (faction is null)
        {
            commander.Assignment = null;
            return;
        }

        var assignedObjective = faction.StrategicState.Objectives
          .Where(candidate => string.Equals(candidate.CommanderId, commander.Id, StringComparison.Ordinal))
          .OrderByDescending(candidate => candidate.Priority)
          .ThenBy(candidate => candidate.Id, StringComparer.Ordinal)
          .FirstOrDefault();

        var nextAssignment = assignedObjective is null
          ? null
          : ToAssignment(assignedObjective);
        if (assignedObjective is not null)
        {
            commander.ActiveObjectiveIds.Add(assignedObjective.Id);
        }

        if (!AssignmentsEqual(commander.Assignment, nextAssignment))
        {
            commander.Assignment = nextAssignment;
            ship.NeedsReplan = true;
            events.Add(new SimulationEventRecord(
              "ship",
              ship.Id,
              nextAssignment is null ? "assignment-cleared" : "assignment-updated",
              nextAssignment is null
                ? $"{ship.Definition.Name} returned to default behavior."
                : $"{ship.Definition.Name} assigned to {nextAssignment.Kind}.",
              DateTimeOffset.UtcNow));
        }
    }

    private static IndustryExpansionProject? ResolveExpansionProject(SimulationWorld world, FactionRuntime faction) =>
      FactionIndustryPlanner.GetActiveExpansionProject(world, faction.Id)
      ?? FactionIndustryPlanner.AnalyzeExpansionNeed(world, faction.Id)
      ?? FactionIndustryPlanner.AnalyzeShipyardNeed(world, faction.Id);

    private static FactionThreatAssessmentRuntime BuildThreatAssessment(
      SimulationWorld world,
      FactionRuntime faction,
      CommanderRuntime commander,
      DateTimeOffset nowUtc)
    {
        var assessment = new FactionThreatAssessmentRuntime
        {
            PlanCycle = commander.PlanningCycle,
            UpdatedAtUtc = nowUtc,
            EnemyFactionCount = world.Factions.Count(candidate => candidate.Id != faction.Id && GeopoliticalSimulationService.HasHostileRelation(world, faction.Id, candidate.Id)),
            EnemyShipCount = world.Ships.Count(ship => ship.Health > 0f && ship.FactionId != faction.Id && GeopoliticalSimulationService.HasHostileRelation(world, faction.Id, ship.FactionId)),
            EnemyStationCount = world.Stations.Count(station => station.FactionId != faction.Id && GeopoliticalSimulationService.HasHostileRelation(world, faction.Id, station.FactionId)),
        };

        var controlledSystems = GeopoliticalSimulationService.GetControlledSystems(world, faction.Id)
          .ToHashSet(StringComparer.Ordinal);
        var factionStationSystems = world.Stations
          .Where(station => station.FactionId == faction.Id)
          .Select(station => station.SystemId)
          .ToHashSet(StringComparer.Ordinal);
        var borderTensions = world.Geopolitics?.Diplomacy.BorderTensions
          .Where(tension => string.Equals(tension.FactionAId, faction.Id, StringComparison.Ordinal)
            || string.Equals(tension.FactionBId, faction.Id, StringComparison.Ordinal))
          .ToList() ?? [];
        var activeWars = world.Geopolitics?.Diplomacy.Wars
          .Where(war => war.Status == "active"
            && (string.Equals(war.FactionAId, faction.Id, StringComparison.Ordinal)
              || string.Equals(war.FactionBId, faction.Id, StringComparison.Ordinal)))
          .ToList() ?? [];

        var threatSignals = world.Systems
          .Select(system =>
          {
              var controlState = GeopoliticalSimulationService.GetSystemControlState(world, system.Definition.Id);
              var strategicProfile = FindStrategicProfile(world, system.Definition.Id);
              var territoryPressure = FindTerritoryPressure(world, faction.Id, system.Definition.Id);
              var systemTensions = borderTensions
            .Where(tension => tension.SystemIds.Contains(system.Definition.Id, StringComparer.Ordinal))
            .ToList();
              var enemyShips = world.Ships
            .Where(ship => ship.Health > 0f
              && ship.FactionId != faction.Id
              && GeopoliticalSimulationService.HasHostileRelation(world, faction.Id, ship.FactionId)
              && ship.SystemId == system.Definition.Id)
            .ToList();
              var enemyStations = world.Stations
            .Where(station => station.FactionId != faction.Id
              && GeopoliticalSimulationService.HasHostileRelation(world, faction.Id, station.FactionId)
              && station.SystemId == system.Definition.Id)
            .ToList();
              if (enemyShips.Count == 0 && enemyStations.Count == 0 && systemTensions.Count == 0 && (territoryPressure?.PressureScore ?? 0f) < 0.08f)
              {
                  return null;
              }

              var primaryEnemyFactionId = enemyShips
            .GroupBy(ship => ship.FactionId, StringComparer.Ordinal)
            .Select(group => (FactionId: group.Key, Weight: group.Count() * 10))
            .Concat(enemyStations
              .GroupBy(station => station.FactionId, StringComparer.Ordinal)
              .Select(group => (FactionId: group.Key, Weight: group.Count() * 25)))
            .GroupBy(entry => entry.FactionId, StringComparer.Ordinal)
            .OrderByDescending(group => group.Sum(entry => entry.Weight))
            .ThenBy(group => group.Key, StringComparer.Ordinal)
            .Select(group => group.Key)
            .FirstOrDefault();

              var warBias = activeWars.Any(war => string.Equals(war.FactionAId, primaryEnemyFactionId, StringComparison.Ordinal) || string.Equals(war.FactionBId, primaryEnemyFactionId, StringComparison.Ordinal))
            ? 24f
            : 0f;
              var priorityBias = controlledSystems.Contains(system.Definition.Id) ? 40 : factionStationSystems.Contains(system.Definition.Id) ? 25 : 0;
              var diplomacyBias = systemTensions.Sum(tension => tension.TensionScore * 26f) + systemTensions.Sum(tension => tension.AccessFriction * 12f);
              var territoryBias = (territoryPressure?.PressureScore ?? 0f) * 35f
            + ((strategicProfile?.IsContested ?? false) ? 24f : 0f)
            + ((strategicProfile?.ZoneKind == "frontier" ? 1f : 0f) * 12f);
              var scopeKind = controlState?.IsContested == true || (factionStationSystems.Contains(system.Definition.Id) && !controlledSystems.Contains(system.Definition.Id))
            ? "contested-system"
            : controlledSystems.Contains(system.Definition.Id)
              ? "controlled-system"
              : "hostile-system";
              return new
              {
                  Signal = new FactionThreatSignalRuntime
                  {
                      ScopeId = system.Definition.Id,
                      ScopeKind = scopeKind,
                      EnemyShipCount = enemyShips.Count,
                      EnemyStationCount = enemyStations.Count,
                      EnemyFactionId = primaryEnemyFactionId,
                  },
                  Score = (enemyStations.Count * 30) + (enemyShips.Count * 10) + priorityBias + diplomacyBias + territoryBias + warBias,
              };
          })
          .Where(entry => entry is not null)
          .Select(entry => entry!)
          .OrderByDescending(entry => entry.Score)
          .ThenBy(entry => entry.Signal.ScopeId, StringComparer.Ordinal)
          .ToList();

        assessment.ThreatSignals.AddRange(threatSignals.Select(entry => entry.Signal));
        assessment.PrimaryThreatSystemId = threatSignals.FirstOrDefault()?.Signal.ScopeId;
        assessment.PrimaryThreatFactionId = threatSignals.FirstOrDefault()?.Signal.EnemyFactionId;
        return assessment;
    }

    private static FactionEconomicAssessmentRuntime BuildEconomicAssessment(
      SimulationWorld world,
      FactionRuntime faction,
      CommanderRuntime commander,
      FactionEconomySnapshot economy,
      IndustryExpansionProject? expansionProject,
      FactionThreatAssessmentRuntime threatAssessment,
      DateTimeOffset nowUtc)
    {
        var controlledSystems = StationSimulationService.GetFactionControlledSystemsCount(world, faction.Id);
        var frontCount = Math.Max(1,
          threatAssessment.ThreatSignals.Count(signal => signal.ScopeKind is "controlled-system" or "contested-system")
          + (expansionProject is null ? 0 : 1));
        var militaryShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && IsMilitaryShip(ship.Definition));
        var minerShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && IsMiningShip(ship.Definition));
        var transportShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && IsTransportShip(ship.Definition));
        var constructorShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && IsConstructionShip(ship.Definition));
        var hasShipyard = world.Stations.Any(station =>
          string.Equals(station.FactionId, faction.Id, StringComparison.Ordinal) &&
          station.InstalledModules.Contains("module_gen_build_l_01", StringComparer.Ordinal));
        var hasWarIndustrySupplyChain = HasOperationalWarIndustry(economy);
        var factionRegions = world.Geopolitics?.EconomyRegions.Regions
          .Where(region => string.Equals(region.FactionId, faction.Id, StringComparison.Ordinal))
          .ToList() ?? [];
        var regionSecurity = world.Geopolitics?.EconomyRegions.SecurityAssessments
          .Where(assessment => factionRegions.Any(region => region.Id == assessment.RegionId))
          .ToList() ?? [];
        var regionEconomics = world.Geopolitics?.EconomyRegions.EconomicAssessments
          .Where(assessment => factionRegions.Any(region => region.Id == assessment.RegionId))
          .ToList() ?? [];
        var regionalBottlenecks = world.Geopolitics?.EconomyRegions.Bottlenecks
          .Where(bottleneck => factionRegions.Any(region => region.Id == bottleneck.RegionId))
          .ToList() ?? [];
        var corridorRisk = world.Geopolitics?.EconomyRegions.Corridors
          .Where(corridor => string.Equals(corridor.FactionId, faction.Id, StringComparison.Ordinal))
          .DefaultIfEmpty()
          .Average(corridor => corridor?.RiskScore ?? 0f) ?? 0f;
        var regionalSupplyRisk = regionSecurity.Count == 0 ? 0f : regionSecurity.Average(assessment => assessment.SupplyRisk);
        var regionalSustainment = regionEconomics.Count == 0 ? 1f : regionEconomics.Average(assessment => assessment.SustainmentScore);

        var assessment = new FactionEconomicAssessmentRuntime
        {
            PlanCycle = commander.PlanningCycle,
            UpdatedAtUtc = nowUtc,
            MilitaryShipCount = militaryShipCount,
            MinerShipCount = minerShipCount,
            TransportShipCount = transportShipCount,
            ConstructorShipCount = constructorShipCount,
            ControlledSystemCount = controlledSystems,
            TargetMilitaryShipCount = Math.Max(frontCount * faction.Doctrine.DesiredMilitaryPerFront, controlledSystems + 2),
            TargetMinerShipCount = Math.Max(controlledSystems * faction.Doctrine.DesiredMinersPerSystem, 2),
            TargetTransportShipCount = Math.Max(controlledSystems * faction.Doctrine.DesiredTransportsPerSystem, 2),
            TargetConstructorShipCount = faction.Doctrine.DesiredConstructors,
            HasShipyard = hasShipyard,
            HasWarIndustrySupplyChain = hasWarIndustrySupplyChain,
            PrimaryExpansionSiteId = expansionProject?.SiteId,
            PrimaryExpansionSystemId = expansionProject?.SystemId,
            ReplacementPressure = MathF.Max(0f, (faction.ShipsLost - faction.Memory.LastObservedShipsLost) * 6f)
            + MathF.Max(0f, frontCount - Math.Max(1, militaryShipCount))
            + (regionalSupplyRisk * 4f),
        };

        assessment.CommoditySignals.AddRange(
          economy.Commodities
            .OrderBy(entry => entry.Key, StringComparer.Ordinal)
            .Select(entry => new FactionCommoditySignalRuntime
            {
                ItemId = entry.Key,
                AvailableStock = entry.Value.AvailableStock,
                OnHand = entry.Value.OnHand,
                ProductionRatePerSecond = entry.Value.ProductionRatePerSecond,
                CommittedProductionRatePerSecond = entry.Value.CommittedProductionRatePerSecond,
                UsageRatePerSecond = entry.Value.OperationalUsageRatePerSecond,
                NetRatePerSecond = entry.Value.NetRatePerSecond,
                ProjectedNetRatePerSecond = entry.Value.ProjectedNetRatePerSecond,
                LevelSeconds = entry.Value.LevelSeconds,
                Level = entry.Value.Level.ToString().ToLowerInvariant(),
                ProjectedProductionRatePerSecond = entry.Value.ProjectedProductionRatePerSecond,
                BuyBacklog = entry.Value.BuyBacklog,
                ReservedForConstruction = entry.Value.ReservedForConstruction,
            }));

        assessment.CriticalShortageCount = assessment.CommoditySignals.Count(signal => signal.Level is "critical" or "low")
          + regionalBottlenecks.Count(bottleneck => bottleneck.Severity >= 2.5f);
        assessment.IndustrialBottleneckItemId = assessment.CommoditySignals
          .OrderByDescending(ComputeCommodityPriority)
          .ThenBy(signal => signal.ItemId, StringComparer.Ordinal)
          .Select(signal => signal.ItemId)
          .FirstOrDefault()
          ?? regionalBottlenecks
            .OrderByDescending(bottleneck => bottleneck.Severity)
            .ThenBy(bottleneck => bottleneck.ItemId, StringComparer.Ordinal)
            .Select(bottleneck => bottleneck.ItemId)
            .FirstOrDefault();

        if (assessment.PrimaryExpansionSystemId is null)
        {
            assessment.PrimaryExpansionSystemId = factionRegions
              .Join(regionEconomics, region => region.Id, economicState => economicState.RegionId, (region, economicState) => new { region, economicState })
              .OrderByDescending(entry => entry.economicState.ConstructionPressure)
              .ThenByDescending(entry => entry.economicState.CorridorDependency)
              .ThenBy(entry => entry.region.Id, StringComparer.Ordinal)
              .Select(entry => entry.region.CoreSystemId)
              .FirstOrDefault();
        }

        var transportCoverage = assessment.TargetTransportShipCount <= 0
          ? 1f
          : Math.Clamp(assessment.TransportShipCount / (float)assessment.TargetTransportShipCount, 0f, 1.35f);
        var minerCoverage = assessment.TargetMinerShipCount <= 0
          ? 1f
          : Math.Clamp(assessment.MinerShipCount / (float)assessment.TargetMinerShipCount, 0f, 1.35f);
        var constructorCoverage = assessment.TargetConstructorShipCount <= 0
          ? 1f
          : Math.Clamp(assessment.ConstructorShipCount / (float)assessment.TargetConstructorShipCount, 0f, 1.35f);
        var shortagePenalty = MathF.Min(0.55f, assessment.CriticalShortageCount * 0.08f);
        var replacementPenalty = MathF.Min(0.45f, assessment.ReplacementPressure / MathF.Max(12f, assessment.TargetMilitaryShipCount * 8f));

        assessment.LogisticsSecurityScore = Math.Clamp(
          (transportCoverage * 0.45f)
          + (minerCoverage * 0.2f)
          + (constructorCoverage * 0.1f)
          + (hasWarIndustrySupplyChain ? 0.2f : 0.05f)
          - shortagePenalty
          - (regionalSupplyRisk * 0.3f)
          - (corridorRisk * 0.18f),
          0f,
          1f);
        assessment.SustainmentScore = Math.Clamp(
          (assessment.LogisticsSecurityScore * 0.55f)
          + ((hasShipyard ? 0.15f : 0f) + (hasWarIndustrySupplyChain ? 0.15f : 0f))
          + ((assessment.MilitaryShipCount >= assessment.TargetMilitaryShipCount ? 0.2f : 0.08f))
          + (regionalSustainment * 0.18f)
          - replacementPenalty,
          0f,
          1f);

        return assessment;
    }

    private static bool HasOperationalWarIndustry(FactionEconomySnapshot economy)
    {
        var energy = economy.GetCommodity("energycells");
        var refined = economy.GetCommodity("refinedmetals");
        var hullparts = economy.GetCommodity("hullparts");
        var claytronics = economy.GetCommodity("claytronics");
        return CommodityOperationalSignal.IsOperational(energy, 180f)
          && CommodityOperationalSignal.IsOperational(refined, 180f)
          && CommodityOperationalSignal.IsOperational(hullparts, 180f)
          && CommodityOperationalSignal.IsOperational(claytronics, 180f);
    }

    private static void UpdateDoctrine(
      SimulationWorld world,
      FactionRuntime faction,
      FactionThreatAssessmentRuntime threatAssessment,
      FactionEconomicAssessmentRuntime economicAssessment,
      IndustryExpansionProject? expansionProject)
    {
        var controlledThreats = threatAssessment.ThreatSignals.Count(signal => signal.ScopeKind == "controlled-system");
        var contestedThreats = threatAssessment.ThreatSignals.Count(signal => signal.ScopeKind == "contested-system");
        var expansionPressure = StationSimulationService.GetFactionExpansionPressure(world, faction.Id);
        var shortagePressure = economicAssessment.CriticalShortageCount;
        var activeWars = world.Geopolitics?.Diplomacy.Wars.Count(war =>
          war.Status == "active"
          && (string.Equals(war.FactionAId, faction.Id, StringComparison.Ordinal) || string.Equals(war.FactionBId, faction.Id, StringComparison.Ordinal))) ?? 0;
        var borderTension = world.Geopolitics?.Diplomacy.BorderTensions
          .Where(tension => string.Equals(tension.FactionAId, faction.Id, StringComparison.Ordinal) || string.Equals(tension.FactionBId, faction.Id, StringComparison.Ordinal))
          .DefaultIfEmpty()
          .Average(tension => tension?.TensionScore ?? 0f) ?? 0f;

        faction.Doctrine.StrategicPosture = activeWars > 0 || controlledThreats > 1
          ? "fortify-and-recover"
          : controlledThreats > 0 || borderTension > 0.45f
            ? expansionProject is null ? "contested" : "defend-and-delay"
            : expansionProject is not null && economicAssessment.SustainmentScore >= faction.Doctrine.OffensiveReadinessThreshold
              ? "growth-through-pressure"
              : shortagePressure > 0 ? "economic-recovery" : "stable-growth";
        faction.Doctrine.MilitaryPosture = activeWars + controlledThreats + contestedThreats switch
        {
            >= 3 => "mobilized",
            > 0 when economicAssessment.SustainmentScore >= faction.Doctrine.OffensiveReadinessThreshold && economicAssessment.MilitaryShipCount >= economicAssessment.TargetMilitaryShipCount => "counteroffensive",
            > 0 => "defensive",
            _ => economicAssessment.MilitaryShipCount > economicAssessment.TargetMilitaryShipCount && economicAssessment.SustainmentScore >= faction.Doctrine.OffensiveReadinessThreshold ? "expeditionary" : "defensive",
        };
        faction.Doctrine.ExpansionPosture = expansionProject is not null
          ? controlledThreats > 0 || economicAssessment.SustainmentScore < 0.58f ? "cautious" : "active"
          : expansionPressure > 0.2f && economicAssessment.SustainmentScore >= 0.55f ? "measured" : "consolidating";
        faction.Doctrine.EconomicPosture = shortagePressure >= 3
          ? "stabilizing"
          : economicAssessment.HasWarIndustrySupplyChain && economicAssessment.SustainmentScore >= 0.7f
            ? "surplus"
            : "self-sufficient";
    }

    private static void UpdateBudget(
      FactionRuntime faction,
      FactionThreatAssessmentRuntime threatAssessment,
      FactionEconomicAssessmentRuntime economicAssessment)
    {
        var reserveCredits = faction.Credits * faction.Doctrine.ReserveCreditsRatio;
        var discretionary = MathF.Max(0f, faction.Credits - reserveCredits);
        var warRatio = threatAssessment.ThreatSignals.Count > 0
          ? MathF.Max(faction.Doctrine.WarBudgetRatio, 0.4f)
          : faction.Doctrine.WarBudgetRatio * 0.5f;
        var expansionRatio = economicAssessment.PrimaryExpansionSystemId is not null
          ? MathF.Max(faction.Doctrine.ExpansionBudgetRatio, 0.25f)
          : faction.Doctrine.ExpansionBudgetRatio * 0.5f;

        faction.StrategicState.Budget = new FactionBudgetRuntime
        {
            ReservedCredits = reserveCredits,
            WarCredits = discretionary * Math.Clamp(warRatio, 0f, 1f),
            ExpansionCredits = discretionary * Math.Clamp(expansionRatio, 0f, 1f),
            ReservedMilitaryAssets = Math.Max(1, (int)MathF.Ceiling(economicAssessment.TargetMilitaryShipCount * faction.Doctrine.ReserveMilitaryRatio)),
            ReservedLogisticsAssets = Math.Max(economicAssessment.TargetTransportShipCount, economicAssessment.TransportShipCount),
            ReservedConstructionAssets = Math.Max(economicAssessment.TargetConstructorShipCount, economicAssessment.ConstructorShipCount),
        };
    }

    private static void UpdateMemory(
      SimulationWorld world,
      FactionRuntime faction,
      FactionThreatAssessmentRuntime threatAssessment,
      FactionEconomicAssessmentRuntime economicAssessment,
      DateTimeOffset nowUtc)
    {
        foreach (var station in world.Stations.Where(station => station.FactionId == faction.Id))
        {
            faction.Memory.KnownSystemIds.Add(station.SystemId);
        }

        foreach (var ship in world.Ships.Where(ship => ship.FactionId == faction.Id))
        {
            faction.Memory.KnownSystemIds.Add(ship.SystemId);
        }

        foreach (var signal in threatAssessment.ThreatSignals)
        {
            faction.Memory.KnownSystemIds.Add(signal.ScopeId);
            if (!string.IsNullOrWhiteSpace(signal.EnemyFactionId))
            {
                faction.Memory.KnownEnemyFactionIds.Add(signal.EnemyFactionId);
            }

            var systemMemory = faction.Memory.SystemMemories.FirstOrDefault(candidate => candidate.SystemId == signal.ScopeId);
            if (systemMemory is null)
            {
                systemMemory = new FactionSystemMemoryRuntime { SystemId = signal.ScopeId };
                faction.Memory.SystemMemories.Add(systemMemory);
            }

            systemMemory.LastSeenAtUtc = nowUtc;
            systemMemory.LastEnemyShipCount = signal.EnemyShipCount;
            systemMemory.LastEnemyStationCount = signal.EnemyStationCount;
            systemMemory.ControlledByFaction = signal.ScopeKind == "controlled-system";
            systemMemory.LastRole = signal.ScopeKind;
            var strategicProfile = FindStrategicProfile(world, signal.ScopeId);
            var territoryPressure = FindTerritoryPressure(world, faction.Id, signal.ScopeId);
            systemMemory.FrontierPressure = ((signal.EnemyStationCount * 0.9f) + (signal.EnemyShipCount * 0.35f))
              + ((territoryPressure?.PressureScore ?? 0f) * 1.4f)
              + ((strategicProfile?.ZoneKind == "frontier" ? 1f : 0f) * 0.35f);
            systemMemory.RouteRisk = MathF.Max(
              GeopoliticalSimulationService.GetSystemRouteRisk(world, signal.ScopeId, faction.Id),
              signal.ScopeKind is "controlled-system" or "contested-system"
                ? MathF.Min(1f, (signal.EnemyShipCount * 0.08f) + (signal.EnemyStationCount * 0.16f))
                : MathF.Min(1f, (signal.EnemyShipCount * 0.05f) + (signal.EnemyStationCount * 0.1f)));
            if (signal.ScopeKind is "controlled-system" or "contested-system")
            {
                systemMemory.LastContestedAtUtc = nowUtc;
            }
        }

        foreach (var systemId in faction.Memory.KnownSystemIds.OrderBy(id => id, StringComparer.Ordinal))
        {
            var systemMemory = faction.Memory.SystemMemories.FirstOrDefault(candidate => candidate.SystemId == systemId);
            if (systemMemory is not null)
            {
                continue;
            }

            faction.Memory.SystemMemories.Add(new FactionSystemMemoryRuntime
            {
                SystemId = systemId,
                LastSeenAtUtc = nowUtc,
                ControlledByFaction = FactionControlsSystem(world, faction.Id, systemId),
                LastRole = FactionControlsSystem(world, faction.Id, systemId) ? "controlled-system" : "observed-system",
                RouteRisk = GeopoliticalSimulationService.GetSystemRouteRisk(world, systemId, faction.Id),
            });
        }

        foreach (var zone in world.Geopolitics?.Territory.Zones.Where(zone => string.Equals(zone.FactionId, faction.Id, StringComparison.Ordinal)) ?? [])
        {
            faction.Memory.KnownSystemIds.Add(zone.SystemId);
            var systemMemory = faction.Memory.SystemMemories.FirstOrDefault(candidate => candidate.SystemId == zone.SystemId);
            if (systemMemory is null)
            {
                continue;
            }

            systemMemory.LastRole = zone.Kind switch
            {
                "contested" => "contested-system",
                "frontier" => "controlled-system",
                "corridor" => "controlled-system",
                _ => systemMemory.LastRole,
            };
            systemMemory.RouteRisk = MathF.Max(systemMemory.RouteRisk, GeopoliticalSimulationService.GetSystemRouteRisk(world, zone.SystemId, faction.Id));
        }

        foreach (var systemMemory in faction.Memory.SystemMemories)
        {
            if (threatAssessment.ThreatSignals.All(signal => signal.ScopeId != systemMemory.SystemId))
            {
                systemMemory.FrontierPressure *= 0.92f;
                systemMemory.RouteRisk *= 0.9f;
            }
        }

        foreach (var signal in economicAssessment.CommoditySignals)
        {
            var commodityMemory = faction.Memory.CommodityMemories.FirstOrDefault(candidate => candidate.ItemId == signal.ItemId);
            if (commodityMemory is null)
            {
                commodityMemory = new FactionCommodityMemoryRuntime { ItemId = signal.ItemId };
                faction.Memory.CommodityMemories.Add(commodityMemory);
            }

            commodityMemory.LastObservedBacklog = signal.BuyBacklog;
            commodityMemory.UpdatedAtUtc = nowUtc;
            commodityMemory.HistoricalShortageScore = (commodityMemory.HistoricalShortageScore * 0.85f)
              + (signal.Level is "critical" ? 1.2f : signal.Level is "low" ? 0.65f : 0f)
              + MathF.Max(0f, -signal.ProjectedNetRatePerSecond * 0.1f);
            commodityMemory.HistoricalSurplusScore = (commodityMemory.HistoricalSurplusScore * 0.85f)
              + (signal.Level == "surplus" ? 0.4f : 0f);
            if (signal.Level is "critical" or "low")
            {
                commodityMemory.LastCriticalAtUtc = nowUtc;
            }

            var impactedSystems = world.Stations
              .Where(station => station.FactionId == faction.Id && station.MarketOrderIds.Any(orderId =>
                world.MarketOrders.Any(order =>
                  order.Id == orderId &&
                  order.Kind == MarketOrderKinds.Buy &&
                  order.ItemId == signal.ItemId &&
                  order.RemainingAmount > 0.01f)))
              .Select(station => station.SystemId)
              .Distinct(StringComparer.Ordinal)
              .ToList();
            foreach (var systemId in impactedSystems)
            {
                var systemMemory = faction.Memory.SystemMemories.FirstOrDefault(candidate => candidate.SystemId == systemId);
                if (systemMemory is null)
                {
                    continue;
                }

                systemMemory.HistoricalShortagePressure = (systemMemory.HistoricalShortagePressure * 0.82f)
                  + (signal.Level is "critical" ? 0.7f : signal.Level is "low" ? 0.35f : 0f);
                if (signal.Level is "critical" or "low")
                {
                    systemMemory.LastShortageAtUtc = nowUtc;
                }
            }
        }

        faction.Memory.SystemMemories.Sort((left, right) => string.Compare(left.SystemId, right.SystemId, StringComparison.Ordinal));
        faction.Memory.CommodityMemories.Sort((left, right) => string.Compare(left.ItemId, right.ItemId, StringComparison.Ordinal));

        if (faction.ShipsBuilt != faction.Memory.LastObservedShipsBuilt)
        {
            AppendOutcome(faction, new FactionOutcomeRecordRuntime
            {
                Id = $"outcome-built-{faction.Memory.LastPlanCycle}-{faction.ShipsBuilt}",
                Kind = "ships-built",
                Summary = $"{faction.Label} has built {faction.ShipsBuilt} ships.",
                OccurredAtUtc = nowUtc,
            });
            faction.Memory.LastObservedShipsBuilt = faction.ShipsBuilt;
        }

        if (faction.ShipsLost != faction.Memory.LastObservedShipsLost)
        {
            AppendOutcome(faction, new FactionOutcomeRecordRuntime
            {
                Id = $"outcome-lost-{faction.Memory.LastPlanCycle}-{faction.ShipsLost}",
                Kind = "ships-lost",
                Summary = $"{faction.Label} has lost {faction.ShipsLost} ships.",
                OccurredAtUtc = nowUtc,
            });
            faction.Memory.LastObservedShipsLost = faction.ShipsLost;
        }

        faction.Memory.LastObservedCredits = faction.Credits;
    }

    private static List<FactionTheaterRuntime> BuildTheaters(
      SimulationWorld world,
      FactionRuntime faction,
      FactionThreatAssessmentRuntime threatAssessment,
      FactionEconomicAssessmentRuntime economicAssessment,
      IndustryExpansionProject? expansionProject,
      DateTimeOffset nowUtc)
    {
        var theaters = new List<FactionTheaterRuntime>();
        var systemMemories = faction.Memory.SystemMemories.ToDictionary(memory => memory.SystemId, StringComparer.Ordinal);

        foreach (var frontLine in (world.Geopolitics?.Territory.FrontLines
          .Where(front => front.FactionIds.Contains(faction.Id, StringComparer.Ordinal))
          .OrderBy(front => front.Id, StringComparer.Ordinal)
          ?? Enumerable.Empty<FrontLineRuntime>()))
        {
            var ownedSystemId = frontLine.SystemIds
              .FirstOrDefault(systemId => GeopoliticalSimulationService.FactionControlsSystem(world, faction.Id, systemId))
              ?? frontLine.SystemIds.FirstOrDefault(systemId => world.Stations.Any(station => station.FactionId == faction.Id && station.SystemId == systemId))
              ?? frontLine.SystemIds.FirstOrDefault();
            if (ownedSystemId is null)
            {
                continue;
            }
            if (theaters.Any(existing => string.Equals(existing.SystemId, ownedSystemId, StringComparison.Ordinal) && existing.Kind == "defense-front"))
            {
                continue;
            }

            var pressure = FindTerritoryPressure(world, faction.Id, ownedSystemId);
            var security = FindRegionalSecurityAssessment(world, faction.Id, ownedSystemId);
            theaters.Add(new FactionTheaterRuntime
            {
                Id = $"theater-defense-{ownedSystemId}",
                Kind = "defense-front",
                SystemId = ownedSystemId,
                Status = "active",
                Priority = 96f + ((pressure?.PressureScore ?? 0f) * 24f) + ((security?.BorderPressure ?? 0f) * 20f),
                SupplyRisk = MathF.Max(pressure?.CorridorRisk ?? 0f, security?.SupplyRisk ?? 0f),
                FriendlyAssetValue = EstimateFriendlyAssetValue(world, faction.Id, ownedSystemId),
                TargetFactionId = frontLine.FactionIds.FirstOrDefault(id => !string.Equals(id, faction.Id, StringComparison.Ordinal)),
                AnchorEntityId = frontLine.Id,
                AnchorPosition = ResolveSystemAnchor(world, ownedSystemId),
                UpdatedAtUtc = nowUtc,
            });
        }

        foreach (var signal in threatAssessment.ThreatSignals
          .Where(candidate => candidate.ScopeKind is "controlled-system" or "contested-system")
          .OrderByDescending(candidate => (candidate.EnemyStationCount * 30) + (candidate.EnemyShipCount * 10))
          .ThenBy(candidate => candidate.ScopeId, StringComparer.Ordinal))
        {
            if (theaters.Any(existing => string.Equals(existing.SystemId, signal.ScopeId, StringComparison.Ordinal) && existing.Kind == "defense-front"))
            {
                continue;
            }

            var memory = systemMemories.GetValueOrDefault(signal.ScopeId);
            theaters.Add(new FactionTheaterRuntime
            {
                Id = $"theater-defense-{signal.ScopeId}",
                Kind = "defense-front",
                SystemId = signal.ScopeId,
                Status = "active",
                Priority = 90f + (signal.EnemyStationCount * 12f) + (signal.EnemyShipCount * 4f) + ((memory?.DefensiveFailures ?? 0) * 6f),
                SupplyRisk = memory?.RouteRisk ?? 0f,
                FriendlyAssetValue = EstimateFriendlyAssetValue(world, faction.Id, signal.ScopeId),
                TargetFactionId = signal.EnemyFactionId,
                AnchorPosition = ResolveSystemAnchor(world, signal.ScopeId),
                UpdatedAtUtc = nowUtc,
            });
        }

        foreach (var memory in faction.Memory.SystemMemories
          .Where(candidate =>
            candidate.ControlledByFaction &&
            (candidate.FrontierPressure > 0.35f || candidate.RouteRisk > 0.3f || candidate.HistoricalShortagePressure > 0.45f) &&
            theaters.All(existing => existing.SystemId != candidate.SystemId || existing.Kind != "defense-front"))
          .OrderByDescending(candidate => (candidate.FrontierPressure * 50f) + (candidate.RouteRisk * 30f) + (candidate.HistoricalShortagePressure * 25f))
          .ThenBy(candidate => candidate.SystemId, StringComparer.Ordinal)
          .Take(2))
        {
            theaters.Add(new FactionTheaterRuntime
            {
                Id = $"theater-frontier-{memory.SystemId}",
                Kind = "defense-front",
                SystemId = memory.SystemId,
                Status = "active",
                Priority = 58f + (memory.FrontierPressure * 22f) + (memory.RouteRisk * 14f) + (memory.HistoricalShortagePressure * 10f),
                SupplyRisk = memory.RouteRisk,
                FriendlyAssetValue = EstimateFriendlyAssetValue(world, faction.Id, memory.SystemId),
                AnchorPosition = ResolveSystemAnchor(world, memory.SystemId),
                UpdatedAtUtc = nowUtc,
            });
        }

        if (CanRunOffensivePosture(faction, economicAssessment, threatAssessment))
        {
            foreach (var target in SelectOffensiveTargets(world, faction, threatAssessment, economicAssessment)
              .Take(2))
            {
                theaters.Add(new FactionTheaterRuntime
                {
                    Id = $"theater-offense-{target.SystemId}",
                    Kind = "offense-front",
                    SystemId = target.SystemId,
                    Status = "active",
                    Priority = target.Priority,
                    SupplyRisk = target.SupplyRisk,
                    FriendlyAssetValue = target.Value,
                    TargetFactionId = target.TargetFactionId,
                    AnchorEntityId = target.AnchorEntityId,
                    AnchorPosition = target.AnchorPosition,
                    UpdatedAtUtc = nowUtc,
                });
            }
        }

        if (expansionProject is not null && CanSupportExpansion(faction, economicAssessment, threatAssessment))
        {
            theaters.Add(new FactionTheaterRuntime
            {
                Id = $"theater-expansion-{expansionProject.SystemId}-{expansionProject.CelestialId}",
                Kind = "expansion-front",
                SystemId = expansionProject.SystemId,
                Status = "active",
                Priority = 65f + (economicAssessment.HasShipyard ? 0f : 15f),
                SupplyRisk = ComputeSystemRisk(world, faction, expansionProject.SystemId),
                FriendlyAssetValue = EstimateFriendlyAssetValue(world, faction.Id, expansionProject.SystemId),
                AnchorEntityId = expansionProject.SiteId ?? expansionProject.CelestialId,
                AnchorPosition = ResolveExpansionAnchor(world, expansionProject),
                UpdatedAtUtc = nowUtc,
            });
        }

        foreach (var signal in economicAssessment.CommoditySignals
          .Where(candidate =>
            candidate.Level is "critical" or "low"
            || candidate.ProjectedNetRatePerSecond < -0.01f
            || faction.Memory.CommodityMemories.FirstOrDefault(memory => memory.ItemId == candidate.ItemId) is { HistoricalShortageScore: > 0.8f })
          .OrderByDescending(candidate => ComputeCommodityPriority(candidate))
          .ThenBy(candidate => candidate.ItemId, StringComparer.Ordinal)
          .Take(3))
        {
            var systemId = FindPrimaryCommoditySystem(world, faction.Id, signal.ItemId) ?? economicAssessment.PrimaryExpansionSystemId ?? world.Systems.First().Definition.Id;
            theaters.Add(new FactionTheaterRuntime
            {
                Id = $"theater-economy-{signal.ItemId}",
                Kind = "economic-front",
                SystemId = systemId,
                Status = "active",
                Priority = 45f + ComputeCommodityPriority(signal),
                SupplyRisk = ComputeSystemRisk(world, faction, systemId),
                FriendlyAssetValue = EstimateFriendlyAssetValue(world, faction.Id, systemId),
                AnchorEntityId = ResolveCommodityAnchorStation(world, faction.Id, signal.ItemId)?.Id,
                UpdatedAtUtc = nowUtc,
            });
        }

        theaters.Sort((left, right) =>
        {
            var priority = right.Priority.CompareTo(left.Priority);
            return priority != 0 ? priority : string.Compare(left.Id, right.Id, StringComparison.Ordinal);
        });

        return theaters;
    }

    private static List<FactionCampaignRuntime> BuildCampaigns(
      SimulationWorld world,
      FactionRuntime faction,
      IReadOnlyList<FactionTheaterRuntime> theaters,
      FactionThreatAssessmentRuntime threatAssessment,
      FactionEconomicAssessmentRuntime economicAssessment,
      IndustryExpansionProject? expansionProject,
      IReadOnlyDictionary<string, FactionCampaignRuntime> previousCampaigns,
      DateTimeOffset nowUtc)
    {
        var campaigns = new List<FactionCampaignRuntime>();

        foreach (var theater in theaters)
        {
            var id = theater.Kind switch
            {
                "defense-front" => $"campaign-defense-{theater.SystemId}",
                "offense-front" => $"campaign-offense-{theater.SystemId}",
                "expansion-front" => $"campaign-expansion-{theater.SystemId}",
                "economic-front" => $"campaign-economy-{ResolveCommodityFromTheaterId(theater.Id) ?? theater.Id}",
                _ => $"campaign-{theater.Id}",
            };
            previousCampaigns.TryGetValue(id, out var previous);
            var systemMemory = theater.SystemId is null ? null : faction.Memory.SystemMemories.FirstOrDefault(memory => memory.SystemId == theater.SystemId);
            var supplyAdequacy = ComputeCampaignSupplyAdequacy(faction, theater, economicAssessment);
            var continuationScore = ComputeCampaignContinuationScore(faction, theater, economicAssessment, systemMemory);
            var pauseReason = ResolveCampaignPauseReason(faction, theater, economicAssessment, threatAssessment, systemMemory);
            var effectiveContinuationScore = previous is not null && previous.ContinuationScore > 0f
              ? MathF.Max(previous.ContinuationScore * 0.7f, continuationScore)
              : continuationScore;
            var campaign = new FactionCampaignRuntime
            {
                Id = id,
                Kind = theater.Kind switch
                {
                    "defense-front" => "defense",
                    "offense-front" => "offense",
                    "expansion-front" => "expansion",
                    "economic-front" => "economic-stabilization",
                    _ => theater.Kind,
                },
                Status = pauseReason is null ? "active" : "paused",
                Priority = theater.Priority,
                TheaterId = theater.Id,
                TargetFactionId = theater.TargetFactionId,
                TargetSystemId = theater.SystemId,
                TargetEntityId = theater.AnchorEntityId,
                CommodityId = theater.Kind == "economic-front" ? ResolveCommodityFromTheaterId(theater.Id) : expansionProject?.CommodityId,
                SupportStationId = expansionProject?.SupportStationId,
                CurrentStepIndex = previous?.CurrentStepIndex ?? 0,
                Summary = BuildCampaignSummary(theater, expansionProject),
                UpdatedAtUtc = nowUtc,
                PauseReason = pauseReason,
                ContinuationScore = effectiveContinuationScore,
                SupplyAdequacy = supplyAdequacy,
                ReplacementPressure = economicAssessment.ReplacementPressure,
                FailureCount = previous?.FailureCount ?? GetCampaignFailureCount(systemMemory, theater.Kind),
                SuccessCount = previous?.SuccessCount ?? GetCampaignSuccessCount(systemMemory, theater.Kind),
                RequiresReinforcement = RequiresReinforcement(theater, economicAssessment, threatAssessment),
            };

            campaign.Steps.AddRange(BuildCampaignSteps(campaign, expansionProject));
            if (previous is not null)
            {
                campaign.CreatedAtUtc = previous.CreatedAtUtc;
                campaign.FleetCommanderId = previous.FleetCommanderId;
            }

            campaigns.Add(campaign);
        }

        if (economicAssessment.MilitaryShipCount < economicAssessment.TargetMilitaryShipCount
            || economicAssessment.ReplacementPressure > 0.5f
            || !economicAssessment.HasWarIndustrySupplyChain)
        {
            previousCampaigns.TryGetValue("campaign-force-build-up", out var previous);
            var campaign = new FactionCampaignRuntime
            {
                Id = "campaign-force-build-up",
                Kind = "force-build-up",
                Status = "active",
                Priority = 60f + ((economicAssessment.TargetMilitaryShipCount - economicAssessment.MilitaryShipCount) * 5f) + (economicAssessment.ReplacementPressure * 10f),
                CurrentStepIndex = previous?.CurrentStepIndex ?? 0,
                Summary = "Expand warfighting capacity.",
                UpdatedAtUtc = nowUtc,
                ContinuationScore = 0.9f,
                SupplyAdequacy = economicAssessment.SustainmentScore,
                ReplacementPressure = economicAssessment.ReplacementPressure,
                FailureCount = previous?.FailureCount ?? 0,
                SuccessCount = previous?.SuccessCount ?? 0,
                RequiresReinforcement = true,
            };
            campaign.Steps.AddRange(
            [
              new FactionPlanStepRuntime { Id = $"{campaign.Id}-step-1", Kind = "stabilize-war-industry", Status = "active", Summary = "Stabilize core war industry inputs." },
        new FactionPlanStepRuntime { Id = $"{campaign.Id}-step-2", Kind = "increase-warship-output", Status = "planned", Summary = "Increase military ship production." },
      ]);
            if (previous is not null)
            {
                campaign.CreatedAtUtc = previous.CreatedAtUtc;
            }

            campaigns.Add(campaign);
        }

        return campaigns
          .OrderByDescending(candidate => candidate.Priority)
          .ThenBy(candidate => candidate.Id, StringComparer.Ordinal)
          .ToList();
    }

    private static List<FactionPlanStepRuntime> BuildCampaignSteps(FactionCampaignRuntime campaign, IndustryExpansionProject? expansionProject)
    {
        return campaign.Kind switch
        {
            "defense" =>
            [
              new FactionPlanStepRuntime { Id = $"{campaign.Id}-stage", Kind = "stage-defense", Status = "active", Summary = "Stage defenders into the contested system." },
        new FactionPlanStepRuntime { Id = $"{campaign.Id}-hold", Kind = "hold-space", Status = "planned", Summary = "Hold the system and protect friendly assets." },
        new FactionPlanStepRuntime { Id = $"{campaign.Id}-sustain", Kind = "sustain-defense", Status = "planned", Summary = "Sustain logistics and restore security." },
      ],
            "offense" =>
            [
              new FactionPlanStepRuntime { Id = $"{campaign.Id}-stage", Kind = "stage-offense", Status = "active", Summary = "Stage assault forces at the target frontier." },
        new FactionPlanStepRuntime { Id = $"{campaign.Id}-strike", Kind = "strike-targets", Status = "planned", Summary = "Engage hostile military and stations." },
        new FactionPlanStepRuntime { Id = $"{campaign.Id}-hold", Kind = "hold-gains", Status = "planned", Summary = "Maintain pressure and deny recovery." },
      ],
            "expansion" =>
            [
              new FactionPlanStepRuntime { Id = $"{campaign.Id}-claim", Kind = "secure-claim", Status = "active", Summary = $"Secure {expansionProject?.CelestialId ?? campaign.TargetEntityId} for construction." },
        new FactionPlanStepRuntime { Id = $"{campaign.Id}-supply", Kind = "supply-site", Status = "planned", Summary = "Move construction materials to the site." },
        new FactionPlanStepRuntime { Id = $"{campaign.Id}-guard", Kind = "guard-site", Status = "planned", Summary = "Defend the expansion site until operational." },
      ],
            "economic-stabilization" =>
            [
              new FactionPlanStepRuntime { Id = $"{campaign.Id}-source", Kind = "secure-supply", Status = "active", Summary = "Secure incoming production and mining sources." },
        new FactionPlanStepRuntime { Id = $"{campaign.Id}-move", Kind = "move-goods", Status = "planned", Summary = "Route logistics toward shortage points." },
        new FactionPlanStepRuntime { Id = $"{campaign.Id}-stabilize", Kind = "stabilize-market", Status = "planned", Summary = "Restore stable stock levels." },
      ],
            _ =>
            [
              new FactionPlanStepRuntime { Id = $"{campaign.Id}-step", Kind = "maintain", Status = "active", Summary = campaign.Summary },
      ],
        };
    }

    private static List<FactionOperationalObjectiveRuntime> BuildObjectives(
      SimulationWorld world,
      FactionRuntime faction,
      IReadOnlyList<FactionTheaterRuntime> theaters,
      IReadOnlyList<FactionCampaignRuntime> campaigns,
      FactionEconomicAssessmentRuntime economicAssessment,
      FactionThreatAssessmentRuntime threatAssessment,
      IndustryExpansionProject? expansionProject,
      IReadOnlyDictionary<string, FactionOperationalObjectiveRuntime> previousObjectives,
      DateTimeOffset nowUtc)
    {
        var objectives = new List<FactionOperationalObjectiveRuntime>();
        var campaignsById = campaigns.ToDictionary(campaign => campaign.Id, StringComparer.Ordinal);

        foreach (var campaign in campaigns)
        {
            var theater = theaters.FirstOrDefault(candidate => candidate.Id == campaign.TheaterId);
            switch (campaign.Kind)
            {
                case "defense":
                    AddDefenseObjectives(world, faction, campaign, theater, objectives, previousObjectives, nowUtc);
                    break;
                case "offense":
                    if (campaign.Status == "active")
                    {
                        AddOffenseObjectives(world, faction, campaign, theater, objectives, previousObjectives, nowUtc);
                    }
                    break;
                case "expansion":
                    if (campaign.Status == "active")
                    {
                        AddExpansionObjectives(world, faction, campaign, theater, expansionProject, objectives, previousObjectives, nowUtc);
                    }
                    break;
                case "economic-stabilization":
                    AddEconomicObjectives(world, faction, campaign, theater, economicAssessment, objectives, previousObjectives, nowUtc);
                    break;
                case "force-build-up":
                    AddForceBuildUpObjectives(world, faction, campaign, economicAssessment, objectives, previousObjectives, nowUtc);
                    break;
            }
        }

        foreach (var objective in objectives)
        {
            if (campaignsById.TryGetValue(objective.CampaignId, out var campaign))
            {
                campaign.ObjectiveIds.Add(objective.Id);
            }
        }

        return objectives
          .OrderByDescending(candidate => candidate.Priority)
          .ThenBy(candidate => candidate.Id, StringComparer.Ordinal)
          .ToList();
    }

    private static void AddDefenseObjectives(
      SimulationWorld world,
      FactionRuntime faction,
      FactionCampaignRuntime campaign,
      FactionTheaterRuntime? theater,
      ICollection<FactionOperationalObjectiveRuntime> objectives,
      IReadOnlyDictionary<string, FactionOperationalObjectiveRuntime> previousObjectives,
      DateTimeOffset nowUtc)
    {
        var stations = world.Stations
          .Where(station => station.FactionId == faction.Id && station.SystemId == campaign.TargetSystemId)
          .OrderByDescending(station => station.InstalledModules.Contains("module_gen_build_l_01", StringComparer.Ordinal) ? 1 : 0)
          .ThenBy(station => station.Id, StringComparer.Ordinal)
          .Take(2)
          .ToList();
        foreach (var station in stations)
        {
            objectives.Add(CreateObjective(
              previousObjectives,
              new FactionOperationalObjectiveRuntime
              {
                  Id = $"{campaign.Id}-protect-station-{station.Id}",
                  CampaignId = campaign.Id,
                  TheaterId = theater?.Id,
                  Kind = ProtectStation,
                  DelegationKind = "ship",
                  BehaviorKind = ProtectStation,
                  Status = "active",
                  Priority = campaign.Priority + 8f,
                  HomeSystemId = station.SystemId,
                  HomeStationId = station.Id,
                  TargetSystemId = station.SystemId,
                  TargetEntityId = station.Id,
                  Notes = $"Protect {station.Label}",
                  UpdatedAtUtc = nowUtc,
              },
              nowUtc));
        }

        objectives.Add(CreateObjective(
          previousObjectives,
          new FactionOperationalObjectiveRuntime
          {
              Id = $"{campaign.Id}-patrol-{campaign.TargetSystemId}",
              CampaignId = campaign.Id,
              TheaterId = theater?.Id,
              Kind = "patrol-front",
              DelegationKind = "ship",
              BehaviorKind = Patrol,
              Status = "active",
              Priority = campaign.Priority + 2f,
              HomeSystemId = campaign.TargetSystemId,
              TargetSystemId = campaign.TargetSystemId,
              TargetPosition = theater?.AnchorPosition ?? ResolveSystemAnchor(world, campaign.TargetSystemId),
              Notes = "Patrol defensive front",
              UpdatedAtUtc = nowUtc,
              UseOrders = true,
              StagingOrderKind = ShipOrderKinds.Move,
              ReinforcementLevel = campaign.RequiresReinforcement ? 2 : 1,
          },
          nowUtc));

        if ((theater?.SupplyRisk ?? 0f) > 0.25f)
        {
            objectives.Add(CreateObjective(
              previousObjectives,
              new FactionOperationalObjectiveRuntime
              {
                  Id = $"{campaign.Id}-police-{campaign.TargetSystemId}",
                  CampaignId = campaign.Id,
                  TheaterId = theater?.Id,
                  Kind = "police-front",
                  DelegationKind = "ship",
                  BehaviorKind = Police,
                  Status = "active",
                  Priority = campaign.Priority + 1f,
                  HomeSystemId = campaign.TargetSystemId,
                  TargetSystemId = campaign.TargetSystemId,
                  TargetPosition = theater?.AnchorPosition ?? ResolveSystemAnchor(world, campaign.TargetSystemId),
                  Notes = "Police frontier logistics routes",
                  UpdatedAtUtc = nowUtc,
                  UseOrders = true,
                  StagingOrderKind = ShipOrderKinds.Move,
                  ReinforcementLevel = 1,
              },
              nowUtc));
        }
    }

    private static void AddOffenseObjectives(
      SimulationWorld world,
      FactionRuntime faction,
      FactionCampaignRuntime campaign,
      FactionTheaterRuntime? theater,
      ICollection<FactionOperationalObjectiveRuntime> objectives,
      IReadOnlyDictionary<string, FactionOperationalObjectiveRuntime> previousObjectives,
      DateTimeOffset nowUtc)
    {
        var enemyStation = world.Stations
          .Where(station => station.FactionId != faction.Id && station.SystemId == campaign.TargetSystemId)
          .OrderBy(station => station.Id, StringComparer.Ordinal)
          .FirstOrDefault();
        if (enemyStation is not null)
        {
            objectives.Add(CreateObjective(
              previousObjectives,
              new FactionOperationalObjectiveRuntime
              {
                  Id = $"{campaign.Id}-strike-station-{enemyStation.Id}",
                  CampaignId = campaign.Id,
                  TheaterId = theater?.Id,
                  Kind = "strike-station",
                  DelegationKind = "ship",
                  BehaviorKind = AttackTarget,
                  Status = "active",
                  Priority = campaign.Priority + 10f,
                  TargetSystemId = enemyStation.SystemId,
                  TargetEntityId = enemyStation.Id,
                  TargetPosition = enemyStation.Position,
                  Notes = $"Strike {enemyStation.Label}",
                  UpdatedAtUtc = nowUtc,
                  UseOrders = true,
                  StagingOrderKind = ShipOrderKinds.Move,
                  ReinforcementLevel = 2,
              },
              nowUtc));
        }

        objectives.Add(CreateObjective(
          previousObjectives,
          new FactionOperationalObjectiveRuntime
          {
              Id = $"{campaign.Id}-hold-front",
              CampaignId = campaign.Id,
              TheaterId = theater?.Id,
              Kind = "hold-front",
              DelegationKind = "ship",
              BehaviorKind = ProtectPosition,
              Status = "active",
              Priority = campaign.Priority + 3f,
              TargetSystemId = campaign.TargetSystemId,
              TargetPosition = theater?.AnchorPosition ?? ResolveSystemAnchor(world, campaign.TargetSystemId),
              Notes = "Maintain assault formation",
              UpdatedAtUtc = nowUtc,
              UseOrders = true,
              StagingOrderKind = ShipOrderKinds.Move,
              ReinforcementLevel = 2,
          },
          nowUtc));

        objectives.Add(CreateObjective(
          previousObjectives,
          new FactionOperationalObjectiveRuntime
          {
              Id = $"{campaign.Id}-fleet-supply",
              CampaignId = campaign.Id,
              TheaterId = theater?.Id,
              Kind = "fleet-sustainment",
              DelegationKind = "ship",
              BehaviorKind = SupplyFleet,
              Status = "active",
              Priority = campaign.Priority + 1.5f,
              HomeSystemId = campaign.TargetSystemId,
              TargetSystemId = campaign.TargetSystemId,
              ItemId = "hullparts",
              Notes = "Sustain frontline fleet operations",
              UpdatedAtUtc = nowUtc,
              UseOrders = true,
              StagingOrderKind = ShipOrderKinds.Move,
              ReinforcementLevel = 1,
          },
          nowUtc));
    }

    private static void AddExpansionObjectives(
      SimulationWorld world,
      FactionRuntime faction,
      FactionCampaignRuntime campaign,
      FactionTheaterRuntime? theater,
      IndustryExpansionProject? expansionProject,
      ICollection<FactionOperationalObjectiveRuntime> objectives,
      IReadOnlyDictionary<string, FactionOperationalObjectiveRuntime> previousObjectives,
      DateTimeOffset nowUtc)
    {
        if (expansionProject is null)
        {
            return;
        }

        objectives.Add(CreateObjective(
          previousObjectives,
          new FactionOperationalObjectiveRuntime
          {
              Id = $"{campaign.Id}-construct-site",
              CampaignId = campaign.Id,
              TheaterId = theater?.Id,
              Kind = "construct-site",
              DelegationKind = "ship",
              BehaviorKind = ConstructStation,
              Status = "active",
              Priority = campaign.Priority + 8f,
              HomeSystemId = expansionProject.SystemId,
              HomeStationId = expansionProject.SupportStationId,
              TargetSystemId = expansionProject.SystemId,
              TargetEntityId = expansionProject.SiteId,
              TargetPosition = ResolveExpansionAnchor(world, expansionProject),
              Notes = $"Construct {expansionProject.ModuleId}",
              UpdatedAtUtc = nowUtc,
              UseOrders = true,
              StagingOrderKind = ShipOrderKinds.Move,
              ReinforcementLevel = 2,
          },
          nowUtc));

        objectives.Add(CreateObjective(
          previousObjectives,
          new FactionOperationalObjectiveRuntime
          {
              Id = $"{campaign.Id}-supply-site",
              CampaignId = campaign.Id,
              TheaterId = theater?.Id,
              Kind = "supply-site",
              DelegationKind = "ship",
              BehaviorKind = FindBuildTasks,
              Status = "active",
              Priority = campaign.Priority + 4f,
              HomeSystemId = expansionProject.SystemId,
              HomeStationId = expansionProject.SupportStationId,
              TargetSystemId = expansionProject.SystemId,
              TargetEntityId = expansionProject.SiteId,
              ItemId = expansionProject.CommodityId,
              Notes = "Supply expansion site",
              UpdatedAtUtc = nowUtc,
              UseOrders = true,
              StagingOrderKind = ShipOrderKinds.Move,
              ReinforcementLevel = 1,
          },
          nowUtc));

        objectives.Add(CreateObjective(
          previousObjectives,
          new FactionOperationalObjectiveRuntime
          {
              Id = $"{campaign.Id}-guard-site",
              CampaignId = campaign.Id,
              TheaterId = theater?.Id,
              Kind = "guard-site",
              DelegationKind = "ship",
              BehaviorKind = ProtectPosition,
              Status = "active",
              Priority = campaign.Priority + 2f,
              TargetSystemId = expansionProject.SystemId,
              TargetPosition = ResolveExpansionAnchor(world, expansionProject),
              TargetEntityId = expansionProject.SiteId,
              Notes = "Guard construction frontier",
              UpdatedAtUtc = nowUtc,
              UseOrders = true,
              StagingOrderKind = ShipOrderKinds.Move,
              ReinforcementLevel = 1,
          },
          nowUtc));

        if (CanMineItem(world, expansionProject.CommodityId))
        {
            objectives.Add(CreateObjective(
              previousObjectives,
              new FactionOperationalObjectiveRuntime
              {
                  Id = $"{campaign.Id}-mine-expansion-input",
                  CampaignId = campaign.Id,
                  TheaterId = theater?.Id,
                  Kind = "mine-expansion-input",
                  DelegationKind = "ship",
                  BehaviorKind = ExpertAutoMine,
                  Status = "active",
                  Priority = campaign.Priority + 1f,
                  HomeSystemId = expansionProject.SystemId,
                  HomeStationId = expansionProject.SupportStationId,
                  TargetSystemId = expansionProject.SystemId,
                  ItemId = expansionProject.CommodityId,
                  Notes = $"Mine {expansionProject.CommodityId} for frontier build-up",
                  UpdatedAtUtc = nowUtc,
                  ReinforcementLevel = 1,
              },
              nowUtc));
        }
    }

    private static void AddEconomicObjectives(
      SimulationWorld world,
      FactionRuntime faction,
      FactionCampaignRuntime campaign,
      FactionTheaterRuntime? theater,
      FactionEconomicAssessmentRuntime economicAssessment,
      ICollection<FactionOperationalObjectiveRuntime> objectives,
      IReadOnlyDictionary<string, FactionOperationalObjectiveRuntime> previousObjectives,
      DateTimeOffset nowUtc)
    {
        var itemId = campaign.CommodityId ?? ResolveCommodityFromTheaterId(theater?.Id);
        if (string.IsNullOrWhiteSpace(itemId))
        {
            return;
        }

        var anchorStation = ResolveCommodityAnchorStation(world, faction.Id, itemId);
        objectives.Add(CreateObjective(
          previousObjectives,
          new FactionOperationalObjectiveRuntime
          {
              Id = $"{campaign.Id}-trade-{itemId}",
              CampaignId = campaign.Id,
              TheaterId = theater?.Id,
              Kind = "trade-shortage",
              DelegationKind = "ship",
              BehaviorKind = FillShortages,
              Status = "active",
              Priority = campaign.Priority + 5f,
              HomeSystemId = anchorStation?.SystemId,
              HomeStationId = anchorStation?.Id,
              TargetSystemId = anchorStation?.SystemId ?? campaign.TargetSystemId,
              TargetEntityId = anchorStation?.Id,
              ItemId = itemId,
              Notes = $"Stabilize {itemId} shortages",
              UpdatedAtUtc = nowUtc,
              ReinforcementLevel = campaign.RequiresReinforcement ? 2 : 1,
          },
          nowUtc));

        if (CanMineItem(world, itemId))
        {
            objectives.Add(CreateObjective(
              previousObjectives,
              new FactionOperationalObjectiveRuntime
              {
                  Id = $"{campaign.Id}-mine-{itemId}",
                  CampaignId = campaign.Id,
                  TheaterId = theater?.Id,
                  Kind = "mine-shortage",
                  DelegationKind = "ship",
                  BehaviorKind = ExpertAutoMine,
                  Status = "active",
                  Priority = campaign.Priority + 3f,
                  HomeSystemId = anchorStation?.SystemId,
                  HomeStationId = anchorStation?.Id,
                  TargetSystemId = campaign.TargetSystemId,
                  ItemId = itemId,
                  Notes = $"Mine {itemId} for economic recovery",
                  UpdatedAtUtc = nowUtc,
                  ReinforcementLevel = 1,
              },
              nowUtc));
        }

        objectives.Add(CreateObjective(
          previousObjectives,
          new FactionOperationalObjectiveRuntime
          {
              Id = $"{campaign.Id}-revisit-{itemId}",
              CampaignId = campaign.Id,
              TheaterId = theater?.Id,
              Kind = "revisit-stations",
              DelegationKind = "ship",
              BehaviorKind = RevisitKnownStations,
              Status = "active",
              Priority = campaign.Priority + 0.5f,
              HomeSystemId = anchorStation?.SystemId,
              HomeStationId = anchorStation?.Id,
              TargetSystemId = anchorStation?.SystemId ?? campaign.TargetSystemId,
              ItemId = itemId,
              Notes = $"Refresh station trade knowledge for {itemId}",
              UpdatedAtUtc = nowUtc,
              ReinforcementLevel = 1,
          },
          nowUtc));
    }

    private static void AddForceBuildUpObjectives(
      SimulationWorld world,
      FactionRuntime faction,
      FactionCampaignRuntime campaign,
      FactionEconomicAssessmentRuntime economicAssessment,
      ICollection<FactionOperationalObjectiveRuntime> objectives,
      IReadOnlyDictionary<string, FactionOperationalObjectiveRuntime> previousObjectives,
      DateTimeOffset nowUtc)
    {
        var shipyard = world.Stations
          .Where(station => station.FactionId == faction.Id && station.InstalledModules.Contains("module_gen_build_l_01", StringComparer.Ordinal))
          .OrderBy(station => station.Id, StringComparer.Ordinal)
          .FirstOrDefault();
        if (shipyard is null)
        {
            return;
        }

        objectives.Add(CreateObjective(
          previousObjectives,
          new FactionOperationalObjectiveRuntime
          {
              Id = $"{campaign.Id}-feed-shipyard",
              CampaignId = campaign.Id,
              Kind = "feed-shipyard",
              DelegationKind = "ship",
              BehaviorKind = FillShortages,
              Status = "active",
              Priority = campaign.Priority + 4f,
              HomeSystemId = shipyard.SystemId,
              HomeStationId = shipyard.Id,
              TargetSystemId = shipyard.SystemId,
              TargetEntityId = shipyard.Id,
              ItemId = economicAssessment.IndustrialBottleneckItemId ?? "hullparts",
              Notes = "Keep shipyard supplied for fleet growth",
              UpdatedAtUtc = nowUtc,
              ReinforcementLevel = 2,
          },
          nowUtc));

        if (!string.IsNullOrWhiteSpace(economicAssessment.IndustrialBottleneckItemId)
            && CanMineItem(world, economicAssessment.IndustrialBottleneckItemId))
        {
            objectives.Add(CreateObjective(
              previousObjectives,
              new FactionOperationalObjectiveRuntime
              {
                  Id = $"{campaign.Id}-mine-bottleneck",
                  CampaignId = campaign.Id,
                  Kind = "mine-bottleneck",
                  DelegationKind = "ship",
                  BehaviorKind = ExpertAutoMine,
                  Status = "active",
                  Priority = campaign.Priority + 2f,
                  HomeSystemId = shipyard.SystemId,
                  HomeStationId = shipyard.Id,
                  TargetSystemId = shipyard.SystemId,
                  ItemId = economicAssessment.IndustrialBottleneckItemId,
                  Notes = $"Mine {economicAssessment.IndustrialBottleneckItemId} for replacement pressure relief",
                  UpdatedAtUtc = nowUtc,
                  ReinforcementLevel = 1,
              },
              nowUtc));
        }
    }

    private static FactionOperationalObjectiveRuntime CreateObjective(
      IReadOnlyDictionary<string, FactionOperationalObjectiveRuntime> previousObjectives,
      FactionOperationalObjectiveRuntime objective,
      DateTimeOffset nowUtc)
    {
        if (previousObjectives.TryGetValue(objective.Id, out var previous))
        {
            objective.CreatedAtUtc = previous.CreatedAtUtc;
            objective.CurrentStepIndex = previous.CurrentStepIndex;
            objective.CommanderId = previous.CommanderId;
            objective.Status = previous.Status;
            objective.ReservedAssetIds.AddRange(previous.ReservedAssetIds.OrderBy(id => id, StringComparer.Ordinal));
        }

        objective.UpdatedAtUtc = nowUtc;
        objective.Steps.AddRange(BuildObjectiveSteps(objective));
        return objective;
    }

    private static IEnumerable<FactionPlanStepRuntime> BuildObjectiveSteps(FactionOperationalObjectiveRuntime objective)
    {
        return
        [
          new FactionPlanStepRuntime
      {
        Id = $"{objective.Id}-step-1",
        Kind = "deploy",
        Status = objective.Status == "paused" ? "paused" : objective.CommanderId is null ? "planned" : "active",
        Summary = "Deploy assigned asset.",
      },
      new FactionPlanStepRuntime
      {
        Id = $"{objective.Id}-step-2",
        Kind = "execute",
        Status = objective.Status == "paused" ? "paused" : "planned",
        Summary = objective.Notes ?? objective.BehaviorKind,
      },
    ];
    }

    private static List<FactionAssetReservationRuntime> BuildReservations(
      SimulationWorld world,
      FactionRuntime faction,
      IReadOnlyList<FactionOperationalObjectiveRuntime> objectives,
      DateTimeOffset nowUtc)
    {
        var reservations = new List<FactionAssetReservationRuntime>();
        var commanders = world.Commanders
          .Where(commander => commander.IsAlive && commander.FactionId == faction.Id && commander.Kind is CommanderKind.Station or CommanderKind.Ship)
          .OrderBy(commander => commander.Kind, StringComparer.Ordinal)
          .ThenBy(commander => commander.Id, StringComparer.Ordinal)
          .ToList();
        var reservedCommanderIds = new HashSet<string>(StringComparer.Ordinal);
        var availableMilitaryCommanders = commanders.Count(commander =>
          commander.Kind == CommanderKind.Ship &&
          world.Ships.FirstOrDefault(ship => ship.Id == commander.ControlledEntityId) is { } commanderShip
          && commanderShip.Health > 0f
          && IsMilitaryShip(commanderShip.Definition));
        var committedMilitaryCommanders = 0;

        foreach (var objective in objectives
          .OrderByDescending(candidate => candidate.Priority + (candidate.ReinforcementLevel * 4f))
          .ThenBy(candidate => candidate.Id, StringComparer.Ordinal))
        {
            if (IsCombatObjective(objective)
                && objective.Priority < 95f
                && availableMilitaryCommanders - committedMilitaryCommanders <= faction.StrategicState.Budget.ReservedMilitaryAssets)
            {
                objective.Status = "reserved";
                continue;
            }

            var commander = SelectCommanderForObjective(world, objective, commanders, reservedCommanderIds);
            if (commander is null)
            {
                continue;
            }

            reservedCommanderIds.Add(commander.Id);
            objective.CommanderId = commander.Id;
            objective.ReservedAssetIds.Clear();
            objective.ReservedAssetIds.Add(commander.ControlledEntityId ?? commander.Id);
            objective.Status = "active";
            objective.CurrentStepIndex = 0;
            if (IsCombatObjective(objective))
            {
                committedMilitaryCommanders += 1;
            }

            reservations.Add(new FactionAssetReservationRuntime
            {
                Id = $"reservation-{objective.Id}-{commander.Id}",
                ObjectiveId = objective.Id,
                CampaignId = objective.CampaignId,
                AssetKind = commander.Kind == CommanderKind.Station ? "station-commander" : "ship-commander",
                AssetId = commander.Id,
                Priority = objective.Priority,
                UpdatedAtUtc = nowUtc,
            });
        }

        return reservations;
    }

    private static CommanderRuntime? SelectCommanderForObjective(
      SimulationWorld world,
      FactionOperationalObjectiveRuntime objective,
      IReadOnlyList<CommanderRuntime> commanders,
      IReadOnlySet<string> reservedCommanderIds)
    {
        return commanders
          .Where(commander =>
            !reservedCommanderIds.Contains(commander.Id) &&
            IsCommanderEligibleForObjective(world, commander, objective))
          .OrderByDescending(commander => ScoreCommanderForObjective(world, commander, objective))
          .ThenBy(commander => commander.Id, StringComparer.Ordinal)
          .FirstOrDefault();
    }

    private static bool IsCommanderEligibleForObjective(SimulationWorld world, CommanderRuntime commander, FactionOperationalObjectiveRuntime objective)
    {
        if (objective.DelegationKind == "station")
        {
            return commander.Kind == CommanderKind.Station
              && (objective.HomeStationId is null || string.Equals(commander.ControlledEntityId, objective.HomeStationId, StringComparison.Ordinal));
        }

        if (commander.Kind != CommanderKind.Ship)
        {
            return false;
        }

        var ship = world.Ships.FirstOrDefault(candidate => candidate.Id == commander.ControlledEntityId);
        if (ship is null || ship.Health <= 0f)
        {
            return false;
        }

        return objective.BehaviorKind switch
        {
            ConstructStation => IsConstructionShip(ship.Definition),
            FindBuildTasks => IsTransportShip(ship.Definition),
            FillShortages or AdvancedAutoTrade or RevisitKnownStations or SupplyFleet => IsTransportShip(ship.Definition),
            LocalAutoMine or AdvancedAutoMine or ExpertAutoMine => IsMiningShip(ship.Definition),
            Patrol or Police or ProtectPosition or ProtectShip or ProtectStation or AttackTarget => IsMilitaryShip(ship.Definition),
            _ => true,
        };
    }

    private static float ScoreCommanderForObjective(SimulationWorld world, CommanderRuntime commander, FactionOperationalObjectiveRuntime objective)
    {
        var skillScore = commander.Skills.Leadership + commander.Skills.Coordination + commander.Skills.Strategy;
        var homeBias = 0f;

        if (commander.Kind == CommanderKind.Station)
        {
            if (string.Equals(commander.ControlledEntityId, objective.HomeStationId, StringComparison.Ordinal))
            {
                homeBias += 25f;
            }

            if (world.Stations.FirstOrDefault(candidate => candidate.Id == commander.ControlledEntityId) is { } station
                && string.Equals(station.SystemId, objective.TargetSystemId, StringComparison.Ordinal))
            {
                homeBias += 12f;
            }

            return homeBias + skillScore;
        }

        var ship = world.Ships.FirstOrDefault(candidate => candidate.Id == commander.ControlledEntityId);
        if (ship is null)
        {
            return float.MinValue;
        }

        if (string.Equals(ship.SystemId, objective.TargetSystemId, StringComparison.Ordinal))
        {
            homeBias += 30f;
        }
        else if (string.Equals(ship.SystemId, objective.HomeSystemId, StringComparison.Ordinal))
        {
            homeBias += 18f;
        }

        if (ship.CommanderId == objective.CommanderId)
        {
            homeBias += 8f;
        }

        var distancePenalty = objective.TargetPosition is null ? 0f : ship.Position.DistanceTo(objective.TargetPosition.Value);
        return homeBias + skillScore - distancePenalty;
    }

    private static List<FactionProductionProgramRuntime> BuildProductionPrograms(
      FactionRuntime faction,
      IReadOnlyList<FactionTheaterRuntime> theaters,
      IReadOnlyList<FactionCampaignRuntime> campaigns,
      FactionEconomicAssessmentRuntime economicAssessment,
      FactionThreatAssessmentRuntime threatAssessment,
      IndustryExpansionProject? expansionProject,
      IReadOnlyDictionary<string, FactionProductionProgramRuntime> previousPrograms)
    {
        var programs = new List<FactionProductionProgramRuntime>();

        programs.Add(CreateProductionProgram(previousPrograms, new FactionProductionProgramRuntime
        {
            Id = $"program-{faction.Id}-military",
            Kind = "military-fleet",
            Status = economicAssessment.MilitaryShipCount >= economicAssessment.TargetMilitaryShipCount ? "stable" : "active",
            Priority = 80f + (threatAssessment.ThreatSignals.Count * 4f),
            ShipKind = MilitaryShipCategory,
            TargetCount = economicAssessment.TargetMilitaryShipCount,
            CurrentCount = economicAssessment.MilitaryShipCount,
            Notes = "Maintain enough military hulls for all active fronts.",
        }));

        programs.Add(CreateProductionProgram(previousPrograms, new FactionProductionProgramRuntime
        {
            Id = $"program-{faction.Id}-miners",
            Kind = "mining-fleet",
            Status = economicAssessment.MinerShipCount >= economicAssessment.TargetMinerShipCount ? "stable" : "active",
            Priority = 60f,
            ShipKind = MiningShipCategory,
            TargetCount = economicAssessment.TargetMinerShipCount,
            CurrentCount = economicAssessment.MinerShipCount,
            Notes = "Maintain raw resource extraction capacity.",
        }));

        programs.Add(CreateProductionProgram(previousPrograms, new FactionProductionProgramRuntime
        {
            Id = $"program-{faction.Id}-transports",
            Kind = "logistics-fleet",
            Status = economicAssessment.TransportShipCount >= economicAssessment.TargetTransportShipCount ? "stable" : "active",
            Priority = 62f,
            ShipKind = TransportShipCategory,
            TargetCount = economicAssessment.TargetTransportShipCount,
            CurrentCount = economicAssessment.TransportShipCount,
            Notes = "Maintain logistics throughput across stations and fronts.",
        }));

        programs.Add(CreateProductionProgram(previousPrograms, new FactionProductionProgramRuntime
        {
            Id = $"program-{faction.Id}-constructors",
            Kind = "construction-fleet",
            Status = economicAssessment.ConstructorShipCount >= economicAssessment.TargetConstructorShipCount ? "stable" : "active",
            Priority = expansionProject is null ? 35f : 68f,
            ShipKind = ConstructionShipCategory,
            TargetCount = economicAssessment.TargetConstructorShipCount,
            CurrentCount = economicAssessment.ConstructorShipCount,
            Notes = "Maintain construction capacity for frontier growth.",
        }));

        if (!economicAssessment.HasWarIndustrySupplyChain)
        {
            programs.Add(CreateProductionProgram(previousPrograms, new FactionProductionProgramRuntime
            {
                Id = $"program-{faction.Id}-war-industry",
                Kind = "war-industry",
                Status = "active",
                Priority = 78f,
                CommodityId = "hullparts",
                TargetCount = 1,
                CurrentCount = economicAssessment.HasWarIndustrySupplyChain ? 1 : 0,
                Notes = "Stabilize war industry bottlenecks.",
            }));
        }

        if (expansionProject is not null)
        {
            programs.Add(CreateProductionProgram(previousPrograms, new FactionProductionProgramRuntime
            {
                Id = $"program-{faction.Id}-expansion",
                Kind = "expansion",
                Status = "active",
                Priority = 72f,
                CampaignId = campaigns.FirstOrDefault(candidate => candidate.Kind == "expansion")?.Id,
                CommodityId = expansionProject.CommodityId,
                ModuleId = expansionProject.ModuleId,
                TargetSystemId = expansionProject.SystemId,
                TargetCount = 1,
                CurrentCount = economicAssessment.PrimaryExpansionSiteId is null ? 0 : 1,
                Notes = $"Expand into {expansionProject.SystemId}.",
            }));
        }

        return programs
          .OrderByDescending(program => program.Priority)
          .ThenBy(program => program.Id, StringComparer.Ordinal)
          .ToList();
    }

    private static FactionProductionProgramRuntime CreateProductionProgram(
      IReadOnlyDictionary<string, FactionProductionProgramRuntime> previousPrograms,
      FactionProductionProgramRuntime program)
    {
        if (previousPrograms.TryGetValue(program.Id, out var previous))
        {
            program.CampaignId ??= previous.CampaignId;
        }

        return program;
    }

    private static void ReconcileCampaignLifecycle(
      SimulationWorld world,
      FactionRuntime faction,
      IReadOnlyDictionary<string, FactionCampaignRuntime> previousCampaigns,
      IReadOnlyCollection<FactionCampaignRuntime> campaigns,
      FactionEconomicAssessmentRuntime economicAssessment,
      FactionThreatAssessmentRuntime threatAssessment,
      DateTimeOffset nowUtc)
    {
        var activeIds = campaigns.Select(campaign => campaign.Id).ToHashSet(StringComparer.Ordinal);
        foreach (var campaign in campaigns)
        {
            if (!previousCampaigns.ContainsKey(campaign.Id))
            {
                AppendDecision(faction, new FactionDecisionLogEntryRuntime
                {
                    Id = $"decision-{campaign.Id}-created",
                    Kind = "campaign-created",
                    Summary = $"Activated {campaign.Kind} campaign {campaign.Id}.",
                    RelatedEntityId = campaign.Id,
                    PlanCycle = faction.StrategicState.PlanCycle,
                    OccurredAtUtc = nowUtc,
                });
            }
        }

        foreach (var previous in previousCampaigns.Values.Where(candidate => !activeIds.Contains(candidate.Id)))
        {
            UpdateCampaignMemoryFromOutcome(world, faction, previous, economicAssessment, threatAssessment, nowUtc);
            AppendDecision(faction, new FactionDecisionLogEntryRuntime
            {
                Id = $"decision-{previous.Id}-completed-{faction.StrategicState.PlanCycle}",
                Kind = "campaign-completed",
                Summary = $"Closed {previous.Kind} campaign {previous.Id}.",
                RelatedEntityId = previous.Id,
                PlanCycle = faction.StrategicState.PlanCycle,
                OccurredAtUtc = nowUtc,
            });
            AppendOutcome(faction, new FactionOutcomeRecordRuntime
            {
                Id = $"outcome-{previous.Id}-{faction.StrategicState.PlanCycle}",
                Kind = "campaign-completed",
                Summary = $"Campaign {previous.Id} left the active strategic set.",
                RelatedCampaignId = previous.Id,
                OccurredAtUtc = nowUtc,
            });
        }
    }

    private static void ReconcileObjectiveLifecycle(
      FactionRuntime faction,
      IReadOnlyDictionary<string, FactionOperationalObjectiveRuntime> previousObjectives,
      IReadOnlyCollection<FactionOperationalObjectiveRuntime> objectives,
      DateTimeOffset nowUtc)
    {
        var activeIds = objectives.Select(objective => objective.Id).ToHashSet(StringComparer.Ordinal);
        foreach (var objective in objectives.Where(candidate => !previousObjectives.ContainsKey(candidate.Id)))
        {
            AppendDecision(faction, new FactionDecisionLogEntryRuntime
            {
                Id = $"decision-{objective.Id}-created",
                Kind = "objective-created",
                Summary = $"Delegated objective {objective.Kind}.",
                RelatedEntityId = objective.Id,
                PlanCycle = faction.StrategicState.PlanCycle,
                OccurredAtUtc = nowUtc,
            });
        }

        foreach (var previous in previousObjectives.Values.Where(candidate => !activeIds.Contains(candidate.Id)))
        {
            AppendDecision(faction, new FactionDecisionLogEntryRuntime
            {
                Id = $"decision-{previous.Id}-retired-{faction.StrategicState.PlanCycle}",
                Kind = "objective-retired",
                Summary = $"Retired objective {previous.Kind}.",
                RelatedEntityId = previous.Id,
                PlanCycle = faction.StrategicState.PlanCycle,
                OccurredAtUtc = nowUtc,
            });
        }
    }

    private static void ReconcileTheaterLifecycle(
      FactionRuntime faction,
      IReadOnlyDictionary<string, FactionTheaterRuntime> previousTheaters,
      IReadOnlyCollection<FactionTheaterRuntime> theaters,
      DateTimeOffset nowUtc)
    {
        var activeIds = theaters.Select(theater => theater.Id).ToHashSet(StringComparer.Ordinal);
        foreach (var theater in theaters.Where(candidate => !previousTheaters.ContainsKey(candidate.Id)))
        {
            AppendDecision(faction, new FactionDecisionLogEntryRuntime
            {
                Id = $"decision-{theater.Id}-opened",
                Kind = "theater-opened",
                Summary = $"Opened {theater.Kind} in {theater.SystemId}.",
                RelatedEntityId = theater.Id,
                PlanCycle = faction.StrategicState.PlanCycle,
                OccurredAtUtc = nowUtc,
            });
        }

        foreach (var previous in previousTheaters.Values.Where(candidate => !activeIds.Contains(candidate.Id)))
        {
            AppendDecision(faction, new FactionDecisionLogEntryRuntime
            {
                Id = $"decision-{previous.Id}-closed-{faction.StrategicState.PlanCycle}",
                Kind = "theater-closed",
                Summary = $"Closed {previous.Kind} in {previous.SystemId}.",
                RelatedEntityId = previous.Id,
                PlanCycle = faction.StrategicState.PlanCycle,
                OccurredAtUtc = nowUtc,
            });
        }
    }

    private static void ReconcileProgramLifecycle(
      FactionRuntime faction,
      IReadOnlyDictionary<string, FactionProductionProgramRuntime> previousPrograms,
      IReadOnlyCollection<FactionProductionProgramRuntime> programs,
      DateTimeOffset nowUtc)
    {
        var activeIds = programs.Select(program => program.Id).ToHashSet(StringComparer.Ordinal);
        foreach (var program in programs.Where(candidate => !previousPrograms.ContainsKey(candidate.Id)))
        {
            AppendDecision(faction, new FactionDecisionLogEntryRuntime
            {
                Id = $"decision-{program.Id}-started",
                Kind = "program-started",
                Summary = $"Started production program {program.Kind}.",
                RelatedEntityId = program.Id,
                PlanCycle = faction.StrategicState.PlanCycle,
                OccurredAtUtc = nowUtc,
            });
        }

        foreach (var previous in previousPrograms.Values.Where(candidate => !activeIds.Contains(candidate.Id)))
        {
            AppendDecision(faction, new FactionDecisionLogEntryRuntime
            {
                Id = $"decision-{previous.Id}-stopped-{faction.StrategicState.PlanCycle}",
                Kind = "program-stopped",
                Summary = $"Stopped production program {previous.Kind}.",
                RelatedEntityId = previous.Id,
                PlanCycle = faction.StrategicState.PlanCycle,
                OccurredAtUtc = nowUtc,
            });
        }
    }

    private static void ApplyDelegation(
      SimulationWorld world,
      FactionRuntime faction,
      CommanderRuntime factionCommander,
      ICollection<SimulationEventRecord> events,
      DateTimeOffset nowUtc)
    {
        foreach (var commander in world.Commanders.Where(candidate => candidate.FactionId == faction.Id))
        {
            commander.ActiveObjectiveIds.Clear();
        }

        foreach (var objective in faction.StrategicState.Objectives.Where(candidate => candidate.CommanderId is not null))
        {
            if (world.Commanders.FirstOrDefault(candidate => candidate.Id == objective.CommanderId) is not { } commander)
            {
                continue;
            }

            commander.ActiveObjectiveIds.Add(objective.Id);
        }

        var fleetCommanders = EnsureFleetCommanders(world, faction, factionCommander, nowUtc);

        var focusCampaign = faction.StrategicState.Campaigns
          .OrderByDescending(candidate => candidate.Priority)
          .ThenBy(candidate => candidate.Id, StringComparer.Ordinal)
          .FirstOrDefault();
        factionCommander.Assignment = new CommanderAssignmentRuntime
        {
            ObjectiveId = focusCampaign?.Id ?? $"objective-strategic-{faction.Id}",
            CampaignId = focusCampaign?.Id,
            TheaterId = focusCampaign?.TheaterId,
            Kind = "strategic-executive",
            BehaviorKind = "strategic-executive",
            Status = "active",
            Priority = 100f,
            HomeSystemId = focusCampaign?.TargetSystemId ?? faction.StrategicState.EconomicAssessment.PrimaryExpansionSystemId,
            TargetSystemId = focusCampaign?.TargetSystemId ?? faction.StrategicState.ThreatAssessment.PrimaryThreatSystemId ?? faction.StrategicState.EconomicAssessment.PrimaryExpansionSystemId,
            TargetEntityId = focusCampaign?.TargetEntityId,
            Notes = focusCampaign?.Summary ?? faction.StrategicState.Status,
            UpdatedAtUtc = nowUtc,
        };

        foreach (var commander in world.Commanders.Where(candidate => candidate.FactionId == faction.Id && candidate.Kind is CommanderKind.Ship or CommanderKind.Station))
        {
            var objective = faction.StrategicState.Objectives
              .Where(candidate => string.Equals(candidate.CommanderId, commander.Id, StringComparison.Ordinal))
              .OrderByDescending(candidate => candidate.Priority)
              .ThenBy(candidate => candidate.Id, StringComparer.Ordinal)
              .FirstOrDefault();
            if (commander.Kind == CommanderKind.Ship
                && world.Ships.FirstOrDefault(ship => ship.Id == commander.ControlledEntityId) is { } ship)
            {
                if (ApplyShipControlSurface(world, faction, factionCommander, commander, ship, objective, fleetCommanders, nowUtc))
                {
                    ship.NeedsReplan = true;
                    ship.LastReplanReason = objective is null ? "faction-fallback-updated" : "faction-objective-updated";
                }
            }

            if (objective is not null)
            {
                commander.NeedsReplan = true;
            }
        }

        RefreshCommanderHierarchy(world, faction.Id);

        events.Add(new SimulationEventRecord(
          "faction",
          faction.Id,
          "strategic-cycle",
          $"{faction.Label} strategic cycle {faction.StrategicState.PlanCycle} updated {faction.StrategicState.Campaigns.Count} campaigns across {faction.StrategicState.Theaters.Count} theaters.",
          nowUtc));
    }

    private static void UpdateFleetCommander(SimulationWorld world, CommanderRuntime commander)
    {
        commander.ReplanTimer = FleetCommanderReplanInterval;
        commander.NeedsReplan = false;
        commander.PlanningCycle += 1;
        commander.ActiveObjectiveIds.Clear();

        var faction = FindFaction(world, commander.FactionId);
        var campaign = faction?.StrategicState.Campaigns.FirstOrDefault(candidate => candidate.Id == commander.ControlledEntityId);
        if (faction is null || campaign is null)
        {
            commander.IsAlive = false;
            commander.Assignment = null;
            return;
        }

        var objectives = faction.StrategicState.Objectives
          .Where(candidate => candidate.CampaignId == campaign.Id && candidate.CommanderId is not null)
          .OrderByDescending(candidate => candidate.Priority)
          .ThenBy(candidate => candidate.Id, StringComparer.Ordinal)
          .ToList();
        foreach (var objective in objectives)
        {
            commander.ActiveObjectiveIds.Add(objective.Id);
        }

        commander.Assignment = new CommanderAssignmentRuntime
        {
            ObjectiveId = campaign.Id,
            CampaignId = campaign.Id,
            TheaterId = campaign.TheaterId,
            Kind = "fleet-command",
            BehaviorKind = campaign.Kind switch
            {
                "offense" => AttackTarget,
                "defense" => ProtectPosition,
                "expansion" => ProtectPosition,
                _ => Patrol,
            },
            Status = campaign.Status,
            Priority = campaign.Priority,
            HomeSystemId = campaign.TargetSystemId,
            TargetSystemId = campaign.TargetSystemId,
            TargetEntityId = campaign.TargetEntityId,
            Notes = campaign.Summary,
            UpdatedAtUtc = DateTimeOffset.UtcNow,
        };
    }

    private static CommanderAssignmentRuntime BuildStationFocusAssignment(
      SimulationWorld world,
      FactionRuntime faction,
      StationRuntime station,
      ConstructionSiteRuntime? activeSite)
    {
        var economic = faction.StrategicState.EconomicAssessment;
        var role = StationSimulationService.DetermineStationRole(station);
        var bottleneckItem = economic.IndustrialBottleneckItemId;
        var nowUtc = DateTimeOffset.UtcNow;

        if (HasStationModules(station, "module_gen_build_l_01")
            && economic.MilitaryShipCount < economic.TargetMilitaryShipCount)
        {
            return new CommanderAssignmentRuntime
            {
                ObjectiveId = $"objective-station-{station.Id}-ship-production",
                Kind = "ship-production-focus",
                BehaviorKind = FillShortages,
                Status = "active",
                Priority = 55f,
                HomeSystemId = station.SystemId,
                HomeStationId = station.Id,
                TargetSystemId = station.SystemId,
                TargetEntityId = station.Id,
                ItemId = bottleneckItem ?? "hullparts",
                Notes = "Prioritize military replacement output",
                UpdatedAtUtc = nowUtc,
            };
        }

        if (!string.IsNullOrWhiteSpace(bottleneckItem) && StationCanProduceItem(world, station, bottleneckItem))
        {
            return new CommanderAssignmentRuntime
            {
                ObjectiveId = $"objective-station-{station.Id}-commodity-focus-{bottleneckItem}",
                Kind = "commodity-focus",
                BehaviorKind = FillShortages,
                Status = "active",
                Priority = 45f,
                HomeSystemId = station.SystemId,
                HomeStationId = station.Id,
                TargetSystemId = station.SystemId,
                TargetEntityId = station.Id,
                ItemId = bottleneckItem,
                Notes = $"Stabilize {bottleneckItem} production",
                UpdatedAtUtc = nowUtc,
            };
        }

        if (activeSite is not null)
        {
            return new CommanderAssignmentRuntime
            {
                ObjectiveId = $"objective-station-{station.Id}-expansion-support",
                Kind = "expansion-support",
                BehaviorKind = FindBuildTasks,
                Status = "active",
                Priority = 40f,
                HomeSystemId = station.SystemId,
                HomeStationId = station.Id,
                TargetSystemId = activeSite.SystemId,
                TargetEntityId = activeSite.Id,
                ItemId = economic.IndustrialBottleneckItemId,
                Notes = $"Support {activeSite.BlueprintId}",
                UpdatedAtUtc = nowUtc,
            };
        }

        return new CommanderAssignmentRuntime
        {
            ObjectiveId = $"objective-station-{station.Id}-oversight",
            Kind = "station-oversight",
            BehaviorKind = FillShortages,
            Status = "active",
            Priority = 30f,
            HomeSystemId = station.SystemId,
            HomeStationId = station.Id,
            TargetSystemId = station.SystemId,
            TargetEntityId = station.Id,
            ItemId = bottleneckItem,
            Notes = role,
            UpdatedAtUtc = nowUtc,
        };
    }

    private static IReadOnlyDictionary<string, CommanderRuntime> EnsureFleetCommanders(
      SimulationWorld world,
      FactionRuntime faction,
      CommanderRuntime factionCommander,
      DateTimeOffset nowUtc)
    {
        var activeCampaignIds = faction.StrategicState.Campaigns
          .Where(campaign =>
            campaign.Status == "active" &&
            faction.StrategicState.Objectives.Any(objective =>
              objective.CampaignId == campaign.Id &&
              objective.CommanderId is not null &&
              (IsCombatObjective(objective) || string.Equals(objective.BehaviorKind, SupplyFleet, StringComparison.Ordinal))))
          .Select(campaign => campaign.Id)
          .ToHashSet(StringComparer.Ordinal);

        world.Commanders.RemoveAll(commander =>
          commander.Kind == CommanderKind.Fleet &&
          commander.FactionId == faction.Id &&
          (commander.ControlledEntityId is null || !activeCampaignIds.Contains(commander.ControlledEntityId)));

        var fleetCommanders = new Dictionary<string, CommanderRuntime>(StringComparer.Ordinal);
        foreach (var campaign in faction.StrategicState.Campaigns.Where(campaign => activeCampaignIds.Contains(campaign.Id)))
        {
            var commander = world.Commanders.FirstOrDefault(candidate =>
              candidate.Kind == CommanderKind.Fleet &&
              candidate.FactionId == faction.Id &&
              string.Equals(candidate.ControlledEntityId, campaign.Id, StringComparison.Ordinal));
            if (commander is null)
            {
                commander = new CommanderRuntime
                {
                    Id = $"commander-fleet-{campaign.Id}",
                    Kind = CommanderKind.Fleet,
                    FactionId = faction.Id,
                    ParentCommanderId = factionCommander.Id,
                    ControlledEntityId = campaign.Id,
                    PolicySetId = factionCommander.PolicySetId,
                    Doctrine = "fleet-control",
                    Skills = new CommanderSkillProfileRuntime
                    {
                        Leadership = Math.Clamp(factionCommander.Skills.Leadership, 3, 5),
                        Coordination = Math.Clamp(factionCommander.Skills.Coordination + 1, 3, 5),
                        Strategy = Math.Clamp(factionCommander.Skills.Strategy, 3, 5),
                    },
                    ReplanTimer = 0f,
                    NeedsReplan = true,
                };
                world.Commanders.Add(commander);
            }

            commander.ParentCommanderId = factionCommander.Id;
            commander.PolicySetId = factionCommander.PolicySetId;
            commander.IsAlive = true;
            commander.Assignment = new CommanderAssignmentRuntime
            {
                ObjectiveId = campaign.Id,
                CampaignId = campaign.Id,
                TheaterId = campaign.TheaterId,
                Kind = "fleet-command",
                BehaviorKind = campaign.Kind switch
                {
                    "offense" => AttackTarget,
                    "defense" => ProtectPosition,
                    "expansion" => ProtectPosition,
                    _ => Patrol,
                },
                Status = campaign.Status,
                Priority = campaign.Priority + 1f,
                HomeSystemId = campaign.TargetSystemId,
                TargetSystemId = campaign.TargetSystemId,
                TargetEntityId = campaign.TargetEntityId,
                Notes = campaign.Summary,
                UpdatedAtUtc = nowUtc,
            };
            campaign.FleetCommanderId = commander.Id;
            fleetCommanders[campaign.Id] = commander;
        }

        return fleetCommanders;
    }

    private static bool ApplyShipControlSurface(
      SimulationWorld world,
      FactionRuntime faction,
      CommanderRuntime factionCommander,
      CommanderRuntime commander,
      ShipRuntime ship,
      FactionOperationalObjectiveRuntime? objective,
      IReadOnlyDictionary<string, CommanderRuntime> fleetCommanders,
      DateTimeOffset nowUtc)
    {
        var desiredParentCommanderId = ResolveDelegatedParent(world, factionCommander, commander, objective, fleetCommanders);
        var parentChanged = !string.Equals(commander.ParentCommanderId, desiredParentCommanderId, StringComparison.Ordinal);
        var policyChanged = !string.Equals(commander.PolicySetId, factionCommander.PolicySetId, StringComparison.Ordinal);
        commander.ParentCommanderId = desiredParentCommanderId;
        commander.PolicySetId = factionCommander.PolicySetId;
        ship.PolicySetId = commander.PolicySetId;

        var desiredBehavior = objective is null
          ? BuildFallbackBehavior(world, ship)
          : BuildBehaviorForObjective(world, ship, objective);
        var behaviorChanged = !DefaultBehaviorsEqual(ship.DefaultBehavior, desiredBehavior);
        if (behaviorChanged)
        {
            ApplyBehavior(ship.DefaultBehavior, desiredBehavior);
        }

        var desiredOrder = BuildAiOrderForObjective(world, ship, objective, nowUtc);
        var ordersChanged = ReconcileAiOrders(ship, desiredOrder);
        var desiredControlSourceKind = objective is null ? "faction-fallback" : "faction-objective";
        var desiredControlSourceId = objective?.Id ?? commander.Id;
        var desiredControlReason = objective?.Notes ?? objective?.Kind ?? desiredBehavior.Kind;
        var controlChanged =
          !string.Equals(ship.ControlSourceKind, desiredControlSourceKind, StringComparison.Ordinal)
          || !string.Equals(ship.ControlSourceId, desiredControlSourceId, StringComparison.Ordinal)
          || !string.Equals(ship.ControlReason, desiredControlReason, StringComparison.Ordinal);
        ship.ControlSourceKind = desiredControlSourceKind;
        ship.ControlSourceId = desiredControlSourceId;
        ship.ControlReason = desiredControlReason;
        ship.LastDeltaSignature = parentChanged || controlChanged ? string.Empty : ship.LastDeltaSignature;
        return policyChanged || behaviorChanged || ordersChanged;
    }

    private static string ResolveDelegatedParent(
      SimulationWorld world,
      CommanderRuntime factionCommander,
      CommanderRuntime commander,
      FactionOperationalObjectiveRuntime? objective,
      IReadOnlyDictionary<string, CommanderRuntime> fleetCommanders)
    {
        if (objective?.CampaignId is not null
            && fleetCommanders.TryGetValue(objective.CampaignId, out var fleetCommander)
            && (IsCombatObjective(objective) || string.Equals(objective.BehaviorKind, SupplyFleet, StringComparison.Ordinal)))
        {
            return fleetCommander.Id;
        }

        if (objective?.HomeStationId is not null
            && world.Stations.FirstOrDefault(station => station.Id == objective.HomeStationId)?.CommanderId is { } stationCommanderId)
        {
            return stationCommanderId;
        }

        return factionCommander.Id;
    }

    private static DefaultBehaviorRuntime BuildFallbackBehavior(SimulationWorld world, ShipRuntime ship)
    {
        var homeStation = ResolveFallbackHomeStation(world, ship);
        if (IsMiningShip(ship.Definition))
        {
            if (homeStation is null)
            {
                return new DefaultBehaviorRuntime
                {
                    Kind = LocalAutoMine,
                    HomeSystemId = ship.SystemId,
                    HomeStationId = null,
                    AreaSystemId = ship.SystemId,
                    ItemId = "ore",
                    Radius = 24f,
                    MaxSystemRange = 0,
                };
            }

            return new DefaultBehaviorRuntime
            {
                Kind = ship.Definition.GetTotalCargoCapacity() >= 120f ? ExpertAutoMine : AdvancedAutoMine,
                HomeSystemId = homeStation?.SystemId ?? ship.SystemId,
                HomeStationId = homeStation?.Id,
                AreaSystemId = homeStation?.SystemId ?? ship.SystemId,
                ItemId = null,
                Radius = 24f,
                MaxSystemRange = ship.Definition.GetTotalCargoCapacity() >= 120f ? 3 : 1,
            };
        }

        if (IsTransportShip(ship.Definition))
        {
            return new DefaultBehaviorRuntime
            {
                Kind = AdvancedAutoTrade,
                HomeSystemId = homeStation?.SystemId ?? ship.SystemId,
                HomeStationId = homeStation?.Id,
                AreaSystemId = homeStation?.SystemId ?? ship.SystemId,
                Radius = 24f,
                MaxSystemRange = 2,
            };
        }

        if (IsConstructionShip(ship.Definition))
        {
            return new DefaultBehaviorRuntime
            {
                Kind = ConstructStation,
                HomeSystemId = homeStation?.SystemId ?? ship.SystemId,
                HomeStationId = homeStation?.Id,
                AreaSystemId = homeStation?.SystemId ?? ship.SystemId,
                Radius = 28f,
                MaxSystemRange = 2,
            };
        }

        if (IsMilitaryShip(ship.Definition))
        {
            var anchor = homeStation?.Position ?? ship.Position;
            var patrolRadius = (homeStation?.Radius ?? 30f) + 90f;
            return new DefaultBehaviorRuntime
            {
                Kind = Patrol,
                HomeSystemId = homeStation?.SystemId ?? ship.SystemId,
                HomeStationId = homeStation?.Id,
                AreaSystemId = homeStation?.SystemId ?? ship.SystemId,
                TargetPosition = anchor,
                PatrolPoints = BuildPatrolPoints(anchor, patrolRadius),
                PatrolIndex = ship.DefaultBehavior.PatrolIndex,
                Radius = patrolRadius,
                MaxSystemRange = 1,
                WaitSeconds = 2f,
                RepeatIndex = ship.DefaultBehavior.RepeatIndex,
            };
        }

        return new DefaultBehaviorRuntime
        {
            Kind = Idle,
            HomeSystemId = homeStation?.SystemId ?? ship.SystemId,
            HomeStationId = homeStation?.Id,
            AreaSystemId = homeStation?.SystemId ?? ship.SystemId,
        };
    }

    private static StationRuntime? ResolveFallbackHomeStation(SimulationWorld world, ShipRuntime ship) =>
      world.Stations
        .Where(station => station.FactionId == ship.FactionId)
        .OrderByDescending(station => station.SystemId == ship.SystemId ? 1 : 0)
        .ThenBy(station => station.Position.DistanceTo(ship.Position))
        .ThenBy(station => station.Id, StringComparer.Ordinal)
        .FirstOrDefault();

    private static DefaultBehaviorRuntime BuildBehaviorForObjective(
      SimulationWorld world,
      ShipRuntime ship,
      FactionOperationalObjectiveRuntime objective)
    {
        var fallback = BuildFallbackBehavior(world, ship);
        var areaSystemId = objective.TargetSystemId ?? objective.HomeSystemId ?? fallback.AreaSystemId ?? ship.SystemId;
        var radius = objective.BehaviorKind switch
        {
            ProtectPosition or ProtectStation or Patrol or Police => MathF.Max(80f, fallback.Radius),
            FollowShip or ProtectShip => MathF.Max(18f, fallback.Radius * 0.6f),
            FillShortages or AdvancedAutoTrade or FindBuildTasks => MathF.Max(20f, fallback.Radius),
            _ => fallback.Radius,
        };
        var maxRange = objective.BehaviorKind switch
        {
            AttackTarget or ProtectPosition or ProtectStation or ProtectShip or Patrol or Police => Math.Max(1, fallback.MaxSystemRange),
            FillShortages or AdvancedAutoTrade or FindBuildTasks or SupplyFleet => Math.Max(2, fallback.MaxSystemRange),
            _ => fallback.MaxSystemRange,
        };

        return new DefaultBehaviorRuntime
        {
            Kind = objective.BehaviorKind,
            HomeSystemId = objective.HomeSystemId ?? fallback.HomeSystemId ?? ship.SystemId,
            HomeStationId = objective.HomeStationId ?? fallback.HomeStationId,
            AreaSystemId = areaSystemId,
            TargetEntityId = objective.TargetEntityId,
            ItemId = objective.ItemId ?? fallback.ItemId,
            PreferredNodeId = fallback.PreferredNodeId,
            PreferredConstructionSiteId = objective.Kind is "construct-site" or "supply-site" ? objective.TargetEntityId : fallback.PreferredConstructionSiteId,
            PreferredModuleId = fallback.PreferredModuleId,
            TargetPosition = objective.TargetPosition ?? fallback.TargetPosition,
            WaitSeconds = objective.BehaviorKind == SupplyFleet ? 4f : fallback.WaitSeconds,
            Radius = radius,
            MaxSystemRange = maxRange,
            KnownStationsOnly = objective.BehaviorKind == RevisitKnownStations,
            PatrolPoints = objective.BehaviorKind == Patrol
            ? BuildPatrolPoints(objective.TargetPosition ?? fallback.TargetPosition ?? ship.Position, radius)
            : [],
            PatrolIndex = ship.DefaultBehavior.PatrolIndex,
            RepeatOrders = [],
            RepeatIndex = ship.DefaultBehavior.RepeatIndex,
        };
    }

    private static void ApplyBehavior(DefaultBehaviorRuntime target, DefaultBehaviorRuntime source)
    {
        target.Kind = source.Kind;
        target.HomeSystemId = source.HomeSystemId;
        target.HomeStationId = source.HomeStationId;
        target.AreaSystemId = source.AreaSystemId;
        target.TargetEntityId = source.TargetEntityId;
        target.ItemId = source.ItemId;
        target.PreferredNodeId = source.PreferredNodeId;
        target.PreferredConstructionSiteId = source.PreferredConstructionSiteId;
        target.PreferredModuleId = source.PreferredModuleId;
        target.TargetPosition = source.TargetPosition;
        target.WaitSeconds = source.WaitSeconds;
        target.Radius = source.Radius;
        target.MaxSystemRange = source.MaxSystemRange;
        target.KnownStationsOnly = source.KnownStationsOnly;
        target.PatrolPoints = source.PatrolPoints.ToList();
        target.PatrolIndex = source.PatrolIndex;
        target.RepeatOrders = source.RepeatOrders.ToList();
        target.RepeatIndex = source.RepeatIndex;
    }

    private static bool DefaultBehaviorsEqual(DefaultBehaviorRuntime left, DefaultBehaviorRuntime right) =>
      string.Equals(left.Kind, right.Kind, StringComparison.Ordinal)
      && string.Equals(left.HomeSystemId, right.HomeSystemId, StringComparison.Ordinal)
      && string.Equals(left.HomeStationId, right.HomeStationId, StringComparison.Ordinal)
      && string.Equals(left.AreaSystemId, right.AreaSystemId, StringComparison.Ordinal)
      && string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
      && string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
      && string.Equals(left.PreferredNodeId, right.PreferredNodeId, StringComparison.Ordinal)
      && string.Equals(left.PreferredConstructionSiteId, right.PreferredConstructionSiteId, StringComparison.Ordinal)
      && string.Equals(left.PreferredModuleId, right.PreferredModuleId, StringComparison.Ordinal)
      && Nullable.Equals(left.TargetPosition, right.TargetPosition)
      && left.WaitSeconds.Equals(right.WaitSeconds)
      && left.Radius.Equals(right.Radius)
      && left.MaxSystemRange == right.MaxSystemRange
      && left.KnownStationsOnly == right.KnownStationsOnly
      && left.PatrolPoints.SequenceEqual(right.PatrolPoints)
      && left.RepeatOrders.Count == right.RepeatOrders.Count
      && left.RepeatOrders.Zip(right.RepeatOrders, ShipOrderTemplatesEqual).All(equal => equal);

    private static bool ShipOrderTemplatesEqual(ShipOrderTemplateRuntime left, ShipOrderTemplateRuntime right) =>
      string.Equals(left.Kind, right.Kind, StringComparison.Ordinal)
      && string.Equals(left.Label, right.Label, StringComparison.Ordinal)
      && string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
      && string.Equals(left.TargetSystemId, right.TargetSystemId, StringComparison.Ordinal)
      && Nullable.Equals(left.TargetPosition, right.TargetPosition)
      && string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
      && string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
      && string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
      && string.Equals(left.NodeId, right.NodeId, StringComparison.Ordinal)
      && string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
      && string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
      && left.WaitSeconds.Equals(right.WaitSeconds)
      && left.Radius.Equals(right.Radius)
      && left.MaxSystemRange == right.MaxSystemRange
      && left.KnownStationsOnly == right.KnownStationsOnly;

    private static ShipOrderRuntime? BuildAiOrderForObjective(
      SimulationWorld world,
      ShipRuntime ship,
      FactionOperationalObjectiveRuntime? objective,
      DateTimeOffset nowUtc)
    {
        if (objective is null || !objective.UseOrders || string.IsNullOrWhiteSpace(objective.StagingOrderKind))
        {
            return null;
        }

        var targetSystemId = objective.TargetSystemId ?? objective.HomeSystemId ?? ship.SystemId;
        var targetPosition = objective.TargetPosition
          ?? ResolveEntityPosition(world, objective.TargetEntityId)
          ?? ship.Position;
        var alreadyInSystem = string.Equals(ship.SystemId, targetSystemId, StringComparison.Ordinal);
        var inPosition = alreadyInSystem && ship.Position.DistanceTo(targetPosition) <= MathF.Max(14f, objective.ReinforcementLevel * 18f);
        if (inPosition)
        {
            return null;
        }

        return new ShipOrderRuntime
        {
            Id = $"ai-order-{objective.Id}",
            Kind = objective.StagingOrderKind,
            SourceKind = ShipOrderSourceKind.Commander,
            SourceId = objective.Id,
            Priority = 90 + objective.ReinforcementLevel,
            InterruptCurrentPlan = true,
            Label = $"{objective.Kind} staging",
            TargetEntityId = objective.TargetEntityId,
            TargetSystemId = targetSystemId,
            TargetPosition = targetPosition,
            DestinationStationId = objective.BehaviorKind == DockAndWait ? objective.TargetEntityId : null,
            ItemId = objective.ItemId,
            WaitSeconds = 0f,
            Radius = MathF.Max(12f, objective.ReinforcementLevel * 18f),
            MaxSystemRange = null,
            KnownStationsOnly = false,
        };
    }

    private static Vector3? ResolveEntityPosition(SimulationWorld world, string? entityId)
    {
        if (entityId is null)
        {
            return null;
        }

        var shipPosition = world.Ships.FirstOrDefault(ship => ship.Id == entityId)?.Position;
        if (shipPosition is not null)
        {
            return shipPosition;
        }

        var stationPosition = world.Stations.FirstOrDefault(station => station.Id == entityId)?.Position;
        if (stationPosition is not null)
        {
            return stationPosition;
        }

        var site = world.ConstructionSites.FirstOrDefault(candidate => candidate.Id == entityId);
        if (site?.CelestialId is { } celestialId)
        {
            return world.Celestials.FirstOrDefault(celestial => celestial.Id == celestialId)?.Position;
        }

        return null;
    }

    private static bool ReconcileAiOrders(ShipRuntime ship, ShipOrderRuntime? desiredOrder)
    {
        var changed = ship.OrderQueue.RemoveAll(order => order.Id.StartsWith("ai-order-", StringComparison.Ordinal) && (desiredOrder is null || !string.Equals(order.Id, desiredOrder.Id, StringComparison.Ordinal))) > 0;
        if (desiredOrder is null)
        {
            return changed;
        }

        var existing = ship.OrderQueue.FirstOrDefault(order => string.Equals(order.Id, desiredOrder.Id, StringComparison.Ordinal));
        if (existing is not null)
        {
            if (ShipOrdersEqual(existing, desiredOrder))
            {
                return changed;
            }

            ship.OrderQueue.Remove(existing);
            changed = true;
        }

        if (ship.OrderQueue.Count >= MaxAiOrdersPerShip)
        {
            changed |= ship.OrderQueue.RemoveAll(order => order.Id.StartsWith("ai-order-", StringComparison.Ordinal)) > 0;
        }

        if (ship.OrderQueue.Count < 8)
        {
            ship.OrderQueue.Add(desiredOrder);
            changed = true;
        }

        return changed;
    }

    private static bool ShipOrdersEqual(ShipOrderRuntime left, ShipOrderRuntime right) =>
      string.Equals(left.Id, right.Id, StringComparison.Ordinal)
      && string.Equals(left.Kind, right.Kind, StringComparison.Ordinal)
      && left.SourceKind == right.SourceKind
      && string.Equals(left.SourceId, right.SourceId, StringComparison.Ordinal)
      && left.Priority == right.Priority
      && left.InterruptCurrentPlan == right.InterruptCurrentPlan
      && string.Equals(left.Label, right.Label, StringComparison.Ordinal)
      && string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
      && string.Equals(left.TargetSystemId, right.TargetSystemId, StringComparison.Ordinal)
      && Nullable.Equals(left.TargetPosition, right.TargetPosition)
      && string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
      && string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
      && string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
      && string.Equals(left.NodeId, right.NodeId, StringComparison.Ordinal)
      && string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
      && string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
      && left.WaitSeconds.Equals(right.WaitSeconds)
      && left.Radius.Equals(right.Radius)
      && left.MaxSystemRange == right.MaxSystemRange
      && left.KnownStationsOnly == right.KnownStationsOnly;

    private static void RefreshCommanderHierarchy(SimulationWorld world, string factionId)
    {
        foreach (var commander in world.Commanders.Where(candidate => candidate.FactionId == factionId))
        {
            commander.SubordinateCommanderIds.Clear();
        }

        var commandersById = world.Commanders.ToDictionary(commander => commander.Id, StringComparer.Ordinal);
        foreach (var commander in world.Commanders.Where(candidate => candidate.FactionId == factionId && candidate.ParentCommanderId is not null))
        {
            if (commander.ParentCommanderId is { } parentCommanderId && commandersById.TryGetValue(parentCommanderId, out var parent))
            {
                parent.SubordinateCommanderIds.Add(commander.Id);
            }
        }
    }

    private static bool IsCombatObjective(FactionOperationalObjectiveRuntime objective) =>
      objective.BehaviorKind is AttackTarget or ProtectPosition or ProtectShip or ProtectStation or Patrol or Police;

    private static float EstimateFriendlyAssetValue(SimulationWorld world, string factionId, string systemId)
    {
        var stationValue = world.Stations.Count(station => station.FactionId == factionId && station.SystemId == systemId) * 35f;
        var shipValue = world.Ships.Count(ship => ship.FactionId == factionId && ship.SystemId == systemId && ship.Health > 0f) * 6f;
        return stationValue + shipValue;
    }

    private static bool CanRunOffensivePosture(
      FactionRuntime faction,
      FactionEconomicAssessmentRuntime economicAssessment,
      FactionThreatAssessmentRuntime threatAssessment)
    {
        var defenseLoad = threatAssessment.ThreatSignals.Count(signal => signal.ScopeKind is "controlled-system" or "contested-system");
        var militarySurplus = economicAssessment.MilitaryShipCount
          - Math.Max(1, economicAssessment.TargetMilitaryShipCount - faction.Doctrine.ReinforcementLeadPerFront);
        return economicAssessment.SustainmentScore >= faction.Doctrine.OffensiveReadinessThreshold
          && economicAssessment.LogisticsSecurityScore >= faction.Doctrine.SupplySecurityBias
          && militarySurplus > 0
          && defenseLoad <= Math.Max(1, economicAssessment.ControlledSystemCount / 2);
    }

    private static bool CanSupportExpansion(
      FactionRuntime faction,
      FactionEconomicAssessmentRuntime economicAssessment,
      FactionThreatAssessmentRuntime threatAssessment) =>
      economicAssessment.ConstructorShipCount > 0
      && economicAssessment.SustainmentScore >= 0.52f
      && threatAssessment.ThreatSignals.Count(signal => signal.ScopeKind == "controlled-system") <= 1
      && faction.StrategicState.Budget.ExpansionCredits > 0f;

    private static float ComputeSystemRisk(SimulationWorld world, FactionRuntime faction, string systemId) =>
      MathF.Max(
        faction.Memory.SystemMemories.FirstOrDefault(memory => memory.SystemId == systemId)?.RouteRisk ?? 0f,
        GeopoliticalSimulationService.GetSystemRouteRisk(world, systemId, faction.Id));

    private static SectorStrategicProfileRuntime? FindStrategicProfile(SimulationWorld world, string systemId) =>
      world.Geopolitics?.Territory.StrategicProfiles.FirstOrDefault(profile => string.Equals(profile.SystemId, systemId, StringComparison.Ordinal));

    private static TerritoryPressureRuntime? FindTerritoryPressure(SimulationWorld world, string factionId, string systemId) =>
      world.Geopolitics?.Territory.Pressures
        .Where(pressure => string.Equals(pressure.SystemId, systemId, StringComparison.Ordinal)
          && (pressure.FactionId is null || string.Equals(pressure.FactionId, factionId, StringComparison.Ordinal)))
        .OrderByDescending(pressure => pressure.PressureScore + pressure.CorridorRisk)
        .ThenBy(pressure => pressure.Id, StringComparer.Ordinal)
        .FirstOrDefault();

    private static RegionalSecurityAssessmentRuntime? FindRegionalSecurityAssessment(SimulationWorld world, string factionId, string systemId)
    {
        var regionId = GeopoliticalSimulationService.GetPrimaryEconomicRegion(world, factionId, systemId)?.Id;
        return regionId is null
          ? null
          : world.Geopolitics?.EconomyRegions.SecurityAssessments.FirstOrDefault(assessment => string.Equals(assessment.RegionId, regionId, StringComparison.Ordinal));
    }

    private static IEnumerable<OffensiveTargetCandidate> SelectOffensiveTargets(
      SimulationWorld world,
      FactionRuntime faction,
      FactionThreatAssessmentRuntime threatAssessment,
      FactionEconomicAssessmentRuntime economicAssessment)
    {
        return threatAssessment.ThreatSignals
          .Where(signal => signal.ScopeKind == "hostile-system")
          .Select(signal =>
          {
              var memory = faction.Memory.SystemMemories.FirstOrDefault(candidate => candidate.SystemId == signal.ScopeId);
              var distancePenalty = faction.Memory.SystemMemories
            .Where(candidate => candidate.ControlledByFaction)
            .Select(candidate => GetSystemDistanceTier(world, candidate.SystemId, signal.ScopeId))
            .DefaultIfEmpty(world.Systems.Count)
            .Min();
              var failurePenalty = ((memory?.OffensiveFailures ?? 0) * 16f) + ((memory?.DefensiveFailures ?? 0) * 4f);
              var value = (signal.EnemyStationCount * 28f) + (signal.EnemyShipCount * 8f);
              var pressureBias = MathF.Max(0f, economicAssessment.SustainmentScore - 0.5f) * 25f;
              var supplyRisk = memory?.RouteRisk ?? 0.1f;
              var priority = 62f + value + pressureBias - (distancePenalty * 9f) - failurePenalty - (supplyRisk * 18f);
              return new OffensiveTargetCandidate(
            signal.ScopeId,
            signal.EnemyFactionId,
            ResolvePrimaryOffensiveAnchor(world, faction.Id, signal.ScopeId),
            ResolveSystemAnchor(world, signal.ScopeId),
            MathF.Max(0f, priority),
            supplyRisk,
            value);
          })
          .Where(candidate => candidate.Priority > 35f)
          .OrderByDescending(candidate => candidate.Priority)
          .ThenBy(candidate => candidate.SystemId, StringComparer.Ordinal);
    }

    private static string? ResolvePrimaryOffensiveAnchor(SimulationWorld world, string factionId, string systemId) =>
      world.Stations
        .Where(station => station.FactionId != factionId && station.SystemId == systemId)
        .OrderByDescending(station => station.InstalledModules.Contains("module_gen_build_l_01", StringComparer.Ordinal) ? 1 : 0)
        .ThenBy(station => station.Id, StringComparer.Ordinal)
        .Select(station => station.Id)
        .FirstOrDefault();

    private static bool StationCanProduceItem(SimulationWorld world, StationRuntime station, string itemId) =>
      world.Recipes.Values.Any(recipe =>
        StationSimulationService.RecipeAppliesToStation(station, recipe)
        && recipe.Outputs.Any(output => string.Equals(output.ItemId, itemId, StringComparison.Ordinal)));

    private static float ComputeCampaignSupplyAdequacy(
      FactionRuntime faction,
      FactionTheaterRuntime theater,
      FactionEconomicAssessmentRuntime economicAssessment)
    {
        var riskPenalty = theater.SupplyRisk * 0.45f;
        var assetBias = MathF.Min(0.25f, theater.FriendlyAssetValue / 200f);
        return Math.Clamp((economicAssessment.SustainmentScore * 0.75f) + assetBias - riskPenalty, 0f, 1f);
    }

    private static float ComputeCampaignContinuationScore(
      FactionRuntime faction,
      FactionTheaterRuntime theater,
      FactionEconomicAssessmentRuntime economicAssessment,
      FactionSystemMemoryRuntime? systemMemory)
    {
        var successBias = theater.Kind switch
        {
            "offense-front" => (systemMemory?.OffensiveSuccesses ?? 0) * 0.08f,
            _ => (systemMemory?.DefensiveSuccesses ?? 0) * 0.06f,
        };
        var failurePenalty = theater.Kind switch
        {
            "offense-front" => (systemMemory?.OffensiveFailures ?? 0) * (0.09f + faction.Doctrine.FailureAversion * 0.1f),
            _ => (systemMemory?.DefensiveFailures ?? 0) * 0.06f,
        };
        return Math.Clamp(
          0.35f
          + (theater.Priority / 160f)
          + (economicAssessment.SustainmentScore * 0.4f)
          + successBias
          - failurePenalty
          - (theater.SupplyRisk * 0.3f),
          0f,
          1.4f);
    }

    private static string? ResolveCampaignPauseReason(
      FactionRuntime faction,
      FactionTheaterRuntime theater,
      FactionEconomicAssessmentRuntime economicAssessment,
      FactionThreatAssessmentRuntime threatAssessment,
      FactionSystemMemoryRuntime? systemMemory)
    {
        return theater.Kind switch
        {
            "offense-front" when !CanRunOffensivePosture(faction, economicAssessment, threatAssessment) => "offensive-readiness-insufficient",
            "offense-front" when (systemMemory?.OffensiveFailures ?? 0) >= 2 && economicAssessment.SustainmentScore < 0.75f => "recover-after-failure",
            "offense-front" when economicAssessment.ReplacementPressure > Math.Max(4f, economicAssessment.TargetMilitaryShipCount * 0.4f) => "replacement-pressure",
            "expansion-front" when !CanSupportExpansion(faction, economicAssessment, threatAssessment) => "expansion-delayed",
            "economic-front" when economicAssessment.CriticalShortageCount == 0 && economicAssessment.SustainmentScore > 0.7f => "economy-stable",
            _ => null,
        };
    }

    private static int GetCampaignFailureCount(FactionSystemMemoryRuntime? systemMemory, string theaterKind) =>
      theaterKind switch
      {
          "offense-front" => systemMemory?.OffensiveFailures ?? 0,
          _ => systemMemory?.DefensiveFailures ?? 0,
      };

    private static int GetCampaignSuccessCount(FactionSystemMemoryRuntime? systemMemory, string theaterKind) =>
      theaterKind switch
      {
          "offense-front" => systemMemory?.OffensiveSuccesses ?? 0,
          _ => systemMemory?.DefensiveSuccesses ?? 0,
      };

    private static bool RequiresReinforcement(
      FactionTheaterRuntime theater,
      FactionEconomicAssessmentRuntime economicAssessment,
      FactionThreatAssessmentRuntime threatAssessment) =>
      theater.Kind == "defense-front"
        ? theater.Priority > 105f || threatAssessment.ThreatSignals.Count(signal => signal.ScopeKind == "controlled-system") > 1
        : theater.Kind == "offense-front"
          ? economicAssessment.SustainmentScore > 0.7f && economicAssessment.MilitaryShipCount > economicAssessment.TargetMilitaryShipCount
          : theater.Kind == "economic-front"
            ? economicAssessment.CriticalShortageCount > 2
            : false;

    private static void UpdateCampaignMemoryFromOutcome(
      SimulationWorld world,
      FactionRuntime faction,
      FactionCampaignRuntime campaign,
      FactionEconomicAssessmentRuntime economicAssessment,
      FactionThreatAssessmentRuntime threatAssessment,
      DateTimeOffset nowUtc)
    {
        var systemMemory = campaign.TargetSystemId is null
          ? null
          : faction.Memory.SystemMemories.FirstOrDefault(memory => memory.SystemId == campaign.TargetSystemId);
        var success = campaign.Kind switch
        {
            "defense" => campaign.TargetSystemId is not null
              && FactionControlsSystem(world, faction.Id, campaign.TargetSystemId)
              && threatAssessment.ThreatSignals.All(signal => signal.ScopeId != campaign.TargetSystemId || signal.ScopeKind == "hostile-system"),
            "offense" => campaign.TargetSystemId is not null
              && (FactionControlsSystem(world, faction.Id, campaign.TargetSystemId)
                  || world.Stations.All(station => station.FactionId == faction.Id || station.SystemId != campaign.TargetSystemId)),
            "expansion" => campaign.TargetSystemId is not null
              && (world.ConstructionSites.Any(site => site.FactionId == faction.Id && site.SystemId == campaign.TargetSystemId)
                  || world.Stations.Any(station => station.FactionId == faction.Id && station.SystemId == campaign.TargetSystemId)),
            "economic-stabilization" => campaign.CommodityId is not null
              && economicAssessment.CommoditySignals.FirstOrDefault(signal => signal.ItemId == campaign.CommodityId) is { Level: not ("critical" or "low") },
            "force-build-up" => economicAssessment.MilitaryShipCount >= economicAssessment.TargetMilitaryShipCount,
            _ => true,
        };

        if (systemMemory is not null)
        {
            if (campaign.Kind == "offense")
            {
                if (success)
                {
                    systemMemory.OffensiveSuccesses += 1;
                }
                else
                {
                    systemMemory.OffensiveFailures += 1;
                }
            }
            else if (campaign.Kind == "defense")
            {
                if (success)
                {
                    systemMemory.DefensiveSuccesses += 1;
                    systemMemory.FrontierPressure *= 0.75f;
                    systemMemory.RouteRisk *= 0.8f;
                }
                else
                {
                    systemMemory.DefensiveFailures += 1;
                }
            }
        }

        AppendOutcome(faction, new FactionOutcomeRecordRuntime
        {
            Id = $"outcome-{campaign.Id}-{nowUtc.ToUnixTimeMilliseconds()}",
            Kind = success ? "campaign-success" : "campaign-failure",
            Summary = $"{campaign.Kind} campaign {campaign.Id} {(success ? "succeeded" : "failed")}.",
            RelatedCampaignId = campaign.Id,
            OccurredAtUtc = nowUtc,
        });
    }

    private static int GetSystemDistanceTier(SimulationWorld world, string originSystemId, string targetSystemId)
    {
        if (string.Equals(originSystemId, targetSystemId, StringComparison.Ordinal))
        {
            return 0;
        }

        var originPosition = world.Systems.FirstOrDefault(system => system.Definition.Id == originSystemId)?.Position ?? Vector3.Zero;
        return world.Systems
          .OrderBy(system => system.Position.DistanceTo(originPosition))
          .ThenBy(system => system.Definition.Id, StringComparer.Ordinal)
          .Select(system => system.Definition.Id)
          .TakeWhile(systemId => !string.Equals(systemId, targetSystemId, StringComparison.Ordinal))
          .Count();
    }

    private static List<Vector3> BuildPatrolPoints(Vector3 anchor, float radius) =>
    [
      new Vector3(anchor.X + radius, anchor.Y, anchor.Z),
    new Vector3(anchor.X, anchor.Y, anchor.Z + radius),
    new Vector3(anchor.X - radius, anchor.Y, anchor.Z),
    new Vector3(anchor.X, anchor.Y, anchor.Z - radius),
  ];

    private sealed record OffensiveTargetCandidate(
      string SystemId,
      string? TargetFactionId,
      string? AnchorEntityId,
      Vector3 AnchorPosition,
      float Priority,
      float SupplyRisk,
      float Value);

    private static CommanderAssignmentRuntime ToAssignment(FactionOperationalObjectiveRuntime objective) => new()
    {
        ObjectiveId = objective.Id,
        CampaignId = objective.CampaignId,
        TheaterId = objective.TheaterId,
        Kind = objective.Kind,
        BehaviorKind = objective.BehaviorKind,
        Status = objective.Status,
        Priority = objective.Priority,
        HomeSystemId = objective.HomeSystemId,
        HomeStationId = objective.HomeStationId,
        TargetSystemId = objective.TargetSystemId,
        TargetEntityId = objective.TargetEntityId,
        TargetPosition = objective.TargetPosition,
        ItemId = objective.ItemId,
        Notes = objective.Notes,
        UpdatedAtUtc = objective.UpdatedAtUtc,
    };

    private static bool AssignmentsEqual(CommanderAssignmentRuntime? left, CommanderAssignmentRuntime? right)
    {
        if (ReferenceEquals(left, right))
        {
            return true;
        }

        if (left is null || right is null)
        {
            return false;
        }

        return string.Equals(left.ObjectiveId, right.ObjectiveId, StringComparison.Ordinal)
          && string.Equals(left.CampaignId, right.CampaignId, StringComparison.Ordinal)
          && string.Equals(left.TheaterId, right.TheaterId, StringComparison.Ordinal)
          && string.Equals(left.Kind, right.Kind, StringComparison.Ordinal)
          && string.Equals(left.BehaviorKind, right.BehaviorKind, StringComparison.Ordinal)
          && string.Equals(left.Status, right.Status, StringComparison.Ordinal)
          && string.Equals(left.HomeSystemId, right.HomeSystemId, StringComparison.Ordinal)
          && string.Equals(left.HomeStationId, right.HomeStationId, StringComparison.Ordinal)
          && string.Equals(left.TargetSystemId, right.TargetSystemId, StringComparison.Ordinal)
          && string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
          && Nullable.Equals(left.TargetPosition, right.TargetPosition)
          && string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
          && left.Priority.Equals(right.Priority);
    }

    private static void AppendDecision(FactionRuntime faction, FactionDecisionLogEntryRuntime entry)
    {
        if (faction.DecisionLog.Any(candidate => candidate.Id == entry.Id))
        {
            return;
        }

        faction.DecisionLog.Add(entry);
        if (faction.DecisionLog.Count > MaxDecisionLogEntries)
        {
            faction.DecisionLog.RemoveRange(0, faction.DecisionLog.Count - MaxDecisionLogEntries);
        }
    }

    private static void AppendOutcome(FactionRuntime faction, FactionOutcomeRecordRuntime entry)
    {
        if (faction.Memory.RecentOutcomes.Any(candidate => candidate.Id == entry.Id))
        {
            return;
        }

        faction.Memory.RecentOutcomes.Add(entry);
        if (faction.Memory.RecentOutcomes.Count > MaxOutcomeEntries)
        {
            faction.Memory.RecentOutcomes.RemoveRange(0, faction.Memory.RecentOutcomes.Count - MaxOutcomeEntries);
        }
    }

    private static string ResolveStrategicStatus(
      IReadOnlyCollection<FactionTheaterRuntime> theaters,
      IReadOnlyCollection<FactionCampaignRuntime> campaigns,
      FactionEconomicAssessmentRuntime economicAssessment,
      FactionThreatAssessmentRuntime threatAssessment)
    {
        if (threatAssessment.ThreatSignals.Any(signal => signal.ScopeKind == "controlled-system"))
        {
            return "defending";
        }

        if (campaigns.Any(campaign => campaign.Kind == "offense"))
        {
            return "offensive";
        }

        if (campaigns.Any(campaign => campaign.Kind == "expansion"))
        {
            return "expanding";
        }

        if (economicAssessment.CommoditySignals.Any(signal => signal.Level is "critical" or "low"))
        {
            return "stabilizing";
        }

        return theaters.Count == 0 ? "stable" : "active";
    }

    private static float ComputeCommodityPriority(FactionCommoditySignalRuntime signal)
    {
        var levelBias = signal.Level switch
        {
            "critical" => 60f,
            "low" => 35f,
            _ => 10f,
        };
        return levelBias
          + signal.BuyBacklog
          + signal.ReservedForConstruction
          + MathF.Max(0f, -signal.ProjectedNetRatePerSecond * 50f);
    }

    private static bool CanMineItem(SimulationWorld world, string itemId) =>
      world.Nodes.Any(node => string.Equals(node.ItemId, itemId, StringComparison.Ordinal));

    private static string? FindPrimaryCommoditySystem(SimulationWorld world, string factionId, string itemId) =>
      world.Geopolitics?.EconomyRegions.Bottlenecks
        .Where(bottleneck => string.Equals(bottleneck.ItemId, itemId, StringComparison.Ordinal))
        .Join(
          world.Geopolitics.EconomyRegions.Regions.Where(region => string.Equals(region.FactionId, factionId, StringComparison.Ordinal)),
          bottleneck => bottleneck.RegionId,
          region => region.Id,
          (bottleneck, region) => new { bottleneck, region })
        .OrderByDescending(entry => entry.bottleneck.Severity)
        .ThenBy(entry => entry.region.Id, StringComparer.Ordinal)
        .Select(entry => entry.region.CoreSystemId)
        .FirstOrDefault()
      ?? world.Stations
        .Where(station => station.FactionId == factionId && GetInventoryAmount(station.Inventory, itemId) > 0.01f)
        .OrderByDescending(station => GetInventoryAmount(station.Inventory, itemId))
        .ThenBy(station => station.Id, StringComparer.Ordinal)
        .Select(station => station.SystemId)
        .FirstOrDefault();

    private static StationRuntime? ResolveCommodityAnchorStation(SimulationWorld world, string factionId, string itemId) =>
      world.Stations
        .Where(station => station.FactionId == factionId)
        .OrderByDescending(station => GetInventoryAmount(station.Inventory, itemId))
        .ThenByDescending(station => station.MarketOrderIds.Count(orderId =>
          world.MarketOrders.Any(order => order.Id == orderId && order.ItemId == itemId && order.Kind == MarketOrderKinds.Buy)))
        .ThenBy(station => station.Id, StringComparer.Ordinal)
        .FirstOrDefault();

    private static string BuildCampaignSummary(FactionTheaterRuntime theater, IndustryExpansionProject? expansionProject) =>
      theater.Kind switch
      {
          "defense-front" => $"Defend {theater.SystemId} from hostile pressure.",
          "offense-front" => $"Project force into {theater.SystemId}.",
          "expansion-front" => $"Expand into {expansionProject?.CelestialId ?? theater.SystemId}.",
          "economic-front" => $"Stabilize commodity shortages around {theater.AnchorEntityId ?? theater.SystemId}.",
          _ => theater.Kind,
      };

    private static string? ResolveCommodityFromTheaterId(string? theaterId)
    {
        if (string.IsNullOrWhiteSpace(theaterId))
        {
            return null;
        }

        const string prefix = "theater-economy-";
        return theaterId.StartsWith(prefix, StringComparison.Ordinal) ? theaterId[prefix.Length..] : null;
    }

    private static Vector3 ResolveSystemAnchor(SimulationWorld world, string? systemId)
    {
        if (systemId is null)
        {
            return Vector3.Zero;
        }

        var station = world.Stations
          .Where(candidate => candidate.SystemId == systemId)
          .OrderBy(candidate => candidate.Id, StringComparer.Ordinal)
          .FirstOrDefault();
        if (station is not null)
        {
            return station.Position;
        }

        var celestial = world.Celestials
          .Where(candidate => candidate.SystemId == systemId)
          .OrderBy(candidate => candidate.Id, StringComparer.Ordinal)
          .FirstOrDefault();
        return celestial?.Position ?? Vector3.Zero;
    }

    private static Vector3 ResolveExpansionAnchor(SimulationWorld world, IndustryExpansionProject project)
    {
        if (project.SiteId is not null
            && world.ConstructionSites.FirstOrDefault(site => site.Id == project.SiteId) is { } site
            && world.Celestials.FirstOrDefault(candidate => candidate.Id == site.CelestialId) is { } siteCelestial)
        {
            return siteCelestial.Position;
        }

        return world.Celestials.FirstOrDefault(candidate => candidate.Id == project.CelestialId)?.Position
          ?? ResolveSystemAnchor(world, project.SystemId);
    }

    private static bool FactionControlsSystem(SimulationWorld world, string factionId, string systemId)
      => GeopoliticalSimulationService.FactionControlsSystem(world, factionId, systemId);
}
