From 5b35d65bd2cc30ffb25b1d8ff323588a64ae8e77 Mon Sep 17 00:00:00 2001 From: meelstorm Date: Fri, 17 Jul 2026 16:47:06 +0000 Subject: [PATCH] feat(gamecli): implement Observation.Build with unit tests --- GameCli.Tests/ObservationTests.cs | 52 +++++++++++++++++++++++++++++++ GameCli/Observation.cs | 25 +++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 GameCli.Tests/ObservationTests.cs create mode 100644 GameCli/Observation.cs diff --git a/GameCli.Tests/ObservationTests.cs b/GameCli.Tests/ObservationTests.cs new file mode 100644 index 0000000..9e53f66 --- /dev/null +++ b/GameCli.Tests/ObservationTests.cs @@ -0,0 +1,52 @@ +using GameCli; +using Xunit; + +namespace GameCli.Tests; + +public class ObservationTests +{ + [Fact] + public void Build_HasSevenDimensions() + { + var ship = new ShipState(0.5f, 0.5f, 0f, 0f, 0f, 0f); + var obs = Observation.Build(ship, target: (0.5f, 0.5f)); + Assert.Equal(7, obs.Length); + } + + [Fact] + public void Build_DisplacementIsTargetMinusShip() + { + var ship = new ShipState(0.3f, 0.4f, 0f, 0f, 0f, 0f); + var obs = Observation.Build(ship, target: (0.7f, 0.9f)); + // dx, dy at indices 0, 1 + Assert.Equal(0.4f, obs[0], precision: 5); + Assert.Equal(0.5f, obs[1], precision: 5); + } + + [Fact] + public void Build_EncodesVelocity() + { + var ship = new ShipState(0.5f, 0.5f, 0.1f, -0.2f, 0f, 0f); + var obs = Observation.Build(ship, target: (0.5f, 0.5f)); + Assert.Equal(0.1f, obs[2], precision: 5); + Assert.Equal(-0.2f, obs[3], precision: 5); + } + + [Fact] + public void Build_EncodesAngleAsSinCos() + { + var ship = new ShipState(0.5f, 0.5f, 0f, 0f, MathF.PI / 2f, 0f); + var obs = Observation.Build(ship, target: (0.5f, 0.5f)); + // sin(π/2)=1, cos(π/2)=0 + Assert.Equal(1f, obs[4], precision: 5); + Assert.Equal(0f, obs[5], precision: 5); + } + + [Fact] + public void Build_LastDimIsAngularVelocity() + { + var ship = new ShipState(0.5f, 0.5f, 0f, 0f, 0f, 0.7f); + var obs = Observation.Build(ship, target: (0.5f, 0.5f)); + Assert.Equal(0.7f, obs[6], precision: 5); + } +} diff --git a/GameCli/Observation.cs b/GameCli/Observation.cs new file mode 100644 index 0000000..0665f72 --- /dev/null +++ b/GameCli/Observation.cs @@ -0,0 +1,25 @@ +namespace GameCli; + +/// +/// Builds the 7-dim observation vector fed to PPO: +/// [dx, dy, vx, vy, sin(angle), cos(angle), angular_velocity] +/// Angle is encoded as sin/cos to avoid the discontinuity at ±π. +/// +public static class Observation +{ + public const int Dim = 7; + + public static float[] Build(ShipState s, (float X, float Y) target) + { + return new float[Dim] + { + target.X - s.X, + target.Y - s.Y, + s.VX, + s.VY, + MathF.Sin(s.Angle), + MathF.Cos(s.Angle), + s.AngularVelocity, + }; + } +}