72 lines
2.9 KiB
C#
72 lines
2.9 KiB
C#
using GameCli;
|
|
using Xunit;
|
|
|
|
namespace GameCli.Tests;
|
|
|
|
public class RewardTests
|
|
{
|
|
[Fact]
|
|
public void AtTarget_ZeroVel_ZeroAngle_Noop_HasZeroReward()
|
|
{
|
|
var ship = new ShipState(0.5f, 0.5f, 0f, 0f, 0f, 0f);
|
|
float r = Reward.Compute(ship, target: (0.5f, 0.5f), action: 0);
|
|
Assert.Equal(0f, r, precision: 5);
|
|
}
|
|
|
|
[Fact]
|
|
public void FarFromTarget_HasLargerNegativeThanNear()
|
|
{
|
|
var near = new ShipState(0.5f, 0.5f, 0f, 0f, 0f, 0f);
|
|
var far = new ShipState(0.9f, 0.9f, 0f, 0f, 0f, 0f);
|
|
float rNear = Reward.Compute(near, target: (0.5f, 0.5f), action: 0);
|
|
float rFar = Reward.Compute(far, target: (0.5f, 0.5f), action: 0);
|
|
Assert.True(rFar < rNear, $"far reward {rFar} should be < near reward {rNear}");
|
|
}
|
|
|
|
[Fact]
|
|
public void HighVelocity_PenalizesReward()
|
|
{
|
|
var stopped = new ShipState(0.5f, 0.5f, 0f, 0f, 0f, 0f);
|
|
var moving = new ShipState(0.5f, 0.5f, 1f, 1f, 0f, 0f);
|
|
float rStop = Reward.Compute(stopped, target: (0.5f, 0.5f), action: 0);
|
|
float rMove = Reward.Compute(moving, target: (0.5f, 0.5f), action: 0);
|
|
Assert.True(rMove < rStop, $"moving reward {rMove} should be < stopped reward {rStop}");
|
|
}
|
|
|
|
[Fact]
|
|
public void Tilted_PenalizesReward()
|
|
{
|
|
var upright = new ShipState(0.5f, 0.5f, 0f, 0f, 0f, 0f);
|
|
var tilted = new ShipState(0.5f, 0.5f, 0f, 0f, 0.8f, 0f);
|
|
float rUp = Reward.Compute(upright, target: (0.5f, 0.5f), action: 0);
|
|
float rTi = Reward.Compute(tilted, target: (0.5f, 0.5f), action: 0);
|
|
Assert.True(rTi < rUp, $"tilted reward {rTi} should be < upright reward {rUp}");
|
|
}
|
|
|
|
[Fact]
|
|
public void FiringEngine_PenalizesReward()
|
|
{
|
|
var ship = new ShipState(0.5f, 0.5f, 0f, 0f, 0f, 0f);
|
|
float rNoop = Reward.Compute(ship, target: (0.5f, 0.5f), action: 0);
|
|
float rMain = Reward.Compute(ship, target: (0.5f, 0.5f), action: 2);
|
|
float rLeft = Reward.Compute(ship, target: (0.5f, 0.5f), action: 1);
|
|
Assert.True(rMain < rNoop, "firing main should be worse than noop");
|
|
Assert.True(rLeft < rNoop, "firing left should be worse than noop");
|
|
}
|
|
|
|
[Fact]
|
|
public void AngleWrap_FullRevolutionsProduceSameReward()
|
|
{
|
|
// Physically identical poses (angle differs by 2Ï€) must produce the same reward.
|
|
// Otherwise a spinning ship accumulates unbounded angle penalty.
|
|
var upright = new ShipState(0.5f, 0.5f, 0f, 0f, 0f, 0f);
|
|
var oneSpin = new ShipState(0.5f, 0.5f, 0f, 0f, 2f * MathF.PI, 0f);
|
|
var fiveSpins = new ShipState(0.5f, 0.5f, 0f, 0f, 10f * MathF.PI, 0f);
|
|
float r0 = Reward.Compute(upright, target: (0.5f, 0.5f), action: 0);
|
|
float r1 = Reward.Compute(oneSpin, target: (0.5f, 0.5f), action: 0);
|
|
float r5 = Reward.Compute(fiveSpins, target: (0.5f, 0.5f), action: 0);
|
|
Assert.Equal(r0, r1, precision: 4);
|
|
Assert.Equal(r0, r5, precision: 4);
|
|
}
|
|
}
|