tests/test_capture_inputs.cs
using HumanoidMocapX.Inference;

static class CaptureInputTests
{
    internal static void Run()
    {
        var person = new YoloBox(10, 20, 110, 220, .8f); var other = new YoloBox(250, 10, 290, 100, .95f);
        var track = new PersonTrack(5); track.Add([other, person]); track.Add([person with { Left = 12, Right = 112 }, other]);
        track.Add([]); track.Add([person with { Left = 16, Right = 116 }]); track.Add([person with { Left = 18, Right = 118 }]);
        var span = track.Complete(); var boxes = span.Boxes;
        MotionTests.Equal(0, span.First); MotionTests.Equal(5, span.Count); MotionTests.Check(span.Note(2, 5) is null, "A fully visible person needs no note");
        for (var i = 0; i < 5; i++) { MotionTests.Equal(10f + i * 2, boxes[i * 4]); MotionTests.Equal(110f + i * 2, boxes[i * 4 + 2]); }
        MotionTests.Throws<InvalidOperationException>(() => track.Add([person]));
        var nobody = new PersonTrack(2); nobody.Add([]); nobody.Add([]); MotionTests.Throws<InvalidOperationException>(() => nobody.Complete());
        var unfinished = new PersonTrack(2); unfinished.Add([person]); MotionTests.Throws<InvalidOperationException>(() => unfinished.Complete());
        // The capture starts where the person first appears and ends at their last detection.
        var late = new PersonTrack(6); late.Add([]); late.Add([]); late.Add([person]); late.Add([]); late.Add([person with { Left = 14, Right = 114 }]); late.Add([]);
        var visible = late.Complete();
        MotionTests.Equal(2, visible.First); MotionTests.Equal(3, visible.Count); MotionTests.Equal(12, visible.Boxes.Length);
        MotionTests.Equal(12f, visible.Boxes[4]); MotionTests.Equal(14f, visible.Boxes[8]); MotionTests.Near(2 / 30d, visible.StartSeconds);
        MotionTests.Equal("The person is in view from 1.07 s to 1.17 s of the video, so only that part was captured.", visible.Note(1, 6));
        var uncertain = new PersonTrack(2); uncertain.Add([person]);
        MotionTests.Throws<InvalidOperationException>(() => uncertain.Add([person, person with { Left = 11, Right = 111 }]));
        // A person lost for more than half a second ends the capture; a later return is not joined on.
        var lost = new PersonTrack(18); lost.Add([person]); for (var i = 0; i < 15; i++) lost.Add([]);
        lost.Add([other]); lost.Add([person]);
        var kept = lost.Complete(); MotionTests.Equal(0, kept.First); MotionTests.Equal(1, kept.Count);
        MotionTests.Throws<ArgumentException>(() => new PersonTrack(1).Add([person with { Left = float.NaN }]));
        var reuse = new PersonTrack(20); var input = new[] { person }; for (var i = 0; i < 4; i++) reuse.Add(input);
        var before = GC.GetAllocatedBytesForCurrentThread(); reuse.Add(input); MotionTests.Equal(0L, GC.GetAllocatedBytesForCurrentThread() - before);
        var noise = new float[491 * 501]; var repeated = new float[noise.Length]; FlowNoise.Fill(noise, 0); FlowNoise.Fill(repeated, 0);
        MotionTests.Check(noise.SequenceEqual(repeated), "Initial noise must repeat for the same seed");
        var mean = noise.Average(v => (double)v); var variance = noise.Average(v => (double)v * v) - mean * mean;
        MotionTests.Near(0, mean, .01); MotionTests.Near(1, variance, .02);
        MotionTests.Check(noise.Min() < -3 && noise.Max() > 3 && noise.All(float.IsFinite), "Normal noise must cover both tails and remain finite");
        FlowNoise.Fill(repeated, 1); MotionTests.Check(!noise.SequenceEqual(repeated), "Different seeds must vary initial noise");
        using var stop = new CancellationTokenSource(); stop.Cancel();
        MotionTests.Throws<OperationCanceledException>(() => FlowNoise.Fill(noise, 0, stop.Token));
        MotionTests.Throws<ArgumentException>(() => FlowNoise.Fill(new float[490], 0));
        Console.WriteLine("PASS: person identity continuity, short-gap interpolation, ambiguous/lost-person rejection, allocation-free tracking and seeded standard-normal latents");
    }
}