tests/test_hand_contact.cs
using System.Numerics;
using HumanoidMocapX;
using HumanoidMocapX.Core.Motion;
using HumanoidMocapX.Inference;

static class HandContactTests
{
    internal static void Run()
    {
        var handstand = Handstand().ToDocument();
        var marked = HandContact.Apply(handstand, ObservationFixture.Observe(handstand));
        MotionTests.Equal(2 * handstand.Frames.Count, marked);
        foreach (var hand in new[] { "HandL", "HandR" })
        {
            var track = handstand.StationaryJoints.Single(t => t.Bone == handstand.Bones[ObservationFixture.Bone(handstand, hand)].Name);
            MotionTests.Check(track.Probability.All(p => p == 1), hand + " must be marked on the floor throughout");
        }
        // Marking again replaces the tracks instead of adding a second set.
        HandContact.Apply(handstand, ObservationFixture.Observe(handstand));
        MotionTests.Equal(2, handstand.StationaryJoints.Count(t => t.Bone.Contains("Hand", StringComparison.OrdinalIgnoreCase) || t.Source.StartsWith("SAM")));

        var standing = MotionTests.Fixture().ToDocument();
        MotionTests.Equal(0, HandContact.Apply(standing, ObservationFixture.Observe(standing)));
        MotionTests.Throws<ArgumentException>(() => HandContact.Apply(standing, new float[3]));
        Console.WriteLine("PASS: hand contact marks hands SAM sees on the floor under a raised body, and not a standing body's");
    }

    /// <summary>The motion fixture upside down with both arms straight overhead, now below it: a two-handed handstand.</summary>
    static FlowMotion Handstand()
    {
        var fixture = MotionTests.Fixture();
        foreach (var pose in fixture.Poses)
        {
            pose[0] = MathF.PI;                          // pelvis turned over about X
            pose[16 * 3 + 2] = MathF.PI / 2;             // left arm (along +X) raised about Z
            pose[17 * 3 + 2] = -MathF.PI / 2;            // right arm (along -X) raised about Z
        }
        return fixture;
    }
}