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);
}
}