Add bounds hazards and triggers

This commit is contained in:
2026-04-16 11:50:37 +02:00
parent c79d5c8f0a
commit 45181d1f78
17 changed files with 405 additions and 25 deletions

View File

@@ -27,11 +27,71 @@ public sealed class SimulationStepTests
Assert.Equal(30, player.AimAxisX);
Assert.Equal(40, player.AimAxisY);
Assert.Equal(3, player.SelectedWeaponSlot);
Assert.Equal(10, player.Health);
Assert.NotEqual(0, player.ButtonMask);
Assert.Equal(result.StateHash, simulation.CurrentSnapshot.StateHash);
Assert.NotEqual(0UL, simulation.CurrentSnapshot.LastRandomValue);
}
[Fact]
public void Step_ClampsPlayerToWorldBounds()
{
Simulation simulation = new(SimulationTestFactory.CreateGameDefinition(new(new(0, 0), new(11, 20))), SimulationTestFactory.CreateConfig(), 7);
var result = simulation.Step(SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 5, 0)));
var player = simulation.CurrentState.GetRequiredPlayer(new(1));
Assert.Equal(11, player.Position.m_X.ToIntRound());
Assert.Contains(result.Events, static e => e.Kind == "PlayerClamped");
}
[Fact]
public void Step_AppliesHazardDamage()
{
Simulation simulation = new(SimulationTestFactory.CreateGameDefinition(hazards: ImmutableArray.Create(new HazardDefinition("lava", new(new(11, 20), new(12, 21)), 3))), SimulationTestFactory.CreateConfig(), 7);
var result = simulation.Step(SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 1, 0)));
var player = simulation.CurrentState.GetRequiredPlayer(new(1));
Assert.Equal(7, player.Health);
Assert.Contains(result.Events, static e => e.Kind == "PlayerDamaged");
}
[Fact]
public void Step_IgnoresHazardsWhenPlayerIsOutside()
{
Simulation simulation = new(SimulationTestFactory.CreateGameDefinition(hazards: ImmutableArray.Create(new HazardDefinition("lava", new(new(50, 50), new(60, 60)), 3))), SimulationTestFactory.CreateConfig(), 7);
var result = simulation.Step(SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 1, 0)));
Assert.Equal(10, simulation.CurrentState.GetRequiredPlayer(new(1)).Health);
Assert.DoesNotContain(result.Events, static e => e.Kind == "PlayerDamaged");
}
[Fact]
public void Step_ActivatesTriggerOnlyOnce()
{
Simulation simulation = new(SimulationTestFactory.CreateGameDefinition(triggers: ImmutableArray.Create(new TriggerDefinition("checkpoint_a", new(new(11, 20), new(12, 21)), "TriggerActivated"))), SimulationTestFactory.CreateConfig(), 7);
var first = simulation.Step(SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 1, 0)));
var second = simulation.Step(SimulationTestFactory.CreateTick(2, new MoveAxisChanged(new(1), 0, 0)));
Assert.Contains(first.Events, static e => e.Kind == "TriggerActivated");
Assert.DoesNotContain(second.Events, static e => e.Kind == "TriggerActivated");
Assert.Contains("checkpoint_a", simulation.CurrentState.ActivatedTriggerIds);
}
[Fact]
public void Step_IgnoresTriggersWhenPlayerIsOutside()
{
Simulation simulation = new(SimulationTestFactory.CreateGameDefinition(triggers: ImmutableArray.Create(new TriggerDefinition("checkpoint_a", new(new(50, 50), new(60, 60)), "TriggerActivated"))), SimulationTestFactory.CreateConfig(), 7);
var result = simulation.Step(SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 1, 0)));
Assert.DoesNotContain(result.Events, static e => e.Kind == "TriggerActivated");
Assert.Empty(simulation.CurrentState.ActivatedTriggerIds);
}
[Fact]
public void Step_RejectsUnexpectedTickNumbers()
{
@@ -65,13 +125,33 @@ public sealed class SimulationStepTests
[Fact]
public void Constructor_RejectsDuplicatePlayers()
{
GameDefinition definition = new(ImmutableArray.Create(new(new(1), new(0, 0)), new PlayerDefinition(new(1), new(2, 3))));
GameDefinition definition = new(new(new(new(0, 0), new(100, 100)), ImmutableArray<HazardDefinition>.Empty, ImmutableArray<TriggerDefinition>.Empty), ImmutableArray.Create(new(new(1), new(0, 0), 10), new PlayerDefinition(new(1), new(2, 3), 10)));
var exception = Assert.Throws<InvalidOperationException>(() => new Simulation(definition, SimulationTestFactory.CreateConfig(), 11));
Assert.Contains("Duplicate player id 1", exception.Message);
}
[Fact]
public void Constructor_RejectsSpawnOutsideBounds()
{
GameDefinition definition = new(new(new(new(0, 0), new(5, 5)), ImmutableArray<HazardDefinition>.Empty, ImmutableArray<TriggerDefinition>.Empty), ImmutableArray.Create(new PlayerDefinition(new(1), new(10, 20), 10)));
var exception = Assert.Throws<InvalidOperationException>(() => new Simulation(definition, SimulationTestFactory.CreateConfig(), 11));
Assert.Contains("spawn must start inside world bounds", exception.Message);
}
[Fact]
public void Constructor_RejectsNonPositiveHealth()
{
GameDefinition definition = new(new(new(new(0, 0), new(100, 100)), ImmutableArray<HazardDefinition>.Empty, ImmutableArray<TriggerDefinition>.Empty), ImmutableArray.Create(new PlayerDefinition(new(1), new(1, 1), 0)));
var exception = Assert.Throws<InvalidOperationException>(() => new Simulation(definition, SimulationTestFactory.CreateConfig(), 11));
Assert.Contains("positive health", exception.Message);
}
[Fact]
public void Constructor_RejectsNullDefinition()
{