using Aetheria.State.Daemon; using GameCult.Aetheria.State.Verse; using System.Globalization; var checks = new AetheriaDaemonYmirSmokeChecks(); checks.Run(); Console.WriteLine("Daemon Ymir projectile smoke passed."); internal sealed class AetheriaDaemonYmirSmokeChecks { public void Run() { YmirMovesProjectileAndReportsStableContact(); DaemonSimulationAppliesYmirHit(); ProjectileDeathEmitsOnce(); MissingPhysicsOwnerCannotAdvanceProjectiles(); MissingWorldPhysicsOwnerCannotAdvanceShips(); TractorRampsAndPullsThroughYmirWithoutTeleportingCargo(); PickupIsCapacityCheckedExactlyOnceAndExpires(); PickupShieldContactCollectsOrBounces(); ThermalCellsUseFossilConductionAndRadiation(); MultipleActorsUseTheSameMovementLever(); AgentClaimsAndCompletesExploreTaskThroughCommands(); SchedulerAssignsHighestPriorityCompatibleTask(); SchedulerRequeuesTaskFromDeadAgent(); SchedulerCollapsesDuplicateAssignmentMarkers(); SchedulerAssignsShortestGalaxyRoute(); AgentTraversesGalaxyRouteBeforeExecutingTask(); IdleAgentReturnsToCanonicalHomeAndDocks(); ControlledShipDoesNotReceiveAutonomousHelmCommands(); AttackAgentControlsOptimumRangeThroughMovementLever(); AgentCompletesAttackTaskThroughTargetFireAndYmir(); AgentCompletesHaulTaskThroughMovementAndCargoCommands(); RejectedHaulTransferDoesNotAdvanceTask(); AgentPatrolsHistoricalOrbitCircuitThroughMovementCommands(); TickReconcilesAndEvaluatesCatalogBehaviors(); AgentMinesAsteroidThroughEquippedBehavior(); CargoCapacityComesFromHullAndCatalogVolumes(); AgentSurveysBodyIntoCorporationKnowledge(); AgentTowsStationIntoPersistentOrbit(); } private static void AgentTowsStationIntoPersistentOrbit() { var tug = Entity(0, 0, "workers"); var station = Entity(1, 0, "workers"); station.Kind = "station"; tug.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Tow]; var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [tug, station], Orbits = [new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "parent-orbit", FixedPositionX = 20 }], Bodies = [new AetheriaRuntimeBodySnapshotCommit { BodyKey = "parent-body", Kind = "planet", OrbitKey = "parent-orbit" }] }; var run = new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "", Zones = [zone], AgentTasks = [new AetheriaRuntimeAgentTaskCommit { TaskId = "tow-1", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Tow, ZoneIndex = 0, Status = AetheriaRuntimeAgentTaskStatuses.Queued, TargetEntityIndex = 1, OrbitParentKey = "parent-body", OrbitDistance = 20, CompletionRadius = 5 }] }; AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-tow-attach-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 1, BuildPublications = false }); Require(run.Zones[0].Entities[0].ChildEntityIndices.Contains(1), "tow pickup must attach station to tug parentage"); RequireEqual("", run.Zones[0].Entities[1].OrbitKey, "attached station must no longer own an orbit"); AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-tow-detach-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 2, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 2, BuildPublications = false }); Require(!run.Zones[0].Entities[0].ChildEntityIndices.Contains(1), "tow delivery must detach station parentage"); Require(!string.IsNullOrWhiteSpace(run.Zones[0].Entities[1].OrbitKey), "delivered station must own a persistent orbit"); Require(run.Zones[0].Orbits.Any(orbit => orbit.OrbitKey == run.Zones[0].Entities[1].OrbitKey && orbit.ParentOrbitKey == "parent-orbit" && Math.Abs(orbit.Distance - 20) < 0.001), "delivered orbit must preserve requested parent and radius"); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, run.AgentTasks[0].Status, "tow task must complete only after detach applies"); } private static void AgentSurveysBodyIntoCorporationKnowledge() { var surveyor = Entity(0, 0, "workers"); surveyor.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; surveyor.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "scanner", Enabled = true } }]; var scanner = CatalogItem("scanner", new AetheriaRuntimeBehaviorPayload(0, "ResourceScanner", 0, [new AetheriaRuntimeBehaviorField(1, PerformanceStat(100)), new AetheriaRuntimeBehaviorField(2, PerformanceStat(4)), new AetheriaRuntimeBehaviorField(3, PerformanceStat(0.5))])); var catalog = new AetheriaRuntimeCatalogSnapshot([scanner], [], []); var run = new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [surveyor], Orbits = [new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "survey-orbit", FixedPositionX = 10 }], Bodies = [new AetheriaRuntimeBodySnapshotCommit { BodyKey = "survey-world", Kind = "planet", OrbitKey = "survey-orbit" }] }], AgentTasks = [new AetheriaRuntimeAgentTaskCommit { TaskId = "survey-1", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Explore, ZoneIndex = 0, Status = AetheriaRuntimeAgentTaskStatuses.Queued, TargetBodyKeys = ["survey-world"] }] }; AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-survey-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 1, SimulationTimeSeconds = 1, Catalog = catalog, BuildPublications = false }); var knowledge = run.CorporationSurveys.Single(); RequireEqual("workers", knowledge.CorporationKey, "survey knowledge must belong to the agent corporation"); RequireEqual("survey-world", knowledge.BodyKey, "survey knowledge must identify the scanned body"); RequireNear(4, knowledge.DensityFloor, 0.000001, "survey must publish the scanner minimum density"); AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-survey-complete-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 2, FixedDeltaSeconds = 1, SimulationTimeSeconds = 2, Catalog = catalog, BuildPublications = false }); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, run.AgentTasks[0].Status, "survey order must complete when corporation knowledge satisfies the scanner threshold"); } private static void CargoCapacityComesFromHullAndCatalogVolumes() { var hull = CatalogItem("hauler-hull"); hull.HullCapacity = PerformanceStat(20); var gear = CatalogItem("scanner"); gear.Volume = 3; var ore = CatalogItem("ore"); ore.Volume = 2; var catalog = new AetheriaRuntimeCatalogSnapshot([hull, gear, ore], [], []); var entity = Entity(0, 0, "workers"); entity.HullItemKey = hull.ItemKey; entity.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = gear.ItemKey } }]; entity.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit { Items = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = ore.ItemKey, Quantity = 4 } }] }]; RequireNear(20, AetheriaRuntimeCargoCapacityQueries.Capacity(entity, catalog), 0.000001, "cargo capacity must evaluate the authored hull performance stat"); RequireNear(11, AetheriaRuntimeCargoCapacityQueries.Occupied(entity, catalog), 0.000001, "equipment and stacked cargo must consume catalog volume"); RequireEqual(4, AetheriaRuntimeCargoCapacityQueries.UnitsThatFit(entity, catalog, ore.ItemKey), "available cargo volume must determine whole units that fit"); } private static void AgentMinesAsteroidThroughEquippedBehavior() { var miner = Entity(0, 0, "workers"); var home = Entity(1, 0, "workers"); miner.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Mine]; miner.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "mining-tool", Enabled = true, Quality = 1, Durability = 1 } }]; var minerHull = CatalogItem("miner-hull"); minerHull.HullCapacity = PerformanceStat(2); var homeHull = CatalogItem("home-hull"); homeHull.HullCapacity = PerformanceStat(100); var iron = CatalogItem("iron"); iron.SimpleCommodityCategory = "ore"; iron.Volume = 1; miner.HullItemKey = minerHull.ItemKey; home.HullItemKey = homeHull.ItemKey; miner.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit()]; home.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit()]; var catalog = new AetheriaRuntimeCatalogSnapshot( [minerHull, homeHull, iron, CatalogItem("mining-tool", new AetheriaRuntimeBehaviorPayload(0, "MiningTool", 0, [new AetheriaRuntimeBehaviorField(1, PerformanceStat(1000)), new AetheriaRuntimeBehaviorField(2, PerformanceStat(1000000000)), new AetheriaRuntimeBehaviorField(3, PerformanceStat(2)), new AetheriaRuntimeBehaviorField(4, PerformanceStat(1000))]))], [], []); var asteroid = new AetheriaRuntimeAsteroidCommit { Distance = 0, Size = 6 }; var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [miner, home], Bodies = [new AetheriaRuntimeBodySnapshotCommit { BodyKey = "belt", Kind = "asteroid_belt", Asteroids = [asteroid], Resources = [new AetheriaRuntimeBodyResourceCommit { ItemKey = "iron", Amount = 1 }] }] }; var run = new AetheriaRuntimeRunCheckpointCommit { GenerationSeed = 7, CurrentZoneIndex = 0, CurrentEntityKey = "", Zones = [zone], AgentTasks = [new AetheriaRuntimeAgentTaskCommit { TaskId = "mine-1", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Mine, ZoneIndex = 0, OriginEntityIndex = 1, CompletionRadius = 10, Status = AetheriaRuntimeAgentTaskStatuses.Queued, TargetBodyKeys = ["belt"] }] }; var result = AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-mining-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 1, SimulationTimeSeconds = 1, Catalog = catalog, BuildPublications = false }); Require(run.AgentTasks[0].Phase == "mining", "mining task must activate its equipped tool"); Require(result.OperationResult.Intents.Behaviors.Count == 1, $"mining behavior command must become daemon intent (applied={string.Join(',', result.Frame.AppliedCommandIds)}, rejected={string.Join(',', result.Frame.RejectedCommandIds)})"); var committedMiner = run.Zones[0].Entities.Single(entity => entity.EntityIndex == 0); var committedAsteroid = run.Zones[0].Bodies[0].Asteroids[0]; Require(committedAsteroid.RespawnTimer > 0, $"mining damage must deplete the asteroid and start historical respawn (damage={committedAsteroid.Damage}, respawn={committedAsteroid.RespawnTimer})"); Require(committedMiner.CargoContents.SelectMany(bay => bay.Items).Any(slot => slot.Item.ItemKey == "iron" && slot.Item.Quantity > 0), "historical mining yield must enter daemon-owned cargo"); AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-mining-offload-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 2, FixedDeltaSeconds = 1, SimulationTimeSeconds = 2, Catalog = catalog, BuildPublications = false }); Require(!run.Zones[0].Entities[0].CargoContents.SelectMany(bay => bay.Items).Any(), "full miner must offload through the shared cargo transfer command"); Require(run.Zones[0].Entities[1].CargoContents.SelectMany(bay => bay.Items).Any(slot => slot.Item.ItemKey == "iron" && slot.Item.Quantity == 1), "home storage must receive the mined commodity"); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, run.AgentTasks[0].Status, "miner must remain assigned after a successful offload"); } private static void TickReconcilesAndEvaluatesCatalogBehaviors() { var entity = Entity(0, 0, "workers"); entity.Equipment = [ new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "mining-tool", Quality = 1, Durability = 1, Enabled = true } } ]; entity.BehaviorStates = Array.Empty(); var payload = new AetheriaRuntimeBehaviorPayload( 0, "MiningTool", 0, [ new AetheriaRuntimeBehaviorField(1, PerformanceStat(12)), new AetheriaRuntimeBehaviorField(2, PerformanceStat(0.8)), new AetheriaRuntimeBehaviorField(3, PerformanceStat(2)), new AetheriaRuntimeBehaviorField(4, PerformanceStat(50)) ]); var catalog = new AetheriaRuntimeCatalogSnapshot( [CatalogItem("mining-tool", payload)], Array.Empty(), Array.Empty()); var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "behavior-query-smoke", CurrentZoneIndex = 0, CurrentEntityKey = "", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] }; AetheriaRuntimeDaemonTickRunner.Tick( Path.Combine(Path.GetTempPath(), "aetheria-behavior-query-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 0, Catalog = catalog, BuildPublications = false }); var behavior = AetheriaRuntimeEquippedBehaviorQueries.Find(entity, catalog, "MiningTool").Single(); RequireEqual("MiningTool", behavior.State.BehaviorKind, "tick must reconcile equipped catalog payloads into persistent behavior state"); RequireNear(12, behavior.EvaluateStat(1), 0.000001, "behavior query must evaluate mining damage from catalog"); RequireNear(50, behavior.EvaluateStat(4), 0.000001, "behavior query must evaluate mining range from catalog"); } private static AetheriaRuntimeBehaviorValue PerformanceStat(double value) => new( "performance-stat", "", 0, false, "", "", [Number(value), Number(value), Number(0), Number(0), Number(0)], Array.Empty()); private static AetheriaRuntimeBehaviorValue Number(double value) => new( "number", "", value, false, "", "", Array.Empty(), Array.Empty()); private static AetheriaRuntimeCatalogItem CatalogItem(string itemKey, params AetheriaRuntimeBehaviorPayload[] payloads) => new( itemKey, itemKey, "equipment", "", "", 0, 1, 1, 1, 1, 1, 1, 1, Array.Empty(), 0, 0, 0, Array.Empty(), Array.Empty(), payloads, "utility", "", payloads.Select(payload => payload.Kind).ToArray(), 1, false, 0, 1, "", "", "", "", "", 0, 1000, Array.Empty(), "", 1, 0, 0, 0, false, 0, 0, "", Array.Empty(), Array.Empty(), "", ""); private static void AgentPatrolsHistoricalOrbitCircuitThroughMovementCommands() { var agent = Entity(0, 0, "workers"); agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Defend]; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "agent-patrol-smoke", CurrentZoneIndex = 0, CurrentEntityKey = "", Zones = [ new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent], Orbits = [ new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "orbit:east", FixedPositionX = 20 }, new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "orbit:west", FixedPositionX = -20 } ], Bodies = [ new AetheriaRuntimeBodySnapshotCommit { BodyKey = "body:east", OrbitKey = "orbit:east", Kind = "planet" }, new AetheriaRuntimeBodySnapshotCommit { BodyKey = "body:west", OrbitKey = "orbit:west", Kind = "planet" } ] } ] }; var issue = AetheriaRuntimeDaemonCommandDocument.Create( AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, "commander-smoke", "starbridge-smoke", 0, ""); issue.CommandId = "issue-patrol"; issue.AgentTask = new AetheriaRuntimeAgentTaskCommand { TaskId = "patrol-orbits", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Defend, Priority = 10, ZoneIndex = 0, CompletionRadius = 5, TargetBodyKeys = ["body:east", "body:west"] }; var sawWestLeg = false; var sawReturnToEast = false; for (var frame = 0; frame < 30; frame++) { var tick = AetheriaRuntimeDaemonTickRunner.Tick( Path.Combine(Path.GetTempPath(), "aetheria-agent-patrol-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = frame, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = frame * 0.1, ObservedCommands = frame == 0 ? [issue] : Array.Empty(), ProjectilePhysics = AetheriaRuntimeProjectilePhysicsUnavailable.Instance, BuildPublications = false }); Require(tick.OperationResult.AppliedCommandIds.Any(id => id.EndsWith(":move", StringComparison.Ordinal)), "patrol controller must drive the shared movement command every tick"); var cursor = run.AgentTasks.Single().CircuitIndex; sawWestLeg |= cursor == 1; sawReturnToEast |= sawWestLeg && cursor == 0; if (sawReturnToEast) break; } Require(sawWestLeg && sawReturnToEast, "patrol must advance through and wrap its authored orbit circuit"); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, run.AgentTasks.Single().Status, "patrol is persistent work and must remain assigned after one circuit"); } private static void RejectedHaulTransferDoesNotAdvanceTask() { var origin = Entity(0, 0, "workers"); origin.CargoContents = [Cargo(("ore", 2, 0, 0))]; var agent = Entity(1, 0, "workers"); agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Haul]; agent.CargoContents = Array.Empty(); var destination = Entity(2, 20, "workers"); destination.CargoContents = [Cargo()]; var task = new AetheriaRuntimeAgentTaskCommit { TaskId = "rejected-haul", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Haul, Priority = 1, ZoneIndex = 0, OriginEntityIndex = 0, TargetEntityIndex = 2, ItemKey = "ore", RequestedQuantity = 1, CompletionRadius = 5, Phase = "pickup" }; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "rejected-haul-smoke", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [origin, agent, destination] }], AgentTasks = [task] }; var planned = AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 1); var reduced = AetheriaRuntimeDaemonOperations.Execute(run, planned); AetheriaRuntimeAgentScheduler.Reconcile(run, 1, reduced.AppliedCommandIds, reduced.RejectedCommandIds); Require(reduced.RejectedCommandIds.Any(id => id.EndsWith(":pickup", StringComparison.Ordinal)), "invalid pickup must be rejected by the normal cargo reducer"); RequireEqual("pickup", task.Phase, "rejected pickup must not advance the haul task"); RequireEqual(0, task.PendingQuantity, "rejected pickup must clear pending transfer state"); RequireEqual(2, CargoQuantity(origin, "ore"), "rejected pickup must leave origin cargo untouched"); } private static void AgentCompletesHaulTaskThroughMovementAndCargoCommands() { var origin = Entity(0, 0, "workers"); origin.Kind = "station"; origin.CargoContents = [Cargo(("ore", 5, 2, 3))]; var agent = Entity(1, 0, "workers"); agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Haul]; agent.CargoContents = [Cargo()]; var destination = Entity(2, 50, "workers"); destination.Kind = "station"; destination.CargoContents = [Cargo()]; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "agent-haul-smoke", CurrentZoneIndex = 0, CurrentEntityKey = "", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [origin, agent, destination] }] }; var issue = AetheriaRuntimeDaemonCommandDocument.Create( AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, "commander-smoke", "starbridge-smoke", 0, ""); issue.CommandId = "issue-haul"; issue.AgentTask = new AetheriaRuntimeAgentTaskCommand { TaskId = "haul-ore", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Haul, Priority = 40, ZoneIndex = 0, OriginEntityIndex = 0, TargetEntityIndex = 2, ItemKey = "ore", Quantity = 3, CompletionRadius = 5 }; var sawPickup = false; var sawDelivery = false; for (var frame = 0; frame < 30; frame++) { var tick = AetheriaRuntimeDaemonTickRunner.Tick( Path.Combine(Path.GetTempPath(), "aetheria-agent-haul-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = frame, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = frame * 0.1, ObservedCommands = frame == 0 ? [issue] : Array.Empty(), ProjectilePhysics = AetheriaRuntimeProjectilePhysicsUnavailable.Instance, BuildPublications = false }); sawPickup |= tick.OperationResult.AppliedCommandIds.Any(id => id.EndsWith(":pickup", StringComparison.Ordinal)); sawDelivery |= tick.OperationResult.AppliedCommandIds.Any(id => id.EndsWith(":delivery", StringComparison.Ordinal)); if (string.Equals(run.AgentTasks.Single().Status, AetheriaRuntimeAgentTaskStatuses.Completed, StringComparison.Ordinal)) break; } Require(sawPickup && sawDelivery, "haul task must use accepted pickup and delivery cargo commands"); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, run.AgentTasks.Single().Status, "haul task must complete only after accepted delivery"); RequireEqual(2, CargoQuantity(origin, "ore"), "origin must retain the unrequested stack quantity"); RequireEqual(3, CargoQuantity(destination, "ore"), "destination must receive exactly the requested quantity"); RequireEqual(0, CargoQuantity(agent, "ore"), "hauler must finish with no in-transit cargo"); } private static AetheriaRuntimeCargoBayLoadoutCommit Cargo(params (string ItemKey, int Quantity, int X, int Y)[] items) => new() { Items = items.Select(item => new AetheriaRuntimeLoadoutItemSlotCommit { X = item.X, Y = item.Y, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = item.ItemKey, Quantity = item.Quantity } }).ToArray() }; private static int CargoQuantity(AetheriaRuntimeEntitySnapshotCommit entity, string itemKey) => (entity.CargoContents ?? Array.Empty()) .SelectMany(bay => bay.Items) .Where(slot => string.Equals(slot.Item.ItemKey, itemKey, StringComparison.Ordinal)) .Sum(slot => slot.Item.Quantity); private static void AgentCompletesAttackTaskThroughTargetFireAndYmir() { var agent = Entity(0, 0, "player"); agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Attack]; agent.WeaponGroups = [new[] { 0 }]; var target = Entity(1, 105, "raider"); target.Kind = "station"; target.StatGrids = [Grid("hull", 24), Grid("shield", 0), Grid("heat", 0)]; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "agent-attack-smoke", CurrentZoneIndex = 0, CurrentEntityKey = "", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent, target] }] }; var issue = AetheriaRuntimeDaemonCommandDocument.Create( AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, "commander-smoke", "starbridge-smoke", 0, ""); issue.CommandId = "issue-attack"; issue.AgentTask = new AetheriaRuntimeAgentTaskCommand { TaskId = "attack-raider", CorporationKey = "player", TaskType = AetheriaRuntimeAgentTaskTypes.Attack, Priority = 100, ZoneIndex = 0, TargetEntityIndex = 1, CompletionRadius = 25, WeaponGroup = 0 }; var sawTargetCommand = false; var sawFireCommand = false; var sawPartialLock = false; var firstLaunchFrame = -1; for (var frame = 0; frame < 60; frame++) { var tick = AetheriaRuntimeDaemonTickRunner.Tick( Path.Combine(Path.GetTempPath(), "aetheria-agent-attack-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = frame, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = frame * 0.1, ObservedCommands = frame == 0 ? [issue] : Array.Empty(), ProjectilePhysics = new AetheriaYmirProjectilePhysics(), BuildPublications = false }); sawTargetCommand |= tick.OperationResult.AppliedCommandIds.Any(id => id.EndsWith(":target", StringComparison.Ordinal)); sawFireCommand |= tick.OperationResult.AppliedCommandIds.Any(id => id.EndsWith(":fire", StringComparison.Ordinal)); var weapon = (agent.WeaponStates ?? Array.Empty()).Single(); sawPartialLock |= weapon.LockProgress > 0 && weapon.LockProgress < 0.99; if (firstLaunchFrame < 0 && run.GameEvents.Any(value => value.Kind == "projectile.launched")) firstLaunchFrame = frame; if (string.Equals(run.AgentTasks.Single().Status, AetheriaRuntimeAgentTaskStatuses.Completed, StringComparison.Ordinal)) break; } Require(sawTargetCommand, "attack agent must target through the shared target command"); Require(sawFireCommand, "attack agent must fire through the shared weapon-group command"); Require(sawPartialLock, "attack agent must acquire a persisted partial weapon lock before firing"); Require(firstLaunchFrame > 1, "accepted fire intent must not bypass progressive weapon lock acquisition"); Require(run.GameEvents.Any(value => value.Kind == "projectile.launched" && value.SourceEntityIndex == agent.EntityIndex), "accepted fire control must emit authoritative projectile launch chronology"); Require(!target.IsActive, $"attack task must end through daemon damage after Ymir projectile contacts; hull={Stat(target, "hull"):0.###} " + $"agent={agent.PositionX:0.###},{agent.PositionZ:0.###} target={target.PositionX:0.###},{target.PositionZ:0.###} " + $"projectiles={run.Zones[0].Projectiles.Count} task={run.AgentTasks.Single().Status}"); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, run.AgentTasks.Single().Status, "attack task must complete when its target dies"); } private static void AttackAgentControlsOptimumRangeThroughMovementLever() { var settings = AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault; var optimum = settings.AttackRange * settings.AttackHoldRatio; var closing = AttackMovementAtDistance(optimum + 30); Require(closing.DirectionX > 0.99 && closing.ScalarValue > 0, "attack agent outside optimum range must close through the shared movement lever"); var retreating = AttackMovementAtDistance(optimum - 30); Require(retreating.DirectionX < -0.99 && retreating.ScalarValue > 0, "attack agent inside optimum range must retreat through the shared movement lever"); var holding = AttackMovementAtDistance(optimum); Require(Math.Abs(holding.DirectionX) < 0.001 && Math.Abs(holding.ScalarValue) < 0.001, "attack agent in the optimum band must hold range instead of charging the target"); } private static AetheriaRuntimeDaemonCommandDocument AttackMovementAtDistance(double distance) { var agent = Entity(0, 0, "workers"); agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Attack]; agent.WeaponGroups = [new[] { 0 }]; var target = Entity(1, distance, "raiders"); var task = new AetheriaRuntimeAgentTaskCommit { TaskId = "range-control", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Attack, ZoneIndex = 0, TargetEntityIndex = 1, WeaponGroup = 0 }; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "agent-range-control-smoke", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent, target] }], AgentTasks = [task] }; return AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 1) .Single(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.SetMoveVector); } private static void SchedulerAssignsHighestPriorityCompatibleTask() { var agent = Entity(0, 0, "workers"); agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; var low = AgentTask("low", 1); var high = AgentTask("high", 99); var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "agent-priority-smoke", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent] }], AgentTasks = [low, high] }; AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 4); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, high.Status, "highest priority compatible task must claim the available agent"); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Queued, low.Status, "lower priority task must remain queued when no controller remains"); RequireEqual("high", agent.AssignedAgentTaskId, "agent assignment must point at the selected task"); } private static void SchedulerRequeuesTaskFromDeadAgent() { var dead = Entity(0, 0, "workers"); dead.IsActive = false; dead.AssignedAgentTaskId = "recover-work"; var replacement = Entity(1, 0, "workers"); replacement.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; var task = new AetheriaRuntimeAgentTaskCommit { TaskId = "recover-work", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Explore, ZoneIndex = 0, Status = AetheriaRuntimeAgentTaskStatuses.Assigned, AssignedEntityIndex = 0, TargetPositionX = 100, DeliveredQuantity = 3, RequestedQuantity = 5 }; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "agent-recovery-smoke", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [dead, replacement] }], AgentTasks = [task] }; AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 8); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, task.Status, "work abandoned by an inactive agent must return to the corporation queue and be reassigned"); RequireEqual(replacement.EntityIndex, task.AssignedEntityIndex, "compatible active replacement must receive abandoned work"); RequireEqual(3, task.DeliveredQuantity, "reassignment must preserve authoritative task progress"); Require(string.IsNullOrWhiteSpace(dead.AssignedAgentTaskId), "inactive agent must not retain reservation authority"); } private static void SchedulerCollapsesDuplicateAssignmentMarkers() { var first = Entity(0, 0, "workers"); first.AssignedAgentTaskId = "single-owner"; var duplicate = Entity(1, 0, "workers"); duplicate.AssignedAgentTaskId = "single-owner"; var task = AgentTask("single-owner", 1); task.Status = AetheriaRuntimeAgentTaskStatuses.Assigned; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "agent-single-owner-smoke", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [first, duplicate] }], AgentTasks = [task] }; AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 9); RequireEqual(1, run.Zones.SelectMany(zone => zone.Entities) .Count(entity => string.Equals(entity.AssignedAgentTaskId, task.TaskId, StringComparison.Ordinal)), "one task must have exactly one active carrier after reconciliation"); RequireEqual(first.EntityIndex, task.AssignedEntityIndex, "duplicate assignment reconciliation must be deterministic"); } private static AetheriaRuntimeAgentTaskCommit AgentTask(string id, int priority) => new() { TaskId = id, CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Explore, Priority = priority, ZoneIndex = 0, TargetPositionX = 100, CompletionRadius = 5 }; private static void SchedulerAssignsShortestGalaxyRoute() { var distant = Entity(0, 0, "workers"); distant.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; var nearby = Entity(0, 0, "workers"); nearby.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; var task = AgentTask("route-choice", 10); task.ZoneIndex = 2; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "agent-route-choice-smoke", Zones = [ new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, PositionX = 0, GravityTerrainRadius = 100, AdjacentZoneIndices = [1], Entities = [distant] }, new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 1, PositionX = 100, GravityTerrainRadius = 100, AdjacentZoneIndices = [0, 2], Entities = Array.Empty() }, new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 2, PositionX = 200, GravityTerrainRadius = 100, AdjacentZoneIndices = [1, 3], Entities = Array.Empty() }, new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 3, PositionX = 300, GravityTerrainRadius = 100, AdjacentZoneIndices = [2], Entities = [nearby] } ], AgentTasks = [task] }; AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 1); RequireEqual("route-choice", nearby.AssignedAgentTaskId, "scheduler must reserve the controller with the shortest galaxy route to the task zone"); Require(string.IsNullOrWhiteSpace(distant.AssignedAgentTaskId), "longer-route controller must remain available"); } private static void AgentTraversesGalaxyRouteBeforeExecutingTask() { var agent = Entity(0, 0, "workers"); agent.EntityId = "worker.cross-zone.stable"; agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; var task = AgentTask("cross-zone", 10); task.ZoneIndex = 2; task.TargetPositionX = 0; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "agent-route-travel-smoke", Zones = [ new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, PositionX = 0, GravityTerrainRadius = 100, AdjacentZoneIndices = [1], Entities = [agent] }, new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 1, PositionX = 100, GravityTerrainRadius = 100, AdjacentZoneIndices = [0, 2], Entities = Array.Empty() }, new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 2, PositionX = 200, GravityTerrainRadius = 100, AdjacentZoneIndices = [1], Entities = Array.Empty() } ], AgentTasks = [task] }; var appliedTravelCommands = 0; var appliedApproachCommands = 0; for (var frame = 0; frame < 24; frame++) { var tick = AetheriaRuntimeDaemonTickRunner.Tick( Path.Combine(Path.GetTempPath(), "aetheria-agent-route-travel-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = frame, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = frame * 0.1, BuildPublications = false }); appliedTravelCommands += tick.OperationResult.AppliedCommandIds.Count(id => id.EndsWith(":travel", StringComparison.Ordinal)); appliedApproachCommands += tick.OperationResult.AppliedCommandIds.Count(id => id.EndsWith(":travel-approach", StringComparison.Ordinal)); if (string.Equals(task.Status, AetheriaRuntimeAgentTaskStatuses.Completed, StringComparison.Ordinal)) break; } Require(appliedApproachCommands > 0, "agent must approach wormholes through shared movement commands and Ymir before transition"); RequireEqual(2, appliedTravelCommands, "agent must traverse each galaxy edge through the shared wormhole command boundary"); Require(run.Zones.Single(zone => zone.ZoneIndex == 2).Entities.Count == 1, "assigned agent must arrive in the task zone without a parallel teleport owner"); RequireEqual("worker.cross-zone.stable", run.Zones.Single(zone => zone.ZoneIndex == 2).Entities.Single().EntityId, "zone transfer must preserve stable entity identity while projection indices change"); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, task.Status, "agent must execute the task only after arriving in its destination zone"); } private static void IdleAgentReturnsToCanonicalHomeAndDocks() { var worker = Entity(0, 0, "workers"); worker.EntityId = "worker.return-home"; worker.HomeEntityId = "station.home"; worker.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Haul]; var home = Entity(0, 60, "workers"); home.EntityId = "station.home"; home.Kind = "station"; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "agent-return-home-smoke", Zones = [ new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, PositionX = 0, GravityTerrainRadius = 100, AdjacentZoneIndices = [1], Entities = [worker] }, new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 1, PositionX = 100, GravityTerrainRadius = 100, AdjacentZoneIndices = [0, 2], Entities = Array.Empty() }, new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 2, PositionX = 200, GravityTerrainRadius = 100, AdjacentZoneIndices = [1], Entities = [home] } ] }; var sawApproach = false; var travelCount = 0; var sawDock = false; var rejectedHomeCommands = new List(); for (var frame = 0; frame < 40; frame++) { var tick = AetheriaRuntimeDaemonTickRunner.Tick( Path.Combine(Path.GetTempPath(), "aetheria-agent-return-home-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = frame, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = frame * 0.1, BuildPublications = false }); sawApproach |= tick.OperationResult.AppliedCommandIds.Any(id => id.Contains(":home-approach", StringComparison.Ordinal)); travelCount += tick.OperationResult.AppliedCommandIds.Count(id => id.EndsWith(":home-travel", StringComparison.Ordinal)); sawDock |= tick.OperationResult.AppliedCommandIds.Any(id => id.EndsWith(":home-dock", StringComparison.Ordinal)); rejectedHomeCommands.AddRange(tick.OperationResult.RejectedCommandIds.Where(id => id.Contains(":home-", StringComparison.Ordinal))); if (sawDock) break; } var homeZone = run.Zones.Single(zone => zone.ZoneIndex == 2); var arrivedWorker = homeZone.Entities.Single(entity => entity.EntityId == "worker.return-home"); var arrivedHome = homeZone.Entities.Single(entity => entity.EntityId == "station.home"); Require(sawApproach, "idle worker must approach its route and home through shared movement commands"); RequireEqual(2, travelCount, "idle worker must traverse the shortest route to its canonical home"); Require(sawDock, $"idle worker must dock through the daemon docking command; worker={arrivedWorker.PositionX:0.###},{arrivedWorker.PositionZ:0.###} home={arrivedHome.PositionX:0.###},{arrivedHome.PositionZ:0.###} rejected={string.Join(",", rejectedHomeCommands)}"); Require(arrivedHome.DockingBayAssignments.Contains(arrivedWorker.EntityIndex), "home docking parentage must be the authoritative completion fact"); var settled = AetheriaRuntimeDaemonTickRunner.Tick( Path.Combine(Path.GetTempPath(), "aetheria-agent-return-home-settled-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 41, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 4.1, BuildPublications = false }); Require(!settled.OperationResult.AppliedCommandIds.Any(id => id.Contains(":home-", StringComparison.Ordinal)), "docked worker must not emit a perpetual homecoming repair loop"); } private static void ControlledShipDoesNotReceiveAutonomousHelmCommands() { var controlled = Entity(0, 100, "workers"); controlled.EntityId = "controlled-worker"; controlled.HomeEntityId = "controlled-home"; controlled.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; var home = Entity(1, 0, "workers"); home.EntityId = "controlled-home"; home.Kind = "station"; var task = AgentTask("controlled-task", 10); var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "controlled-agent-authority-smoke", CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [controlled, home] }], AgentTasks = [task] }; var commands = AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 1); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Queued, task.Status, "manual helm owner must not be conscripted into autonomous corporation work"); Require(commands.Count == 0, "controlled ship must emit no task or return-home commands without delegated helm authority"); } private static void AgentClaimsAndCompletesExploreTaskThroughCommands() { var agent = Entity(0, 0, "workers"); agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "agent-task-smoke", CurrentZoneIndex = 0, CurrentEntityKey = "", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent] }] }; var issue = AetheriaRuntimeDaemonCommandDocument.Create( AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, "commander-smoke", "starbridge-smoke", 0, ""); issue.CommandId = "issue-explore"; issue.AgentTask = new AetheriaRuntimeAgentTaskCommand { TaskId = "explore-east", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Explore, Priority = 50, ZoneIndex = 0, TargetPositionX = 50, TargetPositionZ = 0, CompletionRadius = 5 }; var completed = false; for (var frame = 0; frame <= 20; frame++) { var tick = AetheriaRuntimeDaemonTickRunner.Tick( Path.Combine(Path.GetTempPath(), "aetheria-agent-task-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = frame, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = frame * 0.1, ObservedCommands = frame == 0 ? [issue] : Array.Empty(), ProjectilePhysics = AetheriaRuntimeProjectilePhysicsUnavailable.Instance, BuildPublications = false }); if (frame == 0) { Require(tick.OperationResult.AppliedCommandIds.Contains("issue-explore"), "commander task command must enter the normal tick reducer"); Require(tick.OperationResult.AppliedCommandIds.Any(id => id.Contains(":explore-east:0:move", StringComparison.Ordinal)), "agent movement must return through the same command receipts"); } completed = string.Equals(run.AgentTasks.Single().Status, AetheriaRuntimeAgentTaskStatuses.Completed, StringComparison.Ordinal); if (completed) break; } Require(completed, "agent must complete an explore task without direct position mutation"); Require(string.IsNullOrWhiteSpace(agent.AssignedAgentTaskId), "completed task must release the agent"); Require(agent.PositionX >= 45, "agent must reach the task through repeated movement commands"); var frameDocument = new AetheriaRuntimeDaemonFrameDocument { FrameId = 21, Run = run }; var commander = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildCommander( frameDocument, new AetheriaRuntimeDaemonHealthDocument(), AetheriaRuntimeDaemonCommandBoundaryDocument.Create("agent-task-smoke")); var taskNode = Flatten(commander.Surface.Root) .Single(component => string.Equals(component.Id, "aetheria.starbridge.commander.tasks.explore_east", StringComparison.Ordinal)); RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, taskNode.Props["status"], "commander Eve surface must publish authoritative task status"); Require(commander.Commands.Any(command => string.Equals(command.Command, "aetheria.daemon.issue_agent_task", StringComparison.Ordinal)), "commander Eve surface must advertise task issue command"); var roster = Flatten(commander.Surface.Root) .Single(component => string.Equals(component.Kind, "agent.roster", StringComparison.Ordinal)); var worker = Flatten(roster) .Single(component => string.Equals(component.Kind, "agent.item", StringComparison.Ordinal)); Require(!string.IsNullOrWhiteSpace(worker.Props["entityId"]), "commander Eve roster must expose stable provider-owned worker identity"); Require(worker.Props["capabilities"].Split(',').Contains(AetheriaRuntimeAgentTaskTypes.Explore), "commander Eve roster must expose provider-owned capability claims"); RequireEqual("idle", worker.Props["status"], "commander Eve roster must publish generic status for runtime-independent lowering"); Require(worker.Props["badges"].Split(',').Contains(AetheriaRuntimeAgentTaskTypes.Explore), "commander Eve roster must publish capability badges through the generic item contract"); RequireEqual("", worker.Props["assignedTaskId"], "completed worker assignment must be visible as released on the Eve roster"); RequireEqual("false", worker.Props["controlled"], "commander Eve roster must distinguish autonomous workers from manual helm authority"); } private static void MultipleActorsUseTheSameMovementLever() { var player = Entity(0, 0, "player"); var agent = Entity(1, 0, "worker"); var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "shared-lever-smoke", CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [player, agent] }] }; var commands = new[] { MovementCommand("player-move", "zone.0.entity.0", 1, 0), MovementCommand("agent-move", "zone.0.entity.1", 0, 1) }; var operation = AetheriaRuntimeDaemonOperations.Execute(run, commands); RequireEqual(2, operation.Intents.Movements.Count, "movement intent must retain one lever position per actor"); AetheriaRuntimeDaemonSimulation.Step( run, operation.Intents, 0.1, AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, AetheriaRuntimeProjectilePhysicsUnavailable.Instance, new AetheriaYmirWorldPhysics()); Require(player.VelocityX > 0 && Math.Abs(player.VelocityY) < 0.001, "player command must drive its actor through the shared movement lever"); Require(agent.VelocityY > 0 && Math.Abs(agent.VelocityX) < 0.001, "agent command must drive its actor through the shared movement lever"); } private static AetheriaRuntimeDaemonCommandDocument MovementCommand( string commandId, string actor, double x, double y) { var command = AetheriaRuntimeDaemonCommandDocument.Create( AetheriaRuntimeDaemonCommandKinds.SetMoveVector, "shared-control-smoke", "shared-control-session", 0, actor); command.CommandId = commandId; command.DirectionX = x; command.DirectionY = y; command.ScalarValue = 1; return command; } private static void ThermalCellsUseFossilConductionAndRadiation() { var entity = Entity(7, 0, "player"); entity.StatGrids = [ Grid(AetheriaRuntimeThermalSimulation.TemperatureGrid, 2, 1, 300, 280), Grid(AetheriaRuntimeThermalSimulation.ThermalMassGrid, 2, 1, 1, 1), Grid(AetheriaRuntimeThermalSimulation.ConductivityGrid, 2, 1, 1, 1) ]; AetheriaRuntimeThermalSimulation.AddHeat(entity, 20); RequireNear(310, GridValue(entity, AetheriaRuntimeThermalSimulation.TemperatureGrid, 0), 0.000001, "heat energy must be divided across cells and thermal mass"); RequireNear(290, GridValue(entity, AetheriaRuntimeThermalSimulation.TemperatureGrid, 1), 0.000001, "heat energy must be divided across cells and thermal mass"); AetheriaRuntimeThermalSimulation.Step(entity, 0.1); var expectedHot = (310 / 0.01 + 290) / 101; expectedHot -= Math.Pow(expectedHot, 3) * 0.00000001 * 0.1; var expectedCool = (290 / 0.01 + 310) / 101; expectedCool -= Math.Pow(expectedCool, 3) * 0.00000001 * 0.1; RequireNear(expectedHot, GridValue(entity, AetheriaRuntimeThermalSimulation.TemperatureGrid, 0), 0.000001, "hot cell must follow fossil conduction and radiation"); RequireNear(expectedCool, GridValue(entity, AetheriaRuntimeThermalSimulation.TemperatureGrid, 1), 0.000001, "cool cell must follow fossil conduction and radiation"); RequireNear((expectedHot + expectedCool) / 2, Stat(entity, "heat"), 0.000001, "legacy heat scalar must be derived from cell temperature"); var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "thermal-projection-smoke", CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.7", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] }; var document = AetheriaRuntimeGameDocuments.CurrentEntity(new AetheriaRuntimeDaemonFrameDocument { Run = run }); RequireNear(Stat(entity, "heat"), document.Hud.MeanTemperature, 0.000001, "Eve current-entity state must publish mean temperature"); RequireNear(GridValue(entity, AetheriaRuntimeThermalSimulation.TemperatureGrid, 0), document.Hud.MaximumTemperature, 0.000001, "Eve current-entity state must publish maximum temperature"); Require(document.Hud.ThermalVisibility > 0, "Eve current-entity state must publish thermal visibility"); } private static void YmirMovesProjectileAndReportsStableContact() { var (run, zone, target) = Scenario(); var step = new AetheriaYmirProjectilePhysics().Step(zone, zone.Entities, 0.1); RequireEqual("ymir.core", new AetheriaYmirProjectilePhysics().AuthorityId, "adapter must identify its owner"); RequireEqual(0, step.Projectiles.Count, "contacted projectile must not survive"); RequireEqual(1, step.Hits.Count, "Ymir must report one projectile contact"); RequireEqual(target.EntityIndex, step.Hits[0].TargetEntityIndex, "contact must resolve the daemon entity"); RequireEqual("aetheria.projectile.smoke-projectile", step.Hits[0].ProjectileBodyId, "projectile body id must be stable"); RequireEqual("aetheria.daemon.entity.2", step.Hits[0].TargetBodyId, "entity body id must be stable"); } private static void DaemonSimulationAppliesYmirHit() { var (run, _, target) = Scenario(); AetheriaRuntimeDaemonSimulation.Step( run, new AetheriaRuntimeDaemonIntentState(), 0.1, AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, new AetheriaYmirProjectilePhysics(), new AetheriaYmirWorldPhysics()); RequireEqual(88.0, Stat(target, "hull"), "Aetheria must interpret the Ymir contact as damage"); RequireEqual(0, run.Zones[0].Projectiles.Count, "spent projectile must leave daemon state"); RequireEqual(1, run.GameEvents.Count(value => value.Kind == "projectile.impact" && value.SubjectKey == "smoke-projectile"), "Ymir contact must emit one projectile-identity impact event"); RequireEqual(1, run.GameEvents.Count(value => value.Kind == "entity.damaged" && value.TargetEntityIndex == target.EntityIndex), "daemon damage interpretation must emit one target damage event"); var feedback = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( new AetheriaRuntimeDaemonFrameDocument { FrameId = 0, Run = run }, new AetheriaRuntimeDaemonHealthDocument(), AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); Require(Flatten(feedback.Surface.Root).Any(node => node.Kind == "feedback.event" && node.Props["eventKind"] == "projectile.impact" && node.Props["subjectKey"] == "smoke-projectile"), "Eve feedback must project authoritative projectile impact identity"); } private static void ProjectileDeathEmitsOnce() { var (run, _, target) = Scenario(); target.StatGrids.Single(grid => grid.Name == "hull").Values = [5]; AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, new AetheriaYmirProjectilePhysics(), new AetheriaYmirWorldPhysics(), frameId: 9); Require(!target.IsActive, "lethal Ymir projectile contact must deactivate target"); RequireEqual(1, run.GameEvents.Count(value => value.Kind == "entity.destroyed" && value.TargetEntityIndex == target.EntityIndex), "alive-to-dead transition must emit one destruction event"); } private static void MissingPhysicsOwnerCannotAdvanceProjectiles() { var (run, _, _) = Scenario(); try { AetheriaRuntimeDaemonSimulation.Step( run, new AetheriaRuntimeDaemonIntentState(), 0.1, AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, AetheriaRuntimeProjectilePhysicsUnavailable.Instance, new AetheriaYmirWorldPhysics()); } catch (InvalidOperationException) { return; } throw new InvalidOperationException("daemon advanced a projectile without an authoritative physics owner"); } private static void MissingWorldPhysicsOwnerCannotAdvanceShips() { var entity = Entity(0, 0, "player"); entity.VelocityX = 10; var run = new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] }; try { AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-missing-world-physics.cc"), run, new AetheriaRuntimeDaemonTickOptions { FrameId = 1, FixedDeltaSeconds = 0.1, ProjectilePhysics = AetheriaRuntimeProjectilePhysicsUnavailable.Instance, BuildPublications = false }); } catch (InvalidOperationException) { RequireNear(0, entity.PositionX, 0.000001, "ship must not advance without Ymir world authority"); return; } throw new InvalidOperationException("daemon advanced a ship without an authoritative world physics owner"); } private static void TractorRampsAndPullsThroughYmirWithoutTeleportingCargo() { var ship = Entity(0, 0, "player"); ship.DirectionX = 1; ship.DirectionY = 0; var pickup = new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 3, PositionX = 60, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "salvage", Quantity = 1 }, LifetimeSeconds = 30 }; var run = new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship], DroppedPickups = [pickup] }] }; var command = AetheriaRuntimeDaemonCommandDocument.Create(AetheriaRuntimeDaemonCommandKinds.SetTractorPower, "pilot", "tractor-smoke", 0, "zone.0.entity.0"); command.CommandId = "tractor-on"; command.ScalarValue = 1; AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-tractor-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 0.25, SimulationTimeSeconds = 0.25, ObservedCommands = [command], ProjectilePhysics = AetheriaRuntimeProjectilePhysicsUnavailable.Instance, BuildPublications = false }); RequireNear(0.5, ship.TractorPower, 0.000001, "tractor power must use the fossil two-per-second ramp"); Require(pickup.VelocityX < 0 && pickup.PositionX < 60, "Ymir must pull a pickup inside the forward tractor volume toward the ship"); RequireEqual(1, pickup.Item.Quantity, "tractor force must not consume the pickup item"); RequireEqual(0, CargoQuantity(ship, "salvage"), "scooping must remain a separate capacity-checked transaction"); var frame = new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run }; var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(frame, new AetheriaRuntimeDaemonHealthDocument(), AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); var pickupNode = Flatten(surface.Surface.Root).Single(node => node.Id == "aetheria.daemon.game.world.pickup.3"); RequireEqual("prefab.entity.pickup", pickupNode.Props["assetRef"], "Eve world must reference provider-owned pickup asset semantics"); RequireEqual("salvage", pickupNode.Props["itemKey"], "Eve pickup must expose item identity"); Require(double.Parse(pickupNode.Props["remainingLifetime"], CultureInfo.InvariantCulture) < 30, "Eve pickup must expose daemon-owned remaining lifetime"); var pickupAsset = AetheriaRuntimeAssets.ProjectManifest(null).Assets.Single(asset => asset.Ref.AssetKey == "prefab.entity.pickup"); Require(pickupAsset.Ref.Metadata.TryGetValue("unityAssetPath", out var pickupPath) && pickupPath == "Assets/Prefabs/RPG/Pickups/Tetrahedron.prefab", "provider asset manifest must advertise the pickup visual used by Eve"); } private static void PickupIsCapacityCheckedExactlyOnceAndExpires() { var hull = CatalogItem("pickup-hull"); hull.HullCapacity = PerformanceStat(1); var salvage = CatalogItem("salvage"); salvage.Volume = 1; var catalog = new AetheriaRuntimeCatalogSnapshot([hull, salvage], [], []); var ship = Entity(0, 0, "player"); ship.HullItemKey = hull.ItemKey; ship.CargoContents = [Cargo()]; var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship], DroppedPickups = [new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 7, PositionX = 10, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, LifetimeSeconds = 30 }] }; var run = new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [zone] }; AetheriaRuntimeDaemonCommandDocument Pickup(string id, int index) { var command = AetheriaRuntimeDaemonCommandDocument.Create(AetheriaRuntimeDaemonCommandKinds.PickUpLoot, "pilot", "pickup-smoke", 0, "zone.0.entity.0"); command.CommandId = id; command.TargetEntityKey = "zone.0.entity.0"; command.LootPickup.ItemKey = salvage.ItemKey; command.LootPickup.Quantity = 1; command.LootPickup.PickupIndex = index; return command; } var first = AetheriaRuntimeDaemonOperations.Execute(run, [Pickup("pickup-first", 7)], new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); Require(first.AppliedCommandIds.Contains("pickup-first"), "nearby pickup with capacity must apply"); RequireEqual(1, CargoQuantity(ship, salvage.ItemKey), "successful pickup must enter cargo"); var duplicate = AetheriaRuntimeDaemonOperations.Execute(run, [Pickup("pickup-duplicate", 7)], new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); Require(duplicate.RejectedCommandIds.Contains("pickup-duplicate"), "consumed pickup identity must reject duplicate collection"); zone.DroppedPickups = [new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 8, PositionX = 10, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, LifetimeSeconds = 30 }]; var full = AetheriaRuntimeDaemonOperations.Execute(run, [Pickup("pickup-full", 8)], new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); Require(full.RejectedCommandIds.Contains("pickup-full") && zone.DroppedPickups.Count == 1, "full cargo must reject without deleting pickup"); AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-pickup-expiry-smoke.cc"), run, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 30, SimulationTimeSeconds = 30, ProjectilePhysics = AetheriaRuntimeProjectilePhysicsUnavailable.Instance, BuildPublications = false }); RequireEqual(0, zone.DroppedPickups.Count, "pickup must expire after the fossil thirty-second lifetime"); Require(run.GameEvents.Any(value => value.Kind == "pickup.expired" && value.PickupIndex == 8), "daemon lifetime owner must emit authoritative pickup expiry event"); } private static void PickupShieldContactCollectsOrBounces() { var hull = CatalogItem("contact-hull"); hull.HullCapacity = PerformanceStat(1); var salvage = CatalogItem("contact-salvage"); salvage.Volume = 1; var catalog = new AetheriaRuntimeCatalogSnapshot([hull, salvage], [], []); AetheriaRuntimeRunCheckpointCommit Scenario(bool full) { var ship = Entity(0, 0, "player"); ship.HullItemKey = hull.ItemKey; ship.CargoContents = [full ? Cargo((salvage.ItemKey, 1, 0, 0)) : Cargo()]; return new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship], DroppedPickups = [new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 10, PositionX = 20, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, LifetimeSeconds = 30 }] }] }; } var open = Scenario(false); AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-pickup-contact-open.cc"), open, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 0.1, Catalog = catalog, ProjectilePhysics = AetheriaRuntimeProjectilePhysicsUnavailable.Instance, BuildPublications = false }); RequireEqual(0, open.Zones[0].DroppedPickups.Count, "shield contact with capacity must collect pickup automatically"); RequireEqual(1, CargoQuantity(open.Zones[0].Entities[0], salvage.ItemKey), "contact collection must commit cargo once"); RequireEqual(1, open.GameEvents.Count(value => value.Kind == "pickup.collected" && value.PickupIndex == 10), "contact collection must emit one stable event"); var full = Scenario(true); AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-pickup-contact-full.cc"), full, new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 0.1, Catalog = catalog, ProjectilePhysics = AetheriaRuntimeProjectilePhysicsUnavailable.Instance, BuildPublications = false }); RequireEqual(1, full.Zones[0].DroppedPickups.Count, "full hold must leave contacted pickup alive"); Require(full.Zones[0].DroppedPickups[0].VelocityX > 20, "failed pickup must receive the fossil outward kick"); RequireEqual(1, full.GameEvents.Count(value => value.Kind == "pickup.rejected" && value.PickupIndex == 10), "capacity rejection must emit one stable event"); var feedback = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = full }, new AetheriaRuntimeDaemonHealthDocument(), AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); Require(Flatten(feedback.Surface.Root).Any(node => node.Kind == "feedback.event" && node.Props["eventKind"] == "pickup.rejected" && node.Props["pickupIndex"] == "10"), "Eve surface must project authoritative pickup feedback chronology"); } private static (AetheriaRuntimeRunCheckpointCommit Run, AetheriaRuntimeZoneSnapshotCommit Zone, AetheriaRuntimeEntitySnapshotCommit Target) Scenario() { var source = Entity(1, -100, "player"); var target = Entity(2, 30, "enemy"); var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, target], Projectiles = [ new AetheriaRuntimeProjectileCommit { ProjectileId = "smoke-projectile", SourceEntityIndex = 1, TargetEntityIndex = 2, PositionX = 0, PositionZ = 0, VelocityX = 100, VelocityY = 0, Radius = 1, Damage = 12, LifetimeSeconds = 5, Active = true } ] }; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "ymir-projectile-smoke", CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.1", Zones = [zone] }; return (run, zone, target); } private static AetheriaRuntimeEntitySnapshotCommit Entity(int index, double x, string faction) => new() { EntityIndex = index, Kind = "ship", FactionKey = faction, PositionX = x, PositionZ = 0, TargetEntityIndex = -1, IsActive = true, StatGrids = [Grid("hull", 100), Grid("shield", 0), Grid("heat", 0)] }; private static AetheriaRuntimeEntityStatGridCommit Grid(string name, double value) => new() { Name = name, Width = 1, Height = 1, Values = [value] }; private static AetheriaRuntimeEntityStatGridCommit Grid(string name, int width, int height, params double[] values) => new() { Name = name, Width = width, Height = height, Values = values }; private static double GridValue(AetheriaRuntimeEntitySnapshotCommit entity, string name, int index) => entity.StatGrids.Single(grid => string.Equals(grid.Name, name, StringComparison.OrdinalIgnoreCase)).Values[index]; private static IEnumerable Flatten(AetheriaRuntimeSurfaceComponent root) { yield return root; foreach (var child in root.Children) foreach (var descendant in Flatten(child)) yield return descendant; } private static double Stat(AetheriaRuntimeEntitySnapshotCommit entity, string name) => entity.StatGrids.Single(grid => string.Equals(grid.Name, name, StringComparison.OrdinalIgnoreCase)).Values[0]; private static void RequireEqual(T expected, T actual, string message) { if (!EqualityComparer.Default.Equals(expected, actual)) throw new InvalidOperationException($"{message}. Expected {expected}; actual {actual}."); } private static void Require(bool condition, string message) { if (!condition) throw new InvalidOperationException(message); } private static void RequireNear(double expected, double actual, double tolerance, string message) { if (Math.Abs(expected - actual) > tolerance) throw new InvalidOperationException($"{message}. Expected {expected}; actual {actual}."); } }