Allow every actor to share daemon control levers

This commit is contained in:
Meta Vens 2026-07-11 17:55:50 +01:00
commit c3e401725c
5 changed files with 67 additions and 12 deletions

View file

@ -13,6 +13,57 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
DaemonSimulationAppliesYmirHit();
MissingPhysicsOwnerCannotAdvanceProjectiles();
ThermalCellsUseFossilConductionAndRadiation();
MultipleActorsUseTheSameMovementLever();
}
private static void MultipleActorsUseTheSameMovementLever()
{
var player = Entity(0, 0, "player");
var agent = Entity(1, 0, "worker");
var run = new AetheriaRuntimeRunCheckpointCommit
{
RunId = "shared-lever-smoke",
CurrentZoneIndex = 0,
CurrentEntityKey = "zone.0.entity.0",
Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [player, agent] }]
};
var commands = new[]
{
MovementCommand("player-move", "zone.0.entity.0", 1, 0),
MovementCommand("agent-move", "zone.0.entity.1", 0, 1)
};
var operation = AetheriaRuntimeDaemonOperations.Execute(run, commands);
RequireEqual(2, operation.Intents.Movements.Count, "movement intent must retain one lever position per actor");
AetheriaRuntimeDaemonSimulation.Step(
run,
operation.Intents,
0.1,
AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault,
AetheriaRuntimeProjectilePhysicsUnavailable.Instance);
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()

View file

@ -529,8 +529,8 @@ public class DaemonRuntimeDocumentTests
Assert.AreEqual(2, result.OperationResult.AppliedCommandIds.Count);
Assert.AreEqual(0, result.OperationResult.RejectedCommandIds.Count);
Assert.AreEqual(1, run.Zones[0].Entities[0].TargetEntityIndex);
Assert.AreEqual("zone.0.entity.0", result.Intents.Movement.ActorEntityKey);
Assert.AreEqual(1.0, result.Intents.Movement.DirectionX, 0.0001);
Assert.AreEqual("zone.0.entity.0", result.Intents.Movements.Single().ActorEntityKey);
Assert.AreEqual(1.0, result.Intents.Movements.Single().DirectionX, 0.0001);
Assert.AreEqual(12.5, result.Run.Zones[0].SimulationTimeSeconds, 0.0001);
var frame = result.Frame;
Assert.AreEqual("test-daemon", frame.DaemonId);
@ -4646,10 +4646,10 @@ public class DaemonRuntimeDocumentTests
Assert.AreEqual(5, result.AppliedCommandIds.Count);
Assert.IsTrue(result.Intents.HasAny);
Assert.AreEqual("zone.0.entity.0", result.Intents.Movement.ActorEntityKey);
Assert.AreEqual(0.25, result.Intents.Movement.DirectionX, 0.0001);
Assert.AreEqual(-0.5, result.Intents.Movement.DirectionY, 0.0001);
Assert.AreEqual(0.75, result.Intents.Movement.Magnitude, 0.0001);
Assert.AreEqual("zone.0.entity.0", result.Intents.Movements.Single().ActorEntityKey);
Assert.AreEqual(0.25, result.Intents.Movements.Single().DirectionX, 0.0001);
Assert.AreEqual(-0.5, result.Intents.Movements.Single().DirectionY, 0.0001);
Assert.AreEqual(0.75, result.Intents.Movements.Single().Magnitude, 0.0001);
Assert.AreEqual(1, result.Intents.WeaponGroups.Count);
Assert.IsTrue(result.Intents.WeaponGroups[0].Fire);
Assert.AreEqual(1, result.Intents.WeaponGroups[0].WeaponGroup);

View file

@ -7,7 +7,8 @@ namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaRuntimeDaemonIntentState
{
public AetheriaRuntimeDaemonMovementIntent? Movement { get; set; }
public List<AetheriaRuntimeDaemonMovementIntent> Movements { get; } =
new List<AetheriaRuntimeDaemonMovementIntent>();
public List<AetheriaRuntimeDaemonWeaponGroupIntent> WeaponGroups { get; } =
new List<AetheriaRuntimeDaemonWeaponGroupIntent>();
public List<AetheriaRuntimeDaemonBehaviorIntent> Behaviors { get; } =
@ -23,7 +24,7 @@ namespace GameCult.Aetheria.State.Verse
public bool SensorPingRequested { get; set; }
public bool HasAny =>
Movement != null ||
Movements.Count > 0 ||
SensorPingRequested ||
WeaponGroups.Count > 0 ||
Behaviors.Count > 0 ||

View file

@ -927,13 +927,15 @@ namespace GameCult.Aetheria.State.Verse
return false;
}
intents.Movement = new AetheriaRuntimeDaemonMovementIntent
intents.Movements.RemoveAll(intent =>
string.Equals(intent.ActorEntityKey, actor, StringComparison.Ordinal));
intents.Movements.Add(new AetheriaRuntimeDaemonMovementIntent
{
ActorEntityKey = actor,
DirectionX = command.DirectionX,
DirectionY = command.DirectionY,
Magnitude = command.ScalarValue
};
});
return true;
}

View file

@ -38,7 +38,8 @@ namespace GameCult.Aetheria.State.Verse
EnsureStats(entities, settings);
foreach (var entity in entities)
AetheriaRuntimeThermalSimulation.EnsureState(entity);
ApplyMovementIntent(run, entities, intents?.Movement, settings);
foreach (var movement in intents?.Movements ?? Enumerable.Empty<AetheriaRuntimeDaemonMovementIntent>())
ApplyMovementIntent(run, entities, movement, settings);
StepRaiderAi(entities);
StepTargetPursuit(entities, settings);
StepMovement(entities, deltaSeconds);
@ -86,7 +87,7 @@ namespace GameCult.Aetheria.State.Verse
return;
var entity = entities.FirstOrDefault(candidate => candidate.EntityIndex == entityIndex);
if (entity == null || !IsPlayerOwned(entity) || !IsAlive(entity))
if (entity == null || !IsAlive(entity))
return;
var magnitude = Clamp01(movement.Magnitude);