Aetheria/Aetheria.State.Daemon.Smoke/Program.cs
2026-07-12 01:24:30 +01:00

1803 lines
105 KiB
C#

using Aetheria.State.Daemon;
using GameCult.Aetheria.State.Verse;
using System.Globalization;
var checks = new AetheriaDaemonYmirSmokeChecks();
checks.Run();
Console.WriteLine("Daemon Ymir physical payload smoke passed.");
internal sealed class AetheriaDaemonYmirSmokeChecks
{
public void Run()
{
YmirMovesProjectileAndReportsStableContact();
ConstantWeaponRunsOnDaemonThroughYmirBeamContact();
ChargedWeaponCannotBypassChargeLifecycle();
ChargedWeaponHoldRiskMalfunctionsDeterministically();
DaemonSimulationTreatsYmirHitAsPresentationOnly();
ProjectileContactCannotKill();
MissingPhysicsOwnerCannotAdvanceProjectiles();
MissingWorldPhysicsOwnerCannotAdvanceShips();
TractorRampsAndPullsThroughYmirWithoutTeleportingCargo();
PickupIsCapacityCheckedExactlyOnceAndExpires();
PickupShieldContactCollectsOrBounces();
ThermalCellsUseFossilConductionAndRadiation();
MultipleActorsUseTheSameMovementLever();
AgentClaimsAndCompletesExploreTaskThroughCommands();
SchedulerAssignsHighestPriorityCompatibleTask();
SchedulerRequeuesTaskFromDeadAgent();
SchedulerCollapsesDuplicateAssignmentMarkers();
SchedulerAssignsShortestGalaxyRoute();
AgentTraversesGalaxyRouteBeforeExecutingTask();
IdleAgentReturnsToCanonicalHomeAndDocks();
ControlledShipDoesNotReceiveAutonomousHelmCommands();
AttackAgentControlsOptimumRangeThroughMovementLever();
AttackTaskAdmissionRejectsImpossibleWork();
AgentCompletesAttackTaskThroughTargetFireAndYmir();
AgentCompletesHaulTaskThroughMovementAndCargoCommands();
RejectedHaulTransferDoesNotAdvanceTask();
AgentPatrolsHistoricalOrbitCircuitThroughMovementCommands();
TickReconcilesAndEvaluatesCatalogBehaviors();
DaemonLoadoutsRespectFactionAvailabilityAndHullRoles();
AgentMinesAsteroidThroughEquippedBehavior();
CargoCapacityComesFromHullAndCatalogVolumes();
AgentSurveysBodyIntoCorporationKnowledge();
AgentTowsStationIntoPersistentOrbit();
}
private static void ConstantWeaponRunsOnDaemonThroughYmirBeamContact()
{
var source = Entity(0, 0, "player");
source.DirectionX = 1;
source.DirectionY = 0;
source.TargetEntityIndex = 2;
source.WeaponGroups = [new[] { 0 }];
source.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit
{
Item = new AetheriaRuntimeLoadoutItemCommit
{ ItemKey = "test-beam", Quality = 1, Durability = 1, Enabled = true }
}];
source.BehaviorStates = [new AetheriaRuntimeBehaviorStateCommit
{
OwnerKind = "fixture", OwnerIndex = 0, BehaviorIndex = 0,
BehaviorKind = "Capacitor", CapacitorCharge = 10, CapacitorCapacity = 10, CapacitorEfficiency = 1
}];
source.CargoContents = [Cargo(("beam-ammo", 1, 0, 0))];
var blocker = Entity(1, 40, "neutral");
var selectedTarget = Entity(2, 100, "raider");
var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, blocker, selectedTarget] };
var run = new AetheriaRuntimeRunCheckpointCommit
{
RunId = "constant-weapon-smoke", CurrentZoneIndex = 0,
CurrentEntityKey = "zone.0.entity.0", Zones = [zone]
};
var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.ConstantWeapon, 0,
[
new AetheriaRuntimeBehaviorField(2, PerformanceStat(10)),
new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)),
new AetheriaRuntimeBehaviorField(9, PerformanceStat(2)),
new AetheriaRuntimeBehaviorField(10, PerformanceStat(3)),
new AetheriaRuntimeBehaviorField(12, new AetheriaRuntimeBehaviorValue(
"item-key", "", 0, false, "", "beam-ammo", [], [])),
new AetheriaRuntimeBehaviorField(13, Number(3)),
new AetheriaRuntimeBehaviorField(14, Number(0.3)),
new AetheriaRuntimeBehaviorField(17, Number(0.15))
]);
var catalog = new AetheriaRuntimeCatalogSnapshot([CatalogItem("test-beam", payload)], [], []);
var intents = new AetheriaRuntimeDaemonIntentState();
intents.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent
{
ActorEntityKey = "zone.0.entity.0", WeaponGroup = 0, Fire = true, Active = true
});
for (var frame = 0; frame < 7; frame++)
AetheriaRuntimeDaemonSimulation.Step(run, intents, 0.1,
new AetheriaRuntimeDaemonSimulationSettings(), new AetheriaYmirPhysicalPayloadPhysics(),
new AetheriaYmirWorldPhysics(), catalog, frame, frame * 0.1);
var state = source.WeaponStates.Single(value => value.BehaviorKind == AetheriaRuntimeBehaviorKinds.ConstantWeapon);
Require(run.GameEvents.Count(value => value.Kind == "weapon.firing.started") == 1,
"held constant fire must publish one start transition");
Require(run.GameEvents.Count(value => value.Kind == "weapon.reload.started") == 1 &&
run.GameEvents.Count(value => value.Kind == "weapon.firing.stopped") == 1,
"empty constant weapon magazine must start reload and stop the held effect once");
Require(state.Reloading && !state.Firing,
"constant weapon reload must remain authoritative persisted state");
Require(Math.Abs(Stat(blocker, "hull") - 100) < 0.000001 && Stat(selectedTarget, "hull") < 100,
"constant weapon must resolve against its firing solution rather than a presentation collider");
Require(run.ShotReceipts.Any(value => value.TargetEntityIndex == selectedTarget.EntityIndex &&
value.WeaponItemKey == "test-beam" && value.Hit),
"daemon must publish accepted continuous shot receipts with authored beam identity");
Require(CargoQuantity(source, "beam-ammo") == 0,
"constant weapon reload must consume its reserve cargo through the shared transaction");
Require(source.BehaviorStates.Single(value => value.BehaviorKind == "Capacitor").CapacitorCharge < 10,
"constant weapon must pay elapsed-time energy through canonical capacitor state");
for (var frame = 7; frame < 11; frame++)
AetheriaRuntimeDaemonSimulation.Step(run, intents, 0.1,
new AetheriaRuntimeDaemonSimulationSettings(), new AetheriaYmirPhysicalPayloadPhysics(),
new AetheriaYmirWorldPhysics(), catalog, frame, frame * 0.1);
AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1,
new AetheriaRuntimeDaemonSimulationSettings(), new AetheriaYmirPhysicalPayloadPhysics(),
new AetheriaYmirWorldPhysics(), catalog, 11, 1.1);
Require(!state.Firing && run.GameEvents.Count(value => value.Kind == "weapon.firing.stopped") == 2,
"releasing held fire must stop the persisted constant weapon and publish its transition");
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
new AetheriaRuntimeDaemonFrameDocument { FrameId = 11, Run = run },
new AetheriaRuntimeDaemonHealthDocument(),
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "weapon.state" &&
node.Props["behaviorKind"] == AetheriaRuntimeBehaviorKinds.ConstantWeapon &&
node.Props["itemKey"] == "test-beam" && node.Props["firing"] == "false"),
"Eve world entity must project recoverable generic ConstantWeapon state");
Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "feedback.event" &&
node.Props["eventKind"] == "weapon.firing.started" && node.Props["itemKey"] == "test-beam"),
"Eve feedback stream must project continuous weapon transition chronology");
}
private static void ChargedWeaponCannotBypassChargeLifecycle()
{
var source = Entity(0, 0, "player");
source.DirectionX = 1;
source.TargetEntityIndex = -1;
source.WeaponGroups = [new[] { 0 }];
source.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit
{
Item = new AetheriaRuntimeLoadoutItemCommit
{ ItemKey = "test-charged", Quality = 1, Durability = 1, Enabled = true }
}];
var target = Entity(1, 80, "raider");
var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, target] };
var run = new AetheriaRuntimeRunCheckpointCommit
{
RunId = "charged-weapon-admission-smoke", CurrentZoneIndex = 0,
CurrentEntityKey = "zone.0.entity.0", Zones = [zone]
};
var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.ChargedWeapon, 0,
[
new AetheriaRuntimeBehaviorField(2, PerformanceStat(20)),
new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)),
new AetheriaRuntimeBehaviorField(15, PerformanceStat(1000000000)),
new AetheriaRuntimeBehaviorField(16, PerformanceStat(200)),
new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)),
new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.5)),
new AetheriaRuntimeBehaviorField(21, PerformanceStat(1)),
new AetheriaRuntimeBehaviorField(24, new AetheriaRuntimeBehaviorValue("bool", "", 0, true, "", "", [], [])),
new AetheriaRuntimeBehaviorField(27, Number(2))
]);
var intents = new AetheriaRuntimeDaemonIntentState();
intents.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent
{
ActorEntityKey = "zone.0.entity.0", WeaponGroup = 0, Fire = true, Active = true
});
AetheriaRuntimeDaemonSimulation.Step(run, intents, 0.1,
new AetheriaRuntimeDaemonSimulationSettings(), new AetheriaYmirPhysicalPayloadPhysics(),
new AetheriaYmirWorldPhysics(),
new AetheriaRuntimeCatalogSnapshot([CatalogItem("test-charged", payload)], [], []), 0, 0);
Require(zone.PhysicalPayloads.Count == 0 && !run.GameEvents.Any(value => value.Kind == "shot.committed"),
"pressing an authored ChargedWeapon must not bypass charging through the instant weapon resolver");
Require(Math.Abs(Stat(target, "hull") - 100) < 0.000001,
"charged weapon admission must not apply damage before a daemon-owned release commits the shot");
var state = source.WeaponStates.Single(value => value.BehaviorKind == AetheriaRuntimeBehaviorKinds.ChargedWeapon);
for (var frame = 1; frame < 11; frame++)
AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1,
new AetheriaRuntimeDaemonSimulationSettings(), new AetheriaYmirPhysicalPayloadPhysics(),
new AetheriaYmirWorldPhysics(),
new AetheriaRuntimeCatalogSnapshot([CatalogItem("test-charged", payload)], [], []), frame, frame * 0.1);
Require(state.Charging && state.Charged && state.Charge >= 1 && state.ChargeHoldSeconds > 0,
"one semantic request must precharge without a firing solution and enter persisted hold state");
Require(!run.GameEvents.Any(value => value.Kind == "shot.committed"),
"ready charged weapon must hold rather than invent a firing solution");
source.TargetEntityIndex = target.EntityIndex;
AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1,
new AetheriaRuntimeDaemonSimulationSettings(), new AetheriaYmirPhysicalPayloadPhysics(),
new AetheriaYmirWorldPhysics(),
new AetheriaRuntimeCatalogSnapshot([CatalogItem("test-charged", payload)], [], []), 11, 1.1);
Require(run.GameEvents.Count(value => value.Kind == "weapon.charge.committed") == 1 &&
run.GameEvents.Any(value => value.Kind == "shot.committed" &&
value.ItemKey == "test-charged" && Math.Abs(value.ScalarValue - 40) < 0.000001),
"stored full charge must commit automatically when a firing solution becomes available");
var miss = run.ShotReceipts.Single(value => value.WeaponItemKey == "test-charged");
var missDx = miss.EndpointX - target.PositionX;
var missDz = miss.EndpointZ - target.PositionZ;
Require(!miss.Hit && miss.AppliedDamage == 0 && miss.HitRoll > miss.HitProbability,
"extreme authored dispersion must deterministically resolve a miss without damage");
RequireEqual("none", miss.ImpactKind, "a miss must not advertise a fabricated impact response");
RequireEqual("bolt", miss.PresentationKind, "instant weapon receipts must advertise a runtime-neutral travelling effect");
Require(Math.Sqrt(missDx * missDx + missDz * missDz) > 20 &&
miss.ImpactAngleRoll >= 0 && miss.ImpactAngleRoll < 1 &&
miss.ImpactRadiusRoll >= 0 && miss.ImpactRadiusRoll < 1,
"miss receipt must carry independent named impact rolls and an endpoint outside the target silhouette");
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
new AetheriaRuntimeDaemonFrameDocument { FrameId = 11, Run = run },
new AetheriaRuntimeDaemonHealthDocument(),
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "weapon.state" &&
node.Props.ContainsKey("chargeMalfunctionRisk")),
"Eve must expose charged hold duration and malfunction risk generically");
Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "feedback.event" &&
node.Props["eventKind"] == "weapon.charge.committed"),
"Eve feedback must expose charged solution commit chronology");
}
private static void ChargedWeaponHoldRiskMalfunctionsDeterministically()
{
var source = Entity(0, 0, "player");
source.WeaponGroups = [new[] { 0 }];
var item = new AetheriaRuntimeLoadoutItemCommit
{ ItemKey = "risk-charger", Quality = 1, Durability = 1, Enabled = true };
source.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = item }];
var run = new AetheriaRuntimeRunCheckpointCommit
{
RunId = "charged-risk-smoke", GenerationSeed = 17,
CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0",
Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source] }]
};
var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.ChargedWeapon, 0,
[
new AetheriaRuntimeBehaviorField(2, PerformanceStat(20)),
new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)),
new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)),
new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.5)),
new AetheriaRuntimeBehaviorField(21, PerformanceStat(1)),
new AetheriaRuntimeBehaviorField(25, Number(1)),
new AetheriaRuntimeBehaviorField(26, Number(0.25))
]);
var catalog = new AetheriaRuntimeCatalogSnapshot([CatalogItem(item.ItemKey, payload)], [], []);
for (var frame = 0; frame < 21; frame++)
{
var intents = new AetheriaRuntimeDaemonIntentState();
if (frame == 0) intents.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent
{ ActorEntityKey = "zone.0.entity.0", WeaponGroup = 0, Fire = true, Active = true });
AetheriaRuntimeDaemonSimulation.Step(run, intents, 0.1,
new AetheriaRuntimeDaemonSimulationSettings(), new AetheriaYmirPhysicalPayloadPhysics(),
new AetheriaYmirWorldPhysics(), catalog, frame, frame * 0.1);
}
var state = source.WeaponStates.Single(value => value.BehaviorKind == AetheriaRuntimeBehaviorKinds.ChargedWeapon);
Require(!state.Charging && !state.Charged && state.CoolingDown && state.ChargeRiskChecks == 1 &&
Math.Abs(state.ChargeMalfunctionRisk - 1) < 0.000001,
"held full charge must persist its risk check and fail when authored risk reaches certainty");
RequireNear(0.75, item.Durability, 0.000001,
"charged malfunction must damage canonical equipped-item durability");
Require(run.GameEvents.Count(value => value.Kind == "weapon.charge.malfunctioned" &&
value.SubjectKey == "hold-risk" && value.ItemKey == item.ItemKey) == 1,
"charged hold risk must emit one authoritative malfunction event");
Require(!run.GameEvents.Any(value => value.Kind == "shot.committed"),
"malfunction without a firing solution must never leak a projectile");
}
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<AetheriaRuntimeBehaviorStateCommit>();
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<AetheriaRuntimeCorporation>(),
Array.Empty<AetheriaRuntimeNameFile>());
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 void DaemonLoadoutsRespectFactionAvailabilityAndHullRoles()
{
var cells4 = Enumerable.Range(0, 4).SelectMany(y => Enumerable.Range(0, 4)
.Select(x => new AetheriaRuntimeShapeCell(x, y))).ToArray();
var one = new[] { new AetheriaRuntimeShapeCell(0, 0) };
AetheriaRuntimeCatalogItem Item(string key, string category, string manufacturer, int price,
string hardpointType = "", params string[] behaviors)
{
var item = CatalogItem(key, behaviors.Select((kind, index) =>
new AetheriaRuntimeBehaviorPayload(index, kind, 0, Array.Empty<AetheriaRuntimeBehaviorField>())).ToArray());
item.Category = category; item.ManufacturerKey = manufacturer; item.Price = price;
item.HardpointType = hardpointType; item.ShapeWidth = 1; item.ShapeHeight = 1;
item.OccupiedCells = 1; item.ShapeCells = one; item.BehaviorKinds = behaviors;
return item;
}
var availableHull = Item("available-hull", AetheriaRuntimeItemCategories.Hull, "forge", 100);
availableHull.HardpointType = "Hull";
availableHull.HullType = "Ship"; availableHull.ShapeWidth = 4; availableHull.ShapeHeight = 4;
availableHull.OccupiedCells = 16; availableHull.ShapeCells = cells4;
availableHull.Hardpoints =
[
new AetheriaRuntimeHardpoint("ControlModule", 0, 0, 1, 1, 1, one, "", "None", 0),
new AetheriaRuntimeHardpoint("Weapon", 1, 0, 1, 1, 1, one, "", "None", 0)
];
var unavailableHull = Item("cheap-foreign-hull", AetheriaRuntimeItemCategories.Hull, "foreign", 1);
unavailableHull.HardpointType = "Hull";
unavailableHull.HullType = "Ship"; unavailableHull.ShapeWidth = 4; unavailableHull.ShapeHeight = 4;
unavailableHull.OccupiedCells = 16; unavailableHull.ShapeCells = cells4;
var stationHull = Item("station-hull", AetheriaRuntimeItemCategories.Hull, "forge", 120);
stationHull.HardpointType = "Hull"; stationHull.HullType = "Station";
stationHull.ShapeWidth = 4; stationHull.ShapeHeight = 4; stationHull.OccupiedCells = 16;
stationHull.ShapeCells = cells4;
stationHull.Hardpoints =
[
new AetheriaRuntimeHardpoint("ControlModule", 0, 0, 1, 1, 1, one, "", "None", 0)
];
var cockpit = Item("cockpit", AetheriaRuntimeItemCategories.Gear, "forge", 20, "ControlModule", "Cockpit");
var wrongController = Item("cheap-turret-controller", AetheriaRuntimeItemCategories.Gear, "forge", 1,
"ControlModule", "TurretController");
var weapon = Item("cannon", AetheriaRuntimeItemCategories.Weapon, "forge", 40, "Weapon", "LockWeapon");
var cargo = Item("cargo-bay", AetheriaRuntimeItemCategories.CargoBay, "forge", 30);
cargo.HardpointType = "Internal";
cargo.InteriorShapeWidth = 2; cargo.InteriorShapeHeight = 2; cargo.InteriorOccupiedCells = 4;
cargo.InteriorShapeCells =
[
new AetheriaRuntimeShapeCell(0, 0), new AetheriaRuntimeShapeCell(1, 0),
new AetheriaRuntimeShapeCell(0, 1), new AetheriaRuntimeShapeCell(1, 1)
];
var docking = Item("docking-bay", AetheriaRuntimeItemCategories.DockingBay, "forge", 35);
docking.HardpointType = "Internal";
var capacitor = Item("capacitor", AetheriaRuntimeItemCategories.Gear, "forge", 25, "", "Capacitor");
capacitor.HardpointType = "Internal";
var faction = new AetheriaRuntimeCorporation("forge", "Forge", "F", "", "", "", 1, 1,
[new AetheriaRuntimeCorporationAllegiance("forge", 1)]);
var foreign = new AetheriaRuntimeCorporation("foreign", "Foreign", "X", "", "", "", 1, 2,
[new AetheriaRuntimeCorporationAllegiance("foreign", 1), new AetheriaRuntimeCorporationAllegiance("forge", 0.5)]);
var catalog = new AetheriaRuntimeCatalogSnapshot(
[availableHull, unavailableHull, stationHull, cockpit, wrongController, weapon, cargo, docking, capacitor],
[faction, foreign], Array.Empty<AetheriaRuntimeNameFile>());
var homes = new Dictionary<string, int> { ["forge"] = 0, ["foreign"] = 1 };
var adjacency = new Dictionary<int, IReadOnlyList<int>> { [0] = [1], [1] = [0] };
var first = new AetheriaDaemonLoadoutGenerator(catalog, 42, 0, homes, adjacency).Build("ship", "forge", []);
var second = new AetheriaDaemonLoadoutGenerator(catalog, 42, 0, homes, adjacency).Build("ship", "forge", []);
var station = new AetheriaDaemonLoadoutGenerator(catalog, 84, 0, homes, adjacency).Build("station", "forge", []);
var uninterruptedForge = new AetheriaDaemonLoadoutGenerator(catalog, 101, 0, homes, adjacency);
var interleavedForge = new AetheriaDaemonLoadoutGenerator(catalog, 101, 0, homes, adjacency);
var foreignStream = new AetheriaDaemonLoadoutGenerator(catalog, 202, 1, homes, adjacency);
var uninterruptedSequence = new[]
{
uninterruptedForge.Build("ship", "forge", []),
uninterruptedForge.Build("ship", "forge", [])
};
var interleavedFirst = interleavedForge.Build("ship", "forge", []);
foreignStream.Build("ship", "foreign", []);
var interleavedSecond = interleavedForge.Build("ship", "forge", []);
RequireEqual("available-hull", first.HullItemKey,
"loadout generation must exclude manufacturers outside the faction allegiance graph");
Require(first.Equipment.Any(value => value.ItemKey == "cockpit") &&
first.Equipment.All(value => value.ItemKey != "cheap-turret-controller"),
"ship control hardpoints must require the cockpit role even when the wrong controller is cheaper");
Require(first.Equipment.Any(value => value.ItemKey == "cannon") &&
first.Equipment.Any(value => value.ItemKey == "cargo-bay") &&
first.Equipment.Any(value => value.ItemKey == "capacitor"),
"generated ships must fit hardpoint equipment, a cargo bay, and a capacitor");
Require(first.HullItemKey == second.HullItemKey &&
first.Equipment.Select(value => value.ItemKey).SequenceEqual(second.Equipment.Select(value => value.ItemKey)),
"same seed, map, faction and catalog must produce the same loadout");
Require(first.Receipt.Seed == 42 && first.Receipt.SourceZoneIndex == 0 &&
first.Receipt.AvailabilityFactionKey == "forge" &&
first.Receipt.Selections.Any(value => value.Role == "hull" && value.ItemKey == "available-hull" &&
value.ManufacturerKey == "forge" && value.ManufacturerDistance == 1 && value.Allegiance == 1),
"generated loadouts must carry daemon-authored source and selection provenance");
Require(station.Equipment.Any(value => value.ItemKey == "docking-bay") &&
station.Equipment.Any(value => value.ItemKey == "cargo-bay") &&
station.Equipment.Any(value => value.ItemKey == "capacitor") &&
station.Equipment.Any(value => value.ItemKey == "cheap-turret-controller"),
"stations must fit docking, cargo, capacitor, and turret-controller roles before inventory generation");
Require(station.Cargo.Length == 4 && station.Cargo.All(value => value.Item.ItemKey != "cheap-foreign-hull"),
"station inventory draws must be packed to cargo capacity and exclude unavailable manufacturers");
Require(LoadoutKeys(uninterruptedSequence[0]).SequenceEqual(LoadoutKeys(interleavedFirst)) &&
LoadoutKeys(uninterruptedSequence[1]).SequenceEqual(LoadoutKeys(interleavedSecond)),
"generation in another faction stream must not perturb this faction's continuing loadout sequence");
}
private static IEnumerable<string> LoadoutKeys(AetheriaDaemonLoadout loadout) =>
new[] { loadout.HullItemKey }
.Concat(loadout.Equipment.Select(value => value.ItemKey))
.Concat(loadout.Cargo.Select(value => value.Item.ItemKey));
private static AetheriaRuntimeBehaviorValue PerformanceStat(double value) => new(
"performance-stat",
"",
0,
false,
"",
"",
[Number(value), Number(value), Number(0), Number(0), Number(0)],
Array.Empty<AetheriaRuntimeBehaviorMapEntry>());
private static AetheriaRuntimeBehaviorValue Number(double value) => new(
"number",
"",
value,
false,
"",
"",
Array.Empty<AetheriaRuntimeBehaviorValue>(),
Array.Empty<AetheriaRuntimeBehaviorMapEntry>());
private static AetheriaRuntimeCatalogItem CatalogItem(string itemKey, params AetheriaRuntimeBehaviorPayload[] payloads) => new(
itemKey, itemKey, "equipment", "", "", 0, 1, 1, 1, 1,
1, 1, 1, Array.Empty<AetheriaRuntimeShapeCell>(),
0, 0, 0, Array.Empty<AetheriaRuntimeShapeCell>(),
Array.Empty<AetheriaRuntimeHardpoint>(), payloads,
"utility", "", payloads.Select(payload => payload.Kind).ToArray(),
1, false, 0, 1, "", "", "", "", "",
0, 1000, Array.Empty<AetheriaRuntimeCurveKey>(), "", 1, 0, 0, 0, false,
0, 0, "", Array.Empty<AetheriaRuntimeAudioStat>(), Array.Empty<AetheriaRuntimeCurveKey>(), "", "");
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<AetheriaRuntimeDaemonCommandDocument>(),
PhysicalPayloadPhysics = AetheriaRuntimePhysicalPayloadPhysicsUnavailable.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<AetheriaRuntimeCargoBayLoadoutCommit>();
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<AetheriaRuntimeDaemonCommandDocument>(),
PhysicalPayloadPhysics = AetheriaRuntimePhysicalPayloadPhysicsUnavailable.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<AetheriaRuntimeCargoBayLoadoutCommit>())
.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 }];
agent.Equipment =
[
new AetheriaRuntimeLoadoutItemSlotCommit
{
Item = new AetheriaRuntimeLoadoutItemCommit
{
ItemKey = "test-lock-cannon", Quality = 1, Durability = 1, Enabled = true
}
}
];
agent.BehaviorStates =
[
new AetheriaRuntimeBehaviorStateCommit
{
OwnerKind = "fixture", OwnerIndex = 0, BehaviorIndex = 0,
BehaviorKind = "Capacitor", CapacitorCharge = 50, CapacitorCapacity = 50, CapacitorEfficiency = 1
}
];
agent.CargoContents = [Cargo(("test-ammo", 2, 0, 0))];
var weaponPayload = new AetheriaRuntimeBehaviorPayload(
0,
"LockWeapon",
0,
[
new AetheriaRuntimeBehaviorField(2, PerformanceStat(7)),
new AetheriaRuntimeBehaviorField(6, PerformanceStat(145)),
new AetheriaRuntimeBehaviorField(9, PerformanceStat(5)),
new AetheriaRuntimeBehaviorField(10, PerformanceStat(3)),
new AetheriaRuntimeBehaviorField(12, new AetheriaRuntimeBehaviorValue(
"item-key", "", 0, false, "", "test-ammo",
Array.Empty<AetheriaRuntimeBehaviorValue>(), Array.Empty<AetheriaRuntimeBehaviorMapEntry>())),
new AetheriaRuntimeBehaviorField(13, Number(3)),
new AetheriaRuntimeBehaviorField(14, Number(0.3)),
new AetheriaRuntimeBehaviorField(16, PerformanceStat(330)),
new AetheriaRuntimeBehaviorField(17, PerformanceStat(3)),
new AetheriaRuntimeBehaviorField(18, PerformanceStat(0.3)),
new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.2)),
new AetheriaRuntimeBehaviorField(21, PerformanceStat(2)),
new AetheriaRuntimeBehaviorField(22, PerformanceStat(1)),
new AetheriaRuntimeBehaviorField(23, PerformanceStat(45)),
new AetheriaRuntimeBehaviorField(24, PerformanceStat(1)),
new AetheriaRuntimeBehaviorField(25, PerformanceStat(1))
]);
var catalog = new AetheriaRuntimeCatalogSnapshot(
[CatalogItem("test-lock-cannon", weaponPayload)],
Array.Empty<AetheriaRuntimeCorporation>(),
Array.Empty<AetheriaRuntimeNameFile>());
var target = Entity(1, 105, "raider");
target.Kind = "station";
target.StatGrids = [Grid("hull", 14), 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<AetheriaRuntimeDaemonCommandDocument>(),
Catalog = catalog,
PhysicalPayloadPhysics = new AetheriaYmirPhysicalPayloadPhysics(),
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<AetheriaRuntimeWeaponStateCommit>())
.Single(value => value.OwnerKind == AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind);
sawPartialLock |= weapon.LockProgress > 0 && weapon.LockProgress < 0.99;
if (firstLaunchFrame < 0 && run.GameEvents.Any(value => value.Kind == "shot.committed"))
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 == "shot.committed" && value.SourceEntityIndex == agent.EntityIndex),
"accepted fire control must emit authoritative shot commitment chronology");
Require(run.GameEvents.Any(value => value.Kind == "shot.committed" &&
value.ItemKey == "test-lock-cannon" && Math.Abs(value.ScalarValue - (7.0 / 3.0)) < 0.000001),
"daemon combat must divide authored damage across the configured burst rounds");
Require((agent.WeaponStates ?? Array.Empty<AetheriaRuntimeWeaponStateCommit>()).Any(value => value.OwnerKind == AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind &&
value.OwnerIndex == 0 && value.BehaviorKind == "LockWeapon"),
"weapon progress must belong to the equipped authored behavior instead of a synthetic entity weapon");
var authoredWeapon = agent.WeaponStates.Single(value =>
value.OwnerKind == AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind);
RequireEqual(0, authoredWeapon.Ammo,
"two three-round authored bursts must consume both complete magazines");
RequireNear(40, agent.BehaviorStates.Single(value => value.BehaviorKind == "Capacitor").CapacitorCharge,
0.000001, "two authored bursts must drain exactly their combined energy from canonical capacitor state");
RequireEqual(1, CargoQuantity(agent, "test-ammo"),
"one empty magazine must consume exactly one reserve cargo commodity before reload");
Require(run.GameEvents.Count(value => value.Kind == "shot.committed" && value.ItemKey == "test-lock-cannon") >= 6,
"two authored bursts must commit all six due rounds through the shot resolver");
Require(run.ShotReceipts.Count(value => value.WeaponItemKey == "test-lock-cannon") >= 6 &&
run.ShotReceipts.All(value => value.Hit && value.AppliedDamage == value.NominalDamage),
"zero-spread authored rounds must commit immutable certain-hit receipts before presentation travel");
Require(run.ShotReceipts.Select(value => value.ShotId).Distinct(StringComparer.Ordinal).Count() ==
run.ShotReceipts.Count,
"every burst round must own a stable independent shot identity");
Require(run.GameEvents.Count(value => value.Kind == "weapon.reload.started") == 1 &&
run.GameEvents.Count(value => value.Kind == "weapon.reload.completed") == 1,
"empty magazine transition must publish one authoritative reload start and completion pair");
var reloadStarted = run.GameEvents.Single(value => value.Kind == "weapon.reload.started").FrameId;
var reloadCompleted = run.GameEvents.Single(value => value.Kind == "weapon.reload.completed").FrameId;
Require(reloadCompleted > reloadStarted && !run.GameEvents.Any(value =>
value.Kind == "shot.committed" && value.FrameId > reloadStarted && value.FrameId < reloadCompleted),
"reload interval must be a shot-free authoritative timeline, not a cosmetic client delay");
Require(!target.IsActive,
$"attack task must end through daemon shot resolution before presentation 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].PhysicalPayloads.Count} task={run.AgentTasks.Single().Status}");
RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, run.AgentTasks.Single().Status,
"attack task must complete when its target dies");
var shotSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
new AetheriaRuntimeDaemonFrameDocument { FrameId = 60, Run = run },
new AetheriaRuntimeDaemonHealthDocument(),
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
Require(Flatten(shotSurface.Surface.Root).Any(node => node.Kind == "shot.receipt" &&
node.Props["itemKey"] == "test-lock-cannon" && node.Props["outcome"] == "hit" &&
node.Props["presentationKind"] == "bolt" &&
node.Props.ContainsKey("presentationIntensity") &&
node.Props.ContainsKey("impactKind")),
"Eve must project inspectable deterministic shot receipts without reconstructing combat");
}
private static void AttackTaskAdmissionRejectsImpossibleWork()
{
var agent = Entity(0, 0, "player");
agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Attack];
agent.WeaponGroups = [new[] { 0 }];
var hostile = Entity(1, 100, "raider");
var friendly = Entity(2, 100, "player");
var run = new AetheriaRuntimeRunCheckpointCommit
{
RunId = "attack-admission-smoke",
CurrentEntityKey = "",
Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent, hostile, friendly] }]
};
AetheriaRuntimeDaemonCommandDocument Command(string id, int targetIndex, int weaponGroup)
{
var command = AetheriaRuntimeDaemonCommandDocument.Create(
AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, "commander-smoke", "starbridge-smoke", 0, "");
command.CommandId = id;
command.AgentTask = new AetheriaRuntimeAgentTaskCommand
{
TaskId = id,
CorporationKey = "player",
TaskType = AetheriaRuntimeAgentTaskTypes.Attack,
ZoneIndex = 0,
TargetEntityIndex = targetIndex,
WeaponGroup = weaponGroup
};
return command;
}
var result = AetheriaRuntimeDaemonOperations.Execute(run,
[
Command("missing-target", 99, 0),
Command("friendly-target", 2, 0),
Command("missing-group", 1, 1),
Command("valid-attack", 1, 0)
]);
Require(result.RejectedCommandIds.SequenceEqual(new[] { "missing-target", "friendly-target", "missing-group" }),
"attack admission must reject missing/friendly targets and unavailable weapon groups");
Require(result.AppliedCommandIds.SequenceEqual(new[] { "valid-attack" }),
"attack admission must accept executable hostile work");
Require(run.AgentTasks.Count == 1 && run.AgentTasks[0].TaskId == "valid-attack",
"rejected attack orders must not enter the durable corporation queue");
}
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<AetheriaRuntimeEntitySnapshotCommit>() },
new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 2, PositionX = 200, GravityTerrainRadius = 100, AdjacentZoneIndices = [1, 3], Entities = Array.Empty<AetheriaRuntimeEntitySnapshotCommit>() },
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<AetheriaRuntimeEntitySnapshotCommit>() },
new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 2, PositionX = 200, GravityTerrainRadius = 100, AdjacentZoneIndices = [1], Entities = Array.Empty<AetheriaRuntimeEntitySnapshotCommit>() }
],
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<AetheriaRuntimeEntitySnapshotCommit>() },
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<string>();
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<AetheriaRuntimeDaemonCommandDocument>(),
PhysicalPayloadPhysics = AetheriaRuntimePhysicalPayloadPhysicsUnavailable.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,
AetheriaRuntimePhysicalPayloadPhysicsUnavailable.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 AetheriaYmirPhysicalPayloadPhysics().Step(zone, zone.Entities, 0.1);
RequireEqual("ymir.core", new AetheriaYmirPhysicalPayloadPhysics().AuthorityId, "adapter must identify its owner");
RequireEqual(0, step.PhysicalPayloads.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.physical-payload.smoke-projectile", step.Hits[0].PhysicalPayloadBodyId, "physical payload body id must be stable");
RequireEqual("aetheria.daemon.entity.2", step.Hits[0].TargetBodyId, "entity body id must be stable");
}
private static void DaemonSimulationTreatsYmirHitAsPresentationOnly()
{
var (run, _, target) = Scenario();
AetheriaRuntimeDaemonSimulation.Step(
run,
new AetheriaRuntimeDaemonIntentState(),
0.1,
AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault,
new AetheriaYmirPhysicalPayloadPhysics(),
new AetheriaYmirWorldPhysics());
RequireEqual(100.0, Stat(target, "hull"), "presentation projectile contact must not write combat damage");
RequireEqual(0, run.Zones[0].PhysicalPayloads.Count, "spent projectile must leave daemon state");
RequireEqual(1, run.GameEvents.Count(value => value.Kind == "physical-payload.contact" && value.SubjectKey == "smoke-projectile"),
"Ymir contact must emit one projectile-identity impact event");
RequireEqual(0, run.GameEvents.Count(value => value.Kind == "entity.damaged" && value.TargetEntityIndex == target.EntityIndex),
"presentation projectile contact must not manufacture a 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"] == "physical-payload.contact" && node.Props["subjectKey"] == "smoke-projectile"),
"Eve feedback must project authoritative projectile impact identity");
}
private static void ProjectileContactCannotKill()
{
var (run, _, target) = Scenario();
target.StatGrids.Single(grid => grid.Name == "hull").Values = [5];
AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1,
AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, new AetheriaYmirPhysicalPayloadPhysics(), new AetheriaYmirWorldPhysics(), frameId: 9);
Require(target.IsActive && Math.Abs(Stat(target, "hull") - 5) < 0.000001,
"even nominally lethal presentation contact must leave canonical target state untouched");
RequireEqual(0, run.GameEvents.Count(value => value.Kind == "entity.destroyed" && value.TargetEntityIndex == target.EntityIndex),
"presentation contact must not manufacture a destruction event");
}
private static void MissingPhysicsOwnerCannotAdvanceProjectiles()
{
var (run, _, _) = Scenario();
try
{
AetheriaRuntimeDaemonSimulation.Step(
run,
new AetheriaRuntimeDaemonIntentState(),
0.1,
AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault,
AetheriaRuntimePhysicalPayloadPhysicsUnavailable.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, PhysicalPayloadPhysics = AetheriaRuntimePhysicalPayloadPhysicsUnavailable.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], PhysicalPayloadPhysics = AetheriaRuntimePhysicalPayloadPhysicsUnavailable.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");
var assets = AetheriaRuntimeAssets.ProjectManifest(null).Assets;
var playerAsset = assets.Single(asset => asset.Ref.AssetKey == "prefab.entity.player");
RequireEqual("player", playerAsset.Ref.Metadata["presentationRole"],
"entity prefab fallback must use a specific presentation role instead of the generic world tag");
}
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, PhysicalPayloadPhysics = AetheriaRuntimePhysicalPayloadPhysicsUnavailable.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, PhysicalPayloadPhysics = AetheriaRuntimePhysicalPayloadPhysicsUnavailable.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, PhysicalPayloadPhysics = AetheriaRuntimePhysicalPayloadPhysicsUnavailable.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],
PhysicalPayloads =
[
new AetheriaRuntimePhysicalPayloadCommit
{
PayloadId = "smoke-projectile",
SourceEntityIndex = 1,
TargetEntityIndex = 2,
PositionX = 0,
PositionZ = 0,
VelocityX = 100,
VelocityY = 0,
Radius = 1,
ContactMagnitude = 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<AetheriaRuntimeSurfaceComponent> 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>(T expected, T actual, string message)
{
if (!EqualityComparer<T>.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}.");
}
}