Complete daemon-owned Terminus service loop

This commit is contained in:
Meta Vens 2026-07-13 14:36:43 +01:00
commit 6c2bcd6afb
18 changed files with 559 additions and 53 deletions

View file

@ -2036,6 +2036,8 @@ public class DaemonRuntimeDocumentTests
FactionKey = "player",
IsActive = true,
TargetEntityIndex = 1,
Visibility = 0.37,
VisibilitySourceCount = 2,
PositionX = 0,
PositionZ = 0
},
@ -2178,7 +2180,9 @@ public class DaemonRuntimeDocumentTests
Assert.AreEqual(1.25, weapon.BurstInterval, 0.0001);
Assert.AreEqual(88, zone.Entities[0].StatGrids.Single(grid => grid.Name == "hull").Values[0], 0.0001);
Assert.AreEqual(66, zone.Entities[1].StatGrids.Single(grid => grid.Name == "hull").Values[0], 0.0001);
Assert.AreEqual(222, zone.Entities[0].Visibility, 0.0001);
Assert.AreEqual(0.37, zone.Entities[0].Visibility, 0.0001);
Assert.AreEqual(2, zone.Entities[0].VisibilitySourceCount);
Assert.IsTrue(zone.Entities[0].Contacts.Single(contact => contact.TargetEntityIndex == 1).Visible);
var frame = AetheriaRuntimeDaemonFrameDocument.Create(
run,
@ -2192,6 +2196,84 @@ public class DaemonRuntimeDocumentTests
Assert.AreEqual(120, frame.SimulationSettings.AttackRange, 0.0001);
}
[Test]
public void SensorReachDerivesFromInstalledEquipmentRatherThanEntityKind()
{
var unitStat = new AetheriaRuntimeBehaviorValue(
"performance-stat", "", 0, false, "", "",
new[]
{
NumberValue(1), NumberValue(1), NumberValue(1),
NumberValue(0), NumberValue(1), EmptyBehaviorValue("stat-recipe")
},
Array.Empty<AetheriaRuntimeBehaviorMapEntry>());
var catalog = new AetheriaRuntimeCatalogSnapshot(
new[]
{
CatalogItem("sensor", new[]
{
new AetheriaRuntimeBehaviorPayload(
0, "Sensor", 1,
new[] { new AetheriaRuntimeBehaviorField(3, unitStat) })
})
},
Array.Empty<AetheriaRuntimeCorporation>(),
Array.Empty<AetheriaRuntimeNameFile>());
AetheriaRuntimeLoadoutItemSlotCommit Sensor() => new()
{
Item = new AetheriaRuntimeLoadoutItemCommit
{
ItemKey = "sensor", Quality = 1, Durability = 1, Enabled = true
}
};
var ship = new AetheriaRuntimeEntitySnapshotCommit
{
EntityIndex = 0, Kind = "ship", FactionKey = "player", IsActive = true,
Equipment = new[] { Sensor() }, Visibility = 0.4
};
var station = new AetheriaRuntimeEntitySnapshotCommit
{
EntityIndex = 1, Kind = "station", FactionKey = "player", IsActive = true,
Equipment = new[] { Sensor() }, PositionZ = 10, Visibility = 0.6
};
var arrayShip = new AetheriaRuntimeEntitySnapshotCommit
{
EntityIndex = 2, Kind = "ship", FactionKey = "player", IsActive = true,
Equipment = new[] { Sensor(), Sensor() }, PositionZ = 20, Visibility = 0.8
};
var target = new AetheriaRuntimeEntitySnapshotCommit
{
EntityIndex = 3, Kind = "ship", FactionKey = "neutral", IsActive = true,
PositionX = 250
};
var run = new AetheriaRuntimeRunCheckpointCommit
{
CurrentZoneIndex = 0,
Zones = new[] { new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = new[] { ship, station, arrayShip, target } } }
};
var defaults = AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault;
var settings = new AetheriaRuntimeDaemonSimulationSettings(
defaults.PawnSpeed, defaults.RaiderSpeed, defaults.AttackRange, defaults.AttackHoldRatio,
defaults.PawnProjectileDamage, defaults.RaiderProjectileDamage, defaults.WeaponCooldownSeconds,
defaults.ProjectileSpeed, defaults.ProjectileRadius, defaults.ProjectileLifetimeSeconds,
defaults.ProjectileSpawnOffset, defaults.ProjectileHeatScale, defaults.HeatDissipationPerSecond,
999, 200, defaults.PlayerStationHull, defaults.HostileStationHull, defaults.PlayerEntityHull,
defaults.RaiderEntityHull, defaults.StationShield, defaults.EntityShield);
AetheriaRuntimeDaemonSimulation.Step(
run, new AetheriaRuntimeDaemonIntentState(), 0.1, settings,
new PassthroughPhysicalPayloadPhysics(), new PassthroughWorldPhysics(), catalog);
Assert.IsFalse(ship.Contacts.Single(contact => contact.TargetEntityIndex == 3).Visible);
Assert.IsFalse(station.Contacts.Single(contact => contact.TargetEntityIndex == 3).Visible,
"Entity kind must not grant a station privileged sensor reach.");
Assert.IsTrue(arrayShip.Contacts.Single(contact => contact.TargetEntityIndex == 3).Visible,
"Additional installed arrays should increase reach regardless of entity kind.");
Assert.AreEqual(0.4, ship.Visibility, 0.0001);
Assert.AreEqual(0.6, station.Visibility, 0.0001);
Assert.AreEqual(0.8, arrayShip.Visibility, 0.0001);
}
[Test]
public void TerminusPilotTractorScoopsCargoFromTargetedWreck()
{
@ -4190,10 +4272,36 @@ public class DaemonRuntimeDocumentTests
Assert.AreEqual(0.25, run.Zones[0].Entities[0].ShutdownPerformance, 0.0001);
}
[Test]
public void DaemonOperationsClassifiesPausedCommandsBySimulationDependency()
{
Assert.IsFalse(AetheriaRuntimeDaemonOperations.RequiresSimulationStep(
AetheriaRuntimeDaemonCommandKinds.TradePurchase));
Assert.IsFalse(AetheriaRuntimeDaemonOperations.RequiresSimulationStep(
AetheriaRuntimeDaemonCommandKinds.SetTarget));
Assert.IsFalse(AetheriaRuntimeDaemonOperations.RequiresSimulationStep(
AetheriaRuntimeDaemonCommandKinds.TransferCargoItem));
Assert.IsFalse(AetheriaRuntimeDaemonOperations.RequiresSimulationStep(
AetheriaRuntimeDaemonCommandKinds.SetSimulationRate));
Assert.IsFalse(AetheriaRuntimeDaemonOperations.RequiresSimulationStep(
AetheriaRuntimeDaemonCommandKinds.AdvanceSimulationStep));
Assert.IsTrue(AetheriaRuntimeDaemonOperations.RequiresSimulationStep(
AetheriaRuntimeDaemonCommandKinds.SetMoveVector));
Assert.IsTrue(AetheriaRuntimeDaemonOperations.RequiresSimulationStep(
AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup));
Assert.IsTrue(AetheriaRuntimeDaemonOperations.RequiresSimulationStep(
AetheriaRuntimeDaemonCommandKinds.DockNearest));
Assert.IsTrue(AetheriaRuntimeDaemonOperations.RequiresSimulationStep(
AetheriaRuntimeDaemonCommandKinds.Undock));
}
[Test]
public void DaemonOperationsOwnsTradePurchaseCreditsAndCargoInDaemonState()
{
var run = RunWithTwoEntities();
run.CurrentEntityKey = "zone.0.entity.1";
run.Zones[0].Entities[0].DockingBayAssignments = new[] { 1 };
run.Zones[0].Entities[0].ChildEntityIndices = new[] { 1 };
var command = AetheriaRuntimeDaemonCommandDocument.Create(
AetheriaRuntimeDaemonCommandKinds.TradePurchase,
"codex",
@ -4206,8 +4314,8 @@ public class DaemonRuntimeDocumentTests
command.TradePurchase.PurchaseKind = "commodity";
command.TradePurchase.ItemKey = "ore";
command.TradePurchase.Quantity = 5;
command.TradePurchase.UnitPrice = 40;
command.TradePurchase.TotalPrice = 200;
command.TradePurchase.UnitPrice = 0;
command.TradePurchase.TotalPrice = 0;
command.TradePurchase.StationEntityKey = "zone.0.entity.0";
command.TradePurchase.StationCargoIndex = 0;
command.TradePurchase.TargetEntityKey = "zone.0.entity.1";
@ -4216,7 +4324,16 @@ public class DaemonRuntimeDocumentTests
command.TradePurchase.SourceY = 3;
command.TradePurchase.CreatesDockedShip = false;
var result = AetheriaRuntimeDaemonOperations.Execute(run, new[] { command });
var result = AetheriaRuntimeDaemonOperations.Execute(
run,
new[] { command },
new AetheriaRuntimeDaemonOperationContext
{
Catalog = new AetheriaRuntimeCatalogSnapshot(
new[] { CatalogItem("ore", Array.Empty<AetheriaRuntimeBehaviorPayload>(), price: 40) },
Array.Empty<AetheriaRuntimeCorporation>(),
Array.Empty<AetheriaRuntimeNameFile>())
});
var stationCargo = run.Zones[0].Entities[0].CargoContents[0].Items;
var targetCargo = run.Zones[0].Entities[1].CargoContents[0].Items;
@ -4229,10 +4346,58 @@ public class DaemonRuntimeDocumentTests
Assert.AreEqual(5, targetCargo[0].Item.Quantity);
}
[Test]
public void DaemonOperationsRejectsTradeWhenCurrentEntityIsNotDockedWithoutMutatingState()
{
var run = RunWithTwoEntities();
var command = AetheriaRuntimeDaemonCommandDocument.Create(
AetheriaRuntimeDaemonCommandKinds.TradePurchase,
"codex",
"session-trade-undocked",
30,
"zone.0.entity.1");
command.TradePurchase.ItemKey = "ore";
command.TradePurchase.Quantity = 1;
command.TradePurchase.StationEntityKey = "zone.0.entity.0";
command.TradePurchase.StationCargoIndex = 0;
command.TradePurchase.TargetEntityKey = "zone.0.entity.1";
command.TradePurchase.TargetCargoIndex = 0;
command.TradePurchase.SourceX = 2;
command.TradePurchase.SourceY = 3;
var result = AetheriaRuntimeDaemonOperations.Execute(
run,
new[] { command },
new AetheriaRuntimeDaemonOperationContext
{
Catalog = new AetheriaRuntimeCatalogSnapshot(
new[] { CatalogItem("ore", Array.Empty<AetheriaRuntimeBehaviorPayload>(), price: 40) },
Array.Empty<AetheriaRuntimeCorporation>(),
Array.Empty<AetheriaRuntimeNameFile>())
});
Assert.AreEqual(0, result.AppliedCommandIds.Count);
Assert.AreEqual(1, result.RejectedCommandIds.Count);
Assert.AreEqual(1000, run.Credits);
Assert.AreEqual(12, run.Zones[0].Entities[0].CargoContents[0].Items[0].Item.Quantity);
Assert.AreEqual(0, run.Zones[0].Entities[1].CargoContents[0].Items.Count);
}
[Test]
public void DaemonOperationsCreatesPurchasedDockedShipInDaemonState()
{
var run = RunWithTwoEntities();
run.Zones[0].Entities[1].DockingBayAssignments = new[] { 0, -1 };
run.Zones[0].Entities[1].ChildEntityIndices = new[] { 0 };
run.Zones[0].Entities[1].CargoContents[0].Items = new[]
{
new AetheriaRuntimeLoadoutItemSlotCommit
{
X = 4,
Y = 5,
Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "starter-hull", Quantity = 1 }
}
};
var command = AetheriaRuntimeDaemonCommandDocument.Create(
AetheriaRuntimeDaemonCommandKinds.TradePurchase,
"codex",
@ -4247,10 +4412,23 @@ public class DaemonRuntimeDocumentTests
command.TradePurchase.Quantity = 1;
command.TradePurchase.UnitPrice = 300;
command.TradePurchase.TotalPrice = 300;
command.TradePurchase.StationEntityKey = "zone.0.entity.1";
command.TradePurchase.StationCargoIndex = 0;
command.TradePurchase.TargetEntityKey = "zone.0.entity.1";
command.TradePurchase.SourceX = 4;
command.TradePurchase.SourceY = 5;
command.TradePurchase.CreatesDockedShip = true;
var result = AetheriaRuntimeDaemonOperations.Execute(run, new[] { command });
var result = AetheriaRuntimeDaemonOperations.Execute(
run,
new[] { command },
new AetheriaRuntimeDaemonOperationContext
{
Catalog = new AetheriaRuntimeCatalogSnapshot(
new[] { CatalogItem("starter-hull", Array.Empty<AetheriaRuntimeBehaviorPayload>(), price: 300, hullType: "ship") },
Array.Empty<AetheriaRuntimeCorporation>(),
Array.Empty<AetheriaRuntimeNameFile>())
});
var zone = run.Zones[0];
Assert.AreEqual(1, result.AppliedCommandIds.Count);
@ -5437,7 +5615,9 @@ public class DaemonRuntimeDocumentTests
private static AetheriaRuntimeCatalogItem CatalogItem(
string itemKey,
IReadOnlyList<AetheriaRuntimeBehaviorPayload> behaviorPayloads)
IReadOnlyList<AetheriaRuntimeBehaviorPayload> behaviorPayloads,
int price = 0,
string hullType = "")
{
return new AetheriaRuntimeCatalogItem(
itemKey,
@ -5445,7 +5625,7 @@ public class DaemonRuntimeDocumentTests
"",
"",
"",
0,
price,
0,
0,
0,
@ -5461,7 +5641,7 @@ public class DaemonRuntimeDocumentTests
Array.Empty<AetheriaRuntimeHardpoint>(),
behaviorPayloads,
"",
"",
hullType,
behaviorPayloads.Select(payload => payload.Kind).ToArray(),
1,
false,