Implement deterministic simulation spine
This commit is contained in:
119
tests/SideScrollerGame.Sim.Tests/SimulationReplayTests.cs
Normal file
119
tests/SideScrollerGame.Sim.Tests/SimulationReplayTests.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System.Collections.Immutable;
|
||||
using SideScrollerGame.Sim.Input;
|
||||
using SideScrollerGame.Sim.Replay;
|
||||
using SideScrollerGame.Sim.Serialization;
|
||||
|
||||
namespace SideScrollerGame.Sim.Tests;
|
||||
|
||||
public sealed class SimulationReplayTests
|
||||
{
|
||||
[Fact]
|
||||
public void SameSeedAndActions_ProduceSameHashes()
|
||||
{
|
||||
var definition = SimulationTestFactory.CreateGameDefinition();
|
||||
var config = SimulationTestFactory.CreateConfig();
|
||||
TickActionBatch[] batches =
|
||||
[
|
||||
SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 1, 0)),
|
||||
SimulationTestFactory.CreateTick(2, new AimAxisChanged(new(1), 3, 4)),
|
||||
SimulationTestFactory.CreateTick(3, new ButtonChanged(new(1), InputButton.Dash, true))
|
||||
];
|
||||
|
||||
Simulation left = new(definition, config, 42);
|
||||
Simulation right = new(definition, config, 42);
|
||||
|
||||
var leftHashes = ImmutableArray.CreateBuilder<int>();
|
||||
var rightHashes = ImmutableArray.CreateBuilder<int>();
|
||||
foreach (var batch in batches)
|
||||
{
|
||||
leftHashes.Add(left.Step(batch).StateHash);
|
||||
rightHashes.Add(right.Step(batch).StateHash);
|
||||
}
|
||||
|
||||
Assert.Equal(leftHashes.ToImmutable(), rightHashes.ToImmutable());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DifferentSeeds_ProduceDifferentHashes()
|
||||
{
|
||||
var definition = SimulationTestFactory.CreateGameDefinition();
|
||||
var config = SimulationTestFactory.CreateConfig();
|
||||
var batch = TickActionBatch.Empty(1);
|
||||
|
||||
Simulation left = new(definition, config, 1);
|
||||
Simulation right = new(definition, config, 2);
|
||||
|
||||
Assert.NotEqual(left.Step(batch).StateHash, right.Step(batch).StateHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplayRecord_RoundTripsAndReplaysExpectedHashes()
|
||||
{
|
||||
var definition = SimulationTestFactory.CreateGameDefinition();
|
||||
var config = SimulationTestFactory.CreateConfig();
|
||||
Simulation simulation = new(definition, config, 9);
|
||||
ReplayRecorder recorder = new();
|
||||
|
||||
TickActionBatch[] batches =
|
||||
[
|
||||
SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 2, 0), new AimAxisChanged(new(1), 5, 6)),
|
||||
SimulationTestFactory.CreateTick(2, new ButtonChanged(new(1), InputButton.FireSecondary, true)),
|
||||
SimulationTestFactory.CreateTick(3, new WeaponSlotSelected(new(1), 4))
|
||||
];
|
||||
|
||||
foreach (var batch in batches)
|
||||
{
|
||||
var result = simulation.Step(batch);
|
||||
recorder.Append(batch, result);
|
||||
}
|
||||
|
||||
var replay = recorder.Build(definition, config, 9);
|
||||
var payload = ReplayRecordSerializer.Serialize(replay);
|
||||
var loadedReplay = ReplayRecordSerializer.Deserialize(payload);
|
||||
var replayedHashes = ReplayPlayer.Play(loadedReplay, definition, config);
|
||||
|
||||
Assert.Equal(loadedReplay.Ticks.Select(static tick => tick.ExpectedStateHash).ToImmutableArray(), replayedHashes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplayPlayer_RejectsMismatchedDefinition()
|
||||
{
|
||||
var config = SimulationTestFactory.CreateConfig();
|
||||
var definition = SimulationTestFactory.CreateGameDefinition();
|
||||
ReplayRecord replay = new(123, 9, config.TicksPerSecond, ImmutableArray<RecordedTick>.Empty);
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => ReplayPlayer.Play(replay, definition, config));
|
||||
|
||||
Assert.Contains("content hash", exception.Message.ToLowerInvariant());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplayPlayer_RejectsMismatchedTickRate()
|
||||
{
|
||||
var definition = SimulationTestFactory.CreateGameDefinition();
|
||||
var replay = new ReplayRecorder().Build(definition, SimulationTestFactory.CreateConfig(), 9) with { TicksPerSecond = 30 };
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => ReplayPlayer.Play(replay, definition, SimulationTestFactory.CreateConfig()));
|
||||
|
||||
Assert.Contains("tick rate", exception.Message.ToLowerInvariant());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplayPlayer_RejectsDivergentHashes()
|
||||
{
|
||||
var definition = SimulationTestFactory.CreateGameDefinition();
|
||||
var config = SimulationTestFactory.CreateConfig();
|
||||
Simulation simulation = new(definition, config, 55);
|
||||
ReplayRecorder recorder = new();
|
||||
var batch = SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 1, 0));
|
||||
var result = simulation.Step(batch);
|
||||
recorder.Append(batch, result);
|
||||
|
||||
var replay = recorder.Build(definition, config, 55);
|
||||
var divergentReplay = replay with { Ticks = ImmutableArray.Create(replay.Ticks[0] with { ExpectedStateHash = replay.Ticks[0].ExpectedStateHash + 1 }) };
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => ReplayPlayer.Play(divergentReplay, definition, config));
|
||||
|
||||
Assert.Contains("diverged", exception.Message.ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user