Restore autonomous return home
This commit is contained in:
parent
3a05d1a143
commit
d79aef1053
2 changed files with 160 additions and 1 deletions
|
|
@ -26,6 +26,7 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
SchedulerCollapsesDuplicateAssignmentMarkers();
|
||||
SchedulerAssignsShortestGalaxyRoute();
|
||||
AgentTraversesGalaxyRouteBeforeExecutingTask();
|
||||
IdleAgentReturnsToCanonicalHomeAndDocks();
|
||||
AttackAgentControlsOptimumRangeThroughMovementLever();
|
||||
AgentCompletesAttackTaskThroughTargetFireAndYmir();
|
||||
AgentCompletesHaulTaskThroughMovementAndCargoCommands();
|
||||
|
|
@ -760,6 +761,72 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
"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 AgentClaimsAndCompletesExploreTaskThroughCommands()
|
||||
{
|
||||
var agent = Entity(0, 0, "workers");
|
||||
|
|
|
|||
|
|
@ -95,7 +95,9 @@ namespace GameCult.Aetheria.State.Verse
|
|||
|
||||
ReleaseInvalidAssignments(run);
|
||||
AssignQueuedTasks(run, frameId);
|
||||
return PlanAssignedTasks(run, frameId, catalog, simulationTimeSeconds);
|
||||
var commands = PlanAssignedTasks(run, frameId, catalog, simulationTimeSeconds).ToList();
|
||||
commands.AddRange(PlanIdleReturns(run, frameId));
|
||||
return commands;
|
||||
}
|
||||
|
||||
public static void Reconcile(
|
||||
|
|
@ -335,6 +337,96 @@ namespace GameCult.Aetheria.State.Verse
|
|||
return commands;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AetheriaRuntimeDaemonCommandDocument> PlanIdleReturns(
|
||||
AetheriaRuntimeRunCheckpointCommit run,
|
||||
long frameId)
|
||||
{
|
||||
var commands = new List<AetheriaRuntimeDaemonCommandDocument>();
|
||||
var locations = (run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
|
||||
.Where(zone => zone != null)
|
||||
.SelectMany(zone => (zone.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
|
||||
.Where(entity => entity != null)
|
||||
.Select(entity => (Zone: zone, Entity: entity)))
|
||||
.ToArray();
|
||||
var byId = locations
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value.Entity.EntityId))
|
||||
.ToDictionary(value => value.Entity.EntityId, StringComparer.Ordinal);
|
||||
foreach (var worker in locations
|
||||
.Where(value => value.Entity.IsActive)
|
||||
.Where(value => (value.Entity.AgentTaskCapabilities ?? Array.Empty<string>()).Count > 0)
|
||||
.Where(value => string.IsNullOrWhiteSpace(value.Entity.AssignedAgentTaskId))
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value.Entity.HomeEntityId)))
|
||||
{
|
||||
if (!byId.TryGetValue(worker.Entity.HomeEntityId, out var home) || !home.Entity.IsActive)
|
||||
continue;
|
||||
if (worker.Zone.ZoneIndex != home.Zone.ZoneIndex)
|
||||
{
|
||||
var route = FindZoneRoute(run, worker.Zone.ZoneIndex, home.Zone.ZoneIndex);
|
||||
if (route.Count < 2)
|
||||
continue;
|
||||
var settings = AetheriaRuntimeDaemonRenderSettings.AetheriaDefault;
|
||||
var exit = AetheriaRuntimeDaemonRenderQueries.QueryWormholeExits(
|
||||
run,
|
||||
worker.Zone,
|
||||
AetheriaRuntimeDaemonRenderQueries.ResolveZoneRenderRadius(worker.Zone, 1200),
|
||||
settings.WormholeDistanceRatio)
|
||||
.First(candidate => candidate.TargetZoneIndex == route[1]);
|
||||
var dx = exit.PositionX - worker.Entity.PositionX;
|
||||
var dz = exit.PositionZ - worker.Entity.PositionZ;
|
||||
if (Math.Sqrt(dx * dx + dz * dz) > AetheriaRuntimeDaemonOperationContext.DefaultWormholeExitRadius * 0.8)
|
||||
commands.Add(IdleMovement(worker.Zone.ZoneIndex, worker.Entity, frameId, dx, dz, "home-approach-wormhole"));
|
||||
else
|
||||
commands.Add(IdleCommand(worker.Zone.ZoneIndex, worker.Entity, frameId,
|
||||
AetheriaRuntimeDaemonCommandKinds.EnterWormhole, "home-travel", command => command.TargetZoneIndex = route[1]));
|
||||
continue;
|
||||
}
|
||||
if ((home.Entity.DockingBayAssignments ?? Array.Empty<int>()).Contains(worker.Entity.EntityIndex))
|
||||
continue;
|
||||
var homeDx = home.Entity.PositionX - worker.Entity.PositionX;
|
||||
var homeDz = home.Entity.PositionZ - worker.Entity.PositionZ;
|
||||
var dockingContactDistance = InteractionRadius(home.Entity) + InteractionRadius(worker.Entity);
|
||||
if (Math.Sqrt(homeDx * homeDx + homeDz * homeDz) > dockingContactDistance + 0.5)
|
||||
commands.Add(IdleMovement(worker.Zone.ZoneIndex, worker.Entity, frameId, homeDx, homeDz, "home-approach"));
|
||||
else
|
||||
commands.Add(IdleCommand(worker.Zone.ZoneIndex, worker.Entity, frameId,
|
||||
AetheriaRuntimeDaemonCommandKinds.Dock, "home-dock",
|
||||
command => command.TargetEntityKey = EntityKey(run, home.Zone.ZoneIndex, home.Entity.EntityIndex)));
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeDaemonCommandDocument IdleMovement(
|
||||
int zoneIndex,
|
||||
AetheriaRuntimeEntitySnapshotCommit entity,
|
||||
long frameId,
|
||||
double dx,
|
||||
double dz,
|
||||
string phase)
|
||||
{
|
||||
var length = Math.Sqrt(dx * dx + dz * dz);
|
||||
return IdleCommand(zoneIndex, entity, frameId, AetheriaRuntimeDaemonCommandKinds.SetMoveVector, phase, command =>
|
||||
{
|
||||
command.DirectionX = length <= 0.0001 ? 0 : dx / length;
|
||||
command.DirectionY = length <= 0.0001 ? 0 : dz / length;
|
||||
command.ScalarValue = length <= 0.0001 ? 0 : 1;
|
||||
});
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeDaemonCommandDocument IdleCommand(
|
||||
int zoneIndex,
|
||||
AetheriaRuntimeEntitySnapshotCommit entity,
|
||||
long frameId,
|
||||
AetheriaRuntimeDaemonCommandKinds kind,
|
||||
string phase,
|
||||
Action<AetheriaRuntimeDaemonCommandDocument> configure)
|
||||
{
|
||||
var command = AetheriaRuntimeDaemonCommandDocument.Create(
|
||||
kind, RuntimeId, "daemon-agent-scheduler", frameId, $"zone.{zoneIndex}.entity.{entity.EntityIndex}");
|
||||
command.CommandId = string.Join(":", RuntimeId, entity.EntityId, frameId.ToString(CultureInfo.InvariantCulture), phase);
|
||||
configure(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
private static (AetheriaRuntimeZoneSnapshotCommit? Zone, AetheriaRuntimeEntitySnapshotCommit? Entity) FindAssignedEntity(
|
||||
AetheriaRuntimeRunCheckpointCommit run,
|
||||
AetheriaRuntimeAgentTaskCommit task)
|
||||
|
|
|
|||
Loading…
Reference in a new issue