using System; using Godosa.Core.Diagnostics; using Xunit; namespace Godosa.Core.Tests.Diagnostics; public class FrameTimeStatsTests { /// 99 smooth 10 ms frames plus one 100 ms hitch. private static FrameTimeStats OneHitch() { var stats = new FrameTimeStats(capacity: 100); for (int i = 0; i < 99; i++) stats.Record(0.010, workSeconds: 0.002, simTicks: 0, simSeconds: 0); stats.Record(0.100, workSeconds: 0.002, simTicks: 0, simSeconds: 0); return stats; } [Fact] public void Fps_IsFramesOverTotalTime_NotMeanOfInstantaneousFps() { var s = OneHitch().Summarize(); Assert.Equal(100, s.Frames); // 100 frames / 1.09 s. (Mean of per-frame FPS would be (99*100 + 10)/100 = 99.1.) Assert.Equal(91.743, s.Fps, 3); Assert.Equal(10.9, s.AvgMs, 6); } [Fact] public void Percentiles_UseNearestRank_AndMaxCatchesTheHitch() { var s = OneHitch().Summarize(); Assert.Equal(10.0, s.P50Ms, 6); Assert.Equal(10.0, s.P95Ms, 6); Assert.Equal(10.0, s.P99Ms, 6); // rank ceil(0.99*100) = 99th of 100 Assert.Equal(100.0, s.MaxMs, 6); } [Fact] public void OnePercentLow_AveragesTheSlowestOnePercentOfFrames() { var s = OneHitch().Summarize(); Assert.Equal(10.0, s.OnePercentLowFps, 6); // worst 1 of 100 frames = 100 ms } [Fact] public void Hitches_CountFramesOverTwiceTheMedian() { var stats = new FrameTimeStats(capacity: 10); foreach (var ms in new[] { 10, 10, 10, 10, 10, 20, 21, 35 }) stats.Record(ms / 1000.0, 0, 0, 0); Assert.Equal(2, stats.Summarize().Hitches); // median 10 → 21 and 35 (20 is not > 20) } [Fact] public void Window_DropsOldestFrames() { var stats = new FrameTimeStats(capacity: 4); foreach (var ms in new[] { 100, 100, 3, 4, 5, 6 }) stats.Record(ms / 1000.0, 0, 0, 0); Span intervals = stackalloc float[4]; int n = stats.CopyIntervalsMs(intervals); Assert.Equal(4, n); Assert.Equal(new float[] { 3, 4, 5, 6 }, intervals.ToArray()); Assert.Equal(6.0, stats.Summarize().MaxMs, 4); } [Fact] public void Sim_ReportsAchievedTickRateAndCostPerTick() { var stats = new FrameTimeStats(capacity: 60); for (int i = 0; i < 60; i++) // 1 s at 60 fps; a tick every other frame, 1 ms each stats.Record(1.0 / 60.0, workSeconds: 0.004, simTicks: i % 2, simSeconds: (i % 2) * 0.001); var s = stats.Summarize(); Assert.Equal(30.0, s.SimTickRate, 3); Assert.Equal(1.0, s.AvgTickMs, 6); Assert.Equal(4.0, s.AvgWorkMs, 6); } [Fact] public void Empty_SummarizesToZerosWithoutDividingByZero() { var s = new FrameTimeStats().Summarize(); Assert.Equal(0, s.Frames); Assert.Equal(0.0, s.Fps); Assert.Equal(0.0, s.AvgTickMs); } }