🔍 s&box Package Code Search

Search C# source code, UI razor templates, shaders, and configs across s&box packages.

Showing code results for query: * (158 total matches found)
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Cleanup/FootGroundAlign.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Numerics;
using HumanoidRetargeter.Maths;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;

namespace HumanoidRetargeter.Cleanup;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>Tunables for the grounded-foot stance recalibration pass.</summary>
public sealed class FootGroundAlignOptions
{
    /// <summary>
    /// Dead zone (degrees): measured stance offsets at or below this are genuine planted
    /// articulation (heel-roll bias, natural lean — measured 2–4° on well-rested rigs and
    /// on citizen clips) and are left untouched, keeping the transfer byte-faithful there.
    /// Only offsets beyond it are clearly rest-pose artifacts (measured 12–25° on the
    /// repro rig) and get recalibrated.
    /// </summary>
    public float MinCorrectionDeg { get; set; } = 8f;

    /// <summary>
    /// Maximum mean sole deviation (degrees) a plant may show and still count as a STANCE
    /// for the offset measurement. Plants beyond this are not standing on the sole (crawls,
    /// kneels, prone contact — measured 60–90° there) and are excluded; genuine rest-pose
    /// stance artifacts measure well below it (largest seen: 27°).
    /// </summary>
    public float MaxStanceDeviationDeg { get; set; } = 35f;
}

/// <summary>Per-foot results of a <see cref="FootGroundAlign.Apply"/> run.</summary>
public sealed class FootGroundAlignFootReport
{
    /// <summary>Plants that contributed to the stance measurement.</summary>
    public int StancePlants { get; set; }

    /// <summary>Plants excluded as non-stance (mean sole deviation beyond
    /// <see cref="FootGroundAlignOptions.MaxStanceDeviationDeg"/>).</summary>
    public int SkippedPlants { get; set; }

    /// <summary>Measured planted sole offset from the ground plane, degrees (0 when no
    /// stance plants exist).</summary>
    public float MeasuredOffsetDeg { get; set; }

    /// <summary>Foot correction applied to every frame, degrees (0 = inside the dead zone,
    /// nothing changed).</summary>
    public float AppliedFootDeg { get; set; }

    /// <summary>Toe correction applied to every frame, degrees.</summary>
    public float AppliedToeDeg { get; set; }
}

/// <summary>Results of a <see cref="FootGroundAlign.Apply"/> run.</summary>
public sealed class FootGroundAlignReport
{
    /// <summary>Left-foot results.</summary>
    public required FootGroundAlignFootReport Left { get; init; }

    /// <summary>Right-foot results.</summary>
    public required FootGroundAlignFootReport Right { get; init; }
}

/// <summary>
/// Grounded-foot stance recalibration: measures how far the foot's SOLE sits from the ground
/// plane while planted, and — when that offset is clearly a rest-pose artifact — rotates it
/// out with one constant per foot, applied to every frame of the clip.
/// </summary>
/// <remarks>
/// <para><b>Why a cleanup pass.</b> The solver transfers feet as rest-relative deltas
/// (<see cref="Solve.RoleTransferMode.CharacterDeltaFromRest"/>), so the target keeps its own
/// ankle anatomy — correct whenever the source's rest pose is a flat-footed stance (the delta
/// is then "deviation from standing"). Some rigs ship a NON-stance rest (measured: an
/// Auto-Rig-Pro export whose rest foot sits 12–25° from its planted stance), and that constant
/// offset rides into every frame of the replay — planted feet hover toe-down/heel-up. What a
/// stance actually looks like is animation evidence (planted phases), which a per-frame
/// solver cannot see, so the recalibration lives here.</para>
/// <para><b>Measurement.</b> Per foot: over every planted frame, the sole normal = rest up
/// carried by the foot's world delta from the target bind rest (whose feet stand on the
/// ground by construction); plants whose own mean normal sits beyond
/// <see cref="FootGroundAlignOptions.MaxStanceDeviationDeg"/> are excluded (crawl/kneel/prone
/// contact is not a stance). The pooled mean normal's deviation from up is the stance
/// offset.</para>
/// <para><b>Correction.</b> Offsets inside <see cref="FootGroundAlignOptions.MinCorrectionDeg"/>
/// are genuine articulation — nothing is changed (well-rested rigs and same-rig round trips
/// stay byte-identical through this pass). Beyond it, the shortest-arc rotation taking the
/// pooled normal back to up (pitch+roll only — yaw/toe-out is pose and follows the source)
/// premultiplies the foot's world rotation on EVERY frame: a rest artifact is constant, so
/// the fix is too — within-plant heel-roll, swing styling and frame-to-frame continuity are
/// preserved exactly, and no blending is needed. The toe then receives its own residual
/// constant measured on top of the corrected foot (it neither double-rotates with the foot
/// fix nor inherits the source toe's own rest artifact). Corrections rotate bones about
/// their own joints: ankle positions are untouched, so the pass composes freely with the
/// <see cref="FootPlant"/> position pinning (which preserves foot world rotations).</para>
/// <para><b>Plant intervals come from the caller</b> (the pipeline detects them on the
/// SOURCE clip via <see cref="FootPlant.DetectPlantIntervals"/> — ground truth, immune to
/// the hip-height rescaling that can push target-side trajectories outside the cm-tuned
/// Kovar thresholds). So does the decision to run at all: the pipeline invokes this pass
/// only when the source's normalized rest is implausible as a flat stance (toe at/above
/// ankle level or asymmetric feet — see <c>Retargeter.GroundAlignFeet</c>); on plausible
/// stance rests the solver's rest-relative transfer is already faithful and planted-sole
/// deviations are genuine articulation (boxing stances, heel rolls) that must not be
/// flattened.</para>
/// </remarks>
public static class FootGroundAlign
{
    /// <summary>Measures planted stance offsets and recalibrates feet whose offset is a
    /// rest-pose artifact; returns what was measured and done.</summary>
    /// <param name="frames">Per-frame local transforms (skeleton bone order); modified in place.</param>
    /// <param name="skeleton">Bone hierarchy the frames are expressed against; its bind rest
    /// is the flat-stance reference.</param>
    /// <param name="left">Left leg chain bone indices.</param>
    /// <param name="right">Right leg chain bone indices.</param>
    /// <param name="up">World up direction of the clip's space.</param>
    /// <param name="leftPlants">Left-foot plant intervals (frame indices into
    /// <paramref name="frames"/>; out-of-range parts are clamped/ignored).</param>
    /// <param name="rightPlants">Right-foot plant intervals.</param>
    /// <param name="options">Tunables; defaults used when null.</param>
    public static FootGroundAlignReport Apply(
        List<XForm[]> frames,
        SkeletonModel skeleton,
        FootChain left,
        FootChain right,
        Vector3 up,
        IReadOnlyList<FrameRange> leftPlants,
        IReadOnlyList<FrameRange> rightPlants,
        FootGroundAlignOptions? options = null)
    {
        ArgumentNullException.ThrowIfNull(frames);
        ArgumentNullException.ThrowIfNull(skeleton);
        ArgumentNullException.ThrowIfNull(left);
        ArgumentNullException.ThrowIfNull(right);
        ArgumentNullException.ThrowIfNull(leftPlants);
        ArgumentNullException.ThrowIfNull(rightPlants);

        options ??= new FootGroundAlignOptions();
        var report = new FootGroundAlignReport
        {
            Left = new FootGroundAlignFootReport(),
            Right = new FootGroundAlignFootReport(),
        };
        if (frames.Count == 0 || up.LengthSquared() < 1e-12f)
            return report;
        up = Vector3.Normalize(up);

        RecalibrateFoot(frames, skeleton, left, up, leftPlants, options, report.Left);
        RecalibrateFoot(frames, skeleton, right, up, rightPlants, options, report.Right);
        return report;
    }

    private static void RecalibrateFoot(
        List<XForm[]> frames, SkeletonModel skeleton, FootChain chain, Vector3 up,
        IReadOnlyList<FrameRange> plants, FootGroundAlignOptions options,
        FootGroundAlignFootReport report)
    {
        int n = frames.Count;
        var foot = chain.Ankle;
        var restFootRotInv = Quaternion.Conjugate(skeleton.RestWorld[foot].Rot);
        var maxStanceCos = MathF.Cos(options.MaxStanceDeviationDeg * MathF.PI / 180f);

        // ---- measurement: pooled planted sole normal over the stance plants ----
        var pooled = Vector3.Zero;
        foreach (var plant in plants)
        {
            int start = Math.Max(plant.Start, 0);
            int end = Math.Min(plant.End, n - 1);
            if (start > end)
                continue;

            var plantSum = Vector3.Zero;
            for (int f = start; f <= end; f++)
            {
                var footRot = FkUtil.BoneWorld(frames[f], skeleton, foot).Rot;
                plantSum += Vector3.Transform(up, MathQ.Normalize(footRot * restFootRotInv));
            }
            if (plantSum.LengthSquared() < 1e-8f
                || Vector3.Dot(Vector3.Normalize(plantSum), up) < maxStanceCos)
            {
                report.SkippedPlants++; // not standing on the sole — crawl/kneel/toe contact
                continue;
            }
            report.StancePlants++;
            pooled += plantSum; // frame-count-weighted: longer stances dominate
        }
        if (pooled.LengthSquared() < 1e-8f)
            return;
        pooled = Vector3.Normalize(pooled);

        var offsetDeg = MathQ.AngleBetween(pooled, up) * (180f / MathF.PI);
        report.MeasuredOffsetDeg = offsetDeg;
        if (offsetDeg <= options.MinCorrectionDeg)
            return; // genuine planted articulation — leave the transfer byte-faithful

        // ---- correction: one constant per foot, every frame ----
        var footFix = MathQ.FromTo(pooled, up);
        report.AppliedFootDeg = offsetDeg;

        // Toe residual measured on top of the corrected foot, same dead zone.
        var toeFix = Quaternion.Identity;
        if (chain.Toe is { } toe && skeleton[toe].ParentIndex == foot)
        {
            var restToeRotInv = Quaternion.Conjugate(skeleton.RestWorld[toe].Rot);
            var toePooled = Vector3.Zero;
            foreach (var plant in plants)
            {
                int start = Math.Max(plant.Start, 0);
                int end = Math.Min(plant.End, n - 1);
                for (int f = start; f <= end && f >= 0; f++)
                {
                    var toeRot = FkUtil.BoneWorld(frames[f], skeleton, toe).Rot;
                    toePooled += Vector3.Transform(
                        up, MathQ.Normalize(footFix * toeRot * restToeRotInv));
                }
            }
            if (toePooled.LengthSquared() > 1e-8f)
            {
                toePooled = Vector3.Normalize(toePooled);
                var toeDeg = MathQ.AngleBetween(toePooled, up) * (180f / MathF.PI);
                if (toeDeg > options.MinCorrectionDeg && Vector3.Dot(toePooled, up) >= maxStanceCos)
                {
                    toeFix = MathQ.FromTo(toePooled, up);
                    report.AppliedToeDeg = toeDeg;
                }
            }
        }

        for (int f = 0; f < n; f++)
            CorrectFrame(frames[f], skeleton, chain, footFix, toeFix);
    }

    /// <summary>Premultiplies the foot's world rotation by the constant fix (the joint
    /// position is untouched — the rotation pivots the foot about its own head), then gives
    /// the toe its own residual on top of the corrected foot.</summary>
    private static void CorrectFrame(
        XForm[] locals, SkeletonModel skeleton, FootChain chain,
        Quaternion footFix, Quaternion toeFix)
    {
        var foot = chain.Ankle;
        var parent = skeleton[foot].ParentIndex;
        var parentRot = parent < 0
            ? Quaternion.Identity
            : FkUtil.BoneWorld(locals, skeleton, parent).Rot;

        var footWorld = MathQ.Normalize(parentRot * locals[foot].Rot);
        var newFootWorld = MathQ.Normalize(footFix * footWorld);
        locals[foot] = new XForm(
            locals[foot].Pos, MathQ.Normalize(Quaternion.Conjugate(parentRot) * newFootWorld));

        if (chain.Toe is { } toe && skeleton[toe].ParentIndex == foot)
        {
            // Desired toe world = toeFix ∘ footFix ∘ original world; re-derive its local
            // against the corrected foot so it does not double-rotate with the foot fix.
            var toeWorldOld = MathQ.Normalize(footWorld * locals[toe].Rot);
            var desired = MathQ.Normalize(toeFix * footFix * toeWorldOld);
            locals[toe] = new XForm(
                locals[toe].Pos, MathQ.Normalize(Quaternion.Conjugate(newFootWorld) * desired));
        }
    }
}
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Dl/SameFeatures.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Numerics;
using HumanoidRetargeter.Mapping;
using HumanoidRetargeter.Maths;
using HumanoidRetargeter.Skeleton;
using HumanoidRetargeter.Solve;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;

namespace HumanoidRetargeter.Dl;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>The z-normalization statistics shipped with the SAME checkpoint
/// (<c>ms_dict</c>): per-feature mean/std applied to every input except contact.</summary>
public sealed class SameStats
{
    internal float[] LoM, LoS, GoM, GoS, QM, QS, PM, PS, RM, RS, PvM, PvS, QvM, QvS, PprevM, PprevS;

    /// <summary>Reads the 16 <c>ms.*</c> arrays from a parsed weight blob.</summary>
    public SameStats(SameWeights weights)
    {
        ArgumentNullException.ThrowIfNull(weights);
        LoM = weights.Stat("lo_m"); LoS = weights.Stat("lo_s");
        GoM = weights.Stat("go_m"); GoS = weights.Stat("go_s");
        QM = weights.Stat("q_m"); QS = weights.Stat("q_s");
        PM = weights.Stat("p_m"); PS = weights.Stat("p_s");
        RM = weights.Stat("r_m"); RS = weights.Stat("r_s");
        PvM = weights.Stat("pv_m"); PvS = weights.Stat("pv_s");
        QvM = weights.Stat("qv_m"); QvS = weights.Stat("qv_s");
        PprevM = weights.Stat("pprev_m"); PprevS = weights.Stat("pprev_s");
    }
}

/// <summary>A batched per-frame source graph ready for <see cref="SameModel.Encode"/>.</summary>
public sealed class SameSourceGraph
{
    /// <summary>Normalized node features, flat [FrameCount·JointCount × 32].</summary>
    public required float[] X { get; init; }

    /// <summary>Edge sources (bidirectional + self-loops, all frames).</summary>
    public required int[] EdgeSrc { get; init; }

    /// <summary>Edge destinations.</summary>
    public required int[] EdgeDst { get; init; }

    /// <summary>Frame id per node.</summary>
    public required int[] Batch { get; init; }

    /// <summary>Number of feature frames (matches the clip's frame count in production
    /// mode; native frames − 2 in golden-parity mode).</summary>
    public required int FrameCount { get; init; }

    /// <summary>Graph joints per frame (hips subtree + end joints).</summary>
    public required int JointCount { get; init; }

    /// <summary>Graph node names within one frame (bone names; synthesized leaf tips get
    /// a <c>_end</c> suffix). For diagnostics and parity tests.</summary>
    public required string[] JointNames { get; init; }
}

/// <summary>
/// Source-side feature pipeline of the SAME port (FEASIBILITY.md "C# port work list"
/// steps 1–5): skeleton normalization, cm/Y-up/+Z-facing alignment, per-frame
/// q/p/r/pv/qv/pprev/c features in the root-facing frame, z-normalization, and the
/// bidirectional+self-loop edge list.
/// </summary>
/// <remarks>
/// <para><b>Skeleton normalization without an intermediate skeleton.</b> SAME's
/// <c>motion_normalize</c> rebuilds the rig with identity rest-local rotations and
/// re-expresses every frame against it. Algebraically the normalized motion's world
/// rotations are exactly the world-space deltas from the T-pose,
/// <c>Ĝ(j,t) = G(j,t) · G_tpose(j)⁻¹</c>, its local rotations are
/// <c>Ĝ(parent)⁻¹ · Ĝ(j)</c>, and its world positions equal the original world positions
/// — so this port computes the features directly from FK world transforms, no rebuilt
/// skeleton needed (verified against the Python pipeline by the golden-vector tests).</para>
/// <para><b>T-pose reference.</b> SAME consumes the source clip's first frame as the
/// reference; production keeps that convention but emits one feature frame per clip frame
/// (the sequence is computed over [f0, f0…fN−1] with f0 doubling as the reference — see
/// <see cref="TposeReference"/> for why the rest-pose alternative measurably loses).
/// Golden-parity mode replicates Python's frame accounting exactly (frame 0 = reference,
/// frame 1 dropped).</para>
/// <para><b>Alignment.</b> Features assume cm (guaranteed by the importers), Y-up and
/// rest facing +Z with +X to the character's left. The source is rotated by a world
/// alignment derived from the rig's rest geometry (<see cref="CharacterFrame"/> via the
/// mapping when computable, else the file's axis metadata), snapped to the nearest whole
/// axis permutation (an exact-axis rig must map to the identity — the rest-geometry tilt
/// of a few degrees otherwise leaks into every feature), and shifted so the lowest joint
/// over the clip sits on the ground plane.</para>
/// <para><b>Graph.</b> Nodes are the hips subtree (hips = mapped Hips role, else the
/// shallowest branch bone) in skeleton order — hips is always node 0, which is where the
/// root feature row lives — plus one synthesized end joint per childless leaf (BVH End
/// Sites already import as <c>_end</c> bones and are used as-is; FBX leaves get a
/// half-length continuation of their parent segment).</para>
/// </remarks>
public static class SameFeatures
{
    /// <summary>How the T-pose reference (skeleton normalization + lo/go features) is chosen.</summary>
    public enum TposeReference
    {
        /// <summary>The clip's own first frame — SAME's native convention and the
        /// production default. Empirically the pretrained checkpoint tracks arms FAR
        /// better against the clip's first frame than against a synthesized true T-pose,
        /// even though its training references are T-poses (measured on the fixture clip:
        /// mean role cosine vs the geometric solver 0.94 first-frame vs 0.57 rest-pose,
        /// hands flipping negative — reproduced identically in the Python reference
        /// pipeline, so it is a property of the checkpoint, not of this port).</summary>
        FirstFrame,

        /// <summary>Synthesize the reference from the skeleton's rest pose (the
        /// FEASIBILITY suggestion; kept for experiments — see above for why it lost).</summary>
        RestPose,
    }

    /// <summary>Options for <see cref="BuildSourceGraph"/>; defaults are production mode.</summary>
    public sealed class SourceOptions
    {
        /// <summary>T-pose reference choice (see <see cref="TposeReference"/>).</summary>
        public TposeReference Reference { get; init; } = TposeReference.FirstFrame;

        /// <summary>SAME's native frame accounting: the first frame is consumed as the
        /// reference and the next dropped for its undefined velocity, so the output has
        /// two frames fewer than the clip. Golden-parity tests only — production emits
        /// one feature frame per clip frame (the first frame doubles as the reference
        /// and gets zero velocity).</summary>
        public bool NativeFrameDrop { get; init; }

        /// <summary>Apply the rest-geometry world alignment (Y-up, +Z facing). Disabled
        /// only by golden-parity tests (Python applies none).</summary>
        public bool Align { get; init; } = true;

        /// <summary>Ground both the T-pose reference and the animation: the T-pose is
        /// shifted so its lowest joint sits at height 0 (a BVH rest pose has its root at
        /// the origin and would otherwise put the hips on the floor), and the animation is
        /// shifted by its own lowest joint height over the clip (no-op for the usual
        /// authored-ground-at-0 data). Disabled only by golden-parity tests (the Python
        /// reference consumes data as authored).</summary>
        public bool GroundShift { get; init; } = true;
    }

    private const float ContactHeightCm = 5f;
    private const float ContactSpeedMps = 0.4f;
    private const float VelocityFps = 30f;

    /// <summary>
    /// Builds the batched source graph for one clip: graph selection, alignment, per-frame
    /// features, normalization, edges.
    /// </summary>
    /// <param name="scene">Imported source (cm, native axes).</param>
    /// <param name="clipIndex">Clip to encode.</param>
    /// <param name="map">Source mapping; used only for hips identification and the
    /// rest-geometry alignment (the model itself is skeleton-agnostic). May be sparse —
    /// heuristics cover missing roles.</param>
    /// <param name="stats">Normalization statistics.</param>
    /// <param name="options">Null = production mode.</param>
    public static SameSourceGraph BuildSourceGraph(
        SourceScene scene, int clipIndex, MappingResult? map, SameStats stats, SourceOptions? options = null)
    {
        ArgumentNullException.ThrowIfNull(scene);
        ArgumentNullException.ThrowIfNull(stats);
        options ??= new SourceOptions();
        if (clipIndex < 0 || clipIndex >= scene.Clips.Count)
            throw new ArgumentOutOfRangeException(nameof(clipIndex));
        var clip = scene.Clips[clipIndex];
        if (clip.FrameCount < 1)
            throw new ArgumentException("Clip has no frames.", nameof(clipIndex));
        if (options.NativeFrameDrop && clip.FrameCount < 3)
            throw new ArgumentException("Native frame accounting needs at least 3 frames.", nameof(options));

        var skeleton = scene.Skeleton;
        var hips = FindHips(skeleton, map);
        var nodes = GraphNodes.Build(skeleton, hips);

        var align = options.Align ? ComputeAlignment(skeleton, map, scene) : Quaternion.Identity;

        // T-pose reference world transforms (aligned), grounded on its own lowest joint
        // (a BVH rest pose has the root at the origin — ungrounded, its hips would sit on
        // the floor and every height-bearing feature would be wrong).
        var tposeLocals = options.Reference == TposeReference.RestPose
            ? Pose.Rest(skeleton).Locals
            : clip.Frames[0];
        var tposeWorld = AlignedWorld(skeleton, tposeLocals, align, nodes);
        if (options.GroundShift)
            ShiftToGround(tposeWorld.Pos);

        // The pose sequence the features run over; features are emitted for seq[1..].
        var seq = new List<XForm[]>();
        if (options.NativeFrameDrop)
        {
            for (var f = 1; f < clip.FrameCount; f++)
                seq.Add(clip.Frames[f]);
        }
        else
        {
            seq.Add(clip.Frames[0]); // duplicated: gives the real first frame zero velocity
            for (var f = 0; f < clip.FrameCount; f++)
                seq.Add(clip.Frames[f]);
        }

        var frames = seq.Count - 1;
        var j = nodes.Count;

        // Pass 0: aligned world transforms; ground the whole clip on its lowest joint.
        var worlds = new AlignedFrame[seq.Count];
        for (var t = 0; t < seq.Count; t++)
            worlds[t] = AlignedWorld(skeleton, seq[t], align, nodes);
        if (options.GroundShift)
        {
            var ground = float.PositiveInfinity;
            foreach (var world in worlds)
            {
                foreach (var p in world.Pos)
                    ground = MathF.Min(ground, p.Y);
            }
            if (float.IsFinite(ground) && ground != 0f)
            {
                foreach (var world in worlds)
                {
                    for (var i = 0; i < j; i++)
                        world.Pos[i].Y -= ground;
                }
            }
        }

        // Pass 1: normalized-skeleton local rotations + facing per frame.
        var localRots = new Quaternion[seq.Count][]; // facing-adjusted at the root row
        var facing = new (float Yaw, Vector3 Pos)[seq.Count];
        for (var t = 0; t < seq.Count; t++)
        {
            var world = worlds[t];

            // Normalized-skeleton world rotations: world delta from the T-pose.
            var normWorld = new Quaternion[j];
            for (var i = 0; i < j; i++)
                normWorld[i] = MathQ.Normalize(world.Rot[i] * Quaternion.Conjugate(tposeWorld.Rot[i]));

            // Root facing: yaw (about +Y) of the normalized root rotation, at the root's
            // ground-plane position.
            var yaw = YawAngle(normWorld[0]);
            facing[t] = (yaw, new Vector3(world.Pos[0].X, 0f, world.Pos[0].Z));

            // Normalized-skeleton local rotations; root premultiplied by the inverse facing.
            var locals = new Quaternion[j];
            locals[0] = MathQ.Normalize(Quaternion.CreateFromAxisAngle(Vector3.UnitY, -yaw) * normWorld[0]);
            for (var i = 1; i < j; i++)
            {
                locals[i] = MathQ.Normalize(
                    Quaternion.Conjugate(normWorld[nodes.Parent[i]]) * normWorld[i]);
            }
            localRots[t] = locals;
        }

        // Pass 2: feature rows.
        var x = new float[frames * j * SameModel.InputDim];
        for (var t = 1; t < seq.Count; t++)
        {
            var f = t - 1;
            var (yaw, fpos) = facing[t];
            var invFacing = Quaternion.CreateFromAxisAngle(Vector3.UnitY, -yaw);
            var (yawPrev, fposPrev) = facing[t - 1];
            var invFacingPrev = Quaternion.CreateFromAxisAngle(Vector3.UnitY, -yawPrev);

            // r: facing delta (dθ, dx, dz) + absolute root height.
            var dTheta = WrapPi(yaw - yawPrev);
            var dPlanar = Vector3.Transform(fpos - fposPrev, invFacingPrev);
            var rootHeight = worlds[t].Pos[0].Y;

            for (var i = 0; i < j; i++)
            {
                var row = (f * j + i) * SameModel.InputDim;
                var col = 0;

                // ---- skel: lo, go (tiled per frame) -------------------------------------
                Vector3 lo, go;
                if (i == 0)
                {
                    lo = new Vector3(0f, tposeWorld.Pos[0].Y, 0f);
                    go = lo;
                }
                else
                {
                    lo = tposeWorld.Pos[i] - tposeWorld.Pos[nodes.Parent[i]];
                    go = tposeWorld.Pos[i] - new Vector3(tposeWorld.Pos[0].X, 0f, tposeWorld.Pos[0].Z);
                }
                WriteNorm3(x, row, ref col, lo, stats.LoM, stats.LoS);
                WriteNorm3(x, row, ref col, go, stats.GoM, stats.GoS);

                // ---- q ------------------------------------------------------------------
                WriteNorm6(x, row, ref col, SixD(localRots[t][i]), stats.QM, stats.QS);

                // ---- p (facing-frame-relative global position) --------------------------
                var p = Vector3.Transform(worlds[t].Pos[i] - fpos, invFacing);
                WriteNorm3(x, row, ref col, p, stats.PM, stats.PS);

                // ---- r (root row only; other rows are the mean → zeros after norm) ------
                if (i == 0)
                {
                    x[row + col++] = (dTheta - stats.RM[0]) / stats.RS[0];
                    x[row + col++] = (dPlanar.X - stats.RM[1]) / stats.RS[1];
                    x[row + col++] = (dPlanar.Z - stats.RM[2]) / stats.RS[2];
                    x[row + col++] = (rootHeight - stats.RM[3]) / stats.RS[3];
                }
                else
                {
                    col += 4; // already zero
                }

                // ---- pv (facing-frame velocity, ×30 fps) ---------------------------------
                var pv = Vector3.Transform(worlds[t].Pos[i] - worlds[t - 1].Pos[i], invFacing) * VelocityFps;
                WriteNorm3(x, row, ref col, pv, stats.PvM, stats.PvS);

                // ---- qv (local rotation delta) -------------------------------------------
                var qv = MathQ.Normalize(Quaternion.Conjugate(localRots[t - 1][i]) * localRots[t][i]);
                WriteNorm6(x, row, ref col, SixD(qv), stats.QvM, stats.QvS);

                // ---- pprev (previous position in the CURRENT facing frame) ---------------
                var pprev = Vector3.Transform(worlds[t - 1].Pos[i] - fpos, invFacing);
                WriteNorm3(x, row, ref col, pprev, stats.PprevM, stats.PprevS);

                // ---- c (ground contact; not normalized) -----------------------------------
                var speedMps = (worlds[t].Pos[i] - worlds[t - 1].Pos[i]).Length() * VelocityFps / 100f;
                x[row + col] = worlds[t].Pos[i].Y < ContactHeightCm && speedMps < ContactSpeedMps ? 1f : 0f;
            }
        }

        var (edgeSrc, edgeDst) = BuildEdges(nodes.Parent, frames);
        var batch = new int[frames * j];
        for (var f = 0; f < frames; f++)
        {
            for (var i = 0; i < j; i++)
                batch[f * j + i] = f;
        }

        AssertFinite(x, "SAME source features");
        return new SameSourceGraph
        {
            X = x,
            EdgeSrc = edgeSrc,
            EdgeDst = edgeDst,
            Batch = batch,
            FrameCount = frames,
            JointCount = j,
            JointNames = nodes.Names,
        };
    }

    // ================================================================ graph topology

    /// <summary>The per-frame graph node set: hips-subtree bones in skeleton order
    /// (hips first) plus synthesized end joints for childless leaves.</summary>
    internal sealed class GraphNodes
    {
        /// <summary>Skeleton bone index per node; -1 for synthesized end joints.</summary>
        public required int[] Bone { get; init; }

        /// <summary>Graph-parent node index; -1 for the root (node 0).</summary>
        public required int[] Parent { get; init; }

        /// <summary>For synthesized end joints: the rest-local offset from the leaf bone
        /// (zero vector for real bones).</summary>
        public required Vector3[] EndOffset { get; init; }

        public required string[] Names { get; init; }

        public int Count => Bone.Length;

        public static GraphNodes Build(SkeletonModel skeleton, int hips)
        {
            // Hips subtree, skeleton order (parents precede children, hips first).
            var inSubtree = new bool[skeleton.Count];
            inSubtree[hips] = true;
            var bones = new List<int> { hips };
            for (var i = hips + 1; i < skeleton.Count; i++)
            {
                var parent = skeleton[i].ParentIndex;
                if (parent >= 0 && inSubtree[parent])
                {
                    inSubtree[i] = true;
                    bones.Add(i);
                }
            }

            var nodeOfBone = new Dictionary<int, int>(bones.Count);
            for (var n = 0; n < bones.Count; n++)
                nodeOfBone[bones[n]] = n;

            var hasChild = new bool[skeleton.Count];
            foreach (var b in bones)
            {
                var parent = skeleton[b].ParentIndex;
                if (parent >= 0 && inSubtree[parent])
                    hasChild[parent] = true;
            }

            var bone = new List<int>(bones);
            var parentNode = new List<int>(bones.Count);
            var endOffset = new List<Vector3>(bones.Count);
            var names = new List<string>(bones.Count);
            foreach (var b in bones)
            {
                var p = skeleton[b].ParentIndex;
                parentNode.Add(b == hips ? -1 : nodeOfBone[p]);
                endOffset.Add(Vector3.Zero);
                names.Add(skeleton[b].Name);
            }

            // Synthesized end joints: leaves with no children anywhere in the skeleton.
            // BVH End Sites already import as real `_end`/`_End` bones and ARE the end
            // joints — no tip on a tip. The tip continues the parent→leaf segment at half
            // length — a neutral stand-in for the unknown bone tail (FBX carries none).
            foreach (var b in bones)
            {
                if (hasChild[b]
                    || skeleton[b].Name.EndsWith("_end", StringComparison.OrdinalIgnoreCase))
                    continue;
                var p = skeleton[b].ParentIndex;
                var segment = p >= 0
                    ? skeleton.RestWorld[b].Pos - skeleton.RestWorld[p].Pos
                    : Vector3.Zero;
                var tip = segment.Length() > 1e-4f ? segment * 0.5f : new Vector3(0f, 2f, 0f);
                // Express in the leaf's rest-local frame (applied via the leaf's world rot).
                var local = Vector3.Transform(tip, Quaternion.Conjugate(skeleton.RestWorld[b].Rot));
                bone.Add(-1);
                parentNode.Add(nodeOfBone[b]);
                endOffset.Add(local);
                names.Add(skeleton[b].Name + "_end");
            }

            return new GraphNodes
            {
                Bone = bone.ToArray(),
                Parent = parentNode.ToArray(),
                EndOffset = endOffset.ToArray(),
                Names = names.ToArray(),
            };
        }
    }

    /// <summary>Aligned world transforms of the graph nodes for one pose.</summary>
    internal readonly struct AlignedFrame
    {
        public required Vector3[] Pos { get; init; }
        public required Quaternion[] Rot { get; init; }
    }

    private static AlignedFrame AlignedWorld(
        SkeletonModel skeleton, XForm[] locals, Quaternion align, GraphNodes nodes)
    {
        var world = new Pose(locals).ToWorld(skeleton);
        var pos = new Vector3[nodes.Count];
        var rot = new Quaternion[nodes.Count];
        for (var n = 0; n < nodes.Count; n++)
        {
            XForm w;
            if (nodes.Bone[n] >= 0)
            {
                w = world[nodes.Bone[n]];
            }
            else
            {
                // Synthesized end joint: rides its leaf bone (identity local rotation).
                var leaf = world[nodes.Bone[nodes.Parent[n]]];
                w = new XForm(leaf.TransformPoint(nodes.EndOffset[n]), leaf.Rot);
            }
            pos[n] = Vector3.Transform(w.Pos, align);
            rot[n] = MathQ.Normalize(align * w.Rot);
        }
        return new AlignedFrame { Pos = pos, Rot = rot };
    }

    /// <summary>Bidirectional parent↔child pairs plus one self-loop per node, replicated
    /// per frame with node indices offset.</summary>
    internal static (int[] Src, int[] Dst) BuildEdges(int[] parent, int frames)
    {
        var j = parent.Length;
        var nonRoot = 0;
        for (var i = 0; i < j; i++)
        {
            if (parent[i] >= 0)
                nonRoot++;
        }
        var perFrame = nonRoot * 2 + j;
        var src = new int[perFrame * frames];
        var dst = new int[perFrame * frames];
        var e = 0;
        for (var f = 0; f < frames; f++)
        {
            var offset = f * j;
            for (var i = 0; i < j; i++)
            {
                if (parent[i] < 0)
                    continue;
                src[e] = offset + parent[i];
                dst[e] = offset + i;
                e++;
                src[e] = offset + i;
                dst[e] = offset + parent[i];
                e++;
            }
            for (var i = 0; i < j; i++)
            {
                src[e] = offset + i;
                dst[e] = offset + i;
                e++;
            }
        }
        return (src, dst);
    }

    // ================================================================ alignment + hips

    /// <summary>Mapped Hips role when available, else the shallowest bone with two or more
    /// children (the hips of any humanoid: the legs/spine branch point).</summary>
    internal static int FindHips(SkeletonModel skeleton, MappingResult? map)
    {
        if (map is not null && map.RoleToBone.TryGetValue(BoneRole.Hips, out var mapped)
            && mapped >= 0 && mapped < skeleton.Count)
            return mapped;

        var childCount = new int[skeleton.Count];
        for (var i = 0; i < skeleton.Count; i++)
        {
            if (skeleton[i].ParentIndex >= 0)
                childCount[skeleton[i].ParentIndex]++;
        }

        var best = -1;
        var bestDepth = int.MaxValue;
        for (var i = 0; i < skeleton.Count; i++)
        {
            if (childCount[i] < 2)
                continue;
            var depth = 0;
            for (var a = skeleton[i].ParentIndex; a >= 0; a = skeleton[a].ParentIndex)
                depth++;
            if (depth < bestDepth)
            {
                best = i;
                bestDepth = depth;
            }
        }
        return best >= 0 ? best : 0;
    }

    /// <summary>
    /// World rotation taking the rig into the canonical SAME frame (X = character left,
    /// Y = up, Z = facing): rest-geometry character frame when computable from the mapping,
    /// else the file's recorded axis conventions.
    /// </summary>
    internal static Quaternion ComputeAlignment(SkeletonModel skeleton, MappingResult? map, SourceScene? scene)
    {
        if (map is not null)
        {
            try
            {
                var frame = CharacterFrame.Compute(skeleton, map, skeleton.RestWorld);
                return AlignFromBasis(frame.Lateral, frame.Up, frame.Forward);
            }
            catch (ArgumentException)
            {
                // fall through to axis metadata
            }
        }

        if (scene is not null)
        {
            var up = AxisVector(scene.UpAxis, scene.UpAxisSign);
            var forward = AxisVector(scene.FrontAxis, scene.FrontAxisSign);
            if (MathF.Abs(Vector3.Dot(up, forward)) < 0.5f)
                return AlignFromBasis(Vector3.Cross(up, forward), up, forward);
        }

        return Quaternion.Identity;
    }

    /// <summary>
    /// Rotation mapping the given (left, up, forward) world directions onto (+X, +Y, +Z),
    /// snapped to the nearest whole axis permutation when one is unambiguous: rigs authored
    /// on exact axes (BVH Y-up/+Z, the s&amp;box rig, Z-up FBX) must map by an exact
    /// quarter-turn — the few degrees of rest-geometry tilt (shoulders not exactly above
    /// hips) otherwise leak into every feature and measurably cost accuracy.
    /// </summary>
    internal static Quaternion AlignFromBasis(Vector3 left, Vector3 up, Vector3 forward)
    {
        var l = SnapAxis(left);
        var u = SnapAxis(up);
        var f = SnapAxis(forward);
        if (MathF.Abs(Vector3.Dot(l, u)) > 0.5f || MathF.Abs(Vector3.Dot(l, f)) > 0.5f
            || MathF.Abs(Vector3.Dot(u, f)) > 0.5f)
        {
            // Genuinely oblique rig: keep the exact (orthonormalized) directions.
            l = Vector3.Normalize(left);
            u = Vector3.Normalize(up - l * Vector3.Dot(up, l));
            f = Vector3.Cross(l, u);
        }

        // Row-major with rows = basis images maps +X→left, +Y→up, +Z→forward
        // (System.Numerics row-vector convention); the alignment is its inverse.
        var m = new Matrix4x4(
            l.X, l.Y, l.Z, 0f,
            u.X, u.Y, u.Z, 0f,
            f.X, f.Y, f.Z, 0f,
            0f, 0f, 0f, 1f);
        return Quaternion.Conjugate(MathQ.Normalize(Quaternion.CreateFromRotationMatrix(m)));
    }

    private static Vector3 SnapAxis(Vector3 v)
    {
        var ax = MathF.Abs(v.X);
        var ay = MathF.Abs(v.Y);
        var az = MathF.Abs(v.Z);
        if (ax >= ay && ax >= az)
            return new Vector3(MathF.Sign(v.X), 0f, 0f);
        if (ay >= az)
            return new Vector3(0f, MathF.Sign(v.Y), 0f);
        return new Vector3(0f, 0f, MathF.Sign(v.Z));
    }

    private static Vector3 AxisVector(int axis, int sign) => axis switch
    {
        0 => new Vector3(sign, 0f, 0f),
        2 => new Vector3(0f, 0f, sign),
        _ => new Vector3(0f, sign, 0f),
    };

    // ================================================================ small math

    /// <summary>The yaw (rotation about +Y) closest to <paramref name="q"/> — fairmotion's
    /// <c>Q_closest(q, identity, +Y)</c>, reproduced exactly for parity.</summary>
    internal static float YawAngle(Quaternion q)
    {
        var alpha = Math.Atan2(q.W, q.Y);
        var theta1 = -2.0 * alpha + Math.PI;
        var theta2 = -2.0 * alpha - Math.PI;
        var d1 = q.Y * Math.Sin(theta1 * 0.5) + q.W * Math.Cos(theta1 * 0.5);
        var d2 = q.Y * Math.Sin(theta2 * 0.5) + q.W * Math.Cos(theta2 * 0.5);
        return (float)(d1 > d2 ? theta1 : theta2);
    }

    private static void ShiftToGround(Vector3[] positions)
    {
        var ground = float.PositiveInfinity;
        foreach (var p in positions)
            ground = MathF.Min(ground, p.Y);
        if (!float.IsFinite(ground) || ground == 0f)
            return;
        for (var i = 0; i < positions.Length; i++)
            positions[i].Y -= ground;
    }

    internal static float WrapPi(float angle)
    {
        while (angle > MathF.PI)
            angle -= 2f * MathF.PI;
        while (angle < -MathF.PI)
            angle += 2f * MathF.PI;
        return angle;
    }

    /// <summary>6D rotation representation: the first two columns of the rotation matrix
    /// (<c>R·e_x</c> then <c>R·e_y</c>).</summary>
    internal static (Vector3 C0, Vector3 C1) SixD(Quaternion q)
        => (Vector3.Transform(Vector3.UnitX, q), Vector3.Transform(Vector3.UnitY, q));

    private static void WriteNorm3(float[] x, int row, ref int col, Vector3 v, float[] m, float[] s)
    {
        x[row + col++] = (v.X - m[0]) / s[0];
        x[row + col++] = (v.Y - m[1]) / s[1];
        x[row + col++] = (v.Z - m[2]) / s[2];
    }

    private static void WriteNorm6(float[] x, int row, ref int col, (Vector3 C0, Vector3 C1) sixD, float[] m, float[] s)
    {
        x[row + col++] = (sixD.C0.X - m[0]) / s[0];
        x[row + col++] = (sixD.C0.Y - m[1]) / s[1];
        x[row + col++] = (sixD.C0.Z - m[2]) / s[2];
        x[row + col++] = (sixD.C1.X - m[3]) / s[3];
        x[row + col++] = (sixD.C1.Y - m[4]) / s[4];
        x[row + col++] = (sixD.C1.Z - m[5]) / s[5];
    }

    internal static void AssertFinite(float[] values, string what)
    {
        foreach (var v in values)
        {
            if (!float.IsFinite(v))
                throw new InvalidOperationException($"{what} contain non-finite values.");
        }
    }
}
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Formats/Fbx/FbxBinaryWriter.cs
Game library
#nullable enable annotations

using System;
using System.Buffers.Binary;
using System.IO;
using System.Text;

namespace HumanoidRetargeter.Formats.Fbx;

/// <summary>
/// Serializes an <see cref="FbxNode"/> tree back to binary FBX (version 7400 layout —
/// u32 header fields, universally readable). The inverse of
/// <see cref="FbxTokenizer.Parse"/>: a tree parsed from a 7.x binary file and written
/// here re-parses to an identical tree (arrays are written uncompressed; zlib-encoded
/// inputs therefore round-trip by VALUE, not byte-for-byte).
/// </summary>
/// <remarks>
/// Used by <see cref="FbxBindPoseFixer"/> to persist repaired node transforms. The footer
/// is written the way Blender's exporter does: a fixed 16-byte watermark (importers treat
/// it as opaque), zero padding to a 16-byte boundary, the version echo, 120 zero bytes and
/// the closing magic. The FBX SDK computes a content hash here, but every consumer we
/// target (s&amp;box, Blender, assimp) ignores it.
/// </remarks>
public static class FbxBinaryWriter
{
    private const uint Version = 7400;

    private static readonly byte[] HeaderMagic =
        "Kaydara FBX Binary  \0\x1a\0"u8.ToArray();

    // Blender's fbx_binary.py FOOT_ID + closing magic bytes.
    private static readonly byte[] FooterWatermark =
    {
        0xfa, 0xbc, 0xab, 0x09, 0xd0, 0xc8, 0xd4, 0x66, 0xb1, 0x76, 0xfb, 0x83, 0x1c, 0xf7, 0x26, 0x7e,
    };

    private static readonly byte[] FooterMagic =
    {
        0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0x6e, 0x0c, 0xc0, 0x00, 0x00,
    };

    /// <summary>
    /// Serializes <paramref name="root"/> (a virtual root whose children are the top-level
    /// document nodes, as produced by <see cref="FbxTokenizer.Parse"/>).
    /// </summary>
    public static byte[] Write(FbxNode root)
    {
        ArgumentNullException.ThrowIfNull(root);

        using var ms = new MemoryStream();
        ms.Write(HeaderMagic);
        WriteU32(ms, Version);

        foreach (var child in root.Children)
            WriteNode(ms, child);
        WriteNullRecord(ms);

        WriteFooter(ms);
        return ms.ToArray();
    }

    // ------------------------------------------------------------------ nodes

    private static void WriteNode(MemoryStream ms, FbxNode node)
    {
        long headerAt = ms.Position;
        // Placeholder header: endOffset, numProps, propListLen (patched after the body).
        WriteU32(ms, 0);
        WriteU32(ms, (uint)node.Properties.Count);
        WriteU32(ms, 0);
        var nameBytes = Encoding.ASCII.GetBytes(node.Name);
        if (nameBytes.Length > byte.MaxValue)
            throw new FormatException($"FBX write: node name too long ({node.Name.Length} chars).");
        ms.WriteByte((byte)nameBytes.Length);
        ms.Write(nameBytes);

        long propsAt = ms.Position;
        foreach (var p in node.Properties)
            WriteProperty(ms, p, node.Name);
        long propsLen = ms.Position - propsAt;

        if (node.Children.Count > 0)
        {
            foreach (var child in node.Children)
                WriteNode(ms, child);
            WriteNullRecord(ms);
        }

        long endAt = ms.Position;
        ms.Position = headerAt;
        WriteU32(ms, checked((uint)endAt));
        WriteU32(ms, (uint)node.Properties.Count);
        WriteU32(ms, checked((uint)propsLen));
        ms.Position = endAt;
    }

    private static void WriteNullRecord(MemoryStream ms)
    {
        Span<byte> zeros = stackalloc byte[13];
        zeros.Clear();
        ms.Write(zeros);
    }

    // ------------------------------------------------------------------ properties

    private static void WriteProperty(MemoryStream ms, object value, string owner)
    {
        switch (value)
        {
            case short y:
                ms.WriteByte((byte)'Y');
                WriteI16(ms, y);
                break;
            case bool c:
                ms.WriteByte((byte)'C');
                ms.WriteByte(c ? (byte)1 : (byte)0);
                break;
            case int i:
                ms.WriteByte((byte)'I');
                WriteI32(ms, i);
                break;
            case float f:
                ms.WriteByte((byte)'F');
                WriteF32(ms, f);
                break;
            case double d:
                ms.WriteByte((byte)'D');
                WriteF64(ms, d);
                break;
            case long l:
                ms.WriteByte((byte)'L');
                WriteI64(ms, l);
                break;

            case float[] fa:
                WriteArrayHeader(ms, 'f', fa.Length, 4);
                foreach (var x in fa)
                    WriteF32(ms, x);
                break;
            case double[] da:
                WriteArrayHeader(ms, 'd', da.Length, 8);
                foreach (var x in da)
                    WriteF64(ms, x);
                break;
            case long[] la:
                WriteArrayHeader(ms, 'l', la.Length, 8);
                foreach (var x in la)
                    WriteI64(ms, x);
                break;
            case int[] ia:
                WriteArrayHeader(ms, 'i', ia.Length, 4);
                foreach (var x in ia)
                    WriteI32(ms, x);
                break;
            case bool[] ba:
                WriteArrayHeader(ms, 'b', ba.Length, 1);
                foreach (var x in ba)
                    ms.WriteByte(x ? (byte)1 : (byte)0);
                break;

            case string s:
            {
                ms.WriteByte((byte)'S');
                var bytes = Encoding.UTF8.GetBytes(s);
                WriteU32(ms, (uint)bytes.Length);
                ms.Write(bytes);
                break;
            }
            case byte[] r:
                ms.WriteByte((byte)'R');
                WriteU32(ms, (uint)r.Length);
                ms.Write(r);
                break;

            default:
                throw new FormatException(
                    $"FBX write: node '{owner}': unsupported property CLR type {value?.GetType().Name ?? "null"}.");
        }
    }

    private static void WriteArrayHeader(MemoryStream ms, char code, int count, int elemSize)
    {
        ms.WriteByte((byte)code);
        WriteU32(ms, (uint)count);
        WriteU32(ms, 0); // encoding 0 = uncompressed
        WriteU32(ms, checked((uint)(count * elemSize)));
    }

    // ------------------------------------------------------------------ footer

    private static void WriteFooter(MemoryStream ms)
    {
        ms.Write(FooterWatermark);

        // Zero-pad so the version echo starts 16-aligned (Blender pads at least 1 byte).
        int pad = (int)(16 - ms.Position % 16);
        for (int i = 0; i < pad; i++)
            ms.WriteByte(0);

        WriteU32(ms, Version);
        Span<byte> zeros = stackalloc byte[120];
        zeros.Clear();
        ms.Write(zeros);
        ms.Write(FooterMagic);
    }

    // ------------------------------------------------------------------ primitives

    private static void WriteU32(MemoryStream ms, uint v)
    {
        Span<byte> b = stackalloc byte[4];
        BinaryPrimitives.WriteUInt32LittleEndian(b, v);
        ms.Write(b);
    }

    private static void WriteI16(MemoryStream ms, short v)
    {
        Span<byte> b = stackalloc byte[2];
        BinaryPrimitives.WriteInt16LittleEndian(b, v);
        ms.Write(b);
    }

    private static void WriteI32(MemoryStream ms, int v)
    {
        Span<byte> b = stackalloc byte[4];
        BinaryPrimitives.WriteInt32LittleEndian(b, v);
        ms.Write(b);
    }

    private static void WriteI64(MemoryStream ms, long v)
    {
        Span<byte> b = stackalloc byte[8];
        BinaryPrimitives.WriteInt64LittleEndian(b, v);
        ms.Write(b);
    }

    private static void WriteF32(MemoryStream ms, float v)
    {
        Span<byte> b = stackalloc byte[4];
        BinaryPrimitives.WriteSingleLittleEndian(b, v);
        ms.Write(b);
    }

    private static void WriteF64(MemoryStream ms, double v)
    {
        Span<byte> b = stackalloc byte[8];
        BinaryPrimitives.WriteDoubleLittleEndian(b, v);
        ms.Write(b);
    }
}
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Mapping/ProfileLibrary.cs
Game library
#nullable enable annotations

using System.Collections.Generic;

namespace HumanoidRetargeter.Mapping;

/// <summary>
/// Built-in preset profiles, embedded as C# data (the same data is written to
/// <c>Assets/humanoid_retargeter/profiles/*.json</c> by a regenerate-and-diff test so the
/// shipped JSON can never drift from the code).
/// </summary>
public static class ProfileLibrary
{
    /// <summary>Mixamo / Adobe rigs: <c>mixamorig[N]:</c> namespace, <c>LeftArm</c> /
    /// <c>LeftForeArm</c> / <c>LeftHandIndex1..3</c> style names.</summary>
    public static Profile Mixamo { get; } = BuildMixamo();

    /// <summary>
    /// Reallusion ActorCore / AccuRig / Character Creator rigs (<c>CC_Base_*</c>).
    /// Empirical notes from <c>research/rig_actorcore.json</c>:
    /// <list type="bullet">
    /// <item><c>CC_Base_Hip</c> is the parent of BOTH <c>CC_Base_Pelvis</c> (leg branch) and
    /// <c>CC_Base_Waist</c> (spine branch), i.e. the LCA of legs+spine and the true animated
    /// hips root → it carries <see cref="BoneRole.Hips"/>; <c>CC_Base_Pelvis</c> is a
    /// leg-branch intermediate and stays unmapped.</item>
    /// <item>The neck chain is <c>CC_Base_NeckTwist01 → CC_Base_NeckTwist02 → CC_Base_Head</c>;
    /// despite the name, <c>NeckTwist01</c> IS the neck bone (there is no plain
    /// <c>CC_Base_Neck</c>), so it is the <see cref="BoneRole.Neck"/> alias. NeckTwist02 is
    /// left unmapped. All other Twist/ShareBone helpers are excluded (no aliases).</item>
    /// <item><c>CC_Base_L_ToeBase</c> is the toe role; the co-located
    /// <c>CC_Base_L_ToeBaseShareBone</c> is a helper and must never be mapped.</item>
    /// </list>
    /// </summary>
    public static Profile ActorCoreCc { get; } = BuildActorCoreCc();

    /// <summary>Unreal Engine mannequin (UE4/UE5): <c>pelvis</c>, <c>spine_01..05</c>,
    /// <c>clavicle_l</c>, <c>thumb_01_l</c>, UE5 <c>*_metacarpal_*</c>; <c>*_twist_*</c>
    /// bones have no aliases and are never mapped.</summary>
    public static Profile UeMannequin { get; } = BuildUeMannequin();

    /// <summary>Rokoko / Xsens style BVH rigs: plain <c>Hips</c>/<c>Spine..Spine4</c>/<c>
    /// LeftArm|LeftUpperArm</c> name variants, usually no fingers.</summary>
    public static Profile RokokoBvh { get; } = BuildRokokoBvh();

    /// <summary>
    /// SMPL body model family (AMASS exports, Meshcapade FBX rigs). Joint names per the
    /// published model (vchoutas/smplx <c>joint_names.py</c>, Meshcapade wiki):
    /// <c>pelvis</c>, sided <c>hip→knee→ankle→foot</c> legs (the "hip" joint IS the thigh;
    /// "ankle" is the foot, "foot" is the toe region) and <c>collar→shoulder→elbow→wrist</c>
    /// arms ("shoulder" is the upper arm, "wrist" is the hand; the <c>hand</c> joint is a
    /// finger stub and stays unmapped). Both spellings occur in the wild: <c>left_hip</c>
    /// (model joints) and <c>L_Hip</c> with gendered FBX prefixes <c>m_avg_</c>/<c>f_avg_</c>
    /// (SMPL Unity/FBX rigs). No fingers — that is SMPL-X (<see cref="SmplX"/>), kept as a
    /// separate preset so a finger-less SMPL rig still reaches full optional coverage.
    /// </summary>
    public static Profile Smpl { get; } = BuildSmpl(withFingers: false);

    /// <summary>
    /// SMPL-X: the SMPL body joints (<see cref="Smpl"/>) plus articulated hands —
    /// <c>left_thumb1..3</c>/<c>left_index1..3</c>-style finger joints per
    /// vchoutas/smplx <c>joint_names.py</c> (jaw/eye joints carry no humanoid role).
    /// Evaluated before <see cref="Smpl"/> so it wins the tie on SMPL-X rigs (both score
    /// the body fully; only this one maps the fingers).
    /// </summary>
    public static Profile SmplX { get; } = BuildSmpl(withFingers: true);

    /// <summary>
    /// NVIDIA SOMA uniform-proportion skeleton (SOMA/SEED BVH exports, e.g.
    /// github.com/NVIDIA/soma-retargeter <c>assets/motions/bvh</c>). Mixamo-like upper-body
    /// names, but: spine is <c>Spine1→Spine2→Chest</c> (no plain "Spine"), neck is
    /// <c>Neck1→Neck2</c>, the legs are <c>LeftLeg→LeftShin</c> — SOMA's <c>LeftLeg</c> is
    /// the THIGH (mixamo's is the calf), which is exactly why the mixamo preset must never
    /// claim these rigs — and the four fingers have FOUR segments where segment 1 is a
    /// metacarpal (<c>LeftHandIndex1..4</c>; mixamo's 1..3 are the phalanges), so the
    /// phalanx roles map to segments 2/3/4.
    /// </summary>
    public static Profile SomaBvh { get; } = BuildSomaBvh();

    /// <summary>
    /// Classic BVH / Character-Studio-friendly naming (MotionBuilder "Export BVH to
    /// Character Studio" convention, ACCAD-style mocap BVHs): <c>Hips</c>,
    /// <c>Chest[2..4]</c> spine, arms <c>Collar→Shoulder→Elbow→Wrist</c> (the "Shoulder"
    /// is the upper arm) and legs <c>Hip→Knee→Ankle→Toe</c> (the sided "Hip" is the
    /// thigh). No fingers.
    /// </summary>
    public static Profile ClassicBvh { get; } = BuildClassicBvh();

    /// <summary>
    /// 3ds Max Character Studio Biped rigs: every bone is "&lt;BipedName&gt; &lt;Part&gt;"
    /// where the biped name defaults to <c>Bip01</c> (3ds Max ≤2009) / <c>Bip001</c>
    /// (2010+) per the Autodesk "Naming the Biped" documentation; some exporters mangle
    /// the spaces to underscores (<c>Bip01_L_Thigh</c>), hence the <c>^Bip\d+[ _]</c>
    /// namespace pattern (alias comparison is separator-insensitive, so "L UpperArm" and
    /// "L_UpperArm" normalize identically). Sided bones use a bare mid-name <c>L/R</c>:
    /// <c>L Clavicle→L UpperArm→L Forearm→L Hand</c> arms,
    /// <c>L Thigh→L Calf→L Foot→L Toe0</c> legs. Fingers are numbered chains
    /// <c>L Finger0..4</c> (0 = thumb) with phalanx segments <c>Finger01/Finger02</c>
    /// etc. (MotionBuilder's "3ds Max Biped Template" characterization maps exactly these
    /// names). The COM root <c>Bip01</c> itself, <c>Footsteps</c>, toe segments
    /// <c>Toe01/Toe02</c> and <c>HorseLink</c> carry no aliases and are never mapped.
    /// </summary>
    public static Profile Biped { get; } = BuildBiped();

    /// <summary>
    /// DAZ/Poser classic naming (Poser 4 era figures, DAZ Generation-4 V4/M4, Genesis 1/2,
    /// MakeHuman's "Poser/DAZ names" BVH export — verified against the local
    /// <c>dev/corpus/unknown_rigs/makehuman_cmu_03_03_dazNames.bvh</c>): camel-case bones
    /// with a lower-case <c>l</c>/<c>r</c> side prefix — <c>hip</c> (the translating
    /// root), <c>abdomen[→abdomen2]→chest</c> spine, <c>neck</c>, <c>head</c>,
    /// <c>lCollar→lShldr→lForeArm→lHand</c> arms, <c>lThigh→lShin→lFoot→lToe</c> legs and
    /// <c>lThumb1..3/lIndex1..3/lMid1..3/lRing1..3/lPinky1..3</c> fingers. The
    /// <c>l/rButtock</c> thigh helpers and eye bones carry no aliases and stay unmapped.
    /// DAZ Genesis 3/8/9 renamed the skeleton (<c>abdomenLower</c>, <c>lShldrBend</c>, …)
    /// and is NOT covered by this preset.
    /// </summary>
    public static Profile DazPoser { get; } = BuildDazPoser();

    /// <summary>
    /// Blender Rigify human rigs, per the metarig definition in the rigify add-on
    /// (<c>rigify/metarigs/human.py</c>) and the Blender manual's basic.human reference:
    /// the spine chain is <c>spine→spine.001..spine.006</c> where <c>spine</c> IS the
    /// pelvis/hips bone (it sits at the pelvis and parents the thighs), spine.001–003 are
    /// the torso, spine.004/005 the two neck bones (004 carries <see cref="BoneRole.Neck"/>,
    /// 005 stays unmapped — same policy as ActorCore's NeckTwist02) and spine.006 is the
    /// head. Limbs: <c>shoulder.L→upper_arm.L→forearm.L→hand.L</c>,
    /// <c>thigh.L→shin.L→foot.L→toe.L</c>; fingers <c>thumb.01.L..03.L</c> and
    /// <c>f_index/f_middle/f_ring/f_pinky.01.L..03.L</c>. The <c>^DEF-</c> namespace
    /// pattern also matches rigify's generated deform skeleton (<c>DEF-spine.001</c>,
    /// <c>DEF-upper_arm.L</c>, …); the segmented deform twins (<c>DEF-upper_arm.L.001</c>),
    /// <c>palm.*</c>, <c>pelvis.L/R</c>, <c>heel.02.L</c>, face bones and the generated
    /// ORG-/MCH-/control bones have no aliases and are never mapped.
    /// </summary>
    public static Profile Rigify { get; } = BuildRigify();

    /// <summary>
    /// VRoid Studio / VRM avatars (UniVRM exports): <c>J_Bip_&lt;side&gt;_&lt;Part&gt;</c>
    /// bones where side is <c>C</c> (center), <c>L</c> or <c>R</c> — the standard VRoid
    /// skeleton behind the VRM humanoid spec (vrm-c/vrm-specification, humanoid bone map):
    /// <c>J_Bip_C_Hips/Spine/Chest/UpperChest/Neck/Head</c>,
    /// <c>J_Bip_L_Shoulder→UpperArm→LowerArm→Hand</c>,
    /// <c>J_Bip_L_UpperLeg→LowerLeg→Foot→ToeBase</c>, fingers
    /// <c>J_Bip_L_Thumb1..3/Index1..3/Middle1..3/Ring1..3/Little1..3</c> ("Little" is the
    /// pinky, per the VRM littleProximal/Intermediate/Distal humanoid bones). Secondary
    /// physics/adjust bones (<c>J_Sec_*</c>, <c>J_Adj_*</c>) and the <c>Root</c> bone have
    /// no aliases and are never mapped.
    /// </summary>
    public static Profile Vrm { get; } = BuildVrm();

    /// <summary>
    /// Blender Auto-Rig Pro humanoid FBX exports — bone names verified empirically against
    /// the local user repro <c>dev/corpus/todo/Defenses.fbx</c> (the PunchPerfect family):
    /// <c>.x</c> suffix marks center bones, <c>.l/.r</c> the sides, and the exported limb
    /// deform bones carry the <c>_stretch</c> twin name — <c>root.x</c> is the hips
    /// (under a ground bone <c>root</c>), <c>spine_01.x→spine_02.x→spine_03.x</c>,
    /// <c>neck.x</c>, <c>head.x</c>, arms <c>shoulder.l→arm_stretch.l→forearm_stretch.l→
    /// hand.l</c> (plain "arm", NOT "upperarm"), legs <c>thigh_stretch.l→leg_stretch.l→
    /// foot.l→toes_01.l</c> ("leg" is the calf). Fingers keep Auto-Rig Pro's <c>c_</c>
    /// control prefix on the exported deform chain: <c>c_thumb1.l..3.l</c>,
    /// <c>c_index/c_middle/c_ring/c_pinky1.l..3.l</c>. Leftover finger-tip markers
    /// (<c>mixamorig:LeftHandIndex4</c> in the repro) and <c>root</c> have no aliases.
    /// </summary>
    public static Profile AutoRigPro { get; } = BuildAutoRigPro();

    /// <summary>
    /// Xsens MVN exports (23-segment MVN body model; MVN Animate/Analyze FBX and BVH):
    /// anatomical vertebra names for the spine chain <c>Pelvis→L5→L3→T12→T8</c> (the four
    /// exported lumbar/thoracic segments of the MVN model), <c>Neck→Head</c>, arms
    /// <c>RightShoulder→RightUpperArm→RightForeArm→RightHand</c> and legs
    /// <c>RightUpperLeg→RightLowerLeg→RightFoot→RightToe</c>. Body-suit capture only — no
    /// finger segments (Xsens gloves ship as separate data), so the hands are chain tips.
    /// </summary>
    public static Profile XsensMvn { get; } = BuildXsensMvn();

    /// <summary>
    /// Perception Neuron / Axis Neuron BVH exports: mixamo-like limb and finger names
    /// (<c>RightArm→RightForeArm→RightHand</c>, <c>RightUpLeg→RightLeg→RightFoot</c>,
    /// <c>RightHandThumb1..3</c>) but a FOUR-bone spine (<c>Spine→Spine1..Spine3</c>), no
    /// toe joints (the feet are chain tips), and per-finger <c>RightInHandIndex</c>-style
    /// metacarpal helpers between the hand and the <c>RightHandIndex1..3</c> phalanges.
    /// The InHand metacarpals carry no aliases (barely animated palm helpers; mapping them
    /// as phalanges would shift every curl one joint outward — the SOMA finger bug class).
    /// The extra Spine3 is what lets this preset outscore mixamo on Neuron rigs (and
    /// mixamo's toes keep mixamo ahead on real Mixamo rigs).
    /// </summary>
    public static Profile PerceptionNeuron { get; } = BuildPerceptionNeuron();

    /// <summary>
    /// Source engine ValveBiped skeletons (HL2/GMod humanoids, playermodels):
    /// <c>ValveBiped.Bip01_*</c> names — 3ds-Max-Biped-derived parts behind the fixed
    /// namespace, with underscores and a spine chain that SKIPS Spine3
    /// (<c>Spine→Spine1→Spine2→Spine4</c>), <c>Neck1</c>/<c>Head1</c>, arms
    /// <c>L_Clavicle→L_UpperArm→L_Forearm→L_Hand</c>, legs
    /// <c>L_Thigh→L_Calf→L_Foot→L_Toe0</c> and numbered finger chains
    /// <c>L_Finger0/01/02</c> (0 = thumb) … <c>L_Finger4/41/42</c> (pinky). Evaluated
    /// before <see cref="Biped"/> (same Bip01 ancestry; the plain-Biped preset must never
    /// claim a ValveBiped rig). Attachment/weapon helpers (<c>ValveBiped.forward</c>,
    /// <c>ValveBiped.Anim_Attachment_*</c>) have no aliases and are never mapped.
    /// </summary>
    public static Profile ValveBiped { get; } = BuildValveBiped();

    /// <summary>
    /// DAZ Genesis 3/8(.1) figures: renamed Genesis skeleton (NOT covered by
    /// <see cref="DazPoser"/>) — <c>hip</c> is the translating root and the LCA of the
    /// <c>pelvis</c> leg branch and the <c>abdomenLower→abdomenUpper→chestLower→chestUpper</c>
    /// spine (so <c>hip</c> carries <see cref="BoneRole.Hips"/> and <c>pelvis</c> stays
    /// unmapped, same policy as ActorCore's Hip/Pelvis pair). Neck chain
    /// <c>neckLower→neckUpper→head</c> (neckUpper unmapped, NeckTwist02 policy). Limbs use
    /// Bend/Twist pairs: the Bend bones (<c>lShldrBend</c>, <c>lForearmBend</c>,
    /// <c>lThighBend</c>) are the primary limb bones; the co-linear Twist roll helpers
    /// (<c>lShldrTwist</c>, <c>lForearmTwist</c>, <c>lThighTwist</c>) carry no aliases and
    /// are never mapped. Legs <c>lThighBend→lShin→lFoot→lToe</c> (<c>lMetatarsals</c> is an
    /// arch helper between foot and toe, unmapped). Fingers are the classic DAZ
    /// <c>lThumb1..3/lIndex1..3/lMid1..3/lRing1..3/lPinky1..3</c>. Genesis 9 renamed the
    /// skeleton again (<c>l_upperarm</c>, …) and is NOT covered by this preset.
    /// </summary>
    public static Profile DazGenesis { get; } = BuildDazGenesis();

    /// <summary>
    /// The s&amp;box citizen-family skeleton itself (<c>citizen.vmdl</c>,
    /// <c>citizen_human_*.vmdl</c> and every community model re-rigged on their skeleton):
    /// <c>pelvis</c>, <c>spine_0..2</c>, <c>neck_0</c>, <c>head</c>, reversed-word limb
    /// names <c>arm_upper_L→arm_lower_L→hand_L</c> / <c>leg_upper_L→leg_lower_L→
    /// ankle_L→ball_L</c>, <c>clavicle_L/R</c> and fingers
    /// <c>finger_&lt;name&gt;_{meta,0,1,2}_L</c> (meta = metacarpal, 0/1/2 =
    /// proximal/middle/distal). The alias table mirrors
    /// <see cref="Target.SboxBoneClassifier"/>'s curated role table — kept in sync by a
    /// test. This is what lets a COMPILED s&amp;box model picked as a custom conversion
    /// target (or used as a source) be recognized: the reversed word order
    /// (<c>arm_upper</c>, not <c>upper_arm</c>) defeats generic token matching, which
    /// scored the citizen rig at 5% and made every custom-model pick fail with "not
    /// recognized as humanoid". Twist/helper/IK/face bones (<c>*_twist*</c>,
    /// <c>*_helper*</c>, <c>eye_*</c>, …) have no aliases and are never mapped.
    /// </summary>
    public static Profile Sbox { get; } = BuildSbox();

    /// <summary>
    /// AdvancedSkeleton (Maya auto-rigger, ubiquitous in game rips and mobile-game rigs):
    /// <c>Root_M</c> hips, <c>Spine1_M(→Spine2_M)→Chest_M</c> spine, <c>Neck_M→Head_M</c>
    /// (small rigs parent <c>Head_M</c> straight to the chest with no neck),
    /// <c>Scapula→Shoulder→Elbow→Wrist</c> arms, <c>Hip→Knee→Ankle→Toes</c> legs and
    /// <c>&lt;Name&gt;Finger1..3</c> fingers, all sided <c>_L/_R</c> (center <c>_M</c>).
    /// Twist helpers (<c>ShoulderPart1</c>, <c>HipPart1</c>, …), <c>Cup</c> palm bones and
    /// the face rig carry no aliases. Real case: a Sonic mobile-game rip whose name stage
    /// scored below threshold — the topology fallback then mapped ARMS AND LEGS ONTO THE
    /// HEAD QUILLS (long symmetric chains), playing every clip as garbage.
    /// </summary>
    public static Profile AdvancedSkeleton { get; } = BuildAdvancedSkeleton();

    /// <summary>All built-in presets, in detection order (first wins score ties — see
    /// <see cref="SmplX"/> vs <see cref="Smpl"/>; <see cref="ValveBiped"/> is evaluated
    /// before <see cref="Biped"/> and <see cref="DazGenesis"/> before
    /// <see cref="DazPoser"/> within their families).</summary>
    public static IReadOnlyList<Profile> All { get; } =
        new[]
        {
            Sbox, Mixamo, ActorCoreCc, UeMannequin, XsensMvn, PerceptionNeuron, RokokoBvh,
            SmplX, Smpl, SomaBvh, ClassicBvh, ValveBiped, Biped, DazGenesis, DazPoser,
            Rigify, Vrm, AutoRigPro, AdvancedSkeleton,
        };

    // ---------------------------------------------------------------- advanced skeleton

    private static Profile BuildAdvancedSkeleton()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "Root_M" },
            [BoneRole.Spine0] = new[] { "Spine1_M" },
            [BoneRole.Spine1] = new[] { "Spine2_M" },
            [BoneRole.Spine2] = new[] { "Chest_M" },
            [BoneRole.Neck] = new[] { "Neck_M" },
            [BoneRole.Head] = new[] { "Head_M" },
        };
        foreach (var side in new[] { "L", "R" })
        {
            aliases[Role("Clavicle", side)] = new[] { $"Scapula_{side}" };
            aliases[Role("UpperArm", side)] = new[] { $"Shoulder_{side}" };
            aliases[Role("LowerArm", side)] = new[] { $"Elbow_{side}" };
            aliases[Role("Hand", side)] = new[] { $"Wrist_{side}" };
            aliases[Role("UpperLeg", side)] = new[] { $"Hip_{side}" };
            aliases[Role("LowerLeg", side)] = new[] { $"Knee_{side}" };
            aliases[Role("Foot", side)] = new[] { $"Ankle_{side}" };
            aliases[Role("Toe", side)] = new[] { $"Toes_{side}" };

            foreach (var finger in new[] { "Thumb", "Index", "Middle", "Ring", "Pinky" })
            {
                aliases[Role($"{finger}Prox", side)] = new[] { $"{finger}Finger1_{side}" };
                aliases[Role($"{finger}Mid", side)] = new[] { $"{finger}Finger2_{side}" };
                aliases[Role($"{finger}Dist", side)] = new[] { $"{finger}Finger3_{side}" };
            }
        }

        return new Profile("advanced_skeleton", new string[0], aliases);
    }

    // ---------------------------------------------------------------- sbox

    private static Profile BuildSbox()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "pelvis" },
            [BoneRole.Spine0] = new[] { "spine_0" },
            [BoneRole.Spine1] = new[] { "spine_1" },
            [BoneRole.Spine2] = new[] { "spine_2" },
            [BoneRole.Spine3] = new[] { "spine_3" },
            [BoneRole.Neck] = new[] { "neck_0" },
            [BoneRole.Head] = new[] { "head" },
        };
        foreach (var side in new[] { "L", "R" })
        {
            aliases[Role("Clavicle", side)] = new[] { $"clavicle_{side}" };
            aliases[Role("UpperArm", side)] = new[] { $"arm_upper_{side}" };
            aliases[Role("LowerArm", side)] = new[] { $"arm_lower_{side}" };
            aliases[Role("Hand", side)] = new[] { $"hand_{side}" };
            aliases[Role("UpperLeg", side)] = new[] { $"leg_upper_{side}" };
            aliases[Role("LowerLeg", side)] = new[] { $"leg_lower_{side}" };
            aliases[Role("Foot", side)] = new[] { $"ankle_{side}" };
            aliases[Role("Toe", side)] = new[] { $"ball_{side}" };

            foreach (var (finger, rolePrefix) in new[]
            {
                ("thumb", "Thumb"), ("index", "Index"), ("middle", "Middle"),
                ("ring", "Ring"), ("pinky", "Pinky"),
            })
            {
                aliases[Role($"{rolePrefix}Meta", side)] = new[] { $"finger_{finger}_meta_{side}" };
                aliases[Role($"{rolePrefix}Prox", side)] = new[] { $"finger_{finger}_0_{side}" };
                aliases[Role($"{rolePrefix}Mid", side)] = new[] { $"finger_{finger}_1_{side}" };
                aliases[Role($"{rolePrefix}Dist", side)] = new[] { $"finger_{finger}_2_{side}" };
            }
        }

        return new Profile("sbox", new string[0], aliases);
    }

    // ---------------------------------------------------------------- mixamo

    private static Profile BuildMixamo()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "Hips" },
            [BoneRole.Spine0] = new[] { "Spine" },
            [BoneRole.Spine1] = new[] { "Spine1" },
            [BoneRole.Spine2] = new[] { "Spine2" },
            [BoneRole.Neck] = new[] { "Neck" },
            [BoneRole.Head] = new[] { "Head" },
        };
        foreach (var (roleSide, nameSide) in Sides())
        {
            aliases[Role("Clavicle", roleSide)] = new[] { $"{nameSide}Shoulder" };
            aliases[Role("UpperArm", roleSide)] = new[] { $"{nameSide}Arm" };
            aliases[Role("LowerArm", roleSide)] = new[] { $"{nameSide}ForeArm" };
            aliases[Role("Hand", roleSide)] = new[] { $"{nameSide}Hand" };
            aliases[Role("UpperLeg", roleSide)] = new[] { $"{nameSide}UpLeg" };
            aliases[Role("LowerLeg", roleSide)] = new[] { $"{nameSide}Leg" };
            aliases[Role("Foot", roleSide)] = new[] { $"{nameSide}Foot" };
            aliases[Role("Toe", roleSide)] = new[] { $"{nameSide}ToeBase" };

            foreach (var finger in new[] { "Thumb", "Index", "Middle", "Ring", "Pinky" })
            {
                aliases[Role($"{finger}Prox", roleSide)] = new[] { $"{nameSide}Hand{finger}1" };
                aliases[Role($"{finger}Mid", roleSide)] = new[] { $"{nameSide}Hand{finger}2" };
                aliases[Role($"{finger}Dist", roleSide)] = new[] { $"{nameSide}Hand{finger}3" };
            }
        }
        // Both ':' (FBX namespace) and '_' (namespace mangled by some exporters) forms occur
        // in the wild; some Mixamo downloads ship with no namespace at all, which still
        // matches because the aliases are the bare names.
        return new Profile("mixamo", new[] { "^mixamorig[0-9]*:", "^mixamorig[0-9]*_" }, aliases);
    }

    // ---------------------------------------------------------------- actorcore / cc

    private static Profile BuildActorCoreCc()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "Hip" },
            [BoneRole.Spine0] = new[] { "Waist" },
            [BoneRole.Spine1] = new[] { "Spine01" },
            [BoneRole.Spine2] = new[] { "Spine02" },
            [BoneRole.Neck] = new[] { "NeckTwist01" },
            [BoneRole.Head] = new[] { "Head" },
        };
        foreach (var roleSide in new[] { "L", "R" })
        {
            var nameSide = roleSide; // CC bones use the bare side letter: CC_Base_L_Thigh.
            aliases[Role("Clavicle", roleSide)] = new[] { $"{nameSide}_Clavicle" };
            aliases[Role("UpperArm", roleSide)] = new[] { $"{nameSide}_Upperarm" };
            aliases[Role("LowerArm", roleSide)] = new[] { $"{nameSide}_Forearm" };
            aliases[Role("Hand", roleSide)] = new[] { $"{nameSide}_Hand" };
            aliases[Role("UpperLeg", roleSide)] = new[] { $"{nameSide}_Thigh" };
            aliases[Role("LowerLeg", roleSide)] = new[] { $"{nameSide}_Calf" };
            aliases[Role("Foot", roleSide)] = new[] { $"{nameSide}_Foot" };
            aliases[Role("Toe", roleSide)] = new[] { $"{nameSide}_ToeBase" };

            foreach (var (role, cc) in new[]
            {
                ("Thumb", "Thumb"), ("Index", "Index"), ("Middle", "Mid"), ("Ring", "Ring"), ("Pinky", "Pinky"),
            })
            {
                aliases[Role($"{role}Prox", roleSide)] = new[] { $"{nameSide}_{cc}1" };
                aliases[Role($"{role}Mid", roleSide)] = new[] { $"{nameSide}_{cc}2" };
                aliases[Role($"{role}Dist", roleSide)] = new[] { $"{nameSide}_{cc}3" };
            }
        }
        return new Profile("actorcore_cc", new[] { "^CC_Base_" }, aliases);
    }

    // ---------------------------------------------------------------- ue mannequin

    private static Profile BuildUeMannequin()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "pelvis" },
            [BoneRole.Spine0] = new[] { "spine_01" },
            [BoneRole.Spine1] = new[] { "spine_02" },
            [BoneRole.Spine2] = new[] { "spine_03" },
            [BoneRole.Spine3] = new[] { "spine_04" },
            [BoneRole.Spine4] = new[] { "spine_05" },
            [BoneRole.Neck] = new[] { "neck_01" },
            [BoneRole.Head] = new[] { "head" },
        };
        foreach (var (roleSide, s) in new[] { ("L", "l"), ("R", "r") })
        {
            aliases[Role("Clavicle", roleSide)] = new[] { $"clavicle_{s}" };
            aliases[Role("UpperArm", roleSide)] = new[] { $"upperarm_{s}" };
            aliases[Role("LowerArm", roleSide)] = new[] { $"lowerarm_{s}" };
            aliases[Role("Hand", roleSide)] = new[] { $"hand_{s}" };
            aliases[Role("UpperLeg", roleSide)] = new[] { $"thigh_{s}" };
            aliases[Role("LowerLeg", roleSide)] = new[] { $"calf_{s}" };
            aliases[Role("Foot", roleSide)] = new[] { $"foot_{s}" };
            aliases[Role("Toe", roleSide)] = new[] { $"ball_{s}" };

            foreach (var (role, ue) in new[]
            {
                ("Thumb", "thumb"), ("Index", "index"), ("Middle", "middle"), ("Ring", "ring"), ("Pinky", "pinky"),
            })
            {
                // UE5 mannequin adds metacarpals for the four fingers (not the thumb).
                if (role != "Thumb")
                    aliases[Role($"{role}Meta", roleSide)] = new[] { $"{ue}_metacarpal_{s}" };
                aliases[Role($"{role}Prox", roleSide)] = new[] { $"{ue}_01_{s}" };
                aliases[Role($"{role}Mid", roleSide)] = new[] { $"{ue}_02_{s}" };
                aliases[Role($"{role}Dist", roleSide)] = new[] { $"{ue}_03_{s}" };
            }
        }
        return new Profile("ue_mannequin", new string[0], aliases);
    }

    // ---------------------------------------------------------------- rokoko / xsens bvh

    private static Profile BuildRokokoBvh()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "Hips" },
            // Spine naming varies (Spine, Spine1..Spine4); ordered alias preference plus the
            // used-bone exclusion in the detector shifts the chain up when "Spine" is absent.
            [BoneRole.Spine0] = new[] { "Spine", "Spine1" },
            [BoneRole.Spine1] = new[] { "Spine1", "Spine2" },
            [BoneRole.Spine2] = new[] { "Spine2", "Spine3" },
            [BoneRole.Spine3] = new[] { "Spine3", "Spine4" },
            [BoneRole.Spine4] = new[] { "Spine4" },
            [BoneRole.Neck] = new[] { "Neck", "Neck1" },
            [BoneRole.Head] = new[] { "Head" },
        };
        foreach (var (roleSide, nameSide) in Sides())
        {
            aliases[Role("Clavicle", roleSide)] = new[] { $"{nameSide}Shoulder", $"{nameSide}Collar" };
            aliases[Role("UpperArm", roleSide)] = new[] { $"{nameSide}Arm", $"{nameSide}UpperArm" };
            aliases[Role("LowerArm", roleSide)] = new[] { $"{nameSide}ForeArm", $"{nameSide}LowerArm" };
            aliases[Role("Hand", roleSide)] = new[] { $"{nameSide}Hand" };
            aliases[Role("UpperLeg", roleSide)] = new[] { $"{nameSide}UpLeg", $"{nameSide}Thigh", $"{nameSide}UpperLeg" };
            aliases[Role("LowerLeg", roleSide)] = new[] { $"{nameSide}Leg", $"{nameSide}Shin", $"{nameSide}LowerLeg" };
            aliases[Role("Foot", roleSide)] = new[] { $"{nameSide}Foot" };
            aliases[Role("Toe", roleSide)] = new[] { $"{nameSide}Toe", $"{nameSide}ToeBase" };
        }
        return new Profile("rokoko_bvh", new string[0], aliases);
    }

    // ---------------------------------------------------------------- xsens mvn

    private static Profile BuildXsensMvn()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "Pelvis" },
            // MVN's exported spine segments are the anatomical vertebra levels L5/L3/T12/T8.
            [BoneRole.Spine0] = new[] { "L5" },
            [BoneRole.Spine1] = new[] { "L3" },
            [BoneRole.Spine2] = new[] { "T12" },
            [BoneRole.Spine3] = new[] { "T8" },
            [BoneRole.Neck] = new[] { "Neck" },
            [BoneRole.Head] = new[] { "Head" },
        };
        foreach (var (roleSide, nameSide) in Sides())
        {
            aliases[Role("Clavicle", roleSide)] = new[] { $"{nameSide}Shoulder" };
            aliases[Role("UpperArm", roleSide)] = new[] { $"{nameSide}UpperArm" };
            aliases[Role("LowerArm", roleSide)] = new[] { $"{nameSide}ForeArm" };
            aliases[Role("Hand", roleSide)] = new[] { $"{nameSide}Hand" };
            aliases[Role("UpperLeg", roleSide)] = new[] { $"{nameSide}UpperLeg" };
            aliases[Role("LowerLeg", roleSide)] = new[] { $"{nameSide}LowerLeg" };
            aliases[Role("Foot", roleSide)] = new[] { $"{nameSide}Foot" };
            aliases[Role("Toe", roleSide)] = new[] { $"{nameSide}Toe" };
        }
        // Body-suit capture: no finger segments (see the property remarks).
        return new Profile("xsens_mvn", new string[0], aliases);
    }

    // ---------------------------------------------------------------- perception neuron

    private static Profile BuildPerceptionNeuron()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "Hips" },
            [BoneRole.Spine0] = new[] { "Spine" },
            [BoneRole.Spine1] = new[] { "Spine1" },
            [BoneRole.Spine2] = new[] { "Spine2" },
            [BoneRole.Spine3] = new[] { "Spine3" },
            [BoneRole.Neck] = new[] { "Neck" },
            [BoneRole.Head] = new[] { "Head" },
        };
        foreach (var (roleSide, nameSide) in Sides())
        {
            aliases[Role("Clavicle", roleSide)] = new[] { $"{nameSide}Shoulder" };
            aliases[Role("UpperArm", roleSide)] = new[] { $"{nameSide}Arm" };
            aliases[Role("LowerArm", roleSide)] = new[] { $"{nameSide}ForeArm" };
            aliases[Role("Hand", roleSide)] = new[] { $"{nameSide}Hand" };
            aliases[Role("UpperLeg", roleSide)] = new[] { $"{nameSide}UpLeg" };
            aliases[Role("LowerLeg", roleSide)] = new[] { $"{nameSide}Leg" };
            aliases[Role("Foot", roleSide)] = new[] { $"{nameSide}Foot" };
            // No toe joints in Axis Neuron exports; the feet are chain tips.

            // Phalanges only: the LeftInHandIndex-style metacarpal helpers between hand
            // and phalanges carry no role (see the property remarks).
            foreach (var finger in new[] { "Thumb", "Index", "Middle", "Ring", "Pinky" })
            {
                aliases[Role($"{finger}Prox", roleSide)] = new[] { $"{nameSide}Hand{finger}1" };
                aliases[Role($"{finger}Mid", roleSide)] = new[] { $"{nameSide}Hand{finger}2" };
                aliases[Role($"{finger}Dist", roleSide)] = new[] { $"{nameSide}Hand{finger}3" };
            }
        }
        return new Profile("perception_neuron", new string[0], aliases);
    }

    // ---------------------------------------------------------------- valvebiped

    private static Profile BuildValveBiped()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "Pelvis" },
            [BoneRole.Spine0] = new[] { "Spine" },
            [BoneRole.Spine1] = new[] { "Spine1" },
            [BoneRole.Spine2] = new[] { "Spine2" },
            // The stock HL2 chain skips Spine3 (Spine2's child IS Spine4, the chest);
            // ordered preference + used-bone exclusion also absorbs a variant that has
            // both: Spine3→Spine3 and Spine4→Spine4.
            [BoneRole.Spine3] = new[] { "Spine3", "Spine4" },
            [BoneRole.Spine4] = new[] { "Spine4" },
            [BoneRole.Neck] = new[] { "Neck1" },
            [BoneRole.Head] = new[] { "Head1" },
        };
        foreach (var s in new[] { "L", "R" })
        {
            aliases[Role("Clavicle", s)] = new[] { $"{s}_Clavicle" };
            aliases[Role("UpperArm", s)] = new[] { $"{s}_UpperArm" };
            aliases[Role("LowerArm", s)] = new[] { $"{s}_Forearm" };
            aliases[Role("Hand", s)] = new[] { $"{s}_Hand" };
            aliases[Role("UpperLeg", s)] = new[] { $"{s}_Thigh" };
            aliases[Role("LowerLeg", s)] = new[] { $"{s}_Calf" };
            aliases[Role("Foot", s)] = new[] { $"{s}_Foot" };
            aliases[Role("Toe", s)] = new[] { $"{s}_Toe0" };

            // Biped-style numbered finger chains behind the ValveBiped namespace:
            // Finger0 is the thumb; segments append the phalanx digit (Finger0 →
            // Finger01 → Finger02, Finger1 → Finger11 → …).
            foreach (var (finger, n) in new[]
            {
                ("Thumb", 0), ("Index", 1), ("Middle", 2), ("Ring", 3), ("Pinky", 4),
            })
            {
                aliases[Role($"{finger}Prox", s)] = new[] { $"{s}_Finger{n}" };
                aliases[Role($"{finger}Mid", s)] = new[] { $"{s}_Finger{n}1" };
                aliases[Role($"{finger}Dist", s)] = new[] { $"{s}_Finger{n}2" };
            }
        }
        // The fixed "ValveBiped.Bip01_" namespace: anchored, so plain "Bip01 ..." Character
        // Studio rigs never strip it (and the Biped preset's "^Bip\d+[ _]" never matches
        // the ValveBiped prefix — the two families cannot cross-claim).
        return new Profile("valvebiped", new[] { @"^ValveBiped\.Bip01_" }, aliases);
    }

    // ---------------------------------------------------------------- daz genesis 3/8

    private static Profile BuildDazGenesis()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            // "hip" is the translating root and LCA of the pelvis (leg branch) and the
            // abdomen (spine branch); "pelvis" is a leg-branch intermediate and stays
            // unmapped — same policy as ActorCore's CC_Base_Hip/CC_Base_Pelvis pair.
            [BoneRole.Hips] = new[] { "hip" },
            [BoneRole.Spine0] = new[] { "abdomenLower" },
            [BoneRole.Spine1] = new[] { "abdomenUpper" },
            [BoneRole.Spine2] = new[] { "chestLower" },
            [BoneRole.Spine3] = new[] { "chestUpper" },
            // neckLower→neckUpper→head: neckLower IS the neck; neckUpper stays unmapped
            // (same policy as ActorCore's NeckTwist02 / rigify's spine.005).
            [BoneRole.Neck] = new[] { "neckLower" },
            [BoneRole.Head] = new[] { "head" },
        };
        foreach (var s in new[] { "L", "R" })
        {
            var p = s == "L" ? "l" : "r"; // lower-case side prefix: lShldrBend, rThighBend
            aliases[Role("Clavicle", s)] = new[] { $"{p}Collar" };
            // Bend bones are the primary limb bones; the co-linear *Twist roll helpers
            // have no aliases and are never mapped.
            aliases[Role("UpperArm", s)] = new[] { $"{p}ShldrBend" };
            aliases[Role("LowerArm", s)] = new[] { $"{p}ForearmBend" };
            aliases[Role("Hand", s)] = new[] { $"{p}Hand" };
            aliases[Role("UpperLeg", s)] = new[] { $"{p}ThighBend" };
            aliases[Role("LowerLeg", s)] = new[] { $"{p}Shin" };
            aliases[Role("Foot", s)] = new[] { $"{p}Foot" };
            // lMetatarsals sits between foot and toe (arch helper, unmapped).
            aliases[Role("Toe", s)] = new[] { $"{p}Toe" };

            foreach (var (role, daz) in new[]
            {
                ("Thumb", "Thumb"), ("Index", "Index"), ("Middle", "Mid"), ("Ring", "Ring"), ("Pinky", "Pinky"),
            })
            {
                aliases[Role($"{role}Prox", s)] = new[] { $"{p}{daz}1" };
                aliases[Role($"{role}Mid", s)] = new[] { $"{p}{daz}2" };
                aliases[Role($"{role}Dist", s)] = new[] { $"{p}{daz}3" };
            }
        }
        return new Profile("daz_genesis", new string[0], aliases);
    }

    // ---------------------------------------------------------------- smpl / smpl-x

    private static Profile BuildSmpl(bool withFingers)
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "Pelvis" },
            [BoneRole.Spine0] = new[] { "Spine1" },
            [BoneRole.Spine1] = new[] { "Spine2" },
            [BoneRole.Spine2] = new[] { "Spine3" },
            [BoneRole.Neck] = new[] { "Neck" },
            [BoneRole.Head] = new[] { "Head" },
        };
        foreach (var (roleSide, abbr, word) in new[] { ("L", "L", "left"), ("R", "R", "right") })
        {
            // Both documented spellings per role: abbreviated FBX-rig names ("L_Hip") and
            // spelled model joint names ("left_hip"). Comparison is separator-insensitive.
            aliases[Role("Clavicle", roleSide)] = new[] { $"{abbr}_Collar", $"{word}_collar" };
            aliases[Role("UpperArm", roleSide)] = new[] { $"{abbr}_Shoulder", $"{word}_shoulder" };
            aliases[Role("LowerArm", roleSide)] = new[] { $"{abbr}_Elbow", $"{word}_elbow" };
            aliases[Role("Hand", roleSide)] = new[] { $"{abbr}_Wrist", $"{word}_wrist" };
            aliases[Role("UpperLeg", roleSide)] = new[] { $"{abbr}_Hip", $"{word}_hip" };
            aliases[Role("LowerLeg", roleSide)] = new[] { $"{abbr}_Knee", $"{word}_knee" };
            aliases[Role("Foot", roleSide)] = new[] { $"{abbr}_Ankle", $"{word}_ankle" };
            aliases[Role("Toe", roleSide)] = new[] { $"{abbr}_Foot", $"{word}_foot" };

            if (!withFingers)
                continue;

            // SMPL-X finger joints (left_index1..3 etc., per vchoutas/smplx joint_names.py).
            foreach (var finger in new[] { "thumb", "index", "middle", "ring", "pinky" })
            {
                var name = char.ToUpperInvariant(finger[0]) + finger[1..];
                aliases[Role($"{name}Prox", roleSide)] = new[] { $"{word}_{finger}1" };
                aliases[Role($"{name}Mid", roleSide)] = new[] { $"{word}_{finger}2" };
                aliases[Role($"{name}Dist", roleSide)] = new[] { $"{word}_{finger}3" };
            }
        }
        // Gendered SMPL FBX rigs prefix every bone (m_avg_L_Hip, f_avg_Pelvis).
        return new Profile(withFingers ? "smpl_x" : "smpl", new[] { "^m_avg_", "^f_avg_" }, aliases);
    }

    // ---------------------------------------------------------------- nvidia soma bvh

    private static Profile BuildSomaBvh()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "Hips" },
            [BoneRole.Spine0] = new[] { "Spine1" },
            [BoneRole.Spine1] = new[] { "Spine2" },
            [BoneRole.Spine2] = new[] { "Chest" },
            [BoneRole.Neck] = new[] { "Neck1" },
            [BoneRole.Head] = new[] { "Head" },
        };
        foreach (var (roleSide, nameSide) in Sides())
        {
            aliases[Role("Clavicle", roleSide)] = new[] { $"{nameSide}Shoulder" };
            aliases[Role("UpperArm", roleSide)] = new[] { $"{nameSide}Arm" };
            aliases[Role("LowerArm", roleSide)] = new[] { $"{nameSide}ForeArm" };
            aliases[Role("Hand", roleSide)] = new[] { $"{nameSide}Hand" };
            // SOMA's "Leg" is the thigh, "Shin" the calf — the decisive difference from
            // mixamo, where "Leg" is the calf under "UpLeg".
            aliases[Role("UpperLeg", roleSide)] = new[] { $"{nameSide}Leg" };
            aliases[Role("LowerLeg", roleSide)] = new[] { $"{nameSide}Shin" };
            aliases[Role("Foot", roleSide)] = new[] { $"{nameSide}Foot" };
            aliases[Role("Toe", roleSide)] = new[] { $"{nameSide}ToeBase" };

            // Mixamo-style finger NAMES but not mixamo segmentation: SOMA fingers have four
            // segments where segment 1 is a metacarpal (measured on the repro BVH: Index1
            // sits 3.2 cm from the wrist at the palm base, then a 6.4 cm metacarpal to the
            // Index2 knuckle, then 3.7/2.3 cm phalanges to Index3/Index4) — so 2/3/4 are the
            // phalanges. Mapping 1..3 as Prox/Mid/Dist (mixamo's segmentation) shifted every
            // curl one joint outward and dropped the distal curl entirely (frozen fingers).
            // The thumb is three segments plus *End, mapped 1..3 like mixamo's; *End tip
            // markers carry no role.
            aliases[Role("ThumbProx", roleSide)] = new[] { $"{nameSide}HandThumb1" };
            aliases[Role("ThumbMid", roleSide)] = new[] { $"{nameSide}HandThumb2" };
            aliases[Role("ThumbDist", roleSide)] = new[] { $"{nameSide}HandThumb3" };
            foreach (var finger in new[] { "Index", "Middle", "Ring", "Pinky" })
            {
                aliases[Role($"{finger}Meta", roleSide)] = new[] { $"{nameSide}Hand{finger}1" };
                aliases[Role($"{finger}Prox", roleSide)] = new[] { $"{nameSide}Hand{finger}2" };
                aliases[Role($"{finger}Mid", roleSide)] = new[] { $"{nameSide}Hand{finger}3" };
                aliases[Role($"{finger}Dist", roleSide)] = new[] { $"{nameSide}Hand{finger}4" };
            }
        }
        return new Profile("soma_bvh", new string[0], aliases);
    }

    // ---------------------------------------------------------------- classic bvh

    private static Profile BuildClassicBvh()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "Hips" },
            [BoneRole.Spine0] = new[] { "Chest" },
            [BoneRole.Spine1] = new[] { "Chest2" },
            [BoneRole.Spine2] = new[] { "Chest3" },
            [BoneRole.Spine3] = new[] { "Chest4" },
            [BoneRole.Neck] = new[] { "Neck" },
            [BoneRole.Head] = new[] { "Head" },
        };
        foreach (var (roleSide, nameSide) in Sides())
        {
            aliases[Role("Clavicle", roleSide)] = new[] { $"{nameSide}Collar" };
            aliases[Role("UpperArm", roleSide)] = new[] { $"{nameSide}Shoulder" };
            aliases[Role("LowerArm", roleSide)] = new[] { $"{nameSide}Elbow" };
            aliases[Role("Hand", roleSide)] = new[] { $"{nameSide}Wrist" };
            aliases[Role("UpperLeg", roleSide)] = new[] { $"{nameSide}Hip" };
            aliases[Role("LowerLeg", roleSide)] = new[] { $"{nameSide}Knee" };
            aliases[Role("Foot", roleSide)] = new[] { $"{nameSide}Ankle" };
            aliases[Role("Toe", roleSide)] = new[] { $"{nameSide}Toe" };
        }
        return new Profile("classic_bvh", new string[0], aliases);
    }

    // ---------------------------------------------------------------- 3ds max biped

    private static Profile BuildBiped()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "Pelvis" },
            [BoneRole.Spine0] = new[] { "Spine" },
            [BoneRole.Spine1] = new[] { "Spine1" },
            [BoneRole.Spine2] = new[] { "Spine2" },
            [BoneRole.Spine3] = new[] { "Spine3" },
            [BoneRole.Neck] = new[] { "Neck" },
            [BoneRole.Head] = new[] { "Head" },
        };
        foreach (var s in new[] { "L", "R" })
        {
            aliases[Role("Clavicle", s)] = new[] { $"{s} Clavicle" };
            aliases[Role("UpperArm", s)] = new[] { $"{s} UpperArm" };
            aliases[Role("LowerArm", s)] = new[] { $"{s} Forearm" };
            aliases[Role("Hand", s)] = new[] { $"{s} Hand" };
            aliases[Role("UpperLeg", s)] = new[] { $"{s} Thigh" };
            aliases[Role("LowerLeg", s)] = new[] { $"{s} Calf" };
            aliases[Role("Foot", s)] = new[] { $"{s} Foot" };
            aliases[Role("Toe", s)] = new[] { $"{s} Toe0" };

            // Numbered finger chains: Finger0 is the thumb; segment names append the
            // phalanx digit (Finger0 → Finger01 → Finger02, Finger1 → Finger11 → ...).
            foreach (var (finger, n) in new[]
            {
                ("Thumb", 0), ("Index", 1), ("Middle", 2), ("Ring", 3), ("Pinky", 4),
            })
            {
                aliases[Role($"{finger}Prox", s)] = new[] { $"{s} Finger{n}" };
                aliases[Role($"{finger}Mid", s)] = new[] { $"{s} Finger{n}1" };
                aliases[Role($"{finger}Dist", s)] = new[] { $"{s} Finger{n}2" };
            }
        }
        // "Bip01 "/"Bip001 " biped-name prefix; underscore form covers exporters that
        // mangle the spaces ("Bip01_L_Thigh"). The bare COM root "Bip01" is untouched by
        // the pattern (no trailing separator) and has no alias.
        return new Profile("biped", new[] { @"^Bip\d+[ _]" }, aliases);
    }

    // ---------------------------------------------------------------- daz / poser

    private static Profile BuildDazPoser()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "hip" },
            [BoneRole.Spine0] = new[] { "abdomen" },
            // Poser classic / DAZ Gen4 spine is abdomen→chest; DAZ Genesis 1/2 inserts
            // abdomen2. Ordered preference + used-bone exclusion handles both: without
            // abdomen2 the chest falls back to Spine1 and Spine2 stays unmapped.
            [BoneRole.Spine1] = new[] { "abdomen2", "chest" },
            [BoneRole.Spine2] = new[] { "chest" },
            [BoneRole.Neck] = new[] { "neck" },
            [BoneRole.Head] = new[] { "head" },
        };
        foreach (var s in new[] { "L", "R" })
        {
            var p = s == "L" ? "l" : "r"; // lower-case side prefix: lShldr, rThigh
            aliases[Role("Clavicle", s)] = new[] { $"{p}Collar" };
            aliases[Role("UpperArm", s)] = new[] { $"{p}Shldr" };
            aliases[Role("LowerArm", s)] = new[] { $"{p}ForeArm" };
            aliases[Role("Hand", s)] = new[] { $"{p}Hand" };
            aliases[Role("UpperLeg", s)] = new[] { $"{p}Thigh" };
            aliases[Role("LowerLeg", s)] = new[] { $"{p}Shin" };
            aliases[Role("Foot", s)] = new[] { $"{p}Foot" };
            aliases[Role("Toe", s)] = new[] { $"{p}Toe" };

            foreach (var (role, daz) in new[]
            {
                ("Thumb", "Thumb"), ("Index", "Index"), ("Middle", "Mid"), ("Ring", "Ring"), ("Pinky", "Pinky"),
            })
            {
                aliases[Role($"{role}Prox", s)] = new[] { $"{p}{daz}1" };
                aliases[Role($"{role}Mid", s)] = new[] { $"{p}{daz}2" };
                aliases[Role($"{role}Dist", s)] = new[] { $"{p}{daz}3" };
            }
        }
        return new Profile("daz_poser", new string[0], aliases);
    }

    // ---------------------------------------------------------------- blender rigify

    private static Profile BuildRigify()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            // rigify's "spine" bone sits AT the pelvis and parents both thighs — it is
            // the hips, not a spine link (rigify/metarigs/human.py).
            [BoneRole.Hips] = new[] { "spine" },
            [BoneRole.Spine0] = new[] { "spine.001" },
            [BoneRole.Spine1] = new[] { "spine.002" },
            [BoneRole.Spine2] = new[] { "spine.003" },
            // spine.004 + spine.005 are the two neck bones, spine.006 the head;
            // spine.005 stays unmapped (same policy as ActorCore's NeckTwist02).
            [BoneRole.Neck] = new[] { "spine.004" },
            [BoneRole.Head] = new[] { "spine.006" },
        };
        foreach (var s in new[] { "L", "R" })
        {
            aliases[Role("Clavicle", s)] = new[] { $"shoulder.{s}" };
            aliases[Role("UpperArm", s)] = new[] { $"upper_arm.{s}" };
            aliases[Role("LowerArm", s)] = new[] { $"forearm.{s}" };
            aliases[Role("Hand", s)] = new[] { $"hand.{s}" };
            aliases[Role("UpperLeg", s)] = new[] { $"thigh.{s}" };
            aliases[Role("LowerLeg", s)] = new[] { $"shin.{s}" };
            aliases[Role("Foot", s)] = new[] { $"foot.{s}" };
            aliases[Role("Toe", s)] = new[] { $"toe.{s}" };

            foreach (var (role, rigify) in new[]
            {
                ("Thumb", "thumb"), ("Index", "f_index"), ("Middle", "f_middle"),
                ("Ring", "f_ring"), ("Pinky", "f_pinky"),
            })
            {
                aliases[Role($"{role}Prox", s)] = new[] { $"{rigify}.01.{s}" };
                aliases[Role($"{role}Mid", s)] = new[] { $"{rigify}.02.{s}" };
                aliases[Role($"{role}Dist", s)] = new[] { $"{rigify}.03.{s}" };
            }
        }
        // The generated deform skeleton prefixes every deform bone with "DEF-"; its
        // segmented limb twins ("DEF-upper_arm.L.001") keep their numeric suffix after
        // stripping and therefore never collide with the whole-bone aliases.
        return new Profile("rigify", new[] { "^DEF-" }, aliases);
    }

    // ---------------------------------------------------------------- vroid / vrm

    private static Profile BuildVrm()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "J_Bip_C_Hips" },
            [BoneRole.Spine0] = new[] { "J_Bip_C_Spine" },
            [BoneRole.Spine1] = new[] { "J_Bip_C_Chest" },
            [BoneRole.Spine2] = new[] { "J_Bip_C_UpperChest" },
            [BoneRole.Neck] = new[] { "J_Bip_C_Neck" },
            [BoneRole.Head] = new[] { "J_Bip_C_Head" },
        };
        foreach (var s in new[] { "L", "R" })
        {
            aliases[Role("Clavicle", s)] = new[] { $"J_Bip_{s}_Shoulder" };
            aliases[Role("UpperArm", s)] = new[] { $"J_Bip_{s}_UpperArm" };
            aliases[Role("LowerArm", s)] = new[] { $"J_Bip_{s}_LowerArm" };
            aliases[Role("Hand", s)] = new[] { $"J_Bip_{s}_Hand" };
            aliases[Role("UpperLeg", s)] = new[] { $"J_Bip_{s}_UpperLeg" };
            aliases[Role("LowerLeg", s)] = new[] { $"J_Bip_{s}_LowerLeg" };
            aliases[Role("Foot", s)] = new[] { $"J_Bip_{s}_Foot" };
            aliases[Role("Toe", s)] = new[] { $"J_Bip_{s}_ToeBase" };

            foreach (var (role, vrm) in new[]
            {
                ("Thumb", "Thumb"), ("Index", "Index"), ("Middle", "Middle"),
                ("Ring", "Ring"), ("Pinky", "Little"),
            })
            {
                aliases[Role($"{role}Prox", s)] = new[] { $"J_Bip_{s}_{vrm}1" };
                aliases[Role($"{role}Mid", s)] = new[] { $"J_Bip_{s}_{vrm}2" };
                aliases[Role($"{role}Dist", s)] = new[] { $"J_Bip_{s}_{vrm}3" };
            }
        }
        return new Profile("vrm", new string[0], aliases);
    }

    // ---------------------------------------------------------------- auto-rig pro

    private static Profile BuildAutoRigPro()
    {
        var aliases = new Dictionary<BoneRole, string[]>
        {
            [BoneRole.Hips] = new[] { "root.x" },
            [BoneRole.Spine0] = new[] { "spine_01.x" },
            [BoneRole.Spine1] = new[] { "spine_02.x" },
            [BoneRole.Spine2] = new[] { "spine_03.x" },
            [BoneRole.Neck] = new[] { "neck.x" },
            [BoneRole.Head] = new[] { "head.x" },
        };
        foreach (var s in new[] { "L", "R" })
        {
            var p = s == "L" ? "l" : "r";
            aliases[Role("Clavicle", s)] = new[] { $"shoulder.{p}" };
            aliases[Role("UpperArm", s)] = new[] { $"arm_stretch.{p}" };
            aliases[Role("LowerArm", s)] = new[] { $"forearm_stretch.{p}" };
            aliases[Role("Hand", s)] = new[] { $"hand.{p}" };
            aliases[Role("UpperLeg", s)] = new[] { $"thigh_stretch.{p}" };
            aliases[Role("LowerLeg", s)] = new[] { $"leg_stretch.{p}" };
            aliases[Role("Foot", s)] = new[] { $"foot.{p}" };
            aliases[Role("Toe", s)] = new[] { $"toes_01.{p}" };

            // Exported finger deform bones keep ARP's c_ control prefix (Defenses.fbx).
            foreach (var finger in new[] { "thumb", "index", "middle", "ring", "pinky" })
            {
                var role = char.ToUpperInvariant(finger[0]) + finger[1..];
                aliases[Role($"{role}Prox", s)] = new[] { $"c_{finger}1.{p}" };
                aliases[Role($"{role}Mid", s)] = new[] { $"c_{finger}2.{p}" };
                aliases[Role($"{role}Dist", s)] = new[] { $"c_{finger}3.{p}" };
            }
        }
        return new Profile("auto_rig_pro", new string[0], aliases);
    }

    // ---------------------------------------------------------------- helpers

    private static IEnumerable<(string RoleSide, string NameSide)> Sides()
    {
        yield return ("L", "Left");
        yield return ("R", "Right");
    }

    private static BoneRole Role(string baseName, string side)
        => System.Enum.Parse<BoneRole>(baseName + side);
}
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Solve/ClipMirror.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Numerics;
using HumanoidRetargeter.Mapping;
using HumanoidRetargeter.Maths;
using HumanoidRetargeter.Target;

namespace HumanoidRetargeter.Solve;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>
/// Mirrors a solved TARGET-space clip across the target character's sagittal plane,
/// producing the left/right-swapped twin of an animation (e.g. a right-foot-lead walk from a
/// left-foot-lead one).
/// </summary>
/// <remarks>
/// <para><b>Mirror plane.</b> The plane through the rig-space origin spanned by the target
/// character's up and forward directions; its normal is the character's LATERAL axis,
/// computed from the target rig's rest geometry via <see cref="CharacterFrame"/> (never
/// hardcoded — an arbitrary target may be authored in any axis convention). When the
/// computed lateral lies on a coordinate axis up to float dirt (&lt; 1e-3 on the other two
/// components — true for every axis-aligned authored rig, including the s&amp;box citizen
/// rigs), it is snapped to that exact axis, which makes every reflection below an EXACT
/// sign-flip in IEEE arithmetic and therefore the whole mirror a bit-exact involution
/// (mirror ∘ mirror == identity, verified by test).</para>
/// <para><b>Math.</b> Let M = I − 2n̂n̂ᵀ be the reflection across the plane with unit normal
/// n̂. A world transform W = (R, t) maps to its mirror image by conjugation:
/// W′ = M̂ ∘ W ∘ M̂ (M̂ is its own inverse), giving rotation R′ = M·R·M and translation
/// t′ = M·t. For a quaternion q = (v, w), M·R·M is the rotation by the SAME angle about the
/// REFLECTED axis with REVERSED sense (a reflection flips orientation), i.e.
/// q′ = (2(n̂·v)n̂ − v, w); with n̂ = +X that is exactly q′ = (x, −y, −z, w), and positions
/// reflect as p′ = p − 2(n̂·p)n̂ = (−pₓ, p_y, p_z).</para>
/// <para><b>Locals, not worlds.</b> Because conjugation is a homomorphism
/// (M̂(AB)M̂ = (M̂AM̂)(M̂BM̂)) and world transforms are products of locals down the
/// hierarchy, mirroring every LOCAL transform and permuting bones by their L↔R partner is
/// exactly equivalent to mirroring the FK worlds — provided the partner permutation is
/// hierarchy-consistent (the partner's parent is the parent's partner), which is validated
/// and holds on structurally symmetric humanoid rigs. This avoids FK→inverse-FK float drift
/// entirely, which is what makes the double-mirror identity bit-exact.</para>
/// <para><b>Pairing.</b> Left/right bones are paired by the rig's canonical role annotations
/// first (UpperArmL ↔ UpperArmR, …); role-less bones (twist helpers, IK bones) fall back to
/// <c>_L</c>/<c>_R</c> name-token pairing (<c>arm_upper_L_twist0</c> ↔
/// <c>arm_upper_R_twist0</c>, <c>foot_L_IK_target</c> ↔ <c>foot_R_IK_target</c>); anything
/// unpaired (center bones: pelvis, spine, neck, head) mirrors in place, which reflects its
/// rotation across the sagittal plane and negates its lateral translation. IK-baked helper
/// bones are NOT re-baked after mirroring: conjugation is a homomorphism, so the mirrored
/// copies of the primary clip's final helper channels already hang in exactly the mirrored
/// relationship over the mirrored body (re-baking encoded a divergent convention, the Gate 3
/// review's mechanism 2 of the _M render defect; southpaw project, gate3_review.md 3.4).
/// Channel exclusions on mirrored clips go through <see cref="MirrorSafeExclusions"/>.</para>
/// </remarks>
public static class ClipMirror
{
    /// <summary>Maximum off-axis component magnitude below which the computed lateral axis is
    /// snapped to the exact coordinate axis (authored rigs are axis-aligned; the tiny rest
    /// asymmetries of a real mesh stay far below this).</summary>
    private const float AxisSnapTolerance = 1e-3f;

    /// <summary>
    /// Returns the mirrored copy of <paramref name="frames"/> (one new list, inputs
    /// untouched): per frame, bone i takes the conjugated local transform of its L↔R partner
    /// σ(i). See the class remarks for the math and pairing rules.
    /// </summary>
    /// <param name="frames">Solved per-frame local transforms (target skeleton bone order).</param>
    /// <param name="rig">The target rig (skeleton + roles) the frames belong to.</param>
    /// <exception cref="ArgumentException">Thrown when the rig maps a sided role without its
    /// counterpart, the pairing is not hierarchy-consistent, or the character frame is not
    /// computable — mirroring would silently produce garbage in those cases.</exception>
    public static List<XForm[]> Mirror(List<XForm[]> frames, TargetRig rig)
    {
        ArgumentNullException.ThrowIfNull(frames);
        ArgumentNullException.ThrowIfNull(rig);

        var skeleton = rig.Skeleton;
        var lateral = LateralAxis(rig);
        var pair = BuildPairing(rig);
        var fkFix = HierarchyInconsistentBones(rig, pair);

        var result = new List<XForm[]>(frames.Count);
        var baseWorld = fkFix.Count > 0 ? new XForm[skeleton.Count] : null;
        var mirrorWorld = fkFix.Count > 0 ? new XForm[skeleton.Count] : null;
        foreach (var locals in frames)
        {
            if (locals.Length != skeleton.Count)
                throw new ArgumentException(
                    $"Frame has {locals.Length} bones but the target skeleton has {skeleton.Count}.",
                    nameof(frames));

            var mirrored = new XForm[locals.Length];
            for (var i = 0; i < locals.Length; i++)
            {
                var source = locals[pair[i]];
                mirrored[i] = new XForm(
                    ReflectPoint(source.Pos, lateral),
                    ReflectRotation(source.Rot, lateral));
            }

            // Hierarchy-inconsistent pairs (the citizen parents arm_elbow_helper_R under
            // arm_lower_R_twist0 while _L hangs under arm_lower_L, the W3a-documented
            // rig quirk): the partner's conjugated LOCAL under a non-mirrored parent
            // chain misplaces the bone by the parent-chain difference (measured ~1 in on
            // the elbow/knee helpers). Solve their locals by FK so the mirrored WORLD is
            // the exact reflection of the partner's world (southpaw G8 mirror fix).
            if (fkFix.Count > 0)
            {
                for (var i = 0; i < skeleton.Count; i++)
                {
                    var parent = skeleton[i].ParentIndex;
                    baseWorld![i] = parent < 0
                        ? locals[i]
                        : XForm.Compose(baseWorld[parent], locals[i]);
                }
                for (var i = 0; i < skeleton.Count; i++)
                {
                    var parent = skeleton[i].ParentIndex;
                    if (fkFix.Contains(i))
                    {
                        var desired = new XForm(
                            ReflectPoint(baseWorld![pair[i]].Pos, lateral),
                            ReflectRotation(baseWorld[pair[i]].Rot, lateral));
                        mirrored[i] = parent < 0
                            ? desired
                            : XForm.ToLocal(mirrorWorld![parent], desired);
                    }
                    mirrorWorld![i] = parent < 0
                        ? mirrored[i]
                        : XForm.Compose(mirrorWorld[parent], mirrored[i]);
                }
            }
            result.Add(mirrored);
        }
        return result;
    }

    /// <summary>
    /// Bones whose L/R pairing is NOT hierarchy-consistent (the partner hangs under a
    /// non-mirrored parent). Only constraint-driven helpers can reach this state
    /// (<see cref="BuildPairing"/> fails hard for any other bone); their mirrored locals
    /// need the FK solve in <see cref="Mirror"/> and their DMX channels must be written
    /// (<see cref="MirrorSafeExclusions"/>).
    /// </summary>
    private static HashSet<int> HierarchyInconsistentBones(TargetRig rig, int[] pair)
    {
        var skeleton = rig.Skeleton;
        var result = new HashSet<int>();
        for (var i = 0; i < pair.Length; i++)
        {
            var parent = skeleton[i].ParentIndex;
            var partnerParent = skeleton[pair[i]].ParentIndex;
            var consistent = parent < 0
                ? partnerParent < 0
                : partnerParent == pair[parent];
            if (!consistent)
                result.Add(i);
        }
        return result;
    }

    /// <summary>
    /// Filters a channel-exclusion set for a MIRRORED clip: returns the subset of
    /// <paramref name="excluded"/> that is still safe to leave channel-less after mirroring.
    /// A channel-less bone is rendered/baked at its own REST local under its (mirrored)
    /// parent; the mirrored frames instead carry the conjugated rest local of the bone's
    /// L/R partner. Those agree only when the rig's rest locals are mirror conjugates
    /// (restLocal(i) == conjugate(restLocal(partner(i)))). Bones breaking that symmetry
    /// (measured on the citizen rig: leg/arm *_twist1 chains and neck_clothing, 19 to 37 cm
    /// off) MUST keep explicit mirrored channels or every data consumer (model compiler
    /// sequence bake, render-side helper evaluation) places them wrong: the Gate 3 review's
    /// MECHANISM 1 of the _M render defect (southpaw project, gate3_review.md 3.4).
    /// Truly symmetric helpers stay excluded exactly as on primary clips.
    /// </summary>
    /// <param name="rig">The target rig the exclusion set belongs to.</param>
    /// <param name="excluded">The primary-clip exclusion set (constraint-driven bones).</param>
    /// <returns>The mirror-safe subset, or null when nothing remains excluded.</returns>
    public static IReadOnlySet<int>? MirrorSafeExclusions(TargetRig rig, IReadOnlySet<int>? excluded)
    {
        if (excluded is null || excluded.Count == 0)
            return excluded;

        var skeleton = rig.Skeleton;
        var lateral = LateralAxis(rig);
        var pair = BuildPairing(rig);
        var inconsistent = HierarchyInconsistentBones(rig, pair);

        const float posTolCm = 0.1f;
        const float rotTolDeg = 0.5f;
        var cosTol = MathF.Cos(rotTolDeg * MathF.PI / 360f); // half-angle for quat dot

        var safe = new HashSet<int>();
        foreach (var i in excluded)
        {
            // Hierarchy-inconsistent pairs always need explicit channels: their mirrored
            // locals are FK-solved (see Mirror) and no rest local can stand in for them.
            if (inconsistent.Contains(i))
                continue;

            var own = skeleton[i].RestLocal;
            var partnerRest = skeleton[pair[i]].RestLocal;
            var needed = new XForm(
                ReflectPoint(partnerRest.Pos, lateral),
                ReflectRotation(partnerRest.Rot, lateral));

            var posOk = (own.Pos - needed.Pos).Length() <= posTolCm;
            var dot = MathF.Abs(
                own.Rot.X * needed.Rot.X + own.Rot.Y * needed.Rot.Y
                + own.Rot.Z * needed.Rot.Z + own.Rot.W * needed.Rot.W);
            var rotOk = dot >= cosTol;
            if (posOk && rotOk)
                safe.Add(i);
        }
        return safe.Count > 0 ? safe : null;
    }

    // ================================================================ mirror plane

    /// <summary>The unit mirror normal: the target character's lateral axis from rest
    /// geometry, snapped to an exact coordinate axis when within tolerance (bit-exact
    /// reflections, see class remarks).</summary>
    private static Vector3 LateralAxis(TargetRig rig)
    {
        Vector3 lateral;
        try
        {
            lateral = CharacterFrame.Compute(
                rig.Skeleton, rig.ToMappingResult(), rig.Skeleton.RestWorld).Lateral;
        }
        catch (ArgumentException e)
        {
            throw new ArgumentException(
                $"Cannot mirror: target character frame not computable ({e.Message}).", e);
        }

        var a = Vector3.Abs(lateral);
        if (a.Y <= AxisSnapTolerance && a.Z <= AxisSnapTolerance)
            return Vector3.UnitX;
        if (a.X <= AxisSnapTolerance && a.Z <= AxisSnapTolerance)
            return Vector3.UnitY;
        if (a.X <= AxisSnapTolerance && a.Y <= AxisSnapTolerance)
            return Vector3.UnitZ;
        return lateral; // general (non-axis-aligned) rig: exact involution is lost, math is not
    }

    /// <summary>p′ = p − 2(n̂·p)n̂. With a snapped axis this is an exact sign flip of one
    /// component (IEEE subtraction of representable values is exact).</summary>
    private static Vector3 ReflectPoint(Vector3 p, Vector3 n)
        => p - 2f * Vector3.Dot(p, n) * n;

    /// <summary>q′ = (2(n̂·v)n̂ − v, w): the conjugated rotation M·R·M — same angle, axis
    /// reflected, sense reversed. With n̂ = +X this is (x, −y, −z, w). Components are
    /// preserved exactly (no renormalization), keeping the double mirror bit-exact.</summary>
    private static Quaternion ReflectRotation(Quaternion q, Vector3 n)
    {
        var v = new Vector3(q.X, q.Y, q.Z);
        var reflected = 2f * Vector3.Dot(v, n) * n - v;
        return new Quaternion(reflected.X, reflected.Y, reflected.Z, q.W);
    }

    // ================================================================ L↔R pairing

    /// <summary>
    /// σ: bone → mirror partner (identity for center/unpaired bones). Roles pair first;
    /// role-less bones pair by <c>_L</c>/<c>_R</c> name tokens. Validated to be an involution
    /// consistent with the hierarchy (σ(parent(i)) == parent(σ(i))).
    /// </summary>
    private static int[] BuildPairing(TargetRig rig)
    {
        var skeleton = rig.Skeleton;
        var pair = new int[skeleton.Count];
        for (var i = 0; i < pair.Length; i++)
            pair[i] = i;

        for (var i = 0; i < skeleton.Count; i++)
        {
            if (rig.RoleOf(i) is { } role)
            {
                if (MirrorRole(role) is not { } mirroredRole)
                    continue; // center role: mirrors in place
                pair[i] = rig.BoneForRole(mirroredRole)
                    ?? throw new ArgumentException(
                        $"Cannot mirror: target rig maps role {role} ('{skeleton[i].Name}') "
                        + $"but not its counterpart {mirroredRole}.");
            }
            else
            {
                var partnerName = SwapSideTokens(skeleton[i].Name);
                if (partnerName is null)
                    continue; // no side token: center bone
                var partner = skeleton.IndexOf(partnerName);
                if (partner >= 0)
                    pair[i] = partner;
                // No partner bone: leave in place (e.g. an asymmetric prop bone) — its
                // rotation still mirrors across the sagittal plane.
            }
        }

        for (var i = 0; i < pair.Length; i++)
        {
            // Constraint-driven helper bones are excluded from the output DMX (the model's
            // AnimConstraintList re-drives them at runtime, see Retargeter.EmitClip
            // ChannelExcludedBones), so their mirrored channels are never written. The shipped
            // s&box citizen rig parents these asymmetrically (arm_elbow_helper_R hangs under
            // arm_lower_R_twist0 while arm_elbow_helper_L hangs under arm_lower_L), a benign
            // data quirk that must not fail the whole mirror. Skip the strict L/R
            // hierarchy-consistency requirement for them: their pairing does not affect any
            // written channel. (W3a fix, southpaw project.)
            if (rig.HelpersAreConstraintDriven && rig.ClassOf(i) == BoneClass.ConstraintDriven)
                continue;

            if (pair[pair[i]] != i)
                throw new ArgumentException(
                    $"Cannot mirror: bone pairing is not symmetric ('{skeleton[i].Name}' → "
                    + $"'{skeleton[pair[i]].Name}' → '{skeleton[pair[pair[i]]].Name}').");

            var parent = skeleton[i].ParentIndex;
            var partnerParent = skeleton[pair[i]].ParentIndex;
            var consistent = parent < 0
                ? partnerParent < 0
                : partnerParent == pair[parent];
            if (!consistent)
                throw new ArgumentException(
                    $"Cannot mirror: left/right pairing is not hierarchy-consistent — "
                    + $"'{skeleton[i].Name}' and partner '{skeleton[pair[i]].Name}' hang under "
                    + "non-mirrored parents.");
        }

        return pair;
    }

    /// <summary>UpperArmL → UpperArmR (and back); null for center roles. Every sided
    /// <see cref="BoneRole"/> ends in <c>L</c>/<c>R</c>; no center role does.</summary>
    private static BoneRole? MirrorRole(BoneRole role)
    {
        var name = role.ToString();
        var mirroredName = name[^1] switch
        {
            'L' => name[..^1] + "R",
            'R' => name[..^1] + "L",
            _ => null,
        };
        return mirroredName is not null && Enum.TryParse<BoneRole>(mirroredName, out var mirrored)
            ? mirrored
            : null;
    }

    /// <summary>Swaps <c>L</c>/<c>R</c> underscore-delimited name tokens
    /// (<c>foot_L_IK_target</c> → <c>foot_R_IK_target</c>); null when the name carries no
    /// side token.</summary>
    private static string? SwapSideTokens(string name)
    {
        var tokens = name.Split('_');
        for (var i = 0; i < tokens.Length; i++)
        {
            tokens[i] = tokens[i] switch
            {
                "L" => "R",
                "R" => "L",
                "l" => "r",
                "r" => "l",
                _ => tokens[i],
            };
        }
        var result = string.Join('_', tokens);
        return string.Equals(result, name, StringComparison.Ordinal) ? null : result;
    }
}
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Solve/FingerSolver.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using HumanoidRetargeter.Mapping;
using HumanoidRetargeter.Maths;

namespace HumanoidRetargeter.Solve;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>
/// Finger retargeting. Picks one of three strategies per finger chain:
/// <list type="number">
/// <item><b>1:1 absolute copy</b> (via the <c>transferOneToOne</c> callback into
/// <see cref="GeometricSolver"/>'s body path) when the source and target chains are
/// <i>geometrically identical</i> — same mapped role set, same canonical frames, same
/// normalized rest rotations. This is the same-rig round-trip case and is lossless (exact
/// identity, twist included).</item>
/// <item><b>Direction matching</b> when the phalanx counts match ordinally but the rigs
/// differ (the common cross-rig case, e.g. Mixamo Prox/Mid/Dist onto the s&amp;box finger
/// with its extra metacarpal — which keeps its rest local; a source metacarpal's rotation is
/// implicit in the proximal's absolute direction). Each target phalanx is swung — shortest
/// arc, rotation axis ⊥ the finger axis, hence <b>zero twist by construction</b> — so that its
/// segment direction matches the source phalanx's direction in character-frame coordinates
/// exactly. Curl and splay are both captured by the direction; the source's axial twist is
/// dropped (hinge-joint noise; copying it absolutely would read as roll through the
/// inter-phalanx canonical mismatch between rigs, measured up to ~12° on thumbs).</item>
/// <item><b>Proportional redistribution</b> when phalanx counts differ (e.g. a two-phalanx
/// source finger): per-phalanx local curls — swing-twist about the canonical hinge Y of
/// <c>λ_b = C_b⁻¹·(ΔR_prev⁻¹·ΔR_b)·C_b</c> — are summed over the source chain (metacarpal
/// included) and redistributed over the target phalanges proportional to rest segment
/// lengths; splay (metacarpal + proximal, swing-twist about canonical Z) goes 100% to the
/// target proximal; the X-twist residual is dropped.</item>
/// </list>
/// In every mode target world deltas rebuild hierarchically from the solved target hand:
/// <c>ΔR_i = ΔR_{i-1} · (C_i · λ_i · C_i⁻¹)</c>, then <c>W_i = ΔR_i · R_tgtNormRest,i</c>.
/// Instances are per-solve and not thread-safe.
/// </summary>
internal sealed class FingerSolver
{
    /// <summary>Two canonical frames / rest rotations within this angle count as identical
    /// (same-rig detection for the lossless 1:1 path); cross-rig differences are degrees.</summary>
    private const float SameRigToleranceRad = 1e-3f;

    private enum ChainMode
    {
        DirectionMatch,
        Proportional,
    }

    private readonly struct SourcePhalanx
    {
        public required int Slot { get; init; }
        public required Quaternion C { get; init; }
        public required Quaternion CInv { get; init; }
        public required bool TakesSplay { get; init; }
    }

    private readonly struct Recipient
    {
        public required int TgtBone { get; init; }
        public required Quaternion C { get; init; }
        public required Quaternion CInv { get; init; }
        public required Quaternion RestRot { get; init; }
        public required float Weight { get; init; }
        public required bool Splay { get; init; }
    }

    private sealed class Chain
    {
        public required ChainMode Mode { get; init; }
        public required int SrcHandSlot { get; init; }
        public required int TgtHandBone { get; init; }
        public required Quaternion TgtHandNormRestRotInv { get; init; }
        public required SourcePhalanx[] Sources { get; init; }
        public required Recipient[] Recipients { get; init; }
    }

    private readonly List<Chain> _chains;
    private readonly Quaternion _chrSrcInv;
    private readonly Quaternion _chrTgt;

    private FingerSolver(List<Chain> chains, Quaternion chrSrcInv, Quaternion chrTgt)
    {
        _chains = chains;
        _chrSrcInv = chrSrcInv;
        _chrTgt = chrTgt;
    }

    // ---------------------------------------------------------------- role tables

    private static readonly BoneRole[][] ChainRoles = BuildChainRoles();
    private static readonly HashSet<BoneRole> FingerRoleSet = ChainRoles.SelectMany(c => c.Skip(1)).ToHashSet();

    private static BoneRole[][] BuildChainRoles()
    {
        var chains = new List<BoneRole[]>();
        foreach (var side in new[] { "L", "R" })
        {
            foreach (var finger in new[] { "Thumb", "Index", "Middle", "Ring", "Pinky" })
            {
                // Element 0 is the hand the chain hangs off; 1.. are Meta/Prox/Mid/Dist.
                chains.Add(new[]
                {
                    Enum.Parse<BoneRole>("Hand" + side),
                    Enum.Parse<BoneRole>(finger + "Meta" + side),
                    Enum.Parse<BoneRole>(finger + "Prox" + side),
                    Enum.Parse<BoneRole>(finger + "Mid" + side),
                    Enum.Parse<BoneRole>(finger + "Dist" + side),
                });
            }
        }
        return chains.ToArray();
    }

    /// <summary>True for the 40 per-finger segment roles (Meta/Prox/Mid/Dist × finger × side).</summary>
    public static bool IsFingerRole(BoneRole role) => FingerRoleSet.Contains(role);

    // ---------------------------------------------------------------- build

    /// <summary>
    /// Builds the per-chain plans. Geometrically identical chains are reported through
    /// <paramref name="transferOneToOne"/> instead of being planned here. Returns null when
    /// every mapped chain took that path (or none is mapped).
    /// </summary>
    public static FingerSolver? Build(
        MappingResult sourceMap,
        CanonicalFrames srcCanon,
        IReadOnlyList<XForm> srcNormRest,
        Func<BoneRole, int?> tgtBoneForRole,
        CanonicalFrames tgtCanon,
        IReadOnlyList<XForm> tgtNormRest,
        Quaternion chrSrcInv,
        Quaternion chrTgt,
        Func<int, int> registerSlot,
        Action<BoneRole> transferOneToOne)
    {
        var chains = new List<Chain>();
        foreach (var chainRoles in ChainRoles)
        {
            var handRole = chainRoles[0];
            var metaRole = chainRoles[1];
            var proxRole = chainRoles[2];
            var segments = chainRoles.Skip(1).ToArray();

            var srcRoles = segments
                .Where(r => sourceMap.RoleToBone.ContainsKey(r) && srcCanon.Has(r))
                .ToArray();
            var tgtRoles = segments
                .Where(r => tgtBoneForRole(r) is not null && tgtCanon.Has(r))
                .ToArray();
            if (srcRoles.Length == 0 || tgtRoles.Length == 0)
                continue;

            if (srcRoles.SequenceEqual(tgtRoles) && ChainsCoincide(
                srcRoles, sourceMap, srcCanon, srcNormRest, tgtBoneForRole, tgtCanon, tgtNormRest))
            {
                foreach (var role in srcRoles)
                    transferOneToOne(role);
                continue;
            }

            var srcPhalanges = srcRoles.Where(r => r != metaRole).ToArray();
            var tgtPhalanges = tgtRoles.Where(r => r != metaRole).ToArray();
            var recipientRoles = tgtPhalanges.Length > 0 ? tgtPhalanges : tgtRoles;
            var mode = srcPhalanges.Length == recipientRoles.Length && srcPhalanges.Length > 0
                ? ChainMode.DirectionMatch
                : ChainMode.Proportional;

            // Direction matching consumes only the non-meta phalanges (the metacarpal's
            // motion is implicit in the proximal's absolute direction); redistribution
            // decomposes every mapped source segment including the metacarpal.
            var sourceRolesUsed = mode == ChainMode.DirectionMatch ? srcPhalanges : srcRoles;
            var sources = sourceRolesUsed.Select(r =>
            {
                var c = srcCanon.WorldFrameOf(r);
                return new SourcePhalanx
                {
                    Slot = registerSlot(sourceMap.RoleToBone[r]),
                    C = c,
                    CInv = Quaternion.Conjugate(c),
                    TakesSplay = r == metaRole || r == proxRole,
                };
            }).ToArray();

            var weights = SegmentWeights(tgtRoles, recipientRoles, tgtBoneForRole, tgtNormRest);
            var recipients = recipientRoles.Select((r, i) =>
            {
                var bone = tgtBoneForRole(r)!.Value;
                var c = tgtCanon.WorldFrameOf(r);
                return new Recipient
                {
                    TgtBone = bone,
                    C = c,
                    CInv = Quaternion.Conjugate(c),
                    RestRot = tgtNormRest[bone].Rot,
                    Weight = weights[i],
                    Splay = i == 0,
                };
            }).ToArray();

            var tgtHand = tgtBoneForRole(handRole);
            chains.Add(new Chain
            {
                Mode = mode,
                SrcHandSlot = sourceMap.RoleToBone.TryGetValue(handRole, out var srcHand)
                    ? registerSlot(srcHand)
                    : -1,
                TgtHandBone = tgtHand ?? -1,
                TgtHandNormRestRotInv = tgtHand is int h
                    ? Quaternion.Conjugate(tgtNormRest[h].Rot)
                    : Quaternion.Identity,
                Sources = sources,
                Recipients = recipients,
            });
        }

        return chains.Count > 0 ? new FingerSolver(chains, chrSrcInv, chrTgt) : null;
    }

    /// <summary>Same-rig detection: every chain member's canonical frame and normalized rest
    /// rotation agree between source and target (within float noise). Only then is the 1:1
    /// absolute copy lossless.</summary>
    private static bool ChainsCoincide(
        BoneRole[] roles, MappingResult sourceMap, CanonicalFrames srcCanon,
        IReadOnlyList<XForm> srcNormRest, Func<BoneRole, int?> tgtBoneForRole,
        CanonicalFrames tgtCanon, IReadOnlyList<XForm> tgtNormRest)
    {
        foreach (var role in roles)
        {
            var srcBone = sourceMap.RoleToBone[role];
            var tgtBone = tgtBoneForRole(role)!.Value;
            if (MathQ.AngleBetween(srcCanon.WorldFrameOf(role), tgtCanon.WorldFrameOf(role)) > SameRigToleranceRad
                || MathQ.AngleBetween(srcNormRest[srcBone].Rot, tgtNormRest[tgtBone].Rot) > SameRigToleranceRad)
            {
                return false;
            }
        }
        return true;
    }

    /// <summary>Normalized rest segment lengths of the recipient phalanges (the proportional
    /// curl weights). The distal segment, having no chain child, is estimated as 0.8× its
    /// preceding segment.</summary>
    private static float[] SegmentWeights(
        BoneRole[] tgtRoles, BoneRole[] recipientRoles,
        Func<BoneRole, int?> tgtBoneForRole, IReadOnlyList<XForm> tgtNormRest)
    {
        var positions = tgtRoles.Select(r => tgtNormRest[tgtBoneForRole(r)!.Value].Pos).ToArray();
        var weights = new float[recipientRoles.Length];
        for (var i = 0; i < recipientRoles.Length; i++)
        {
            var j = Array.IndexOf(tgtRoles, recipientRoles[i]);
            weights[i] = j + 1 < positions.Length
                ? (positions[j + 1] - positions[j]).Length()
                : j > 0 ? 0.8f * (positions[j] - positions[j - 1]).Length() : 1f;
        }

        var sum = weights.Sum();
        if (sum <= 1e-6f)
            return Enumerable.Repeat(1f / weights.Length, weights.Length).ToArray();
        for (var i = 0; i < weights.Length; i++)
            weights[i] /= sum;
        return weights;
    }

    // ---------------------------------------------------------------- per frame

    /// <summary>
    /// Solves the planned chains for one frame. <paramref name="srcDeltas"/> holds the
    /// registered source world rotation deltas (from normalized rest); solved target world
    /// rotations are written into <paramref name="rot"/>/<paramref name="solved"/>. The target
    /// hands must already be solved (body pass runs first).
    /// </summary>
    public void Apply(Quaternion[] srcDeltas, bool[] solved, Quaternion[] rot)
    {
        foreach (var chain in _chains)
        {
            var acc = chain.TgtHandBone >= 0 && solved[chain.TgtHandBone]
                ? MathQ.Normalize(rot[chain.TgtHandBone] * chain.TgtHandNormRestRotInv)
                : Quaternion.Identity;

            if (chain.Mode == ChainMode.DirectionMatch)
                ApplyDirectionMatch(chain, srcDeltas, acc, solved, rot);
            else
                ApplyProportional(chain, srcDeltas, acc, solved, rot);
        }
    }

    private void ApplyDirectionMatch(
        Chain chain, Quaternion[] srcDeltas, Quaternion acc, bool[] solved, Quaternion[] rot)
    {
        for (var i = 0; i < chain.Recipients.Length; i++)
        {
            var sp = chain.Sources[i];
            var rc = chain.Recipients[i];

            // Source phalanx direction in character coords; re-expressed in the target world,
            // then relative to the already-reconstructed parent delta, then in the phalanx's
            // canonical frame — where the rest direction is unit X.
            var srcAbs = MathQ.Normalize(_chrSrcInv * srcDeltas[sp.Slot] * sp.C);
            var dirChr = Vector3.Transform(Vector3.UnitX, srcAbs);
            var dirTgtWorld = Vector3.Transform(dirChr, _chrTgt);
            var dirLocal = Vector3.Transform(dirTgtWorld, Quaternion.Conjugate(acc));
            var dirCanon = Vector3.Transform(dirLocal, rc.CInv);

            // Shortest-arc swing X -> dir: rotation axis ⊥ X, so it carries zero finger-axis
            // twist by construction.
            var swing = MathQ.FromTo(Vector3.UnitX, dirCanon);

            acc = MathQ.Normalize(acc * (rc.C * swing * rc.CInv));
            rot[rc.TgtBone] = MathQ.Normalize(acc * rc.RestRot);
            solved[rc.TgtBone] = true;
        }
    }

    private static void ApplyProportional(
        Chain chain, Quaternion[] srcDeltas, Quaternion acc, bool[] solved, Quaternion[] rot)
    {
        // Decompose: total local curl over the chain, splay from metacarpal + proximal.
        var prev = chain.SrcHandSlot >= 0 ? srcDeltas[chain.SrcHandSlot] : Quaternion.Identity;
        float totalCurl = 0f, splay = 0f;
        foreach (var sp in chain.Sources)
        {
            var dr = srcDeltas[sp.Slot];
            var local = MathQ.Normalize(Quaternion.Conjugate(prev) * dr);
            var canon = MathQ.Normalize(sp.CInv * local * sp.C);

            MathQ.SwingTwist(canon, Vector3.UnitY, out var swing, out var curlQ);
            totalCurl += SignedAngle(curlQ, Vector3.UnitY);

            if (sp.TakesSplay)
            {
                MathQ.SwingTwist(swing, Vector3.UnitZ, out _, out var splayQ);
                splay += SignedAngle(splayQ, Vector3.UnitZ);
            }

            prev = dr;
        }

        foreach (var rc in chain.Recipients)
        {
            var mu = Quaternion.CreateFromAxisAngle(Vector3.UnitY, totalCurl * rc.Weight);
            if (rc.Splay)
                mu = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, splay) * mu;

            acc = MathQ.Normalize(acc * (rc.C * mu * rc.CInv));
            rot[rc.TgtBone] = MathQ.Normalize(acc * rc.RestRot);
            solved[rc.TgtBone] = true;
        }
    }

    /// <summary>Signed rotation angle of an axis-aligned twist quaternion about
    /// <paramref name="axis"/>, wrapped to (−π, π].</summary>
    private static float SignedAngle(Quaternion twist, Vector3 axis)
    {
        var s = twist.X * axis.X + twist.Y * axis.Y + twist.Z * axis.Z;
        var angle = 2f * MathF.Atan2(s, twist.W);
        if (angle > MathF.PI)
            angle -= 2f * MathF.PI;
        else if (angle < -MathF.PI)
            angle += 2f * MathF.PI;
        return angle;
    }
}
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Solve/SolveOptions.cs
Game library
#nullable enable annotations

using System.Collections.Generic;
using HumanoidRetargeter.Mapping;

namespace HumanoidRetargeter.Solve;

/// <summary>How a mapped role's rotation is transferred by the <see cref="GeometricSolver"/>.</summary>
public enum RoleTransferMode
{
    /// <summary>
    /// Absolute canonical-orientation matching: the target's animated chain direction is
    /// driven to <b>equal</b> the source's (in character-frame coordinates). Right for limbs
    /// and the spine — the pose IS the direction — but it also imposes the source rig's rest
    /// proportions/posture on roles whose rest directions legitimately differ between rigs.
    /// </summary>
    AbsoluteDirection,

    /// <summary>
    /// Rest-relative delta: the source's canonical-space rotation <i>delta from its own
    /// normalized rest</i> is replayed onto the <b>target's</b> normalized rest
    /// (<c>W_t(f) = C_t·ΔC(f)·C_t⁻¹·R_tgtNormRest</c> with
    /// <c>ΔC(f) = C_s⁻¹·ΔR(f)·C_s</c>). The target keeps its own rest carriage (shoulder
    /// line height, neck-base angle) and moves with the source. Identical to
    /// <see cref="AbsoluteDirection"/> when source and target rigs coincide.
    /// </summary>
    DeltaFromRest,

    /// <summary>
    /// Character-space delta: the source's world-rotation delta from its normalized rest is
    /// re-expressed in character coordinates and applied to the <b>target's</b> normalized
    /// rest (<c>W_t(f) = M·ΔR(f)·M⁻¹·R_tgtNormRest</c> with <c>M = Q_tgt·Q_src⁻¹</c>, the
    /// same character basis change <see cref="AbsoluteDirection"/> premultiplies). Like
    /// <see cref="DeltaFromRest"/> the target keeps its own rest carriage, but the delta
    /// keeps its <i>world</i> rotation axes instead of being remapped through the per-role
    /// canonical frames — the faithful replay when the rigs' rest chain directions diverge
    /// so far that canonical-axis remapping would tilt every rotation axis by that
    /// divergence (measured 23–44° on feet: CMU/ARP ankle anatomy vs the s&amp;box rig's
    /// steep ankle, where canonical remapping mis-pitched planted feet by up to 47°).
    /// Identical to the other modes when source and target rigs coincide.
    /// </summary>
    CharacterDeltaFromRest,
}

/// <summary>Options controlling a single retarget solve (one clip → one output clip).</summary>
public sealed class SolveOptions
{
    /// <summary>
    /// Default per-role transfer modes: shoulder girdle and neck carriage are
    /// <see cref="RoleTransferMode.DeltaFromRest"/> (each rig's clavicle line / neck-base
    /// direction is rig anatomy, not pose — absolute matching was measured to drag the
    /// s&amp;box shoulders 6–28° toward the source's flatter/lower clavicle line and is the
    /// "low shoulders, hunched neck" artifact), and feet are
    /// <see cref="RoleTransferMode.CharacterDeltaFromRest"/> (a rest foot→toe direction is
    /// ankle anatomy too — rigs diverge 11–44° from the s&amp;box rig's steep ankle, so
    /// absolute matching pitched planted feet up to 25° off flat, the "feet bent
    /// upward/inward" artifact; the character-space delta keeps the rotation's world axes,
    /// which canonical-frame remapping would tilt by that same divergence). The head is
    /// <see cref="RoleTransferMode.CharacterDeltaFromRest"/> for the same reason: the rest
    /// neck→head direction is head-joint-placement anatomy (measured 0–27° forward lean
    /// across neutral-rest rigs vs the s&amp;box rig's 25.5°), so the target keeps its own
    /// neutral skull attitude and replays the source's attitude <i>changes</i> — for the
    /// head this computes exactly what the previous virtual-frame absolute matching did.
    /// Two solver fallbacks adjust these defaults per rig pair: on a toe-less source the
    /// foot entries become <see cref="RoleTransferMode.DeltaFromRest"/> (virtual-foot
    /// fallback), and a source whose normalized rest head attitude is implausible as a
    /// neutral carriage (a posed bind — e.g. a chin-down/tilted fighting-stance rest,
    /// measured 40.7° forward / 16.9° lateral on such a rig where the delta replay read
    /// ~12° "looking up at an angle") switches the head to
    /// <see cref="RoleTransferMode.AbsoluteDirection"/> so the gaze follows the source
    /// absolutely instead of replaying deltas from a posed reference (see the
    /// <see cref="GeometricSolver"/> remarks for both). Everything else (limbs, spine,
    /// toes, fingers) stays absolute: there the worldspace direction IS the pose.
    /// </summary>
    public static IReadOnlyDictionary<BoneRole, RoleTransferMode> DefaultTransferModes { get; } =
        new Dictionary<BoneRole, RoleTransferMode>
        {
            [BoneRole.ClavicleL] = RoleTransferMode.DeltaFromRest,
            [BoneRole.ClavicleR] = RoleTransferMode.DeltaFromRest,
            [BoneRole.Neck] = RoleTransferMode.DeltaFromRest,
            [BoneRole.Head] = RoleTransferMode.CharacterDeltaFromRest,
            [BoneRole.FootL] = RoleTransferMode.CharacterDeltaFromRest,
            [BoneRole.FootR] = RoleTransferMode.CharacterDeltaFromRest,
        };

    /// <summary>
    /// Per-role transfer modes. Null (default) = <see cref="DefaultTransferModes"/> plus the
    /// solver's fallback heuristics (a toe-less source's virtual foot direction overrides
    /// the foot default to <see cref="RoleTransferMode.DeltaFromRest"/>, and a posed-rest
    /// source head overrides the head default to
    /// <see cref="RoleTransferMode.AbsoluteDirection"/> — see the
    /// <see cref="GeometricSolver"/> remarks). A non-null map REPLACES the defaults entirely
    /// and disables every fallback heuristic: each role uses exactly the mode in the map, and
    /// roles absent from it are <see cref="RoleTransferMode.AbsoluteDirection"/>. Pass an
    /// empty dictionary for fully absolute (legacy) behavior — API callers supplying a map
    /// opt out of all heuristics.
    /// </summary>
    public IReadOnlyDictionary<BoneRole, RoleTransferMode>? TransferModes { get; init; }

    /// <summary>
    /// Scale applied to the pelvis translation components perpendicular to the character up
    /// direction. Null (default) = automatic: target hip height / source hip height, both
    /// measured on the normalized rests.
    /// </summary>
    public float? HipScaleHorizontal { get; init; }

    /// <summary>
    /// Scale applied to the pelvis translation component along the character up direction.
    /// Null (default) = the same automatic hip-height ratio as <see cref="HipScaleHorizontal"/>.
    /// </summary>
    public float? HipScaleVertical { get; init; }

    /// <summary>Whether finger roles are transferred; when false, target finger bones keep
    /// their rest locals.</summary>
    public bool TransferFingers { get; init; } = true;

    /// <summary>Output clip name; null = the source clip's name.</summary>
    public string? ClipName { get; init; }

    /// <summary>Index of the source clip to retarget (<c>SourceScene.Clips</c>).</summary>
    public int ClipIndex { get; init; }
}
notpointless.chomnr_humanoid_retargeter / HumanoidRetargeter/Cleanup/FootGroundAlign.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Numerics;
using HumanoidRetargeter.Maths;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;

namespace HumanoidRetargeter.Cleanup;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>Tunables for the grounded-foot stance recalibration pass.</summary>
public sealed class FootGroundAlignOptions
{
    /// <summary>
    /// Dead zone (degrees): measured stance offsets at or below this are genuine planted
    /// articulation (heel-roll bias, natural lean — measured 2–4° on well-rested rigs and
    /// on citizen clips) and are left untouched, keeping the transfer byte-faithful there.
    /// Only offsets beyond it are clearly rest-pose artifacts (measured 12–25° on the
    /// repro rig) and get recalibrated.
    /// </summary>
    public float MinCorrectionDeg { get; set; } = 8f;

    /// <summary>
    /// Maximum mean sole deviation (degrees) a plant may show and still count as a STANCE
    /// for the offset measurement. Plants beyond this are not standing on the sole (crawls,
    /// kneels, prone contact — measured 60–90° there) and are excluded; genuine rest-pose
    /// stance artifacts measure well below it (largest seen: 27°).
    /// </summary>
    public float MaxStanceDeviationDeg { get; set; } = 35f;
}

/// <summary>Per-foot results of a <see cref="FootGroundAlign.Apply"/> run.</summary>
public sealed class FootGroundAlignFootReport
{
    /// <summary>Plants that contributed to the stance measurement.</summary>
    public int StancePlants { get; set; }

    /// <summary>Plants excluded as non-stance (mean sole deviation beyond
    /// <see cref="FootGroundAlignOptions.MaxStanceDeviationDeg"/>).</summary>
    public int SkippedPlants { get; set; }

    /// <summary>Measured planted sole offset from the ground plane, degrees (0 when no
    /// stance plants exist).</summary>
    public float MeasuredOffsetDeg { get; set; }

    /// <summary>Foot correction applied to every frame, degrees (0 = inside the dead zone,
    /// nothing changed).</summary>
    public float AppliedFootDeg { get; set; }

    /// <summary>Toe correction applied to every frame, degrees.</summary>
    public float AppliedToeDeg { get; set; }
}

/// <summary>Results of a <see cref="FootGroundAlign.Apply"/> run.</summary>
public sealed class FootGroundAlignReport
{
    /// <summary>Left-foot results.</summary>
    public required FootGroundAlignFootReport Left { get; init; }

    /// <summary>Right-foot results.</summary>
    public required FootGroundAlignFootReport Right { get; init; }
}

/// <summary>
/// Grounded-foot stance recalibration: measures how far the foot's SOLE sits from the ground
/// plane while planted, and — when that offset is clearly a rest-pose artifact — rotates it
/// out with one constant per foot, applied to every frame of the clip.
/// </summary>
/// <remarks>
/// <para><b>Why a cleanup pass.</b> The solver transfers feet as rest-relative deltas
/// (<see cref="Solve.RoleTransferMode.CharacterDeltaFromRest"/>), so the target keeps its own
/// ankle anatomy — correct whenever the source's rest pose is a flat-footed stance (the delta
/// is then "deviation from standing"). Some rigs ship a NON-stance rest (measured: an
/// Auto-Rig-Pro export whose rest foot sits 12–25° from its planted stance), and that constant
/// offset rides into every frame of the replay — planted feet hover toe-down/heel-up. What a
/// stance actually looks like is animation evidence (planted phases), which a per-frame
/// solver cannot see, so the recalibration lives here.</para>
/// <para><b>Measurement.</b> Per foot: over every planted frame, the sole normal = rest up
/// carried by the foot's world delta from the target bind rest (whose feet stand on the
/// ground by construction); plants whose own mean normal sits beyond
/// <see cref="FootGroundAlignOptions.MaxStanceDeviationDeg"/> are excluded (crawl/kneel/prone
/// contact is not a stance). The pooled mean normal's deviation from up is the stance
/// offset.</para>
/// <para><b>Correction.</b> Offsets inside <see cref="FootGroundAlignOptions.MinCorrectionDeg"/>
/// are genuine articulation — nothing is changed (well-rested rigs and same-rig round trips
/// stay byte-identical through this pass). Beyond it, the shortest-arc rotation taking the
/// pooled normal back to up (pitch+roll only — yaw/toe-out is pose and follows the source)
/// premultiplies the foot's world rotation on EVERY frame: a rest artifact is constant, so
/// the fix is too — within-plant heel-roll, swing styling and frame-to-frame continuity are
/// preserved exactly, and no blending is needed. The toe then receives its own residual
/// constant measured on top of the corrected foot (it neither double-rotates with the foot
/// fix nor inherits the source toe's own rest artifact). Corrections rotate bones about
/// their own joints: ankle positions are untouched, so the pass composes freely with the
/// <see cref="FootPlant"/> position pinning (which preserves foot world rotations).</para>
/// <para><b>Plant intervals come from the caller</b> (the pipeline detects them on the
/// SOURCE clip via <see cref="FootPlant.DetectPlantIntervals"/> — ground truth, immune to
/// the hip-height rescaling that can push target-side trajectories outside the cm-tuned
/// Kovar thresholds). So does the decision to run at all: the pipeline invokes this pass
/// only when the source's normalized rest is implausible as a flat stance (toe at/above
/// ankle level or asymmetric feet — see <c>Retargeter.GroundAlignFeet</c>); on plausible
/// stance rests the solver's rest-relative transfer is already faithful and planted-sole
/// deviations are genuine articulation (boxing stances, heel rolls) that must not be
/// flattened.</para>
/// </remarks>
public static class FootGroundAlign
{
    /// <summary>Measures planted stance offsets and recalibrates feet whose offset is a
    /// rest-pose artifact; returns what was measured and done.</summary>
    /// <param name="frames">Per-frame local transforms (skeleton bone order); modified in place.</param>
    /// <param name="skeleton">Bone hierarchy the frames are expressed against; its bind rest
    /// is the flat-stance reference.</param>
    /// <param name="left">Left leg chain bone indices.</param>
    /// <param name="right">Right leg chain bone indices.</param>
    /// <param name="up">World up direction of the clip's space.</param>
    /// <param name="leftPlants">Left-foot plant intervals (frame indices into
    /// <paramref name="frames"/>; out-of-range parts are clamped/ignored).</param>
    /// <param name="rightPlants">Right-foot plant intervals.</param>
    /// <param name="options">Tunables; defaults used when null.</param>
    public static FootGroundAlignReport Apply(
        List<XForm[]> frames,
        SkeletonModel skeleton,
        FootChain left,
        FootChain right,
        Vector3 up,
        IReadOnlyList<FrameRange> leftPlants,
        IReadOnlyList<FrameRange> rightPlants,
        FootGroundAlignOptions? options = null)
    {
        ArgumentNullException.ThrowIfNull(frames);
        ArgumentNullException.ThrowIfNull(skeleton);
        ArgumentNullException.ThrowIfNull(left);
        ArgumentNullException.ThrowIfNull(right);
        ArgumentNullException.ThrowIfNull(leftPlants);
        ArgumentNullException.ThrowIfNull(rightPlants);

        options ??= new FootGroundAlignOptions();
        var report = new FootGroundAlignReport
        {
            Left = new FootGroundAlignFootReport(),
            Right = new FootGroundAlignFootReport(),
        };
        if (frames.Count == 0 || up.LengthSquared() < 1e-12f)
            return report;
        up = Vector3.Normalize(up);

        RecalibrateFoot(frames, skeleton, left, up, leftPlants, options, report.Left);
        RecalibrateFoot(frames, skeleton, right, up, rightPlants, options, report.Right);
        return report;
    }

    private static void RecalibrateFoot(
        List<XForm[]> frames, SkeletonModel skeleton, FootChain chain, Vector3 up,
        IReadOnlyList<FrameRange> plants, FootGroundAlignOptions options,
        FootGroundAlignFootReport report)
    {
        int n = frames.Count;
        var foot = chain.Ankle;
        var restFootRotInv = Quaternion.Conjugate(skeleton.RestWorld[foot].Rot);
        var maxStanceCos = MathF.Cos(options.MaxStanceDeviationDeg * MathF.PI / 180f);

        // ---- measurement: pooled planted sole normal over the stance plants ----
        var pooled = Vector3.Zero;
        foreach (var plant in plants)
        {
            int start = Math.Max(plant.Start, 0);
            int end = Math.Min(plant.End, n - 1);
            if (start > end)
                continue;

            var plantSum = Vector3.Zero;
            for (int f = start; f <= end; f++)
            {
                var footRot = FkUtil.BoneWorld(frames[f], skeleton, foot).Rot;
                plantSum += Vector3.Transform(up, MathQ.Normalize(footRot * restFootRotInv));
            }
            if (plantSum.LengthSquared() < 1e-8f
                || Vector3.Dot(Vector3.Normalize(plantSum), up) < maxStanceCos)
            {
                report.SkippedPlants++; // not standing on the sole — crawl/kneel/toe contact
                continue;
            }
            report.StancePlants++;
            pooled += plantSum; // frame-count-weighted: longer stances dominate
        }
        if (pooled.LengthSquared() < 1e-8f)
            return;
        pooled = Vector3.Normalize(pooled);

        var offsetDeg = MathQ.AngleBetween(pooled, up) * (180f / MathF.PI);
        report.MeasuredOffsetDeg = offsetDeg;
        if (offsetDeg <= options.MinCorrectionDeg)
            return; // genuine planted articulation — leave the transfer byte-faithful

        // ---- correction: one constant per foot, every frame ----
        var footFix = MathQ.FromTo(pooled, up);
        report.AppliedFootDeg = offsetDeg;

        // Toe residual measured on top of the corrected foot, same dead zone.
        var toeFix = Quaternion.Identity;
        if (chain.Toe is { } toe && skeleton[toe].ParentIndex == foot)
        {
            var restToeRotInv = Quaternion.Conjugate(skeleton.RestWorld[toe].Rot);
            var toePooled = Vector3.Zero;
            foreach (var plant in plants)
            {
                int start = Math.Max(plant.Start, 0);
                int end = Math.Min(plant.End, n - 1);
                for (int f = start; f <= end && f >= 0; f++)
                {
                    var toeRot = FkUtil.BoneWorld(frames[f], skeleton, toe).Rot;
                    toePooled += Vector3.Transform(
                        up, MathQ.Normalize(footFix * toeRot * restToeRotInv));
                }
            }
            if (toePooled.LengthSquared() > 1e-8f)
            {
                toePooled = Vector3.Normalize(toePooled);
                var toeDeg = MathQ.AngleBetween(toePooled, up) * (180f / MathF.PI);
                if (toeDeg > options.MinCorrectionDeg && Vector3.Dot(toePooled, up) >= maxStanceCos)
                {
                    toeFix = MathQ.FromTo(toePooled, up);
                    report.AppliedToeDeg = toeDeg;
                }
            }
        }

        for (int f = 0; f < n; f++)
            CorrectFrame(frames[f], skeleton, chain, footFix, toeFix);
    }

    /// <summary>Premultiplies the foot's world rotation by the constant fix (the joint
    /// position is untouched — the rotation pivots the foot about its own head), then gives
    /// the toe its own residual on top of the corrected foot.</summary>
    private static void CorrectFrame(
        XForm[] locals, SkeletonModel skeleton, FootChain chain,
        Quaternion footFix, Quaternion toeFix)
    {
        var foot = chain.Ankle;
        var parent = skeleton[foot].ParentIndex;
        var parentRot = parent < 0
            ? Quaternion.Identity
            : FkUtil.BoneWorld(locals, skeleton, parent).Rot;

        var footWorld = MathQ.Normalize(parentRot * locals[foot].Rot);
        var newFootWorld = MathQ.Normalize(footFix * footWorld);
        locals[foot] = new XForm(
            locals[foot].Pos, MathQ.Normalize(Quaternion.Conjugate(parentRot) * newFootWorld));

        if (chain.Toe is { } toe && skeleton[toe].ParentIndex == foot)
        {
            // Desired toe world = toeFix ∘ footFix ∘ original world; re-derive its local
            // against the corrected foot so it does not double-rotate with the foot fix.
            var toeWorldOld = MathQ.Normalize(footWorld * locals[toe].Rot);
            var desired = MathQ.Normalize(toeFix * footFix * toeWorldOld);
            locals[toe] = new XForm(
                locals[toe].Pos, MathQ.Normalize(Quaternion.Conjugate(newFootWorld) * desired));
        }
    }
}
notpointless.chomnr_humanoid_retargeter / HumanoidRetargeter/Formats/Bvh/BvhImporter.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Numerics;
using System.Text;
using HumanoidRetargeter.Maths;
using HumanoidRetargeter.Skeleton;

namespace HumanoidRetargeter.Formats.Bvh;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>Options for <see cref="BvhImporter.Import"/>.</summary>
public sealed class BvhImportOptions
{
    /// <summary>Fixed resampling rate for the motion data, frames per second.</summary>
    public float SampleFps { get; init; } = 30f;
}

/// <summary>
/// BVH (Biovision Hierarchy) → <see cref="SourceScene"/> importer.
/// </summary>
/// <remarks>
/// <para><b>Format conventions implemented</b> (verified against Blender's
/// <c>io_anim_bvh</c> importer, which is the project's ground-truth extractor):</para>
/// <list type="bullet">
/// <item><b>Rest pose:</b> each joint's rest local translation is its <c>OFFSET</c>; rest
/// rotation is identity (BVH stores no rest orientation).</item>
/// <item><b>Rotation channels:</b> the channel list order IS the rotation order. The listed
/// rotations apply left-to-right as intrinsic rotations, which in this library's
/// column-vector convention (<c>a * b</c> applies <c>b</c> first) is the product
/// <c>R = R_chan1 * R_chan2 * R_chan3</c> — e.g. <c>Zrotation Yrotation Xrotation</c> gives
/// <c>R = Rz * Ry * Rx</c>. This matches Blender, which builds
/// <c>Euler((x,y,z), reversed(channelOrder))</c> for the same matrix. Angles are degrees.</item>
/// <item><b>Position channels:</b> when a joint has any position channel, the channel values
/// REPLACE the joint's local translation (missing components are 0) — they are not added to
/// the <c>OFFSET</c>. This is Blender's behavior; in practice roots have OFFSET 0 so the two
/// readings only diverge on non-root position channels (e.g. Bandai-Namco exports).</item>
/// <item><b>End Sites:</b> synthesized as a channel-less leaf bone named
/// <c>"&lt;parent&gt;_end"</c> so chain tips keep their direction information (Blender instead
/// folds them into the parent bone's tail).</item>
/// </list>
/// <para><b>Units</b>: BVH files carry no unit declaration. Heuristic: compute the rest
/// skeleton height (max−min world Y over all joints); if it is &lt; 10 the file is assumed
/// to be in meters and all translations (offsets AND position channels, root included) are
/// scaled ×100 to centimeters, otherwise it is assumed to already be centimeters (×1).
/// Millimeter-scale files (height &gt; 400) are not special-cased — they are rare and
/// ambiguous against cm mocap of long ranges; <see cref="SourceScene.UnitScaleCm"/> records
/// whichever factor was applied for diagnostics.</para>
/// <para><b>Calibration rest-frame trim</b>: mocap exports (CMU asf/amc conversions among
/// them) often prepend a skeleton-calibration segment — the rest pose itself (all rotation
/// channels ≈ 0), hard-cut (or blend-ramped over 2–3 frames) into the real motion. Played
/// back it reads as a T-pose flash at t = 0. The importer drops such a segment from either
/// clip end when ALL of (measured margins in parentheses, over the corpus + repro files):
/// every segment frame is rest-like (max joint rotation vs the identity rest ≤ 40°;
/// calibration frames/ramps measure ≤ 24°, real clip edges ≥ 80°); the segment is short
/// (≤ 4 rest-like frames — hard cuts measure 1, blend ramps 2; longer rest-like leads are
/// content); the clip beyond it is NOT rest-like; and the discontinuity where the segment
/// exits into the motion is both large in absolute terms (≥ 20°; measured 25–177°) and
/// large versus the clip's own typical inter-frame delta (≥ 4× the median; real clip edges
/// measure ≤ 8° at ≤ ~1× the median). A qualifying segment that exits through a multi-frame
/// blend RAMP (measured 22–25°/frame for 3 frames on a makehuman-retarget export) has the
/// ramp trimmed too, until the motion settles — 8 frames total per end at most. A clip that
/// legitimately starts near rest (an idle) is continuous into the motion and never trips
/// the discontinuity gates. Note the trim can never remove
/// the reference frame a non-anatomical stick bind needs for its rest rebuild (see
/// <c>RestNormalizer</c>): it only removes frames that MATCH the identity-rotation bind,
/// and a frame matching a stick bind carries no rest information the bind itself lacks —
/// the next (real) frame is then strictly the better reference.</para>
/// <para><b>Resampling</b>: motion frames are resampled from the file's <c>Frame Time</c>
/// grid onto <see cref="BvhImportOptions.SampleFps"/>. Each native frame's euler channels are
/// converted to a quaternion FIRST and bracketing frames are then slerped (positions lerped).
/// Interpolating raw euler angles across frames would mostly work at mocap densities
/// (30–120 fps, small per-frame deltas) but breaks down when an angle wraps ±180° between
/// frames; per-frame quaternion + slerp has no such failure mode, so that is what we do.</para>
/// <para><b>Axes</b>: BVH is conventionally Y-up / Z-forward / X-right. Native axes are
/// preserved (no conversion), matching the FBX importer's policy; the conventional axes are
/// recorded on the <see cref="SourceScene"/> (up = Y, front = Z, coord = X).</para>
/// </remarks>
public static class BvhImporter
{
    private const float MeterHeightThreshold = 10f;

    /// <summary>Parses BVH bytes and builds the source scene.</summary>
    /// <exception cref="FormatException">Malformed or truncated BVH.</exception>
    public static SourceScene Import(byte[] data, BvhImportOptions? options = null)
    {
        ArgumentNullException.ThrowIfNull(data);
        options ??= new BvhImportOptions();
        if (!(options.SampleFps > 0f) || !float.IsFinite(options.SampleFps))
            throw new ArgumentOutOfRangeException(nameof(options), "SampleFps must be positive.");

        var cursor = new TokenCursor(Encoding.UTF8.GetString(data));

        // ---- HIERARCHY -----------------------------------------------------------------
        cursor.ExpectKeyword("HIERARCHY");
        var joints = new List<Joint>();
        int channelCount = 0;
        if (!cursor.PeekIs("ROOT"))
            throw new FormatException("BVH: expected ROOT after HIERARCHY.");
        while (cursor.PeekIs("ROOT")) // multiple roots are out of spec but harmless to accept
        {
            cursor.Next();
            ParseJoint(cursor, joints, parent: -1, ref channelCount);
        }

        // ---- MOTION ---------------------------------------------------------------------
        cursor.ExpectKeyword("MOTION");
        cursor.ExpectKeyword("FRAMES:");
        int frameCount = cursor.NextInt();
        if (frameCount < 0)
            throw new FormatException($"BVH: negative frame count {frameCount}.");
        cursor.ExpectKeyword("FRAME");
        cursor.ExpectKeyword("TIME:");
        float frameTime = cursor.NextFloat();
        if (!(frameTime > 0f) || !float.IsFinite(frameTime))
            throw new FormatException($"BVH: invalid Frame Time {frameTime}.");

        var motion = new float[frameCount][];
        for (int f = 0; f < frameCount; f++)
        {
            var row = new float[channelCount];
            for (int c = 0; c < channelCount; c++)
                row[c] = cursor.NextFloat();
            motion[f] = row;
        }

        // ---- units heuristic --------------------------------------------------------------
        float unitScale = HeuristicUnitScale(joints);

        // ---- skeleton ----------------------------------------------------------------------
        var defs = new List<BoneDefinition>(joints.Count);
        foreach (var j in joints)
        {
            defs.Add(new BoneDefinition(
                j.Name,
                j.Parent < 0 ? null : joints[j.Parent].Name,
                new XForm(j.Offset * unitScale, Quaternion.Identity)));
        }
        var skeleton = Skeleton.Skeleton.Create(defs);

        // ---- clip ----------------------------------------------------------------------------
        var clips = new List<Clip>();
        if (frameCount > 0)
            clips.Add(ResampleClip(joints, skeleton, motion, frameTime, unitScale, options.SampleFps));

        // BVH conventional axes: Y-up (1), Z-front (2), X-coord (0) — recorded, not converted.
        // RestPlacementAuthored = false: the BVH rest skeleton is OFFSETs only (root at the
        // file origin, no authored world placement), while MOTION root positions live in
        // absolute capture-volume coordinates — the two share no common ground/origin, so
        // the solver must normalize clip placement against the rest skeleton
        // (see SourceScene.RestPlacementAuthored and GeometricSolver remarks).
        return new SourceScene(
            skeleton, clips, unitScale,
            upAxis: 1, upAxisSign: 1,
            frontAxis: 2, frontAxisSign: 1,
            coordAxis: 0, coordAxisSign: 1,
            originalUpAxis: -1)
        {
            RestPlacementAuthored = false,
        };
    }

    // =====================================================================================
    // hierarchy parsing
    // =====================================================================================

    private sealed class Joint
    {
        public required string Name;
        public required int Parent;          // index into the joint list, -1 for roots
        public Vector3 Offset;               // raw file units
        public int PosX = -1, PosY = -1, PosZ = -1;            // motion column per position axis
        public List<(int Axis, int Column)> Rot = new();        // rotation channels in file order
        public bool HasPos => PosX >= 0 || PosY >= 0 || PosZ >= 0;
    }

    private static void ParseJoint(TokenCursor cursor, List<Joint> joints, int parent, ref int channelCount)
    {
        // Joint name: tokens up to '{', joined with '_' (mirrors Blender's handling of
        // names containing spaces).
        var nameParts = new List<string>();
        while (!cursor.PeekIs("{"))
        {
            if (cursor.AtEnd)
                throw new FormatException("BVH: unexpected end of file in joint name.");
            nameParts.Add(cursor.Next());
        }
        if (nameParts.Count == 0)
            throw new FormatException("BVH: joint with no name.");
        string name = UniqueName(string.Join('_', nameParts), joints);

        cursor.ExpectKeyword("{");
        cursor.ExpectKeyword("OFFSET");
        var joint = new Joint { Name = name, Parent = parent };
        joint.Offset = new Vector3(cursor.NextFloat(), cursor.NextFloat(), cursor.NextFloat());
        int index = joints.Count;
        joints.Add(joint);

        if (cursor.PeekIs("CHANNELS"))
        {
            cursor.Next();
            int n = cursor.NextInt();
            if (n < 0 || n > 6)
                throw new FormatException($"BVH: joint '{name}' has invalid channel count {n}.");
            for (int i = 0; i < n; i++)
            {
                string channel = cursor.Next();
                int column = channelCount++;
                switch (channel.ToUpperInvariant())
                {
                    case "XPOSITION": joint.PosX = column; break;
                    case "YPOSITION": joint.PosY = column; break;
                    case "ZPOSITION": joint.PosZ = column; break;
                    case "XROTATION": joint.Rot.Add((0, column)); break;
                    case "YROTATION": joint.Rot.Add((1, column)); break;
                    case "ZROTATION": joint.Rot.Add((2, column)); break;
                    default:
                        throw new FormatException($"BVH: unknown channel '{channel}' on joint '{name}'.");
                }
            }
        }

        while (!cursor.PeekIs("}"))
        {
            if (cursor.AtEnd)
                throw new FormatException($"BVH: unexpected end of file inside joint '{name}'.");
            if (cursor.PeekIs("JOINT"))
            {
                cursor.Next();
                ParseJoint(cursor, joints, index, ref channelCount);
            }
            else if (cursor.PeekIs("END"))
            {
                cursor.Next();
                cursor.ExpectKeyword("SITE");
                while (!cursor.PeekIs("{")) // a name after "End Site" is out of spec; skip it
                {
                    if (cursor.AtEnd)
                        throw new FormatException("BVH: unexpected end of file in End Site.");
                    cursor.Next();
                }
                cursor.ExpectKeyword("{");
                cursor.ExpectKeyword("OFFSET");
                var endOffset = new Vector3(cursor.NextFloat(), cursor.NextFloat(), cursor.NextFloat());
                cursor.ExpectKeyword("}");

                // Synthesize a channel-less leaf so the chain tip's direction is kept.
                joints.Add(new Joint
                {
                    Name = UniqueName(name + "_end", joints),
                    Parent = index,
                    Offset = endOffset,
                });
            }
            else
            {
                throw new FormatException(
                    $"BVH: unexpected token '{cursor.Next()}' inside joint '{name}'.");
            }
        }
        cursor.ExpectKeyword("}");
    }

    private static string UniqueName(string name, List<Joint> joints)
    {
        bool Taken(string candidate)
        {
            foreach (var j in joints)
                if (string.Equals(j.Name, candidate, StringComparison.Ordinal))
                    return true;
            return false;
        }

        if (!Taken(name))
            return name;
        for (int i = 1; ; i++)
        {
            string candidate = $"{name}#{i}";
            if (!Taken(candidate))
                return candidate;
        }
    }

    // =====================================================================================
    // units
    // =====================================================================================

    /// <summary>
    /// Meters-vs-centimeters heuristic: rest skeleton height (max−min world Y over all
    /// joints, end sites included) &lt; 10 → meters → ×100; otherwise centimeters → ×1.
    /// </summary>
    private static float HeuristicUnitScale(List<Joint> joints)
    {
        Span<float> worldY = joints.Count <= 256 ? stackalloc float[joints.Count] : new float[joints.Count];
        float min = float.MaxValue, max = float.MinValue;
        for (int i = 0; i < joints.Count; i++)
        {
            worldY[i] = (joints[i].Parent < 0 ? 0f : worldY[joints[i].Parent]) + joints[i].Offset.Y;
            min = MathF.Min(min, worldY[i]);
            max = MathF.Max(max, worldY[i]);
        }
        float height = max - min;
        return height > 0f && height < MeterHeightThreshold ? 100f : 1f;
    }

    // =====================================================================================
    // motion sampling
    // =====================================================================================

    /// <summary>
    /// Decodes every native frame to per-joint local transforms (quaternions built per frame
    /// from the joint's channel order), drops leading/trailing calibration rest frames (see
    /// class remarks), then resamples onto the <paramref name="fps"/> grid — positions
    /// lerped, rotations slerped between the bracketing native frames.
    /// </summary>
    private static Clip ResampleClip(
        List<Joint> joints, Skeleton.Skeleton skeleton, float[][] motion,
        float frameTime, float unitScale, float fps)
    {
        int jointCount = joints.Count;
        int nativeCount = motion.Length;

        // Joint order may differ from skeleton bone order (topological sort) — map.
        var toSkeleton = new int[jointCount];
        for (int i = 0; i < jointCount; i++)
            toSkeleton[i] = skeleton.IndexOf(joints[i].Name);

        // Native-frame locals.
        var native = new XForm[nativeCount][];
        for (int f = 0; f < nativeCount; f++)
        {
            var row = motion[f];
            var locals = new XForm[jointCount];
            for (int i = 0; i < jointCount; i++)
                locals[i] = EvaluateLocal(joints[i], row, unitScale);
            native[f] = locals;
        }

        // Calibration rest-frame trim: a short rest-like segment per clip end (class remarks).
        int first = 0;
        int last = nativeCount - 1;
        if (nativeCount >= 3)
        {
            float typicalDeltaDeg = TypicalNeighborRotDeltaDeg(native);
            first += CalibrationSegmentLength(native, first, last, step: +1, typicalDeltaDeg);
            last -= CalibrationSegmentLength(native, last, first, step: -1, typicalDeltaDeg);
        }
        int trimmedCount = last - first + 1;

        double duration = (trimmedCount - 1) * (double)frameTime;
        int outCount = Math.Max(1, (int)Math.Round(duration * fps) + 1);

        var frames = new List<XForm[]>(outCount);
        for (int f = 0; f < outCount; f++)
        {
            double s = f / (double)fps / frameTime; // position on the trimmed native frame grid
            int i0 = first + Math.Clamp((int)Math.Floor(s), 0, trimmedCount - 1);
            int i1 = Math.Min(i0 + 1, last);
            float u = Math.Clamp((float)(s - (i0 - first)), 0f, 1f);

            var frame = new XForm[skeleton.Count];
            var a = native[i0];
            var b = native[i1];
            for (int i = 0; i < jointCount; i++)
            {
                frame[toSkeleton[i]] = new XForm(
                    Vector3.Lerp(a[i].Pos, b[i].Pos, u),
                    MathQ.Normalize(Quaternion.Slerp(a[i].Rot, b[i].Rot, u)));
            }
            frames.Add(frame);
        }

        // NativeFps records the file's authored frame rate (1 / FrameTime): external frame
        // ranges (Unity .meta clipAnimations) are expressed in it.
        float nativeFps = frameTime > 0f ? (float)(1.0 / frameTime) : fps;
        return new Clip("motion", fps, looping: false, frames, nativeFps);
    }

    // ---------------------------------------------------------------- calibration trim

    /// <summary>A frame counts as rest-like only below this max-joint rotation angle vs the
    /// identity-rotation bind (measured: calibration frames/ramps ≤ 24°, real edges ≥ 80°).</summary>
    private const float CalibrationRestMaxDeg = 40f;

    /// <summary>Absolute floor on the discontinuity out of the rest-like segment (measured:
    /// calibration exits 25–177°, continuous real clip edges ≤ 8°).</summary>
    private const float CalibrationJumpMinDeg = 20f;

    /// <summary>The segment-exit discontinuity must also exceed this multiple of the clip's
    /// median inter-frame delta — a clip idling near rest never trips this.</summary>
    private const float CalibrationJumpTypicalRatio = 4f;

    /// <summary>Longest rest-like calibration segment trimmed per clip end. Hard cuts are
    /// 1 frame (CMU asf/amc exports); rest→motion blend ramps measure 2 rest-like frames
    /// (a makehuman-retarget export). Longer rest-like leads are content, left alone.</summary>
    private const int CalibrationMaxSegmentFrames = 4;

    /// <summary>Absolute floor on a blend-ramp frame's delta for the ramp extension
    /// (measured ramp deltas 15–25°/frame; settled motion ≤ 6°).</summary>
    private const float CalibrationRampMinDeg = 10f;

    /// <summary>Hard cap on the total trim per clip end (rest-like segment + blend ramp;
    /// measured worst case 5 frames on the makehuman-retarget export).</summary>
    private const int CalibrationMaxTrimFrames = 8;

    /// <summary>
    /// Length of the prepended (<paramref name="step"/> = +1, scanning from
    /// <paramref name="edge"/> toward <paramref name="stop"/>) or appended (−1)
    /// skeleton-calibration segment, 0 when there is none. The segment is a short run of
    /// rest-like frames (≤ <see cref="CalibrationMaxSegmentFrames"/>) that exits into
    /// NON-rest-like motion (≥ 2 frames of which must remain) through a discontinuity that
    /// is large both absolutely and against the clip's typical inter-frame delta. When the
    /// exit is a multi-frame blend RAMP rather than a hard cut (measured: 2 rest-like frames
    /// then 22–25°/frame for 3 more), the ramp frames are consumed too — until the motion
    /// settles to ordinary deltas — bounded by <see cref="CalibrationMaxTrimFrames"/> total.
    /// See class remarks for the measured margins.
    /// </summary>
    private static int CalibrationSegmentLength(
        XForm[][] native, int edge, int stop, int step, float typicalDeltaDeg)
    {
        int length = 0;
        int f = edge;
        while (f != stop && length <= CalibrationMaxSegmentFrames
               && MaxRotDeltaDeg(native[f], null) <= CalibrationRestMaxDeg)
        {
            length++;
            f += step;
        }
        if (length is 0 or > CalibrationMaxSegmentFrames)
            return 0;

        // f = first frame past the rest-like segment; require a real (non-rest-like) clip
        // of ≥ 2 frames beyond it and a calibration-grade cut between segment and motion.
        if (f == stop || MaxRotDeltaDeg(native[f], null) <= CalibrationRestMaxDeg)
            return 0;
        float jump = MaxRotDeltaDeg(native[f - step], native[f]);
        if (jump < CalibrationJumpMinDeg || jump < CalibrationJumpTypicalRatio * typicalDeltaDeg)
            return 0;

        // Blend-ramp extension: consume frames still moving at calibration-ramp speed until
        // the motion settles (leaving ≥ 2 frames past the trim).
        float rampFloor = MathF.Max(
            CalibrationRampMinDeg, CalibrationJumpTypicalRatio * typicalDeltaDeg);
        while (length < CalibrationMaxTrimFrames
               && f != stop && f + step != stop
               && MaxRotDeltaDeg(native[f], native[f + step]) >= rampFloor)
        {
            length++;
            f += step;
        }
        return length;
    }

    /// <summary>Max joint rotation angle (degrees) between two decoded frames, or — when
    /// <paramref name="b"/> is null — against the identity-rotation BVH bind rest.</summary>
    private static float MaxRotDeltaDeg(XForm[] a, XForm[]? b)
    {
        float max = 0f;
        for (int i = 0; i < a.Length; i++)
        {
            float angle = MathQ.AngleBetween(a[i].Rot, b is null ? Quaternion.Identity : b[i].Rot);
            max = MathF.Max(max, angle);
        }
        return max * (180f / MathF.PI);
    }

    /// <summary>Median of the per-pair max joint rotation deltas over the clip's INTERIOR
    /// consecutive frame pairs (both edge pairs excluded — they are the trim candidates).</summary>
    private static float TypicalNeighborRotDeltaDeg(XForm[][] native)
    {
        int pairCount = native.Length - 3; // pairs (1,2) … (n-3, n-2)
        if (pairCount <= 0)
            return 0f;
        var deltas = new float[pairCount];
        for (int f = 0; f < pairCount; f++)
            deltas[f] = MaxRotDeltaDeg(native[f + 1], native[f + 2]);
        Array.Sort(deltas);
        return deltas[pairCount / 2];
    }

    /// <summary>One joint's local transform from one motion row (see class remarks).</summary>
    private static XForm EvaluateLocal(Joint joint, float[] row, float unitScale)
    {
        // Position channels replace the OFFSET; absent channels (or no position channels at
        // all) fall back per Blender's semantics described in the class remarks.
        Vector3 pos = joint.HasPos
            ? new Vector3(
                joint.PosX >= 0 ? row[joint.PosX] : 0f,
                joint.PosY >= 0 ? row[joint.PosY] : 0f,
                joint.PosZ >= 0 ? row[joint.PosZ] : 0f)
            : joint.Offset;

        // R = R_chan1 * R_chan2 * R_chan3 (column-vector convention; degrees in the file).
        var rot = Quaternion.Identity;
        foreach (var (axis, column) in joint.Rot)
        {
            float radians = row[column] * (MathF.PI / 180f);
            var axisVector = axis switch
            {
                0 => Vector3.UnitX,
                1 => Vector3.UnitY,
                _ => Vector3.UnitZ,
            };
            rot *= Quaternion.CreateFromAxisAngle(axisVector, radians);
        }

        return new XForm(pos * unitScale, MathQ.Normalize(rot));
    }

    // =====================================================================================
    // tokenizer
    // =====================================================================================

    /// <summary>Whitespace token stream over the BVH text (BVH is line-format agnostic).</summary>
    private sealed class TokenCursor
    {
        private readonly string[] _tokens;
        private int _pos;

        public TokenCursor(string text)
            => _tokens = text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);

        public bool AtEnd => _pos >= _tokens.Length;

        public bool PeekIs(string keywordUpper)
            => _pos < _tokens.Length &&
               string.Equals(_tokens[_pos], keywordUpper, StringComparison.OrdinalIgnoreCase);

        public string Next()
        {
            if (AtEnd)
                throw new FormatException("BVH: unexpected end of file.");
            return _tokens[_pos++];
        }

        public void ExpectKeyword(string keywordUpper)
        {
            string token = Next();
            if (!string.Equals(token, keywordUpper, StringComparison.OrdinalIgnoreCase))
                throw new FormatException($"BVH: expected '{keywordUpper}', found '{token}'.");
        }

        public int NextInt()
        {
            string token = Next();
            if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out int value))
                throw new FormatException($"BVH: expected an integer, found '{token}'.");
            return value;
        }

        public float NextFloat()
        {
            string token = Next();
            if (!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out float value) ||
                !float.IsFinite(value))
                throw new FormatException($"BVH: expected a number, found '{token}'.");
            return value;
        }
    }
}
notpointless.chomnr_humanoid_retargeter / HumanoidRetargeter/Formats/Dmx/DmxWriter.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using HumanoidRetargeter.Skeleton;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;

namespace HumanoidRetargeter.Formats.Dmx;

/// <summary>Options for <see cref="DmxWriter.Write"/>.</summary>
public sealed class DmxWriteOptions
{
    /// <summary>Model/clip name written into the DmeModel element (e.g. the sequence name).</summary>
    public string Name { get; set; } = "";

    /// <summary>Free-form provenance note written as the DmeDCCMakefile source name
    /// (fbx2dmx writes the source .fbx path here).</summary>
    public string SourceNote { get; set; } = "";

    /// <summary>When true (default, matching fbx2dmx output) the file declares a Y-up axis
    /// system; when false it declares Z-up. Data is written as-is either way.</summary>
    public bool UpAxisY { get; set; } = true;

    /// <summary>
    /// Skeleton bone indices that get NO DmeChannel pair: the bones keep their DmeJoint and
    /// bind (rest) transform, but no animation channels are written for them — the engine then
    /// drives them itself (e.g. ConstraintDriven twist/helper bones, design §3). Null (default)
    /// writes channels for every bone.
    /// </summary>
    public IReadOnlySet<int>? ChannelExcludedBones { get; set; }
}

/// <summary>
/// Writes an animation DMX in <c>keyvalues2_noids</c> text encoding, replicating the exact
/// element/attribute shape of fbx2dmx output (authoritative reference:
/// <c>dev/m0/ref_idlepose.dmx</c>): a root DmElement holding an inline DmeModel (joint GUID
/// refs + bind base state), a top-level DmeAnimationList with one DmeChannelsClip carrying a
/// position and an orientation channel per bone, and top-level DmeTransform/DmeJoint elements
/// the channels and joint lists reference by GUID. Output is fully deterministic: GUIDs are
/// MD5-derived from the options name and an element path, and export tags use fixed
/// placeholder strings.
/// </summary>
public static class DmxWriter
{
    private const string Header = "<!-- dmx encoding keyvalues2_noids 4 format model 22 -->";

    /// <summary>
    /// Serializes <paramref name="clip"/> on <paramref name="skeleton"/> to DMX text.
    /// Frames must contain one local transform per bone in skeleton order.
    /// </summary>
    /// <exception cref="ArgumentException">Thrown when the clip is empty or a frame's bone
    /// count does not match the skeleton.</exception>
    public static string Write(SkeletonModel skeleton, Clip clip, DmxWriteOptions options)
    {
        ArgumentNullException.ThrowIfNull(skeleton);
        ArgumentNullException.ThrowIfNull(clip);
        ArgumentNullException.ThrowIfNull(options);

        if (clip.FrameCount == 0)
            throw new ArgumentException("Clip has no frames.", nameof(clip));
        for (var f = 0; f < clip.FrameCount; f++)
        {
            if (clip.Frames[f].Length != skeleton.Count)
                throw new ArgumentException(
                    $"Frame {f} has {clip.Frames[f].Length} bone transforms, skeleton has {skeleton.Count}.",
                    nameof(clip));
        }

        var w = new Emitter();
        var animListGuid = GuidString(options.Name, "animationList");
        var jointGuids = new string[skeleton.Count];
        var transformGuids = new string[skeleton.Count];
        for (var i = 0; i < skeleton.Count; i++)
        {
            jointGuids[i] = GuidString(options.Name, "joint:" + skeleton[i].Name);
            transformGuids[i] = GuidString(options.Name, "transform:" + skeleton[i].Name);
        }

        w.Raw(Header);

        // ---- root DmElement -------------------------------------------------
        w.BeginTopLevel("DmElement");
        w.Attr("name", "string", "root");

        w.BeginInlineAttr("skeleton", "DmeModel");
        w.Attr("name", "string", options.Name);
        w.BeginInlineAttr("transform", "DmeTransform");
        w.Attr("position", "vector3", "0 0 0");
        w.Attr("orientation", "quaternion", "0 0 0 1");
        w.EndInlineAttr();
        w.Attr("shape", "element", "");
        w.Attr("visible", "bool", "1");

        w.BeginArray("children");
        var roots = new List<int>();
        for (var i = 0; i < skeleton.Count; i++)
        {
            if (skeleton[i].ParentIndex < 0)
                roots.Add(i);
        }
        for (var r = 0; r < roots.Count; r++)
            w.ElementRef(jointGuids[roots[r]], last: r == roots.Count - 1);
        w.EndArray();

        w.BeginArray("jointList");
        for (var i = 0; i < skeleton.Count; i++)
            w.ElementRef(jointGuids[i], last: i == skeleton.Count - 1);
        w.EndArray();

        w.BeginArray("baseStates");
        w.BeginArrayElement("DmeTransformList");
        w.Attr("name", "string", "bind");
        w.BeginArray("transforms");
        for (var i = 0; i < skeleton.Count; i++)
        {
            w.BeginArrayElement("DmeTransform");
            w.Attr("name", "string", skeleton[i].Name);
            w.Attr("position", "vector3", Vec(skeleton[i].RestLocal));
            w.Attr("orientation", "quaternion", Quat(skeleton[i].RestLocal));
            w.EndArrayElement(last: i == skeleton.Count - 1);
        }
        w.EndArray();
        w.EndArrayElement(last: true);
        w.EndArray();

        w.Attr("upAxis", "string", options.UpAxisY ? "Y" : "Z");
        w.BeginInlineAttr("axisSystem", "DmeAxisSystem");
        w.Attr("upAxis", "int", options.UpAxisY ? "2" : "3");
        w.Attr("forwardParity", "int", "2");
        w.Attr("coordSys", "int", "0");
        w.EndInlineAttr();
        w.Attr("animationList", "element", animListGuid);
        w.EndInlineAttr(); // skeleton DmeModel

        w.BeginInlineAttr("makefile", "DmeDCCMakefile");
        w.Attr("name", "string", "makefile");
        w.BeginArray("sources");
        w.BeginArrayElement("DmeSource");
        w.Attr("name", "string", options.SourceNote);
        w.EndArrayElement(last: true);
        w.EndArray();
        w.EndInlineAttr();

        // Deterministic placeholders — never wall-clock/user data, so output is reproducible.
        w.BeginInlineAttr("exportTags", "DmeExportTags");
        w.Attr("name", "string", "exportTags");
        w.Attr("date", "string", "2026/01/01");
        w.Attr("time", "string", "12:00:00 am");
        w.Attr("user", "string", "retargeter");
        w.Attr("machine", "string", "retargeter");
        w.Attr("app", "string", "humanoid-retargeter");
        w.Attr("appVersion", "string", "1.0");
        w.Attr("cmdLine", "string", "humanoid-retargeter");
        w.Attr("pwd", "string", "");
        w.EndInlineAttr();

        w.Attr("animationList", "element", animListGuid);
        w.EndTopLevel();

        // ---- DmeAnimationList ----------------------------------------------
        w.BeginTopLevel("DmeAnimationList");
        w.Attr("id", "elementid", animListGuid);
        w.Attr("name", "string", "anim");
        w.BeginArray("animations");
        w.BeginArrayElement("DmeChannelsClip");
        w.Attr("name", "string", "anim");

        w.BeginInlineAttr("timeFrame", "DmeTimeFrame");
        w.Attr("start", "time", Time(0.0));
        w.Attr("duration", "time", Time((clip.FrameCount - 1) / (double)clip.Fps));
        w.Attr("offset", "time", Time(0.0));
        w.Attr("scale", "float", "1");
        w.EndInlineAttr();

        w.Attr("color", "color", "0 0 0 0");
        w.Attr("text", "string", "");
        w.Attr("mute", "bool", "0");
        w.BeginArray("trackGroups");
        w.EndArray();
        w.Attr("displayScale", "float", "1");

        var channelBones = new List<int>(skeleton.Count);
        for (var i = 0; i < skeleton.Count; i++)
        {
            if (options.ChannelExcludedBones is null || !options.ChannelExcludedBones.Contains(i))
                channelBones.Add(i);
        }

        w.BeginArray("channels");
        for (var n = 0; n < channelBones.Count; n++)
        {
            var i = channelBones[n];
            WriteChannel(w, skeleton, clip, i, transformGuids[i], position: true, last: false);
            WriteChannel(w, skeleton, clip, i, transformGuids[i], position: false,
                last: n == channelBones.Count - 1);
        }
        w.EndArray();

        w.Attr("frameRate", "int",
            ((int)MathF.Round(clip.Fps)).ToString(CultureInfo.InvariantCulture));
        w.EndArrayElement(last: true);
        w.EndArray();
        w.EndTopLevel();

        // ---- top-level channel-target DmeTransforms (rest values) -----------
        for (var i = 0; i < skeleton.Count; i++)
        {
            w.BeginTopLevel("DmeTransform");
            w.Attr("id", "elementid", transformGuids[i]);
            w.Attr("name", "string", skeleton[i].Name);
            w.Attr("position", "vector3", Vec(skeleton[i].RestLocal));
            w.Attr("orientation", "quaternion", Quat(skeleton[i].RestLocal));
            w.EndTopLevel();
        }

        // ---- top-level DmeJoints --------------------------------------------
        for (var i = 0; i < skeleton.Count; i++)
        {
            w.BeginTopLevel("DmeJoint");
            w.Attr("id", "elementid", jointGuids[i]);
            w.Attr("name", "string", skeleton[i].Name);
            w.Attr("transform", "element", transformGuids[i]);
            w.Attr("shape", "element", "");
            w.Attr("visible", "bool", "1");
            w.BeginArray("children");
            var children = new List<int>();
            for (var c = 0; c < skeleton.Count; c++)
            {
                if (skeleton[c].ParentIndex == i)
                    children.Add(c);
            }
            for (var c = 0; c < children.Count; c++)
                w.ElementRef(jointGuids[children[c]], last: c == children.Count - 1);
            w.EndArray();
            w.EndTopLevel();
        }

        return w.ToString();
    }

    /// <summary>
    /// Deterministic element GUID: MD5 over <c>"&lt;name&gt;\n&lt;path&gt;"</c> (UTF-8)
    /// interpreted as <see cref="Guid"/> bytes. Exposed so tests can verify the scheme.
    /// </summary>
    public static Guid ElementGuid(string name, string path)
        => new(MD5.HashData(Encoding.UTF8.GetBytes(name + "\n" + path)));

    private static string GuidString(string name, string path)
        => ElementGuid(name, path).ToString("D", CultureInfo.InvariantCulture);

    // ---------------------------------------------------------------- channels

    private static void WriteChannel(Emitter w, SkeletonModel skeleton, Clip clip, int bone,
        string transformGuid, bool position, bool last)
    {
        var logClass = position ? "DmeVector3Log" : "DmeQuaternionLog";
        var layerClass = position ? "DmeVector3LogLayer" : "DmeQuaternionLogLayer";
        var logName = position ? "vector3 log" : "quaternion log";

        w.BeginArrayElement("DmeChannel");
        w.Attr("name", "string", skeleton[bone].Name + (position ? "_p" : "_o"));
        w.Attr("fromElement", "element", "");
        w.Attr("fromAttribute", "string", "");
        w.Attr("fromIndex", "int", "0");
        w.Attr("toElement", "element", transformGuid);
        w.Attr("toAttribute", "string", position ? "position" : "orientation");
        w.Attr("toIndex", "int", "0");
        w.Attr("mode", "int", "3");

        w.BeginInlineAttr("log", logClass);
        w.Attr("name", "string", logName);
        w.BeginArray("layers");
        w.BeginArrayElement(layerClass);
        w.Attr("name", "string", logName);

        w.BeginArray("times", "time_array");
        for (var f = 0; f < clip.FrameCount; f++)
            w.ArrayValue(Time(f / (double)clip.Fps), last: f == clip.FrameCount - 1);
        w.EndArray();

        w.BeginArray("curvetypes", "int_array");
        w.EndArray();

        w.BeginArray("values", position ? "vector3_array" : "quaternion_array");
        // Orientation values are hemisphere-aligned on the fly (q and -q are the same
        // rotation, but the engine interpolates between DMX samples numerically — see
        // QuaternionContinuity). The clip itself is never mutated.
        var prev = System.Numerics.Quaternion.Identity;
        for (var f = 0; f < clip.FrameCount; f++)
        {
            var x = clip.Frames[f][bone];
            string value;
            if (position)
            {
                value = Vec(x);
            }
            else
            {
                var q = x.Rot;
                if (f > 0 && System.Numerics.Quaternion.Dot(prev, q) < 0f)
                    q = System.Numerics.Quaternion.Negate(q);
                prev = q;
                value = Quat(q);
            }
            w.ArrayValue(value, last: f == clip.FrameCount - 1);
        }
        w.EndArray();

        w.EmptyBinaryAttr("compressed");
        w.EndArrayElement(last: true);
        w.EndArray(); // layers

        w.Attr("curveinfo", "element", "");
        w.Attr("usedefaultvalue", "bool", "0");
        w.Attr("defaultvalue", position ? "vector3" : "quaternion", position ? "0 0 0" : "0 0 0 1");
        w.BeginArray("bookmarksX", "time_array");
        w.EndArray();
        w.BeginArray("bookmarksY", "time_array");
        w.EndArray();
        w.BeginArray("bookmarksZ", "time_array");
        w.EndArray();
        w.EndInlineAttr(); // log

        w.EndArrayElement(last);
    }

    // ---------------------------------------------------------------- formatting

    /// <summary>fbx2dmx float style: up to 10 decimal places, trailing zeros stripped,
    /// invariant culture, negative zero normalized.</summary>
    private static string F(float value)
    {
        if (value == 0f)
            return "0";
        return ((double)value).ToString("0.##########", CultureInfo.InvariantCulture);
    }

    private static string Time(double seconds)
        => seconds.ToString("0.0000", CultureInfo.InvariantCulture);

    private static string Vec(in Maths.XForm x)
        => $"{F(x.Pos.X)} {F(x.Pos.Y)} {F(x.Pos.Z)}";

    private static string Quat(in Maths.XForm x) => Quat(x.Rot);

    private static string Quat(in System.Numerics.Quaternion q)
        => $"{F(q.X)} {F(q.Y)} {F(q.Z)} {F(q.W)}";

    // ---------------------------------------------------------------- emitter

    /// <summary>
    /// Low-level keyvalues2 text emitter reproducing fbx2dmx layout quirks: CRLF endings,
    /// tab indentation, a trailing space after array-typed attribute names, and an
    /// indentation-only line after every inline element attribute closes.
    /// </summary>
    private sealed class Emitter
    {
        private readonly StringBuilder _sb = new();
        private int _indent;

        public void Raw(string text)
        {
            _sb.Append(text).Append("\r\n");
        }

        private void Line(string text)
        {
            _sb.Append('\t', _indent).Append(text).Append("\r\n");
        }

        public void Attr(string name, string type, string value)
            => Line($"\"{name}\" \"{type}\" \"{value}\"");

        public void BeginTopLevel(string className)
        {
            Line($"\"{className}\"");
            Line("{");
            _indent++;
        }

        public void EndTopLevel()
        {
            _indent--;
            Line("}");
            _sb.Append("\r\n"); // blank separator after every top-level element (incl. the last)
        }

        public void BeginInlineAttr(string name, string className)
        {
            Line($"\"{name}\" \"{className}\"");
            Line("{");
            _indent++;
        }

        public void EndInlineAttr()
        {
            _indent--;
            Line("}");
            Line(""); // indentation-only line, as fbx2dmx emits
        }

        public void BeginArrayElement(string className)
        {
            Line($"\"{className}\"");
            Line("{");
            _indent++;
        }

        public void EndArrayElement(bool last)
        {
            _indent--;
            Line(last ? "}" : "},");
        }

        public void BeginArray(string name, string type = "element_array")
        {
            Line($"\"{name}\" \"{type}\" ");
            Line("[");
            _indent++;
        }

        public void EndArray()
        {
            _indent--;
            Line("]");
        }

        public void ElementRef(string guid, bool last)
            => Line($"\"element\" \"{guid}\"" + (last ? "" : ","));

        public void ArrayValue(string value, bool last)
            => Line($"\"{value}\"" + (last ? "" : ","));

        public void EmptyBinaryAttr(string name)
        {
            Line($"\"{name}\" \"binary\" ");
            Line("\"");
            Line("\"");
        }

        public override string ToString() => _sb.ToString();
    }
}
notpointless.chomnr_humanoid_retargeter / HumanoidRetargeter/Formats/Fbx/FbxBindPoseFixer.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Numerics;
using HumanoidRetargeter.Maths;

namespace HumanoidRetargeter.Formats.Fbx;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>
/// Repairs FBX files that were exported MID-POSE: their node transforms (Lcl
/// Translation/Rotation) hold an animation snapshot while the true skeleton bind lives in
/// the file's Pose/BindPose section. Engines that build the skeleton from node transforms
/// (s&amp;box does; the FBX SDK's own samples do) then import a posed "bind" — the skin
/// stays self-consistent so the model LOOKS fine at rest, but every anatomical assumption
/// about the skeleton (leg chains point down, hands mirror) is silently wrong and
/// retargeted motion comes out mangled on exactly the posed bones. Found in the wild on
/// Auto-Rig Pro exports whose IK'd hands/feet were left posed (one leg at hip height).
/// </summary>
public static class FbxBindPoseFixer
{
    /// <summary>Bones whose node transform is further than this (native units, as a
    /// fraction of skeleton height) from their BindPose matrix count as posed.</summary>
    private const float PositionToleranceOfHeight = 0.01f;

    /// <summary>Rotation disagreement (degrees) that counts as posed.</summary>
    private const float RotationToleranceDeg = 2.0f;

    /// <summary>
    /// Detects the mid-pose condition and rewrites node transforms to the BindPose. Returns
    /// null when the file needs no repair (no BindPose section, or node transforms already
    /// agree with it); otherwise the repaired file bytes.
    /// <paramref name="report"/> always describes what was found.
    /// </summary>
    public static byte[]? TryFix(byte[] fbx, out string report)
    {
        ArgumentNullException.ThrowIfNull(fbx);

        FbxNode root;
        FbxScene scene;
        try
        {
            root = FbxTokenizer.Parse(fbx);
            scene = FbxScene.Build(root);
        }
        catch (FormatException e)
        {
            report = $"not parseable ({e.Message})";
            return null;
        }

        if (scene.BindPose.Count == 0)
        {
            report = "no BindPose section";
            return null;
        }

        // Evaluate the ORIGINAL node-transform FK, roots first.
        var originalWorld = new Dictionary<long, Matrix4x4>();
        var order = new List<FbxObject>();
        foreach (var model in scene.Models)
            VisitModel(model, scene, originalWorld, order);

        // Skeleton height (native units) for the position tolerance.
        float minUp = float.MaxValue, maxUp = float.MinValue;
        foreach (var w in originalWorld.Values)
        {
            var t = w.Translation;
            float up = MathF.Max(MathF.Abs(t.Y), MathF.Abs(t.Z));
            minUp = MathF.Min(minUp, up);
            maxUp = MathF.Max(maxUp, up);
        }
        float posTolerance = MathF.Max(0.0001f, (maxUp - minUp) * PositionToleranceOfHeight);

        // Any bone posed away from its bind?
        int posedCount = 0;
        foreach (var model in order)
        {
            if (!scene.BindPose.TryGetValue(model.Id, out var bind))
                continue;
            var fk = FbxTransform.ToRigid(originalWorld[model.Id]);
            var target = FbxTransform.ToRigid(bind);
            if ((fk.Pos - target.Pos).Length() > posTolerance
                || MathQ.AngleBetween(fk.Rot, target.Rot) > RotationToleranceDeg * MathF.PI / 180f)
            {
                posedCount++;
            }
        }
        if (posedCount == 0)
        {
            report = $"node transforms match the BindPose ({scene.BindPose.Count} entries)";
            return null;
        }

        // Rewrite every BindPose-backed model's local transform so FK lands on the bind.
        // correctedWorld carries the repair down the hierarchy for bones WITHOUT a
        // BindPose entry (helpers keep their original locals under corrected parents).
        var correctedWorld = new Dictionary<long, Matrix4x4>();
        int patched = 0, skipped = 0;
        foreach (var model in order)
        {
            var parent = model.ModelParent;
            Matrix4x4 parentWorld = parent is not null && correctedWorld.TryGetValue(parent.Id, out var pw)
                ? pw
                : Matrix4x4.Identity;

            if (!scene.BindPose.TryGetValue(model.Id, out var bindWorld))
            {
                // No bind info: keep the original local under the (possibly corrected) parent.
                var transform = FbxTransform.FromModel(scene, model);
                correctedWorld[model.Id] = transform.LocalMatrixDefault() * parentWorld;
                continue;
            }

            if (!Matrix4x4.Invert(parentWorld, out var invParent))
            {
                correctedWorld[model.Id] = bindWorld;
                skipped++;
                continue;
            }

            // Row-vector: World = Local · ParentWorld  ⇒  Local = World · ParentWorld⁻¹.
            var desiredLocal = FbxTransform.ToRigid(bindWorld * invParent);
            if (PatchModelLocal(scene, model, desiredLocal))
                patched++;
            else
                skipped++;

            // Children FK from the ACTUAL bind either way (unpatchable bones are rare and
            // their children still deserve correct parent frames).
            correctedWorld[model.Id] = bindWorld;
        }

        report = $"{posedCount} bones were exported mid-pose; repaired {patched}"
            + (skipped > 0 ? $", {skipped} left as-is (pivots/scale beyond the safe rewrite)" : "");
        if (patched == 0)
            return null;
        return FbxBinaryWriter.Write(root);
    }

    private static void VisitModel(
        FbxObject model, FbxScene scene,
        Dictionary<long, Matrix4x4> world, List<FbxObject> order)
    {
        if (world.ContainsKey(model.Id))
            return;
        Matrix4x4 parentWorld = Matrix4x4.Identity;
        if (model.ModelParent is { } parent)
        {
            VisitModel(parent, scene, world, order);
            parentWorld = world[parent.Id];
        }
        var transform = FbxTransform.FromModel(scene, model);
        world[model.Id] = transform.LocalMatrixDefault() * parentWorld;
        order.Add(model);
    }

    // ------------------------------------------------------------------ patching

    /// <summary>
    /// Rewrites one Model's Lcl Translation/Rotation so its local evaluates to
    /// <paramref name="desiredLocal"/>. Verified by re-evaluating through the full FBX
    /// transform formula — models using pivots/offsets/scale that the rewrite cannot
    /// express are left untouched (returns false).
    /// </summary>
    private static bool PatchModelLocal(FbxScene scene, FbxObject model, XForm desiredLocal)
    {
        var transform = FbxTransform.FromModel(scene, model);

        // R_total = Pre · R · Post⁻¹  ⇒  R = Pre⁻¹ · R_total · Post
        var r = MathQ.Normalize(
            Quaternion.Conjugate(transform.PreRotation)
            * desiredLocal.Rot
            * transform.PostRotation);

        var eulerDeg = QuaternionToEulerDegrees(r, transform.RotationOrder);

        // Full-formula verification (catches pivots, scale, decomposition branches).
        var check = new FbxTransform
        {
            LclTranslation = desiredLocal.Pos,
            LclRotationDeg = eulerDeg,
            LclScaling = transform.LclScaling,
            PreRotation = transform.PreRotation,
            PostRotation = transform.PostRotation,
            RotationOffset = transform.RotationOffset,
            RotationPivot = transform.RotationPivot,
            ScalingOffset = transform.ScalingOffset,
            ScalingPivot = transform.ScalingPivot,
            RotationOrder = transform.RotationOrder,
        };
        var evaluated = FbxTransform.ToRigid(check.LocalMatrixDefault());
        float posScale = MathF.Max(1f, desiredLocal.Pos.Length());
        if ((evaluated.Pos - desiredLocal.Pos).Length() > 0.001f * posScale
            || MathQ.AngleBetween(evaluated.Rot, desiredLocal.Rot) > 0.1f * MathF.PI / 180f)
        {
            return false;
        }

        SetProperty70(model.Node, "Lcl Translation", "Lcl Translation", "A",
            desiredLocal.Pos.X, desiredLocal.Pos.Y, desiredLocal.Pos.Z);
        SetProperty70(model.Node, "Lcl Rotation", "Lcl Rotation", "A",
            eulerDeg.X, eulerDeg.Y, eulerDeg.Z);
        return true;
    }

    /// <summary>Sets (or adds) a 3-double P entry in the node's Properties70 block.</summary>
    private static void SetProperty70(
        FbxNode modelNode, string name, string type, string flags,
        double x, double y, double z)
    {
        var block = modelNode.Child("Properties70");
        if (block is null)
        {
            block = new FbxNode("Properties70");
            modelNode.Children.Insert(0, block);
        }

        foreach (var p in block.ChildrenNamed("P"))
        {
            if (p.Properties.Count >= 1 && p.Properties[0] is string n && n == name)
            {
                // Values live at indices 4.. — replace, extending if the entry was short.
                while (p.Properties.Count < 7)
                    p.Properties.Add(0.0);
                p.Properties[4] = x;
                p.Properties[5] = y;
                p.Properties[6] = z;
                return;
            }
        }

        var entry = new FbxNode("P");
        entry.Properties.Add(name);
        entry.Properties.Add(type);
        entry.Properties.Add("");
        entry.Properties.Add(flags);
        entry.Properties.Add(x);
        entry.Properties.Add(y);
        entry.Properties.Add(z);
        block.Children.Add(entry);
    }

    // ------------------------------------------------------------------ euler decomposition

    /// <summary>
    /// Decomposes a quaternion into FBX euler degrees for the given RotationOrder, the
    /// exact inverse of <see cref="FbxTransform.EulerDegreesToQuaternion"/>. Tait-Bryan
    /// extraction on the column-convention rotation matrix.
    /// </summary>
    public static Vector3 QuaternionToEulerDegrees(Quaternion q, int order)
    {
        // Column-convention matrix C (v' = C·v): C = transpose of System.Numerics' row form.
        var m = Matrix4x4.CreateFromQuaternion(q);
        // C[r,c]: row r, column c.
        float c00 = m.M11, c01 = m.M21, c02 = m.M31;
        float c10 = m.M12, c11 = m.M22, c12 = m.M32;
        float c20 = m.M13, c21 = m.M23, c22 = m.M33;

        const float radToDeg = 180f / MathF.PI;
        float a, b, c;
        switch (order)
        {
            case 0: // XYZ: C = Rz·Ry·Rx
                b = MathF.Asin(Math.Clamp(-c20, -1f, 1f));
                a = MathF.Atan2(c21, c22);
                c = MathF.Atan2(c10, c00);
                return new Vector3(a * radToDeg, b * radToDeg, c * radToDeg);
            case 1: // XZY: C = Ry·Rz·Rx
                b = MathF.Asin(Math.Clamp(c10, -1f, 1f));
                a = MathF.Atan2(-c12, c11);
                c = MathF.Atan2(-c20, c00);
                return new Vector3(a * radToDeg, c * radToDeg, b * radToDeg);
            case 2: // YZX: C = Rx·Rz·Ry
                b = MathF.Asin(Math.Clamp(-c01, -1f, 1f));
                a = MathF.Atan2(c02, c00);
                c = MathF.Atan2(c21, c11);
                return new Vector3(c * radToDeg, a * radToDeg, b * radToDeg);
            case 3: // YXZ: C = Rz·Rx·Ry
                b = MathF.Asin(Math.Clamp(c21, -1f, 1f));
                a = MathF.Atan2(-c20, c22);
                c = MathF.Atan2(-c01, c11);
                return new Vector3(b * radToDeg, a * radToDeg, c * radToDeg);
            case 4: // ZXY: C = Ry·Rx·Rz
                b = MathF.Asin(Math.Clamp(-c12, -1f, 1f));
                a = MathF.Atan2(c02, c22);
                c = MathF.Atan2(c10, c11);
                return new Vector3(b * radToDeg, a * radToDeg, c * radToDeg);
            case 5: // ZYX: C = Rx·Ry·Rz
            case 6: // eSphericXYZ treated as XYZ on read; mirror that here
            default:
                if (order == 5)
                {
                    b = MathF.Asin(Math.Clamp(c02, -1f, 1f));
                    a = MathF.Atan2(-c01, c00);
                    c = MathF.Atan2(-c12, c22);
                    return new Vector3(c * radToDeg, b * radToDeg, a * radToDeg);
                }
                goto case 0;
        }
    }
}
notpointless.chomnr_humanoid_retargeter / HumanoidRetargeter/RetargetRequest.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using HumanoidRetargeter.Cleanup;
using HumanoidRetargeter.Formats;
using HumanoidRetargeter.Mapping;
using HumanoidRetargeter.Solve;
using HumanoidRetargeter.Target;

namespace HumanoidRetargeter;

/// <summary>Which solver retargets a request's clips (design §10).</summary>
public enum SolverKind
{
    /// <summary>The deterministic <see cref="Solve.GeometricSolver"/> (default; better
    /// wherever a role mapping exists).</summary>
    Geometric,

    /// <summary>The experimental skeleton-agnostic deep-learning solver
    /// (<see cref="Dl.DlSolver"/>, SAME pretrained checkpoint) — the no-profile fallback.
    /// Requires <see cref="RetargetTargetSpec.DlWeights"/>; ignores per-role mapping
    /// (only hips/alignment heuristics consult it) and leaves fingers at rest.</summary>
    DeepLearning,
}

/// <summary>
/// One source animation file to retarget (engine-agnostic: bytes in, no file IO). Every
/// request runs its OWN profile detection, so a single batch may mix Mixamo + ActorCore +
/// BVH sources — unless <see cref="MappingOverride"/> supplies a mapping explicitly.
/// </summary>
public sealed class RetargetRequest
{
    /// <summary>Solver choice for this request's clips. <see cref="SolverKind.DeepLearning"/>
    /// requires the batch's <see cref="RetargetTargetSpec.DlWeights"/> to be set; the
    /// conversion fails per-clip with a clear error otherwise.</summary>
    public SolverKind Solver { get; init; } = SolverKind.Geometric;

    /// <summary>Raw bytes of the source file (.fbx, .bvh, .glb, .gltf, .vrm, .anm or .an5).</summary>
    public required byte[] SourceData { get; init; }

    /// <summary>
    /// Source file name (used for the report and DMX provenance). The extension drives the
    /// format choice (<c>.fbx</c> / <c>.bvh</c> / <c>.glb</c> / <c>.gltf</c> / <c>.vrm</c> —
    /// a VRM is a glTF container whose authored humanoid bone map becomes the mapping — /
    /// <c>.anm</c> / <c>.an5</c> RenderWare animations, which additionally need
    /// <see cref="SkeletonData"/>); when the extension is unknown the content is sniffed
    /// (FBX binary magic / "FBXHeaderExtension" / BVH "HIERARCHY" / GLB 'glTF' magic /
    /// glTF JSON / RenderWare 0x1B animation chunk).
    /// </summary>
    public required string SourceFileName { get; init; }

    /// <summary>
    /// Raw bytes of a companion SKELETON file for formats whose animation files carry no
    /// skeleton of their own: RenderWare <c>.anm</c>/<c>.an5</c> sources require the
    /// character model's <c>.dff</c> here (callers resolve the file — e.g. a .dff sitting
    /// next to the animation; the facade does no file IO). Ignored by self-contained
    /// formats. A RenderWare request without it fails with an instructive error.
    /// </summary>
    public byte[]? SkeletonData { get; init; }

    /// <summary>
    /// Caller-supplied identity of this request, echoed verbatim on every produced
    /// <see cref="ClipResult.SourceId"/> so callers can join results back to their own
    /// entries unambiguously (e.g. the editor window passes the FULL file path here, since
    /// two files in different folders may share the same <see cref="SourceFileName"/>).
    /// Null = <see cref="SourceFileName"/>.
    /// </summary>
    public string? SourceId { get; init; }

    /// <summary>
    /// Import sample rate the source clips are resampled to (BVH native frames / FBX curves
    /// are evaluated on this grid). Null = the importer default (30 fps).
    /// </summary>
    public float? SampleFps { get; init; }

    /// <summary>
    /// Restricts the conversion to ONE take of the source file (0-based index into the
    /// imported scene's clips). Null = convert all takes. Out of range fails the request's
    /// clip result with a clear error (the batch continues). UI listings that expand a
    /// multi-take file into one entry per take submit one request per selected take.
    /// When <see cref="ClipDefinitions"/> is set this index addresses the DEFINITIONS
    /// instead (each definition is what a UI row represents then).
    /// </summary>
    public int? TakeIndex { get; init; }

    /// <summary>
    /// Optional external clip definitions, parsed from a Unity <c>&lt;file&gt;.fbx.meta</c>
    /// sidecar (<see cref="UnityMeta.ParseClipAnimations"/>): Unity animation packs ship FBX
    /// files whose clips are sub-ranges of ONE source timeline. When set (non-empty), the
    /// conversion produces one output clip per definition instead of one per take: the
    /// definition's take (matched by <see cref="ExternalClipDef.TakeName"/>, falling back to
    /// the file's first take) is sliced to the definition's native-frame range
    /// (<see cref="UnityMeta.Slice"/>), named <see cref="ExternalClipDef.Name"/> (sanitized
    /// like take names, collision-suffixed across the batch) and looped per
    /// <see cref="ExternalClipDef.Loop"/> unless <see cref="LoopingOverride"/> is set.
    /// <see cref="TakeIndex"/> then indexes INTO this list. Null = no definitions.
    /// </summary>
    public IReadOnlyList<ExternalClipDef>? ClipDefinitions { get; init; }

    /// <summary>
    /// UI-supplied mapping (manual mapping table or a user preset loaded Editor-side).
    /// Null = auto-detect per request: preset profiles via <see cref="ProfileDetector"/>,
    /// then the <see cref="AutoMapper"/> as best-effort fallback.
    /// </summary>
    public MappingResult? MappingOverride { get; init; }

    /// <summary>Solver tunables (hip scales, finger transfer). ClipIndex/ClipName are managed
    /// by the pipeline per take and ignored here. Null = defaults.</summary>
    public SolveOptions? Solve { get; init; }

    /// <summary>
    /// Root-motion handling. <see cref="RootMotionMode.Extract"/> on a target without a
    /// dedicated animated root bone (the s&amp;box rig: pelvis is parentless, root_IK is
    /// IkBaked) leaves the frames untouched and instead sets the ExtractMotion flag on the
    /// clip's vmdl AnimFile entry — Source 2's compile-time extraction replaces the missing
    /// bone-level extraction. <see cref="RootMotionMode.InPlace"/> always operates on the
    /// hips directly.
    /// </summary>
    public RootMotionMode RootMotion { get; init; } = RootMotionMode.Off;

    /// <summary>Run the Kovar foot-plant cleanup pass on the solved frames (default on).</summary>
    public bool FootPlantCleanup { get; init; } = true;

    /// <summary>
    /// Copy the source clip's per-frame LOCAL translations onto same-named target bones
    /// (hips and its ancestors excluded — trajectory stays solver-owned). For SAME-RIG
    /// conversions of authored takes (a target FBX's own embedded animations): the solver
    /// pins every non-hips bone to its rest translation, silently dropping a Biped take's
    /// animated spine/thigh translations (~19cm of authored body sway on a death fall).
    /// Meaningless across different rigs — leave off (default) for real retargets.
    /// </summary>
    public bool PreserveSourceTranslations { get; init; }

    /// <summary>
    /// Optional arm end-effector IK pass pulling the wrists onto limb-length-normalized
    /// source hand positions. Default OFF: the geometric solver already matches anatomical
    /// directions, so arm IK only helps reach-critical work (props, contact poses) and can
    /// otherwise disturb elbow styling.
    /// </summary>
    public bool ArmEffectorIk { get; init; }

    /// <summary>
    /// Generate <c>AE_FOOTSTEP</c> AnimEvent nodes on each produced clip's vmdl AnimFile
    /// entry (default OFF). After solving and cleanup, foot-plant intervals are detected on
    /// the SOLVED target clip (<see cref="Cleanup.FootPlant.DetectPlantIntervals"/>); each
    /// plant's start frame is a touchdown and becomes one footstep event, in the exact node
    /// shape the shipped citizen data uses (see <see cref="Target.FootstepEvents"/>).
    /// Skipped (with a report note) when the target rig lacks complete leg chains.
    /// </summary>
    public bool GenerateFootstepEvents { get; init; }

    /// <summary>
    /// Additionally produce a mirrored twin of every converted clip (default OFF), named
    /// <c>&lt;clip&gt;_M</c> (collision-suffixed across the batch as usual). Mirroring runs
    /// in TARGET space on the solved clip (<see cref="Solve.ClipMirror"/>): left/right role
    /// bone channels swap and everything is reflected across the target character's sagittal
    /// plane; IK-baked helper bones are re-baked from the mirrored body afterwards.
    /// </summary>
    public bool CreateMirroredVariant { get; init; }

    /// <summary>
    /// Additionally register an additive (delta) twin of every converted clip in the
    /// generated/augmented vmdl (default OFF), named <c>&lt;clip&gt;_delta</c> (the shipped
    /// citizen naming; collision-suffixed across the batch as usual). The twin is a second
    /// AnimFile entry REUSING the clip's DMX with an <c>AnimSubtract</c> child
    /// (<c>anim_name</c> = the base sequence, <c>frame</c> = 0) — exactly the shipped
    /// <c>IdleLayer_01</c>/<c>IdleLayer_01_delta</c> pattern, where resourcecompiler
    /// subtracts the reference frame at compile time (no frame math happens here). The
    /// resulting <c>_delta</c> sequence is what s&amp;box layered animation additively
    /// blends on top of a base pose.
    /// </summary>
    public bool CreateAdditiveVariant { get; init; }

    /// <summary>Output clip name override; with multiple takes an index suffix is appended.
    /// Null = the source take name.</summary>
    public string? ClipNameOverride { get; init; }

    /// <summary>Force the looping flag on the output sequence(s); null = the source clip's flag.</summary>
    public bool? LoopingOverride { get; init; }
}

/// <summary>
/// Axis/unit convention of a <see cref="RetargetTargetSpec"/>'s rig data — drives the DMX
/// axis-system declaration, foot-plant threshold units, and the editor preview's
/// rig-space → engine-space conversion.
/// </summary>
public enum TargetUpAxis
{
    /// <summary>
    /// The s&amp;box source convention: rig authored in centimeters, Y-up (the shipped
    /// citizen rig, FBX targets). The vmdl's ScaleAndMirror 0.3937 + resourcecompiler's
    /// Y-up→Z-up conversion take it to engine space at compile time. Default.
    /// </summary>
    YUpCm,

    /// <summary>
    /// Engine space already: rig read from a compiled model's <c>Model.Bones</c>
    /// (inches, Z-up). The DMX declares a Z-up axis system so the compiler performs no
    /// further axis conversion.
    /// </summary>
    ZUpEngine,

    /// <summary>
    /// A Z-up rig authored in centimeters: FBX targets whose GlobalSettings declare a Z
    /// up-axis (UE and 3ds Max exports; Maya/Blender exports are Y-up). The DMX declares
    /// Z-up (no compile-time rotation — the mesh source is in the same Z-up space) while
    /// the vmdl's ScaleAndMirror 0.3937 still converts cm→inches. Without this, a Z-up
    /// FBX target compiles lying on its back.
    /// </summary>
    ZUpCm,
}

/// <summary>
/// The conversion target shared by all requests of one <see cref="Retargeter.Convert"/> /
/// <see cref="Retargeter.ConvertBatch"/> call: the rig plus the vmdl generation parameters.
/// </summary>
public sealed class RetargetTargetSpec
{
    /// <summary>The s&amp;box-source → engine-units vmdl scale (cm rigs like the citizen).</summary>
    public const float SboxSourceScale = 0.3937f;

    /// <summary>The committed asset path of the s&amp;box human male model.</summary>
    public const string SboxHumanMalePath = "models/citizen_human/citizen_human_male.vmdl";

    /// <summary>The committed asset path of the classic (4-finger) s&amp;box citizen model.</summary>
    public const string SboxCitizenPath = "models/citizen/citizen.vmdl";

    /// <summary>Target rig (skeleton + bone classes + roles).</summary>
    public required TargetRig Rig { get; set; }

    /// <summary>ModelModifier_ScaleAndMirror scale written into standalone vmdls:
    /// <c>0.3937</c> for cm-authored s&amp;box-source rigs, <c>1.0</c> for engine-unit rigs
    /// (the modifier node is omitted at 1.0).</summary>
    public required float VmdlScale { get; init; }

    /// <summary>base_model_name of generated standalone vmdls (the model that owns the mesh).</summary>
    public string BaseModelPath { get; init; } = "";

    /// <summary>
    /// Assets-relative mesh source file (e.g. an <c>.fbx</c>) embedded in generated
    /// standalone vmdls as a <c>RenderMeshList/RenderMeshFile</c> node. Custom FBX targets
    /// have no compiled base model to point <see cref="BaseModelPath"/> at — without a mesh
    /// source their standalone vmdl compiles into an EMPTY model (0 bones, 0 sequences) and
    /// playing it does nothing. Callers own copying the file into the project (this type
    /// does no IO); settable so the editor can fill it at convert time once the output
    /// folder is known. Empty (default) = no mesh node.
    /// </summary>
    public string MeshFilePath { get; set; } = "";

    /// <summary>
    /// Import scale of <see cref="MeshFilePath"/> (raw mesh-file units → the target
    /// skeleton's units). resourcecompiler reads mesh files' raw values ignoring their unit
    /// metadata, while the importer normalizes the target skeleton to centimeters — a
    /// meters-authored FBX therefore needs 100 here (the importer's recorded
    /// source-unit→cm factor) for the mesh to match the animation skeleton.
    /// </summary>
    public float MeshImportScale { get; set; } = 1.0f;

    /// <summary>
    /// Material remaps written into generated standalone vmdls as a
    /// MaterialGroupList/DefaultMaterialGroup (bare mesh material reference → assets-relative
    /// vmat path, e.g. <c>"mi_dante_head.vmat" → "animations/retargeted/mi_dante_head.vmat"</c>).
    /// FBX materials carry bare names the compiler cannot resolve as resource paths
    /// ("Trying to load an illegal resource name X.vmat"); this remap table — the same
    /// mechanism the shipped citizen vmdl uses — points them at real files. Null/empty =
    /// no material group node (default).
    /// </summary>
    public IReadOnlyDictionary<string, string>? MaterialRemaps { get; set; }

    /// <summary>
    /// Additional AnimFile entries appended to generated/augmented vmdls verbatim —
    /// the target FBX's OWN embedded animations (an FBX with an animation on it must keep
    /// that animation when new ones are retargeted onto it; the AnimFile references the
    /// FBX directly, exactly like the shipped citizen animation list references its
    /// Citizen@*.fbx files, so the import is lossless). Augmentation skips entries the
    /// existing vmdl already carries (idempotent re-runs). Null/empty = none (default).
    /// </summary>
    public IReadOnlyList<Target.AnimEntry>? ExtraAnimFiles { get; set; }

    /// <summary>default_root_bone_name of the generated AnimationList (also the bone vmdl
    /// ExtractMotion nodes operate on).</summary>
    public string DefaultRootBone { get; set; } = "pelvis";

    /// <summary>
    /// Axis/unit convention of <see cref="Rig"/>. <see cref="TargetUpAxis.YUpCm"/> (default)
    /// for cm Y-up source-space rigs (DMX declares Y-up, compiler converts);
    /// <see cref="TargetUpAxis.ZUpEngine"/> for rigs read from compiled engine models
    /// (DMX declares Z-up so no double conversion happens at compile, and cm-tuned cleanup
    /// thresholds are rescaled to inches).
    /// </summary>
    public TargetUpAxis UpAxis { get; init; } = TargetUpAxis.YUpCm;

    /// <summary>
    /// Raw bytes of the committed SAME weight blob
    /// (<c>Assets/humanoid_retargeter/dl/same_v1.weights</c>; callers do the file IO).
    /// Required only when a request selects <see cref="SolverKind.DeepLearning"/>; the
    /// solver instance is built once per batch from these bytes.
    /// </summary>
    public byte[]? DlWeights { get; init; }

    /// <summary>
    /// The shipped s&amp;box default target: rig parsed from the committed
    /// <c>Assets/humanoid_retargeter/target_rig_sbox.json</c> text (callers do the file IO),
    /// 0.3937 vmdl scale, citizen human male base model, pelvis root. Pass the committed
    /// SAME weight bytes as <paramref name="dlWeights"/> to enable the deep-learning solver.
    /// </summary>
    public static RetargetTargetSpec SboxDefault(string targetRigJson, byte[]? dlWeights = null) => new()
    {
        Rig = TargetRig.SboxDefault(targetRigJson),
        VmdlScale = SboxSourceScale,
        BaseModelPath = SboxHumanMalePath,
        DefaultRootBone = "pelvis",
        DlWeights = dlWeights,
    };

    /// <summary>
    /// The classic (4-finger) s&amp;box citizen target: rig parsed from the committed
    /// <c>Assets/humanoid_retargeter/target_rig_sbox_citizen.json</c> text (callers do the
    /// file IO), 0.3937 vmdl scale, citizen base model, pelvis root, Y-up cm. The rig has no
    /// pinky bones, so pinky roles stay unassigned — the engine's own constraints handle the
    /// pinky at runtime for models that have one. Pass the committed SAME weight bytes as
    /// <paramref name="dlWeights"/> to enable the deep-learning solver.
    /// </summary>
    public static RetargetTargetSpec SboxCitizen(string targetRigJson, byte[]? dlWeights = null) => new()
    {
        Rig = TargetRig.Load(targetRigJson),
        VmdlScale = SboxSourceScale,
        BaseModelPath = SboxCitizenPath,
        DefaultRootBone = "pelvis",
        UpAxis = TargetUpAxis.YUpCm,
        DlWeights = dlWeights,
    };
}

/// <summary>Options for <see cref="Retargeter.ConvertBatch"/> output assembly.</summary>
public sealed class BatchOptions
{
    /// <summary>
    /// When set, the batch additionally augments this existing vmdl text (all successful
    /// clips spliced into its AnimationList via <see cref="VmdlAugmenter"/>) and returns the
    /// result in <see cref="RetargetBatchResult.AugmentedVmdl"/>.
    /// </summary>
    public string? AugmentVmdlText { get; init; }

    /// <summary>Assets-relative folder the DMX files will be written to by the caller; used
    /// to build each AnimFile's <c>source_filename</c>.</summary>
    public string DmxFolderRelative { get; init; } = "animations/retargeted";

    /// <summary>
    /// Animation source paths (<c>source_filename</c> values of the augment target's existing
    /// AnimFile nodes, assets-relative) that the IO-owning caller has determined NO LONGER
    /// EXIST on disk. Stale AnimFile entries referencing them are REMOVED from the augmented
    /// vmdl (reported on <see cref="RetargetBatchResult.Warnings"/>) — one unresolvable
    /// source otherwise fails the ENTIRE vmdl recompile ("Node 'X' resolve failure"), taking
    /// every newly added animation down with it. Entries this batch overwrites (their DMX is
    /// about to be written) are never pruned. The facade itself never touches the filesystem:
    /// callers probe <see cref="Target.VmdlAugmenter.CollectAnimSourcePaths"/> results against
    /// their content roots and pass the missing ones here. Null/empty = keep everything.
    /// </summary>
    public IReadOnlyCollection<string>? MissingAnimSources { get; init; }

    /// <summary>Auto-suffix colliding clip names (<c>_2</c>, <c>_3</c>, …) across the whole
    /// batch (default on). When off, duplicate names are kept as-is.</summary>
    public bool AutoSuffixCollisions { get; init; } = true;

    /// <summary>
    /// After conversion, scan the batch's successful clip names for directional locomotion
    /// families (default OFF): <c>_N</c>/<c>_NE</c>/…/<c>_NW</c> compass suffixes and
    /// <c>_Forward</c>/<c>_Backward</c>(/<c>_Back</c>)/<c>_Left</c>/<c>_Right</c> word forms
    /// sharing a stem. Each complete family (all four cardinals) is grouped under a Folder
    /// node with a <c>2DBlend</c> wired to the citizen <c>move_x</c>/<c>move_y</c> pose
    /// parameters, replicating the shipped citizen locomotion layout (see
    /// <see cref="Target.LocomotionSetDetector"/>); detection results land on
    /// <see cref="RetargetBatchResult.LocomotionSets"/>. Custom (non-citizen) base models
    /// must declare <c>move_x</c>/<c>move_y</c> pose parameters themselves for the blends to
    /// be drivable.
    /// </summary>
    public bool DetectLocomotionSets { get; init; }
}
notpointless.chomnr_humanoid_retargeter / HumanoidRetargeter/Solve/HandGeometry.cs
Game library
#nullable enable annotations

using System.Collections.Generic;
using System.Numerics;
using HumanoidRetargeter.Mapping;
using HumanoidRetargeter.Maths;

namespace HumanoidRetargeter.Solve;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>
/// Hand rest-geometry helpers shared by <see cref="CanonicalFrames"/> (finger secondary axes)
/// and <see cref="RestNormalizer"/> (palm-down roll correction). Everything derives from joint
/// positions only — bone local axes carry no anatomical meaning on the s&amp;box rig.
/// </summary>
internal static class HandGeometry
{
    private static readonly BoneRole[] LeftProximals =
    {
        BoneRole.ThumbProxL, BoneRole.IndexProxL, BoneRole.MiddleProxL, BoneRole.RingProxL, BoneRole.PinkyProxL,
    };

    private static readonly BoneRole[] RightProximals =
    {
        BoneRole.ThumbProxR, BoneRole.IndexProxR, BoneRole.MiddleProxR, BoneRole.RingProxR, BoneRole.PinkyProxR,
    };

    // Index → pinky order; the knuckle line is taken from the first and last mapped of these.
    private static readonly BoneRole[] LeftNonThumbProximals =
    {
        BoneRole.IndexProxL, BoneRole.MiddleProxL, BoneRole.RingProxL, BoneRole.PinkyProxL,
    };

    private static readonly BoneRole[] RightNonThumbProximals =
    {
        BoneRole.IndexProxR, BoneRole.MiddleProxR, BoneRole.RingProxR, BoneRole.PinkyProxR,
    };

    /// <summary>
    /// Midpoint of all mapped finger proximal heads of one hand (the hand's anatomical
    /// "chain child" point), or null when no finger proximal is mapped.
    /// </summary>
    public static Vector3? FingerProximalMidpoint(MappingResult map, IReadOnlyList<XForm> worldRest, bool left)
    {
        var sum = Vector3.Zero;
        var count = 0;
        foreach (var role in left ? LeftProximals : RightProximals)
        {
            if (map.RoleToBone.TryGetValue(role, out var index))
            {
                sum += worldRest[index].Pos;
                count++;
            }
        }
        return count > 0 ? sum / count : null;
    }

    /// <summary>
    /// Dorsal palm normal of one hand: the unit vector pointing out of the <b>back</b> of the
    /// hand (away from the palm), or null when the hand/finger geometry is unmapped or
    /// degenerate.
    /// </summary>
    /// <remarks>
    /// Formula (mirror-consistent by construction, verified on the ActorCore fixture by the
    /// finger-curl test): <c>dorsal = sideSign · cross(knuckle, fingerDir)</c> with
    /// <c>sideSign = +1</c> left / <c>−1</c> right, <c>knuckle = IndexProx.head −
    /// PinkyProx.head</c> (first/last mapped non-thumb proximal), and <c>fingerDir =
    /// FingerProximalMidpoint − Hand.head</c>. On every fixture rig the thumb proximal lies on
    /// the −dorsal (palmar) side of the hand plane, grounding the sign anatomically. A positive
    /// rotation about a finger frame's hinge axis (frame Y = cross(dorsal, fingerChainDir))
    /// curls the fingertip toward the palm on <b>both</b> hands.
    /// </remarks>
    public static Vector3? Dorsal(MappingResult map, IReadOnlyList<XForm> worldRest, bool left)
    {
        if (!map.RoleToBone.TryGetValue(left ? BoneRole.HandL : BoneRole.HandR, out var handIndex))
            return null;
        var hand = worldRest[handIndex].Pos;

        var nonThumb = left ? LeftNonThumbProximals : RightNonThumbProximals;
        Vector3? first = null, last = null;
        foreach (var role in nonThumb)
        {
            if (!map.RoleToBone.TryGetValue(role, out var index))
                continue;
            first ??= worldRest[index].Pos;
            last = worldRest[index].Pos;
        }
        if (first is null || last is null || (first.Value - last.Value).LengthSquared() < 1e-8f)
            return null;

        var midpoint = FingerProximalMidpoint(map, worldRest, left);
        if (midpoint is null)
            return null;

        var knuckle = first.Value - last.Value;
        var fingerDir = midpoint.Value - hand;
        var raw = Vector3.Cross(knuckle, fingerDir) * (left ? 1f : -1f);
        return raw.LengthSquared() < 1e-8f ? null : Vector3.Normalize(raw);
    }
}
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/AssemblyInfo.cs
Game library
#nullable enable annotations

// Engine-agnostic retargeting core. No Sandbox/Editor references allowed in this tree:
// these sources also compile in the plain net8.0 dev harness (dev/HumanoidRetargeter.Dev.csproj).
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Formats/Gltf/GltfDocument.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
using System.Text.Json;

namespace HumanoidRetargeter.Formats.Gltf;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>One glTF node, reduced to what skeleton import needs (TRS rest + hierarchy).</summary>
internal sealed class GltfNode
{
    public string? Name;
    public int[] Children = Array.Empty<int>();
    public int Parent = -1;
    public bool HasMesh;

    // Rest local transform: TRS properties, or the decomposed "matrix" property (the spec
    // makes them exclusive; animated nodes must use TRS). Shear is not representable.
    public Vector3 Translation;                       // meters
    public Quaternion Rotation = Quaternion.Identity; // xyzw
    public Vector3 Scale = Vector3.One;
}

/// <summary>One decoded animation channel: keyframe times + values for one node property.</summary>
internal sealed class GltfChannel
{
    public required int NodeIndex;
    public required bool IsRotation;     // true = rotation (VEC4 quat), false = translation (VEC3)
    public required float[] Times;       // seconds, ascending
    public required float[] Values;      // flattened; 4 (or 3) floats per element
    public required string Interpolation; // LINEAR / STEP / CUBICSPLINE

    /// <summary>Floats per element (3 translation / 4 rotation).</summary>
    public int Comps => IsRotation ? 4 : 3;

    /// <summary>Elements stored per key: CUBICSPLINE keys carry in-tangent/value/out-tangent.</summary>
    public int ElementsPerKey => Interpolation == "CUBICSPLINE" ? 3 : 1;

    /// <summary>Number of keys.</summary>
    public int KeyCount => Times.Length;
}

/// <summary>One glTF animation with its decoded rotation/translation channels.</summary>
internal sealed class GltfAnimation
{
    public string? Name;
    public List<GltfChannel> Channels { get; } = new();
}

/// <summary>
/// Container + JSON layer of the glTF importer: parses a GLB binary container or a plain
/// .gltf JSON document, resolves buffers (GLB BIN chunk and base64 <c>data:</c> URIs — file
/// IO is banned in Code/, so external file URIs throw), and decodes nodes, skin joints and
/// animation samplers into plain arrays. Throws <see cref="FormatException"/> on anything
/// malformed or unsupported.
/// </summary>
internal sealed class GltfDocument
{
    private const uint GlbMagic = 0x46546C67;     // 'glTF' little-endian
    private const uint ChunkJson = 0x4E4F534A;    // 'JSON'
    private const uint ChunkBin = 0x004E4942;     // 'BIN\0'

    /// <summary>All nodes, indexed as in the file, with parents resolved from children lists.</summary>
    public List<GltfNode> Nodes { get; } = new();

    /// <summary>Union of all skins' joint node indices.</summary>
    public HashSet<int> SkinJoints { get; } = new();

    /// <summary>All animations with decoded rotation/translation channels (scale/weights ignored).</summary>
    public List<GltfAnimation> Animations { get; } = new();

    /// <summary>
    /// The VRM humanoid bone map authored in the file, when present: VRM bone name
    /// (<c>hips</c>, <c>leftUpperArm</c>, …) → node index. Read from BOTH extension layouts:
    /// VRM 0.x <c>extensions.VRM.humanoid.humanBones</c> (an ARRAY of
    /// <c>{ "bone": "hips", "node": 14 }</c> entries) and VRM 1.0
    /// <c>extensions.VRMC_vrm.humanoid.humanBones</c> (an OBJECT
    /// <c>{ "hips": { "node": 14 }, … }</c>). Null when the file carries neither.
    /// </summary>
    public Dictionary<string, int>? VrmHumanBones { get; private set; }

    /// <summary>Which VRM extension supplied <see cref="VrmHumanBones"/>: <c>0</c> for the
    /// 0.x <c>VRM</c> extension, <c>1</c> for the 1.0 <c>VRMC_vrm</c> extension, <c>-1</c>
    /// when none.</summary>
    public int VrmVersion { get; private set; } = -1;

    private GltfDocument()
    {
    }

    /// <summary>Parses GLB or plain-JSON glTF bytes.</summary>
    /// <exception cref="FormatException">Truncated/malformed container, invalid JSON,
    /// unresolvable buffers, or unsupported accessor layouts.</exception>
    public static GltfDocument Parse(byte[] data)
    {
        ArgumentNullException.ThrowIfNull(data);

        byte[] json;
        byte[]? bin = null;
        if (data.Length >= 4 && ReadU32(data, 0) == GlbMagic)
            (json, bin) = ParseGlbContainer(data);
        else
            json = data;

        JsonElement root;
        try
        {
            // Parse via string: Memory<T>/ReadOnlyMemory<T> are not on the s&box runtime
            // whitelist (SB1000), and the string path also lets us strip a UTF-8 BOM
            // (Utf8JsonReader rejects raw BOM bytes). Clone detaches from the disposed
            // JsonDocument.
            var text = System.Text.Encoding.UTF8.GetString(json).TrimStart('\uFEFF');
            using var doc = JsonDocument.Parse(text);
            root = doc.RootElement.Clone();
        }
        catch (JsonException e)
        {
            throw new FormatException($"glTF: invalid JSON ({e.Message})");
        }

        if (root.ValueKind != JsonValueKind.Object || !root.TryGetProperty("asset", out _))
            throw new FormatException("glTF: missing required 'asset' object (not a glTF file?).");

        var document = new GltfDocument();
        var buffers = ResolveBuffers(root, bin);
        document.ReadNodes(root);
        document.ReadSkins(root);
        document.ReadAnimations(root, buffers);
        document.ReadVrmHumanoid(root);
        return document;
    }

    // ================================================================== VRM humanoid

    /// <summary>
    /// Reads the authored humanoid bone map of a VRM file (a .vrm is a regular glTF 2.0/GLB
    /// container plus a VRM extension). VRM 1.0's <c>VRMC_vrm</c> wins when both extensions
    /// are present. Defensive throughout: malformed entries and out-of-range node indices
    /// are skipped (a broken bone map degrades to the regular detection cascade rather than
    /// failing the import).
    /// </summary>
    private void ReadVrmHumanoid(JsonElement root)
    {
        if (!root.TryGetProperty("extensions", out var extensions)
            || extensions.ValueKind != JsonValueKind.Object)
            return;

        // ---- VRM 1.0: extensions.VRMC_vrm.humanoid.humanBones = { "<bone>": { "node": n } } ----
        if (TryGetHumanBones(extensions, "VRMC_vrm", out var humanBones1)
            && humanBones1.ValueKind == JsonValueKind.Object)
        {
            var map = new Dictionary<string, int>(StringComparer.Ordinal);
            foreach (var property in humanBones1.EnumerateObject())
            {
                if (property.Value.ValueKind == JsonValueKind.Object
                    && property.Value.TryGetProperty("node", out var node)
                    && node.ValueKind == JsonValueKind.Number
                    && node.TryGetInt32(out var index)
                    && index >= 0 && index < Nodes.Count)
                {
                    map[property.Name] = index;
                }
            }
            if (map.Count > 0)
            {
                VrmHumanBones = map;
                VrmVersion = 1;
                return;
            }
        }

        // ---- VRM 0.x: extensions.VRM.humanoid.humanBones = [ { "bone": "...", "node": n } ] ----
        if (TryGetHumanBones(extensions, "VRM", out var humanBones0)
            && humanBones0.ValueKind == JsonValueKind.Array)
        {
            var map = new Dictionary<string, int>(StringComparer.Ordinal);
            foreach (var entry in humanBones0.EnumerateArray())
            {
                if (entry.ValueKind == JsonValueKind.Object
                    && entry.TryGetProperty("bone", out var bone)
                    && bone.ValueKind == JsonValueKind.String
                    && entry.TryGetProperty("node", out var node)
                    && node.ValueKind == JsonValueKind.Number
                    && node.TryGetInt32(out var index)
                    && index >= 0 && index < Nodes.Count)
                {
                    map[bone.GetString()!] = index;
                }
            }
            if (map.Count > 0)
            {
                VrmHumanBones = map;
                VrmVersion = 0;
            }
        }
    }

    private static bool TryGetHumanBones(JsonElement extensions, string extensionName, out JsonElement humanBones)
    {
        humanBones = default;
        return extensions.TryGetProperty(extensionName, out var vrm)
            && vrm.ValueKind == JsonValueKind.Object
            && vrm.TryGetProperty("humanoid", out var humanoid)
            && humanoid.ValueKind == JsonValueKind.Object
            && humanoid.TryGetProperty("humanBones", out humanBones);
    }

    // ================================================================== GLB container

    /// <summary>GLB layout: 12-byte header (magic 'glTF', u32 version = 2, u32 length),
    /// then chunks of (u32 length, u32 type, bytes): one JSON chunk, optionally one BIN.</summary>
    private static (byte[] Json, byte[]? Bin) ParseGlbContainer(byte[] data)
    {
        if (data.Length < 12)
            throw new FormatException("GLB: truncated header (need 12 bytes).");

        uint version = ReadU32(data, 4);
        if (version != 2)
            throw new FormatException($"GLB: unsupported container version {version} (expected 2).");

        long declared = ReadU32(data, 8);
        if (declared > data.Length)
            throw new FormatException(
                $"GLB: truncated file (header declares {declared} bytes, got {data.Length}).");

        byte[]? json = null, bin = null;
        long offset = 12;
        while (offset + 8 <= declared)
        {
            long length = ReadU32(data, (int)offset);
            uint type = ReadU32(data, (int)offset + 4);
            offset += 8;
            if (offset + length > data.Length)
                throw new FormatException("GLB: truncated chunk (declared length exceeds the file).");

            if (type == ChunkJson && json is null)
                json = data.AsSpan((int)offset, (int)length).ToArray();
            else if (type == ChunkBin && bin is null)
                bin = data.AsSpan((int)offset, (int)length).ToArray();
            // Unknown chunk types are skipped per spec.

            offset += length + (length % 4 == 0 ? 0 : 4 - length % 4); // chunks are 4-aligned
        }

        if (json is null)
            throw new FormatException("GLB: no JSON chunk found.");
        return (json, bin);
    }

    private static uint ReadU32(byte[] data, int offset)
        => (uint)(data[offset] | data[offset + 1] << 8 | data[offset + 2] << 16 | data[offset + 3] << 24);

    // ================================================================== buffers

    /// <summary>
    /// Resolves every entry of <c>buffers</c>: no <c>uri</c> = the GLB BIN chunk (spec: only
    /// buffer 0 may do this), <c>data:</c> URIs are base64-decoded inline. External file
    /// URIs are NOT supported — this library does no file IO; users should export .glb.
    /// </summary>
    private static List<byte[]> ResolveBuffers(JsonElement root, byte[]? bin)
    {
        var buffers = new List<byte[]>();
        if (!root.TryGetProperty("buffers", out var array) || array.ValueKind != JsonValueKind.Array)
            return buffers;

        foreach (var buffer in array.EnumerateArray())
        {
            if (!buffer.TryGetProperty("uri", out var uriProp))
            {
                buffers.Add(bin ?? throw new FormatException(
                    "glTF: buffer has no uri but the file has no GLB BIN chunk."));
                continue;
            }

            var uri = uriProp.GetString() ?? "";
            if (uri.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
            {
                int comma = uri.IndexOf(',');
                if (comma < 0 || !uri[..comma].EndsWith(";base64", StringComparison.OrdinalIgnoreCase))
                    throw new FormatException("glTF: only base64 data: URIs are supported for buffers.");
                try
                {
                    buffers.Add(Convert.FromBase64String(uri[(comma + 1)..]));
                }
                catch (Exception e) when (e is FormatException or ArgumentException)
                {
                    throw new FormatException("glTF: invalid base64 in buffer data: URI.");
                }
            }
            else
            {
                throw new FormatException(
                    $"glTF: buffer references an external file ('{uri}') which this importer cannot "
                    + "read (no file IO). Export as .glb (binary, self-contained) instead.");
            }
        }
        return buffers;
    }

    // ================================================================== nodes + skins

    private void ReadNodes(JsonElement root)
    {
        if (!root.TryGetProperty("nodes", out var array) || array.ValueKind != JsonValueKind.Array)
            return;

        Span<float> m = stackalloc float[16]; // matrix scratch (outside the loop: CA2014)
        foreach (var n in array.EnumerateArray())
        {
            var node = new GltfNode
            {
                Name = n.TryGetProperty("name", out var name) ? name.GetString() : null,
                HasMesh = n.TryGetProperty("mesh", out _),
            };

            if (n.TryGetProperty("children", out var children) && children.ValueKind == JsonValueKind.Array)
            {
                var list = new List<int>();
                foreach (var c in children.EnumerateArray())
                    list.Add(c.GetInt32());
                node.Children = list.ToArray();
            }

            if (n.TryGetProperty("matrix", out var matrix) && matrix.ValueKind == JsonValueKind.Array)
            {
                // Column-major 16 floats; the element order maps 1:1 onto System.Numerics'
                // row-vector matrices (translation in elements 12..14 either way).
                int i = 0;
                foreach (var v in matrix.EnumerateArray())
                {
                    if (i >= 16)
                        break;
                    m[i++] = v.GetSingle();
                }
                if (i < 16)
                    throw new FormatException("glTF: node matrix has fewer than 16 elements.");
                var local = new Matrix4x4(
                    m[0], m[1], m[2], m[3],
                    m[4], m[5], m[6], m[7],
                    m[8], m[9], m[10], m[11],
                    m[12], m[13], m[14], m[15]);
                if (Matrix4x4.Decompose(local, out var scale, out var rot, out var pos))
                {
                    node.Translation = pos;
                    node.Rotation = rot;
                    node.Scale = scale;
                }
                else
                {
                    node.Translation = local.Translation; // degenerate: keep position at least
                }
            }
            else
            {
                node.Translation = ReadVec3(n, "translation", Vector3.Zero);
                node.Scale = ReadVec3(n, "scale", Vector3.One);
                if (n.TryGetProperty("rotation", out var r) && r.ValueKind == JsonValueKind.Array
                    && r.GetArrayLength() >= 4)
                {
                    node.Rotation = new Quaternion(
                        r[0].GetSingle(), r[1].GetSingle(), r[2].GetSingle(), r[3].GetSingle());
                }
            }

            Nodes.Add(node);
        }

        // Resolve parents (per spec a node is referenced by at most one other node's children).
        for (int i = 0; i < Nodes.Count; i++)
        {
            foreach (var child in Nodes[i].Children)
            {
                if (child < 0 || child >= Nodes.Count)
                    throw new FormatException($"glTF: node {i} references nonexistent child {child}.");
                if (Nodes[child].Parent < 0)
                    Nodes[child].Parent = i;
            }
        }
    }

    private static Vector3 ReadVec3(JsonElement element, string property, Vector3 fallback)
    {
        if (!element.TryGetProperty(property, out var v) || v.ValueKind != JsonValueKind.Array
            || v.GetArrayLength() < 3)
            return fallback;
        return new Vector3(v[0].GetSingle(), v[1].GetSingle(), v[2].GetSingle());
    }

    private void ReadSkins(JsonElement root)
    {
        if (!root.TryGetProperty("skins", out var array) || array.ValueKind != JsonValueKind.Array)
            return;

        foreach (var skin in array.EnumerateArray())
        {
            if (!skin.TryGetProperty("joints", out var joints) || joints.ValueKind != JsonValueKind.Array)
                continue;
            foreach (var j in joints.EnumerateArray())
            {
                int index = j.GetInt32();
                if (index >= 0 && index < Nodes.Count)
                    SkinJoints.Add(index);
            }
        }
    }

    // ================================================================== animations

    private void ReadAnimations(JsonElement root, List<byte[]> buffers)
    {
        if (!root.TryGetProperty("animations", out var array) || array.ValueKind != JsonValueKind.Array)
            return;

        root.TryGetProperty("accessors", out var accessors);
        root.TryGetProperty("bufferViews", out var views);

        foreach (var a in array.EnumerateArray())
        {
            var animation = new GltfAnimation
            {
                Name = a.TryGetProperty("name", out var name) ? name.GetString() : null,
            };

            if (!a.TryGetProperty("channels", out var channels) || !a.TryGetProperty("samplers", out var samplers))
            {
                Animations.Add(animation);
                continue;
            }

            foreach (var channel in channels.EnumerateArray())
            {
                if (!channel.TryGetProperty("target", out var target)
                    || !target.TryGetProperty("node", out var nodeProp)
                    || !target.TryGetProperty("path", out var pathProp))
                    continue; // extension targets (e.g. KHR_animation_pointer) are ignored

                var path = pathProp.GetString();
                if (path is not ("rotation" or "translation"))
                    continue; // scale / weights channels are ignored by design

                int node = nodeProp.GetInt32();
                if (node < 0 || node >= Nodes.Count)
                    continue;

                int samplerIndex = channel.TryGetProperty("sampler", out var s) ? s.GetInt32() : -1;
                if (samplerIndex < 0 || samplerIndex >= samplers.GetArrayLength())
                    throw new FormatException("glTF: animation channel references a nonexistent sampler.");
                var sampler = samplers[samplerIndex];

                var interpolation = sampler.TryGetProperty("interpolation", out var interp)
                    ? interp.GetString() ?? "LINEAR"
                    : "LINEAR";

                bool isRotation = path == "rotation";
                int comps = isRotation ? 4 : 3;

                var times = ReadAccessor(accessors, views, buffers,
                    RequiredInt(sampler, "input", "animation sampler"), 1, normalizedAllowed: false);
                var values = ReadAccessor(accessors, views, buffers,
                    RequiredInt(sampler, "output", "animation sampler"), comps, normalizedAllowed: isRotation);

                int elementsPerKey = interpolation == "CUBICSPLINE" ? 3 : 1;
                if (times.Length == 0 || values.Length < times.Length * elementsPerKey * comps)
                    continue; // empty or under-filled sampler: nothing usable

                animation.Channels.Add(new GltfChannel
                {
                    NodeIndex = node,
                    IsRotation = isRotation,
                    Times = times,
                    Values = values,
                    Interpolation = interpolation,
                });
            }

            Animations.Add(animation);
        }
    }

    private static int RequiredInt(JsonElement element, string property, string context)
    {
        if (!element.TryGetProperty(property, out var v))
            throw new FormatException($"glTF: {context} is missing '{property}'.");
        return v.GetInt32();
    }

    // ================================================================== accessors

    /// <summary>
    /// Decodes an accessor to floats. Component types: f32 directly; normalized i8/u8/i16/u16
    /// per the spec's normalization rules when <paramref name="normalizedAllowed"/> (rotation
    /// outputs); anything else throws. Honors accessor/bufferView byte offsets and an
    /// explicit byteStride. Sparse accessors are not supported.
    /// </summary>
    private static float[] ReadAccessor(
        JsonElement accessors, JsonElement views, List<byte[]> buffers,
        int accessorIndex, int expectedComps, bool normalizedAllowed)
    {
        if (accessors.ValueKind != JsonValueKind.Array || accessorIndex < 0
            || accessorIndex >= accessors.GetArrayLength())
            throw new FormatException($"glTF: accessor {accessorIndex} does not exist.");
        var accessor = accessors[accessorIndex];

        if (accessor.TryGetProperty("sparse", out _))
            throw new FormatException("glTF: sparse accessors are not supported.");

        var type = accessor.TryGetProperty("type", out var t) ? t.GetString() : null;
        int comps = type switch
        {
            "SCALAR" => 1,
            "VEC3" => 3,
            "VEC4" => 4,
            _ => throw new FormatException($"glTF: unsupported accessor type '{type}'."),
        };
        if (comps != expectedComps)
            throw new FormatException(
                $"glTF: accessor {accessorIndex} is {type}, expected {expectedComps} component(s).");

        int count = RequiredInt(accessor, "count", "accessor");
        int componentType = RequiredInt(accessor, "componentType", "accessor");
        bool normalized = accessor.TryGetProperty("normalized", out var n) && n.GetBoolean();

        // The count is attacker-controlled: validate it BEFORE any allocation sized by it.
        // Negative would throw OverflowException from the array allocation (breaking the
        // FormatException malformed-file contract); huge would OOM; count * comps can wrap.
        if (count < 0)
            throw new FormatException($"glTF: accessor {accessorIndex} has a negative count ({count}).");

        int compSize = componentType switch
        {
            5126 => 4,            // FLOAT
            5120 or 5121 => 1,    // BYTE / UNSIGNED_BYTE
            5122 or 5123 => 2,    // SHORT / UNSIGNED_SHORT
            _ => throw new FormatException(
                $"glTF: unsupported accessor componentType {componentType}."),
        };
        if (componentType != 5126 && !(normalized && normalizedAllowed))
            throw new FormatException(
                $"glTF: accessor {accessorIndex} must be float (or a normalized integer "
                + "rotation output).");

        int elementSize = comps * compSize;

        if (!accessor.TryGetProperty("bufferView", out var viewIndexProp))
        {
            // Zero-filled when no bufferView (legal per spec) — but then nothing backs the
            // count, so cap it by the file's total decoded buffer bytes (a real file's
            // accessors never outgrow its payload; a small floor keeps tiny legitimate
            // zero-filled accessors working in buffer-less documents).
            long totalBufferBytes = 0;
            foreach (var b in buffers)
                totalBufferBytes += b.Length;
            long capacity = Math.Min(
                Math.Max(totalBufferBytes / elementSize, 65536),
                int.MaxValue / comps); // keeps count * comps int-representable
            if (count > capacity)
                throw new FormatException(
                    $"glTF: accessor {accessorIndex} count {count} exceeds what the file's "
                    + "buffers could back (malformed or hostile file).");
            return new float[checked(count * comps)];
        }

        int viewIndex = viewIndexProp.GetInt32();
        if (views.ValueKind != JsonValueKind.Array || viewIndex < 0 || viewIndex >= views.GetArrayLength())
            throw new FormatException($"glTF: bufferView {viewIndex} does not exist.");
        var view = views[viewIndex];

        int bufferIndex = RequiredInt(view, "buffer", "bufferView");
        if (bufferIndex < 0 || bufferIndex >= buffers.Count)
            throw new FormatException($"glTF: buffer {bufferIndex} does not exist.");
        var buffer = buffers[bufferIndex];

        int viewOffset = view.TryGetProperty("byteOffset", out var vo) ? vo.GetInt32() : 0;
        int accessorOffset = accessor.TryGetProperty("byteOffset", out var ao) ? ao.GetInt32() : 0;
        int stride = view.TryGetProperty("byteStride", out var st) ? st.GetInt32() : elementSize;
        if (stride < elementSize)
            throw new FormatException("glTF: bufferView byteStride is smaller than the element size.");

        // Bounds check in long arithmetic BEFORE allocating: the backing range must fit the
        // buffer, which also caps count at buffer.Length / stride (+1) — so the allocation
        // below is bounded by the actual file size and checked() can no longer overflow.
        long start = (long)viewOffset + accessorOffset;
        long end = start + (long)(count - 1) * stride + elementSize;
        if (count > 0 && (start < 0 || end > buffer.Length))
            throw new FormatException(
                $"glTF: accessor {accessorIndex} reads past the end of its buffer (truncated file?).");

        var result = new float[checked(count * comps)];
        for (int element = 0; element < count; element++)
        {
            int offset = (int)(start + (long)element * stride);
            for (int c = 0; c < comps; c++)
            {
                int at = offset + c * compSize;
                result[element * comps + c] = componentType switch
                {
                    5126 => BitConverter.ToSingle(buffer, at),
                    5120 => MathF.Max((sbyte)buffer[at] / 127f, -1f),
                    5121 => buffer[at] / 255f,
                    5122 => MathF.Max(BitConverter.ToInt16(buffer, at) / 32767f, -1f),
                    _ => BitConverter.ToUInt16(buffer, at) / 65535f,
                };
            }
        }
        return result;
    }
}
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Maths/XForm.cs
Game library
#nullable enable annotations

using System;
using System.Numerics;

namespace HumanoidRetargeter.Maths;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>
/// A rigid transform: rotation followed by translation (no scale or shear).
/// </summary>
/// <remarks>
/// Project conventions (fixed for the whole library):
/// <list type="bullet">
/// <item>Positions are centimeters.</item>
/// <item>Quaternions are XYZW unit quaternions (<see cref="System.Numerics.Quaternion"/> native layout).</item>
/// <item>Column-vector convention: a local-space point maps to outer space as
/// <c>p' = rotate(Rot, p) + Pos</c>, and <c>a * b</c> on quaternions applies <c>b</c> first.</item>
/// </list>
/// </remarks>
public struct XForm : IEquatable<XForm>
{
    /// <summary>Translation component, in centimeters.</summary>
    public Vector3 Pos;

    /// <summary>Rotation component, XYZW unit quaternion.</summary>
    public Quaternion Rot;

    /// <summary>Creates a transform from a translation and a rotation.</summary>
    public XForm(Vector3 pos, Quaternion rot)
    {
        Pos = pos;
        Rot = rot;
    }

    /// <summary>The identity transform (zero translation, identity rotation).</summary>
    public static XForm Identity => new(Vector3.Zero, Quaternion.Identity);

    /// <summary>
    /// Composes a parent transform with a child-local transform, producing the child's
    /// transform in the parent's outer space (world = parent ∘ local):
    /// <c>pos = parent.Pos + rotate(parent.Rot, local.Pos)</c>, <c>rot = parent.Rot * local.Rot</c>.
    /// The resulting rotation is re-normalized to suppress floating-point drift.
    /// </summary>
    public static XForm Compose(in XForm parent, in XForm local)
        => new(
            parent.Pos + Vector3.Transform(local.Pos, parent.Rot),
            MathQ.Normalize(parent.Rot * local.Rot));

    /// <summary>
    /// Returns the inverse transform, such that <c>Compose(x, x.Inverse())</c> and
    /// <c>Compose(x.Inverse(), x)</c> are both identity.
    /// </summary>
    public readonly XForm Inverse()
    {
        var invRot = Quaternion.Conjugate(MathQ.Normalize(Rot));
        return new XForm(-Vector3.Transform(Pos, invRot), invRot);
    }

    /// <summary>
    /// Re-expresses a world transform relative to a parent world transform; the inverse of
    /// <see cref="Compose"/>: <c>ToLocal(p, Compose(p, l)) == l</c>.
    /// </summary>
    public static XForm ToLocal(in XForm parentWorld, in XForm world)
        => Compose(parentWorld.Inverse(), world);

    /// <summary>Transforms a point from this transform's local space to its outer space.</summary>
    public readonly Vector3 TransformPoint(Vector3 point) => Pos + Vector3.Transform(point, Rot);

    /// <summary>Rotates a direction vector by this transform's rotation (translation ignored).</summary>
    public readonly Vector3 TransformVector(Vector3 vector) => Vector3.Transform(vector, Rot);

    /// <inheritdoc />
    public readonly bool Equals(XForm other) => Pos.Equals(other.Pos) && Rot.Equals(other.Rot);

    /// <inheritdoc />
    public override readonly bool Equals(object? obj) => obj is XForm other && Equals(other);

    /// <inheritdoc />
    public override readonly int GetHashCode() => HashCode.Combine(Pos, Rot);

    /// <summary>Componentwise equality (no tolerance).</summary>
    public static bool operator ==(XForm left, XForm right) => left.Equals(right);

    /// <summary>Componentwise inequality (no tolerance).</summary>
    public static bool operator !=(XForm left, XForm right) => !left.Equals(right);

    /// <inheritdoc />
    public override readonly string ToString() => $"XForm(Pos={Pos}, Rot={Rot})";
}
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Solve/CanonicalFrames.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Numerics;
using HumanoidRetargeter.Mapping;
using HumanoidRetargeter.Maths;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;

namespace HumanoidRetargeter.Solve;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>
/// Canonical anatomical frames: one world-space rest basis per mapped <see cref="BoneRole"/>,
/// derived from rest <b>geometry</b> (joint head positions) of any rig plus its mapping.
/// Built with the same deterministic convention on source and target, so world-rotation deltas
/// conjugated through these frames transfer between rigs with different bone local axes
/// (the s&amp;box Citizen rig's local axes encode no anatomy — bone-Y points chest-forward).
/// </summary>
/// <remarks>
/// <para><b>Frame convention</b> — for each role the frame quaternion <c>F</c> rotates unit
/// axes onto: <c>X = P</c> (primary), <c>Z</c> = the secondary hint <c>S</c> orthonormalized
/// against <c>P</c>, <c>Y = cross(Z, X)</c> (right-handed; for fingers Y is the curl hinge).</para>
/// <para><b>Primary axis P</b> = normalize(chain-child head − bone head), where the chain
/// child is the next <i>mapped</i> role down the bone's anatomical chain
/// (Hips→Spine0..4→Neck→Head; Clavicle→UpperArm→LowerArm→Hand; UpperLeg→LowerLeg→Foot→Toe;
/// per-finger Meta→Prox→Mid→Dist). Tips: the Head inherits its previous chain segment
/// (neck→head — the skull-base axis, real anatomy; measured 0–27° forward of character up
/// across neutral-rest rigs), falling back to a virtual character-up extension only when
/// that segment is absent or degenerate; Hand points at the midpoint of its mapped finger
/// proximals (else along the forearm); Foot without a toe and Toe extend along character
/// forward; finger distals extend along their previous segment. Other bones with nothing
/// mapped below inherit the previous chain segment's direction.</para>
/// <para><b>Secondary axis S</b> by bone class: spine/neck/head/hips and legs use character
/// forward (knee hinge lateral); clavicle/arms/hands use <c>cross(P, characterUp)</c>
/// (elbow hinge ⊥ limb in the character's horizontal plane at T-pose), falling back to
/// character forward when P is vertical; feet/toes use character up; fingers use the hand's
/// dorsal palm normal (see <see cref="HandGeometry.Dorsal"/>) so a positive rotation about
/// frame Y curls fingertips toward the palm on both hands.</para>
/// <para>When used by the solver, build the frames on the <see cref="RestNormalizer"/>-
/// normalized rest via <see cref="Build(SkeletonModel, MappingResult, IReadOnlyList{XForm})"/>;
/// this class itself just measures whatever rest it is given.</para>
/// </remarks>
public sealed class CanonicalFrames
{
    private readonly Dictionary<BoneRole, Quaternion> _frames;
    private readonly HashSet<BoneRole> _virtualPrimary;

    /// <summary>Character forward (the direction the toes point at rest), unit length.</summary>
    public Vector3 CharacterForward { get; }

    /// <summary>Character up (hips toward shoulders at rest), unit length.</summary>
    public Vector3 CharacterUp { get; }

    /// <summary>Rest hip height above the lowest foot/toe point, along character up, cm.</summary>
    public float HipHeight { get; }

    private CanonicalFrames(
        Dictionary<BoneRole, Quaternion> frames, HashSet<BoneRole> virtualPrimary,
        Vector3 forward, Vector3 up, float hipHeight)
    {
        _frames = frames;
        _virtualPrimary = virtualPrimary;
        CharacterForward = forward;
        CharacterUp = up;
        HipHeight = hipHeight;
    }

    /// <summary>True when a canonical frame exists for <paramref name="role"/> (the role is
    /// mapped and its chain geometry is resolvable).</summary>
    public bool Has(BoneRole role) => _frames.ContainsKey(role);

    /// <summary>
    /// True when the role's primary axis is a <b>virtual</b> character-axis extension rather
    /// than real joint geometry (e.g. a Foot with no mapped Toe extends along character
    /// forward; mapped Toes extend along character forward by convention; a Head whose
    /// neck→head segment is degenerate extends along character up). Absolute direction
    /// matching against a virtual primary imposes an arbitrary direction, so the solver
    /// falls back to delta transfer when the source is virtual but the target is real
    /// (see <see cref="GeometricSolver"/> remarks).
    /// </summary>
    public bool HasVirtualPrimary(BoneRole role) => _virtualPrimary.Contains(role);

    /// <summary>The world-space canonical rest frame of <paramref name="role"/>.</summary>
    /// <exception cref="InvalidOperationException">Thrown when <see cref="Has"/> is false for
    /// the role.</exception>
    public Quaternion WorldFrameOf(BoneRole role)
        => _frames.TryGetValue(role, out var frame)
            ? frame
            : throw new InvalidOperationException($"No canonical frame for role {role} (not mapped or unresolvable).");

    /// <summary>Builds frames from the skeleton's bind rest (<c>skeleton.RestWorld</c>).</summary>
    public static CanonicalFrames Build(SkeletonModel skeleton, MappingResult map)
        => Build(skeleton, map, (skeleton ?? throw new ArgumentNullException(nameof(skeleton))).RestWorld);

    /// <summary>
    /// Builds frames from explicit rest world transforms (e.g. a <see cref="RestPose"/>
    /// produced by <see cref="RestNormalizer"/>), indexed like <c>skeleton.Bones</c>.
    /// </summary>
    public static CanonicalFrames Build(
        SkeletonModel skeleton, MappingResult map, IReadOnlyList<XForm> worldRest)
    {
        ArgumentNullException.ThrowIfNull(skeleton);
        ArgumentNullException.ThrowIfNull(map);
        ArgumentNullException.ThrowIfNull(worldRest);
        if (worldRest.Count != skeleton.Count)
            throw new ArgumentException(
                $"worldRest has {worldRest.Count} entries for a {skeleton.Count}-bone skeleton.");

        var cf = CharacterFrame.Compute(skeleton, map, worldRest);
        var frames = new Dictionary<BoneRole, Quaternion>();
        var virtualPrimary = new HashSet<BoneRole>();

        foreach (var (chain, kind, left) in Chains())
            BuildChainFrames(chain, kind, left, map, worldRest, cf, frames, virtualPrimary);

        return new CanonicalFrames(frames, virtualPrimary, cf.Forward, cf.Up, cf.HipHeight);
    }

    // ---------------------------------------------------------------- chain construction

    private enum ChainKind
    {
        Body,
        Arm,
        Leg,
        Finger,
    }

    private static IEnumerable<(BoneRole[] Chain, ChainKind Kind, bool Left)> Chains()
    {
        yield return (new[]
        {
            BoneRole.Hips, BoneRole.Spine0, BoneRole.Spine1, BoneRole.Spine2, BoneRole.Spine3,
            BoneRole.Spine4, BoneRole.Neck, BoneRole.Head,
        }, ChainKind.Body, false);

        foreach (var left in new[] { true, false })
        {
            var s = left ? "L" : "R";
            yield return (new[]
            {
                Role("Clavicle", s), Role("UpperArm", s), Role("LowerArm", s), Role("Hand", s),
            }, ChainKind.Arm, left);
            yield return (new[]
            {
                Role("UpperLeg", s), Role("LowerLeg", s), Role("Foot", s), Role("Toe", s),
            }, ChainKind.Leg, left);

            foreach (var finger in new[] { "Thumb", "Index", "Middle", "Ring", "Pinky" })
            {
                yield return (new[]
                {
                    Role(finger + "Meta", s), Role(finger + "Prox", s),
                    Role(finger + "Mid", s), Role(finger + "Dist", s),
                }, ChainKind.Finger, left);
            }
        }
    }

    private static BoneRole Role(string baseName, string side) => Enum.Parse<BoneRole>(baseName + side);

    private static void BuildChainFrames(
        BoneRole[] chain, ChainKind kind, bool left, MappingResult map,
        IReadOnlyList<XForm> worldRest, CharacterFrame cf, Dictionary<BoneRole, Quaternion> frames,
        HashSet<BoneRole> virtualPrimary)
    {
        // Collapse to the mapped chain members; gaps are skipped so e.g. a missing Spine1
        // makes Spine0 point straight at Spine2.
        var mapped = new List<(BoneRole Role, Vector3 Pos)>(chain.Length);
        foreach (var role in chain)
        {
            if (map.RoleToBone.TryGetValue(role, out var index))
                mapped.Add((role, worldRest[index].Pos));
        }

        Vector3? dorsal = kind == ChainKind.Finger ? HandGeometry.Dorsal(map, worldRest, left) : null;

        for (var i = 0; i < mapped.Count; i++)
        {
            var (role, pos) = mapped[i];
            Vector3? prevDir = i > 0 ? pos - mapped[i - 1].Pos : null;

            var (primary, isVirtual) = i + 1 < mapped.Count
                ? ((Vector3?)(mapped[i + 1].Pos - pos), false)
                : TipPrimary(kind, role, pos, prevDir, left, map, worldRest, cf);
            if (primary is null || primary.Value.LengthSquared() < 1e-8f)
                continue;

            var secondary = Secondary(kind, role, primary.Value, dorsal, cf);
            frames[role] = BasisFromPrimarySecondary(primary.Value, secondary, cf);
            if (isVirtual)
                virtualPrimary.Add(role);
        }
    }

    /// <summary>Primary direction for the last mapped bone of a chain. <c>Virtual</c> is true
    /// when the direction is a character-axis convention rather than this rig's real joint
    /// geometry (see <see cref="HasVirtualPrimary"/>).</summary>
    private static (Vector3? Dir, bool Virtual) TipPrimary(
        ChainKind kind, BoneRole role, Vector3 pos, Vector3? prevDir, bool left,
        MappingResult map, IReadOnlyList<XForm> worldRest, CharacterFrame cf)
    {
        switch (kind)
        {
            case ChainKind.Body:
                // Head: its primary is the REAL previous chain segment (neck→head — the
                // skull-base axis; the rest lean of that segment is head-joint-placement
                // anatomy the delta transfer modes reference, and the posed-rest gaze
                // fallback measures — see GeometricSolver remarks). Only a degenerate or
                // absent segment falls back to the virtual character-up extension (e.g. a
                // head stacked on the neck). A body chain that ends early keeps its
                // previous segment direction, defaulting to up.
                if (role == BoneRole.Head)
                    return prevDir is { } seg && seg.LengthSquared() >= 1e-8f ? (seg, false) : (cf.Up, true);
                return prevDir is not null ? (prevDir, false) : (cf.Up, true);

            case ChainKind.Arm:
                if (role is BoneRole.HandL or BoneRole.HandR)
                {
                    var knuckles = HandGeometry.FingerProximalMidpoint(map, worldRest, left);
                    if (knuckles is not null)
                        return (knuckles.Value - pos, false);
                }
                return (prevDir, false); // along the forearm / previous segment; null → no frame

            case ChainKind.Leg:
                // Foot without a mapped toe, and the toe itself, extend along character
                // forward (toes point forward by the character-frame convention).
                if (role is BoneRole.FootL or BoneRole.FootR or BoneRole.ToeL or BoneRole.ToeR)
                    return (cf.Forward, true);
                return (prevDir, false);

            case ChainKind.Finger:
                if (prevDir is not null)
                    return (prevDir, false); // distal tip extrapolates its previous segment
                // Single mapped finger bone: point away from the hand when possible.
                var handRole = left ? BoneRole.HandL : BoneRole.HandR;
                if (map.RoleToBone.TryGetValue(handRole, out var handIndex))
                    return (pos - worldRest[handIndex].Pos, false);
                return (null, false);

            default:
                return (null, false);
        }
    }

    /// <summary>Secondary (Z) hint by bone class; see the class remarks for rationale.</summary>
    private static Vector3 Secondary(ChainKind kind, BoneRole role, Vector3 primary, Vector3? dorsal, CharacterFrame cf)
    {
        switch (kind)
        {
            case ChainKind.Body:
                return cf.Forward;

            case ChainKind.Arm:
            {
                var hinge = Vector3.Cross(Vector3.Normalize(primary), cf.Up);
                return hinge.LengthSquared() < 1e-6f ? cf.Forward : hinge;
            }

            case ChainKind.Leg:
                // Feet and toes lie near the character-forward direction, so they use up as
                // the secondary; thigh/calf use forward (knee hinge lateral).
                if (role is BoneRole.FootL or BoneRole.FootR or BoneRole.ToeL or BoneRole.ToeR)
                    return cf.Up;
                return cf.Forward;

            case ChainKind.Finger:
                if (dorsal is not null)
                    return dorsal.Value;
                var fallback = Vector3.Cross(Vector3.Normalize(primary), cf.Up);
                return fallback.LengthSquared() < 1e-6f ? cf.Forward : fallback;

            default:
                return cf.Forward;
        }
    }

    /// <summary>
    /// Orthonormal right-handed basis: <c>X = normalize(primary)</c>, <c>Z = secondary</c>
    /// Gram-Schmidt-orthonormalized against X (falling back to character forward, then up,
    /// then world axes when degenerate), <c>Y = cross(Z, X)</c>.
    /// </summary>
    private static Quaternion BasisFromPrimarySecondary(Vector3 primary, Vector3 secondary, CharacterFrame cf)
    {
        var x = Vector3.Normalize(primary);

        var z = Orthonormalized(secondary, x)
            ?? Orthonormalized(cf.Forward, x)
            ?? Orthonormalized(cf.Up, x)
            ?? Orthonormalized(Vector3.UnitZ, x)
            ?? Orthonormalized(Vector3.UnitX, x)!.Value;

        var y = Vector3.Cross(z, x);

        // System.Numerics matrices act on row vectors: the rows are the images of the unit
        // axes under the rotation (row1 = R*X, row2 = R*Y, row3 = R*Z).
        var m = new Matrix4x4(
            x.X, x.Y, x.Z, 0f,
            y.X, y.Y, y.Z, 0f,
            z.X, z.Y, z.Z, 0f,
            0f, 0f, 0f, 1f);

        return MathQ.Normalize(Quaternion.CreateFromRotationMatrix(m));
    }

    private static Vector3? Orthonormalized(Vector3 hint, Vector3 x)
    {
        var z = hint - x * Vector3.Dot(hint, x);
        return z.LengthSquared() < 1e-6f ? null : Vector3.Normalize(z);
    }
}
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Solve/HandGeometry.cs
Game library
#nullable enable annotations

using System.Collections.Generic;
using System.Numerics;
using HumanoidRetargeter.Mapping;
using HumanoidRetargeter.Maths;

namespace HumanoidRetargeter.Solve;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>
/// Hand rest-geometry helpers shared by <see cref="CanonicalFrames"/> (finger secondary axes)
/// and <see cref="RestNormalizer"/> (palm-down roll correction). Everything derives from joint
/// positions only — bone local axes carry no anatomical meaning on the s&amp;box rig.
/// </summary>
internal static class HandGeometry
{
    private static readonly BoneRole[] LeftProximals =
    {
        BoneRole.ThumbProxL, BoneRole.IndexProxL, BoneRole.MiddleProxL, BoneRole.RingProxL, BoneRole.PinkyProxL,
    };

    private static readonly BoneRole[] RightProximals =
    {
        BoneRole.ThumbProxR, BoneRole.IndexProxR, BoneRole.MiddleProxR, BoneRole.RingProxR, BoneRole.PinkyProxR,
    };

    // Index → pinky order; the knuckle line is taken from the first and last mapped of these.
    private static readonly BoneRole[] LeftNonThumbProximals =
    {
        BoneRole.IndexProxL, BoneRole.MiddleProxL, BoneRole.RingProxL, BoneRole.PinkyProxL,
    };

    private static readonly BoneRole[] RightNonThumbProximals =
    {
        BoneRole.IndexProxR, BoneRole.MiddleProxR, BoneRole.RingProxR, BoneRole.PinkyProxR,
    };

    /// <summary>
    /// Midpoint of all mapped finger proximal heads of one hand (the hand's anatomical
    /// "chain child" point), or null when no finger proximal is mapped.
    /// </summary>
    public static Vector3? FingerProximalMidpoint(MappingResult map, IReadOnlyList<XForm> worldRest, bool left)
    {
        var sum = Vector3.Zero;
        var count = 0;
        foreach (var role in left ? LeftProximals : RightProximals)
        {
            if (map.RoleToBone.TryGetValue(role, out var index))
            {
                sum += worldRest[index].Pos;
                count++;
            }
        }
        return count > 0 ? sum / count : null;
    }

    /// <summary>
    /// Dorsal palm normal of one hand: the unit vector pointing out of the <b>back</b> of the
    /// hand (away from the palm), or null when the hand/finger geometry is unmapped or
    /// degenerate.
    /// </summary>
    /// <remarks>
    /// Formula (mirror-consistent by construction, verified on the ActorCore fixture by the
    /// finger-curl test): <c>dorsal = sideSign · cross(knuckle, fingerDir)</c> with
    /// <c>sideSign = +1</c> left / <c>−1</c> right, <c>knuckle = IndexProx.head −
    /// PinkyProx.head</c> (first/last mapped non-thumb proximal), and <c>fingerDir =
    /// FingerProximalMidpoint − Hand.head</c>. On every fixture rig the thumb proximal lies on
    /// the −dorsal (palmar) side of the hand plane, grounding the sign anatomically. A positive
    /// rotation about a finger frame's hinge axis (frame Y = cross(dorsal, fingerChainDir))
    /// curls the fingertip toward the palm on <b>both</b> hands.
    /// </remarks>
    public static Vector3? Dorsal(MappingResult map, IReadOnlyList<XForm> worldRest, bool left)
    {
        if (!map.RoleToBone.TryGetValue(left ? BoneRole.HandL : BoneRole.HandR, out var handIndex))
            return null;
        var hand = worldRest[handIndex].Pos;

        var nonThumb = left ? LeftNonThumbProximals : RightNonThumbProximals;
        Vector3? first = null, last = null;
        foreach (var role in nonThumb)
        {
            if (!map.RoleToBone.TryGetValue(role, out var index))
                continue;
            first ??= worldRest[index].Pos;
            last = worldRest[index].Pos;
        }
        if (first is null || last is null || (first.Value - last.Value).LengthSquared() < 1e-8f)
            return null;

        var midpoint = FingerProximalMidpoint(map, worldRest, left);
        if (midpoint is null)
            return null;

        var knuckle = first.Value - last.Value;
        var fingerDir = midpoint.Value - hand;
        var raw = Vector3.Cross(knuckle, fingerDir) * (left ? 1f : -1f);
        return raw.LengthSquared() < 1e-8f ? null : Vector3.Normalize(raw);
    }
}
notpointless.chomnr_humanoid_retargeter / Code/HumanoidRetargeter/Solve/TargetRigMapping.cs
Game library
#nullable enable annotations

using HumanoidRetargeter.Mapping;
using HumanoidRetargeter.Target;

namespace HumanoidRetargeter.Solve;

/// <summary>
/// Bridges a <see cref="TargetRig"/>'s role annotations into the <see cref="MappingResult"/>
/// shape shared with source mappings, so target-side machinery (rest normalization, canonical
/// frames) can run on the exact same code paths as the source side.
/// </summary>
public static class TargetRigMappingExtensions
{
    /// <summary>Role → target bone index mapping of the rig's annotated animated bones.</summary>
    public static MappingResult ToMappingResult(this TargetRig rig)
    {
        ArgumentNullException.ThrowIfNull(rig);

        var map = new MappingResult(rig.Name, MappingSource.Preset) { Confidence = 1f };
        for (var i = 0; i < rig.Skeleton.Count; i++)
        {
            if (rig.RoleOf(i) is BoneRole role)
                map.RoleToBone[role] = i;
        }
        return map;
    }
}
notpointless.chomnr_humanoid_retargeter / HumanoidRetargeter/Assembly.cs
Game library
#nullable enable annotations

// Global usings for the s&box in-engine compiler.
//
// The plain net8.0 dev harness gets these automatically via <ImplicitUsings>,
// but s&box's compiler injects no BCL usings at all - without this file the
// library fails to compile inside the editor (CS0246 on List<>, IEnumerable<>,
// FormatException, ...). Duplicating the SDK's implicit set is harmless there
// (verified: no warnings).

global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;

// NOTE on Vector3: s&box declares its own Vector3 in the *global namespace*,
// which wins over `using System.Numerics;` imports during name lookup and
// breaks this System.Numerics-based core (.X/.Y/.Z, static helpers, delegate
// signatures). A global using-alias does NOT fix this (CS0576: alias conflicts
// with the global-namespace type at every use site). The working fix is a
// *namespace-scoped* alias, declared after the file-scoped namespace line:
//
//     namespace HumanoidRetargeter.Xyz;
//     using Vector3 = System.Numerics.Vector3;
//
// Every file in this tree that uses the simple name Vector3 carries that line.
// (Quaternion and Matrix4x4 are not global-namespace types in s&box and need
// no alias.)
Debug: View Raw JSON Response
{
    "TotalCount": 158,
    "Files": [
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Cleanup/FootGroundAlign.cs",
            "FileName": "FootGroundAlign.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidRetargeter.Maths;\r\nusing SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;\r\n\r\nnamespace HumanoidRetargeter.Cleanup;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>Tunables for the grounded-foot stance recalibration pass.</summary>\r\npublic sealed class FootGroundAlignOptions\r\n{\r\n    /// <summary>\r\n    /// Dead zone (degrees): measured stance offsets at or below this are genuine planted\r\n    /// articulation (heel-roll bias, natural lean \u2014 measured 2\u20134\u00b0 on well-rested rigs and\r\n    /// on citizen clips) and are left untouched, keeping the transfer byte-faithful there.\r\n    /// Only offsets beyond it are clearly rest-pose artifacts (measured 12\u201325\u00b0 on the\r\n    /// repro rig) and get recalibrated.\r\n    /// </summary>\r\n    public float MinCorrectionDeg { get; set; } = 8f;\r\n\r\n    /// <summary>\r\n    /// Maximum mean sole deviation (degrees) a plant may show and still count as a STANCE\r\n    /// for the offset measurement. Plants beyond this are not standing on the sole (crawls,\r\n    /// kneels, prone contact \u2014 measured 60\u201390\u00b0 there) and are excluded; genuine rest-pose\r\n    /// stance artifacts measure well below it (largest seen: 27\u00b0).\r\n    /// </summary>\r\n    public float MaxStanceDeviationDeg { get; set; } = 35f;\r\n}\r\n\r\n/// <summary>Per-foot results of a <see cref=\"FootGroundAlign.Apply\"/> run.</summary>\r\npublic sealed class FootGroundAlignFootReport\r\n{\r\n    /// <summary>Plants that contributed to the stance measurement.</summary>\r\n    public int StancePlants { get; set; }\r\n\r\n    /// <summary>Plants excluded as non-stance (mean sole deviation beyond\r\n    /// <see cref=\"FootGroundAlignOptions.MaxStanceDeviationDeg\"/>).</summary>\r\n    public int SkippedPlants { get; set; }\r\n\r\n    /// <summary>Measured planted sole offset from the ground plane, degrees (0 when no\r\n    /// stance plants exist).</summary>\r\n    public float MeasuredOffsetDeg { get; set; }\r\n\r\n    /// <summary>Foot correction applied to every frame, degrees (0 = inside the dead zone,\r\n    /// nothing changed).</summary>\r\n    public float AppliedFootDeg { get; set; }\r\n\r\n    /// <summary>Toe correction applied to every frame, degrees.</summary>\r\n    public float AppliedToeDeg { get; set; }\r\n}\r\n\r\n/// <summary>Results of a <see cref=\"FootGroundAlign.Apply\"/> run.</summary>\r\npublic sealed class FootGroundAlignReport\r\n{\r\n    /// <summary>Left-foot results.</summary>\r\n    public required FootGroundAlignFootReport Left { get; init; }\r\n\r\n    /// <summary>Right-foot results.</summary>\r\n    public required FootGroundAlignFootReport Right { get; init; }\r\n}\r\n\r\n/// <summary>\r\n/// Grounded-foot stance recalibration: measures how far the foot's SOLE sits from the ground\r\n/// plane while planted, and \u2014 when that offset is clearly a rest-pose artifact \u2014 rotates it\r\n/// out with one constant per foot, applied to every frame of the clip.\r\n/// </summary>\r\n/// <remarks>\r\n/// <para><b>Why a cleanup pass.</b> The solver transfers feet as rest-relative deltas\r\n/// (<see cref=\"Solve.RoleTransferMode.CharacterDeltaFromRest\"/>), so the target keeps its own\r\n/// ankle anatomy \u2014 correct whenever the source's rest pose is a flat-footed stance (the delta\r\n/// is then \"deviation from standing\"). Some rigs ship a NON-stance rest (measured: an\r\n/// Auto-Rig-Pro export whose rest foot sits 12\u201325\u00b0 from its planted stance), and that constant\r\n/// offset rides into every frame of the replay \u2014 planted feet hover toe-down/heel-up. What a\r\n/// stance actually looks like is animation evidence (planted phases), which a per-frame\r\n/// solver cannot see, so the recalibration lives here.</para>\r\n/// <para><b>Measurement.</b> Per foot: over every planted frame, the sole normal = rest up\r\n/// carried by the foot's world delta from the target bind rest (whose feet stand on the\r\n/// ground by construction); plants whose own mean normal sits beyond\r\n/// <see cref=\"FootGroundAlignOptions.MaxStanceDeviationDeg\"/> are excluded (crawl/kneel/prone\r\n/// contact is not a stance). The pooled mean normal's deviation from up is the stance\r\n/// offset.</para>\r\n/// <para><b>Correction.</b> Offsets inside <see cref=\"FootGroundAlignOptions.MinCorrectionDeg\"/>\r\n/// are genuine articulation \u2014 nothing is changed (well-rested rigs and same-rig round trips\r\n/// stay byte-identical through this pass). Beyond it, the shortest-arc rotation taking the\r\n/// pooled normal back to up (pitch+roll only \u2014 yaw/toe-out is pose and follows the source)\r\n/// premultiplies the foot's world rotation on EVERY frame: a rest artifact is constant, so\r\n/// the fix is too \u2014 within-plant heel-roll, swing styling and frame-to-frame continuity are\r\n/// preserved exactly, and no blending is needed. The toe then receives its own residual\r\n/// constant measured on top of the corrected foot (it neither double-rotates with the foot\r\n/// fix nor inherits the source toe's own rest artifact). Corrections rotate bones about\r\n/// their own joints: ankle positions are untouched, so the pass composes freely with the\r\n/// <see cref=\"FootPlant\"/> position pinning (which preserves foot world rotations).</para>\r\n/// <para><b>Plant intervals come from the caller</b> (the pipeline detects them on the\r\n/// SOURCE clip via <see cref=\"FootPlant.DetectPlantIntervals\"/> \u2014 ground truth, immune to\r\n/// the hip-height rescaling that can push target-side trajectories outside the cm-tuned\r\n/// Kovar thresholds). So does the decision to run at all: the pipeline invokes this pass\r\n/// only when the source's normalized rest is implausible as a flat stance (toe at/above\r\n/// ankle level or asymmetric feet \u2014 see <c>Retargeter.GroundAlignFeet</c>); on plausible\r\n/// stance rests the solver's rest-relative transfer is already faithful and planted-sole\r\n/// deviations are genuine articulation (boxing stances, heel rolls) that must not be\r\n/// flattened.</para>\r\n/// </remarks>\r\npublic static class FootGroundAlign\r\n{\r\n    /// <summary>Measures planted stance offsets and recalibrates feet whose offset is a\r\n    /// rest-pose artifact; returns what was measured and done.</summary>\r\n    /// <param name=\"frames\">Per-frame local transforms (skeleton bone order); modified in place.</param>\r\n    /// <param name=\"skeleton\">Bone hierarchy the frames are expressed against; its bind rest\r\n    /// is the flat-stance reference.</param>\r\n    /// <param name=\"left\">Left leg chain bone indices.</param>\r\n    /// <param name=\"right\">Right leg chain bone indices.</param>\r\n    /// <param name=\"up\">World up direction of the clip's space.</param>\r\n    /// <param name=\"leftPlants\">Left-foot plant intervals (frame indices into\r\n    /// <paramref name=\"frames\"/>; out-of-range parts are clamped/ignored).</param>\r\n    /// <param name=\"rightPlants\">Right-foot plant intervals.</param>\r\n    /// <param name=\"options\">Tunables; defaults used when null.</param>\r\n    public static FootGroundAlignReport Apply(\r\n        List<XForm[]> frames,\r\n        SkeletonModel skeleton,\r\n        FootChain left,\r\n        FootChain right,\r\n        Vector3 up,\r\n        IReadOnlyList<FrameRange> leftPlants,\r\n        IReadOnlyList<FrameRange> rightPlants,\r\n        FootGroundAlignOptions? options = null)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(frames);\r\n        ArgumentNullException.ThrowIfNull(skeleton);\r\n        ArgumentNullException.ThrowIfNull(left);\r\n        ArgumentNullException.ThrowIfNull(right);\r\n        ArgumentNullException.ThrowIfNull(leftPlants);\r\n        ArgumentNullException.ThrowIfNull(rightPlants);\r\n\r\n        options ??= new FootGroundAlignOptions();\r\n        var report = new FootGroundAlignReport\r\n        {\r\n            Left = new FootGroundAlignFootReport(),\r\n            Right = new FootGroundAlignFootReport(),\r\n        };\r\n        if (frames.Count == 0 || up.LengthSquared() < 1e-12f)\r\n            return report;\r\n        up = Vector3.Normalize(up);\r\n\r\n        RecalibrateFoot(frames, skeleton, left, up, leftPlants, options, report.Left);\r\n        RecalibrateFoot(frames, skeleton, right, up, rightPlants, options, report.Right);\r\n        return report;\r\n    }\r\n\r\n    private static void RecalibrateFoot(\r\n        List<XForm[]> frames, SkeletonModel skeleton, FootChain chain, Vector3 up,\r\n        IReadOnlyList<FrameRange> plants, FootGroundAlignOptions options,\r\n        FootGroundAlignFootReport report)\r\n    {\r\n        int n = frames.Count;\r\n        var foot = chain.Ankle;\r\n        var restFootRotInv = Quaternion.Conjugate(skeleton.RestWorld[foot].Rot);\r\n        var maxStanceCos = MathF.Cos(options.MaxStanceDeviationDeg * MathF.PI / 180f);\r\n\r\n        // ---- measurement: pooled planted sole normal over the stance plants ----\r\n        var pooled = Vector3.Zero;\r\n        foreach (var plant in plants)\r\n        {\r\n            int start = Math.Max(plant.Start, 0);\r\n            int end = Math.Min(plant.End, n - 1);\r\n            if (start > end)\r\n                continue;\r\n\r\n            var plantSum = Vector3.Zero;\r\n            for (int f = start; f <= end; f++)\r\n            {\r\n                var footRot = FkUtil.BoneWorld(frames[f], skeleton, foot).Rot;\r\n                plantSum += Vector3.Transform(up, MathQ.Normalize(footRot * restFootRotInv));\r\n            }\r\n            if (plantSum.LengthSquared() < 1e-8f\r\n                || Vector3.Dot(Vector3.Normalize(plantSum), up) < maxStanceCos)\r\n            {\r\n                report.SkippedPlants++; // not standing on the sole \u2014 crawl/kneel/toe contact\r\n                continue;\r\n            }\r\n            report.StancePlants++;\r\n            pooled += plantSum; // frame-count-weighted: longer stances dominate\r\n        }\r\n        if (pooled.LengthSquared() < 1e-8f)\r\n            return;\r\n        pooled = Vector3.Normalize(pooled);\r\n\r\n        var offsetDeg = MathQ.AngleBetween(pooled, up) * (180f / MathF.PI);\r\n        report.MeasuredOffsetDeg = offsetDeg;\r\n        if (offsetDeg <= options.MinCorrectionDeg)\r\n            return; // genuine planted articulation \u2014 leave the transfer byte-faithful\r\n\r\n        // ---- correction: one constant per foot, every frame ----\r\n        var footFix = MathQ.FromTo(pooled, up);\r\n        report.AppliedFootDeg = offsetDeg;\r\n\r\n        // Toe residual measured on top of the corrected foot, same dead zone.\r\n        var toeFix = Quaternion.Identity;\r\n        if (chain.Toe is { } toe && skeleton[toe].ParentIndex == foot)\r\n        {\r\n            var restToeRotInv = Quaternion.Conjugate(skeleton.RestWorld[toe].Rot);\r\n            var toePooled = Vector3.Zero;\r\n            foreach (var plant in plants)\r\n            {\r\n                int start = Math.Max(plant.Start, 0);\r\n                int end = Math.Min(plant.End, n - 1);\r\n                for (int f = start; f <= end && f >= 0; f++)\r\n                {\r\n                    var toeRot = FkUtil.BoneWorld(frames[f], skeleton, toe).Rot;\r\n                    toePooled += Vector3.Transform(\r\n                        up, MathQ.Normalize(footFix * toeRot * restToeRotInv));\r\n                }\r\n            }\r\n            if (toePooled.LengthSquared() > 1e-8f)\r\n            {\r\n                toePooled = Vector3.Normalize(toePooled);\r\n                var toeDeg = MathQ.AngleBetween(toePooled, up) * (180f / MathF.PI);\r\n                if (toeDeg > options.MinCorrectionDeg && Vector3.Dot(toePooled, up) >= maxStanceCos)\r\n                {\r\n                    toeFix = MathQ.FromTo(toePooled, up);\r\n                    report.AppliedToeDeg = toeDeg;\r\n                }\r\n            }\r\n        }\r\n\r\n        for (int f = 0; f < n; f++)\r\n            CorrectFrame(frames[f], skeleton, chain, footFix, toeFix);\r\n    }\r\n\r\n    /// <summary>Premultiplies the foot's world rotation by the constant fix (the joint\r\n    /// position is untouched \u2014 the rotation pivots the foot about its own head), then gives\r\n    /// the toe its own residual on top of the corrected foot.</summary>\r\n    private static void CorrectFrame(\r\n        XForm[] locals, SkeletonModel skeleton, FootChain chain,\r\n        Quaternion footFix, Quaternion toeFix)\r\n    {\r\n        var foot = chain.Ankle;\r\n        var parent = skeleton[foot].ParentIndex;\r\n        var parentRot = parent < 0\r\n            ? Quaternion.Identity\r\n            : FkUtil.BoneWorld(locals, skeleton, parent).Rot;\r\n\r\n        var footWorld = MathQ.Normalize(parentRot * locals[foot].Rot);\r\n        var newFootWorld = MathQ.Normalize(footFix * footWorld);\r\n        locals[foot] = new XForm(\r\n            locals[foot].Pos, MathQ.Normalize(Quaternion.Conjugate(parentRot) * newFootWorld));\r\n\r\n        if (chain.Toe is { } toe && skeleton[toe].ParentIndex == foot)\r\n        {\r\n            // Desired toe world = toeFix \u2218 footFix \u2218 original world; re-derive its local\r\n            // against the corrected foot so it does not double-rotate with the foot fix.\r\n            var toeWorldOld = MathQ.Normalize(footWorld * locals[toe].Rot);\r\n            var desired = MathQ.Normalize(toeFix * footFix * toeWorldOld);\r\n            locals[toe] = new XForm(\r\n                locals[toe].Pos, MathQ.Normalize(Quaternion.Conjugate(newFootWorld) * desired));\r\n        }\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Dl/SameFeatures.cs",
            "FileName": "SameFeatures.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidRetargeter.Mapping;\r\nusing HumanoidRetargeter.Maths;\r\nusing HumanoidRetargeter.Skeleton;\r\nusing HumanoidRetargeter.Solve;\r\nusing SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;\r\n\r\nnamespace HumanoidRetargeter.Dl;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>The z-normalization statistics shipped with the SAME checkpoint\r\n/// (<c>ms_dict</c>): per-feature mean/std applied to every input except contact.</summary>\r\npublic sealed class SameStats\r\n{\r\n    internal float[] LoM, LoS, GoM, GoS, QM, QS, PM, PS, RM, RS, PvM, PvS, QvM, QvS, PprevM, PprevS;\r\n\r\n    /// <summary>Reads the 16 <c>ms.*</c> arrays from a parsed weight blob.</summary>\r\n    public SameStats(SameWeights weights)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(weights);\r\n        LoM = weights.Stat(\"lo_m\"); LoS = weights.Stat(\"lo_s\");\r\n        GoM = weights.Stat(\"go_m\"); GoS = weights.Stat(\"go_s\");\r\n        QM = weights.Stat(\"q_m\"); QS = weights.Stat(\"q_s\");\r\n        PM = weights.Stat(\"p_m\"); PS = weights.Stat(\"p_s\");\r\n        RM = weights.Stat(\"r_m\"); RS = weights.Stat(\"r_s\");\r\n        PvM = weights.Stat(\"pv_m\"); PvS = weights.Stat(\"pv_s\");\r\n        QvM = weights.Stat(\"qv_m\"); QvS = weights.Stat(\"qv_s\");\r\n        PprevM = weights.Stat(\"pprev_m\"); PprevS = weights.Stat(\"pprev_s\");\r\n    }\r\n}\r\n\r\n/// <summary>A batched per-frame source graph ready for <see cref=\"SameModel.Encode\"/>.</summary>\r\npublic sealed class SameSourceGraph\r\n{\r\n    /// <summary>Normalized node features, flat [FrameCount\u00b7JointCount \u00d7 32].</summary>\r\n    public required float[] X { get; init; }\r\n\r\n    /// <summary>Edge sources (bidirectional + self-loops, all frames).</summary>\r\n    public required int[] EdgeSrc { get; init; }\r\n\r\n    /// <summary>Edge destinations.</summary>\r\n    public required int[] EdgeDst { get; init; }\r\n\r\n    /// <summary>Frame id per node.</summary>\r\n    public required int[] Batch { get; init; }\r\n\r\n    /// <summary>Number of feature frames (matches the clip's frame count in production\r\n    /// mode; native frames \u2212 2 in golden-parity mode).</summary>\r\n    public required int FrameCount { get; init; }\r\n\r\n    /// <summary>Graph joints per frame (hips subtree + end joints).</summary>\r\n    public required int JointCount { get; init; }\r\n\r\n    /// <summary>Graph node names within one frame (bone names; synthesized leaf tips get\r\n    /// a <c>_end</c> suffix). For diagnostics and parity tests.</summary>\r\n    public required string[] JointNames { get; init; }\r\n}\r\n\r\n/// <summary>\r\n/// Source-side feature pipeline of the SAME port (FEASIBILITY.md \"C# port work list\"\r\n/// steps 1\u20135): skeleton normalization, cm/Y-up/+Z-facing alignment, per-frame\r\n/// q/p/r/pv/qv/pprev/c features in the root-facing frame, z-normalization, and the\r\n/// bidirectional+self-loop edge list.\r\n/// </summary>\r\n/// <remarks>\r\n/// <para><b>Skeleton normalization without an intermediate skeleton.</b> SAME's\r\n/// <c>motion_normalize</c> rebuilds the rig with identity rest-local rotations and\r\n/// re-expresses every frame against it. Algebraically the normalized motion's world\r\n/// rotations are exactly the world-space deltas from the T-pose,\r\n/// <c>\u011c(j,t) = G(j,t) \u00b7 G_tpose(j)\u207b\u00b9</c>, its local rotations are\r\n/// <c>\u011c(parent)\u207b\u00b9 \u00b7 \u011c(j)</c>, and its world positions equal the original world positions\r\n/// \u2014 so this port computes the features directly from FK world transforms, no rebuilt\r\n/// skeleton needed (verified against the Python pipeline by the golden-vector tests).</para>\r\n/// <para><b>T-pose reference.</b> SAME consumes the source clip's first frame as the\r\n/// reference; production keeps that convention but emits one feature frame per clip frame\r\n/// (the sequence is computed over [f0, f0\u2026fN\u22121] with f0 doubling as the reference \u2014 see\r\n/// <see cref=\"TposeReference\"/> for why the rest-pose alternative measurably loses).\r\n/// Golden-parity mode replicates Python's frame accounting exactly (frame 0 = reference,\r\n/// frame 1 dropped).</para>\r\n/// <para><b>Alignment.</b> Features assume cm (guaranteed by the importers), Y-up and\r\n/// rest facing +Z with +X to the character's left. The source is rotated by a world\r\n/// alignment derived from the rig's rest geometry (<see cref=\"CharacterFrame\"/> via the\r\n/// mapping when computable, else the file's axis metadata), snapped to the nearest whole\r\n/// axis permutation (an exact-axis rig must map to the identity \u2014 the rest-geometry tilt\r\n/// of a few degrees otherwise leaks into every feature), and shifted so the lowest joint\r\n/// over the clip sits on the ground plane.</para>\r\n/// <para><b>Graph.</b> Nodes are the hips subtree (hips = mapped Hips role, else the\r\n/// shallowest branch bone) in skeleton order \u2014 hips is always node 0, which is where the\r\n/// root feature row lives \u2014 plus one synthesized end joint per childless leaf (BVH End\r\n/// Sites already import as <c>_end</c> bones and are used as-is; FBX leaves get a\r\n/// half-length continuation of their parent segment).</para>\r\n/// </remarks>\r\npublic static class SameFeatures\r\n{\r\n    /// <summary>How the T-pose reference (skeleton normalization + lo/go features) is chosen.</summary>\r\n    public enum TposeReference\r\n    {\r\n        /// <summary>The clip's own first frame \u2014 SAME's native convention and the\r\n        /// production default. Empirically the pretrained checkpoint tracks arms FAR\r\n        /// better against the clip's first frame than against a synthesized true T-pose,\r\n        /// even though its training references are T-poses (measured on the fixture clip:\r\n        /// mean role cosine vs the geometric solver 0.94 first-frame vs 0.57 rest-pose,\r\n        /// hands flipping negative \u2014 reproduced identically in the Python reference\r\n        /// pipeline, so it is a property of the checkpoint, not of this port).</summary>\r\n        FirstFrame,\r\n\r\n        /// <summary>Synthesize the reference from the skeleton's rest pose (the\r\n        /// FEASIBILITY suggestion; kept for experiments \u2014 see above for why it lost).</summary>\r\n        RestPose,\r\n    }\r\n\r\n    /// <summary>Options for <see cref=\"BuildSourceGraph\"/>; defaults are production mode.</summary>\r\n    public sealed class SourceOptions\r\n    {\r\n        /// <summary>T-pose reference choice (see <see cref=\"TposeReference\"/>).</summary>\r\n        public TposeReference Reference { get; init; } = TposeReference.FirstFrame;\r\n\r\n        /// <summary>SAME's native frame accounting: the first frame is consumed as the\r\n        /// reference and the next dropped for its undefined velocity, so the output has\r\n        /// two frames fewer than the clip. Golden-parity tests only \u2014 production emits\r\n        /// one feature frame per clip frame (the first frame doubles as the reference\r\n        /// and gets zero velocity).</summary>\r\n        public bool NativeFrameDrop { get; init; }\r\n\r\n        /// <summary>Apply the rest-geometry world alignment (Y-up, +Z facing). Disabled\r\n        /// only by golden-parity tests (Python applies none).</summary>\r\n        public bool Align { get; init; } = true;\r\n\r\n        /// <summary>Ground both the T-pose reference and the animation: the T-pose is\r\n        /// shifted so its lowest joint sits at height 0 (a BVH rest pose has its root at\r\n        /// the origin and would otherwise put the hips on the floor), and the animation is\r\n        /// shifted by its own lowest joint height over the clip (no-op for the usual\r\n        /// authored-ground-at-0 data). Disabled only by golden-parity tests (the Python\r\n        /// reference consumes data as authored).</summary>\r\n        public bool GroundShift { get; init; } = true;\r\n    }\r\n\r\n    private const float ContactHeightCm = 5f;\r\n    private const float ContactSpeedMps = 0.4f;\r\n    private const float VelocityFps = 30f;\r\n\r\n    /// <summary>\r\n    /// Builds the batched source graph for one clip: graph selection, alignment, per-frame\r\n    /// features, normalization, edges.\r\n    /// </summary>\r\n    /// <param name=\"scene\">Imported source (cm, native axes).</param>\r\n    /// <param name=\"clipIndex\">Clip to encode.</param>\r\n    /// <param name=\"map\">Source mapping; used only for hips identification and the\r\n    /// rest-geometry alignment (the model itself is skeleton-agnostic). May be sparse \u2014\r\n    /// heuristics cover missing roles.</param>\r\n    /// <param name=\"stats\">Normalization statistics.</param>\r\n    /// <param name=\"options\">Null = production mode.</param>\r\n    public static SameSourceGraph BuildSourceGraph(\r\n        SourceScene scene, int clipIndex, MappingResult? map, SameStats stats, SourceOptions? options = null)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(scene);\r\n        ArgumentNullException.ThrowIfNull(stats);\r\n        options ??= new SourceOptions();\r\n        if (clipIndex < 0 || clipIndex >= scene.Clips.Count)\r\n            throw new ArgumentOutOfRangeException(nameof(clipIndex));\r\n        var clip = scene.Clips[clipIndex];\r\n        if (clip.FrameCount < 1)\r\n            throw new ArgumentException(\"Clip has no frames.\", nameof(clipIndex));\r\n        if (options.NativeFrameDrop && clip.FrameCount < 3)\r\n            throw new ArgumentException(\"Native frame accounting needs at least 3 frames.\", nameof(options));\r\n\r\n        var skeleton = scene.Skeleton;\r\n        var hips = FindHips(skeleton, map);\r\n        var nodes = GraphNodes.Build(skeleton, hips);\r\n\r\n        var align = options.Align ? ComputeAlignment(skeleton, map, scene) : Quaternion.Identity;\r\n\r\n        // T-pose reference world transforms (aligned), grounded on its own lowest joint\r\n        // (a BVH rest pose has the root at the origin \u2014 ungrounded, its hips would sit on\r\n        // the floor and every height-bearing feature would be wrong).\r\n        var tposeLocals = options.Reference == TposeReference.RestPose\r\n            ? Pose.Rest(skeleton).Locals\r\n            : clip.Frames[0];\r\n        var tposeWorld = AlignedWorld(skeleton, tposeLocals, align, nodes);\r\n        if (options.GroundShift)\r\n            ShiftToGround(tposeWorld.Pos);\r\n\r\n        // The pose sequence the features run over; features are emitted for seq[1..].\r\n        var seq = new List<XForm[]>();\r\n        if (options.NativeFrameDrop)\r\n        {\r\n            for (var f = 1; f < clip.FrameCount; f++)\r\n                seq.Add(clip.Frames[f]);\r\n        }\r\n        else\r\n        {\r\n            seq.Add(clip.Frames[0]); // duplicated: gives the real first frame zero velocity\r\n            for (var f = 0; f < clip.FrameCount; f++)\r\n                seq.Add(clip.Frames[f]);\r\n        }\r\n\r\n        var frames = seq.Count - 1;\r\n        var j = nodes.Count;\r\n\r\n        // Pass 0: aligned world transforms; ground the whole clip on its lowest joint.\r\n        var worlds = new AlignedFrame[seq.Count];\r\n        for (var t = 0; t < seq.Count; t++)\r\n            worlds[t] = AlignedWorld(skeleton, seq[t], align, nodes);\r\n        if (options.GroundShift)\r\n        {\r\n            var ground = float.PositiveInfinity;\r\n            foreach (var world in worlds)\r\n            {\r\n                foreach (var p in world.Pos)\r\n                    ground = MathF.Min(ground, p.Y);\r\n            }\r\n            if (float.IsFinite(ground) && ground != 0f)\r\n            {\r\n                foreach (var world in worlds)\r\n                {\r\n                    for (var i = 0; i < j; i++)\r\n                        world.Pos[i].Y -= ground;\r\n                }\r\n            }\r\n        }\r\n\r\n        // Pass 1: normalized-skeleton local rotations + facing per frame.\r\n        var localRots = new Quaternion[seq.Count][]; // facing-adjusted at the root row\r\n        var facing = new (float Yaw, Vector3 Pos)[seq.Count];\r\n        for (var t = 0; t < seq.Count; t++)\r\n        {\r\n            var world = worlds[t];\r\n\r\n            // Normalized-skeleton world rotations: world delta from the T-pose.\r\n            var normWorld = new Quaternion[j];\r\n            for (var i = 0; i < j; i++)\r\n                normWorld[i] = MathQ.Normalize(world.Rot[i] * Quaternion.Conjugate(tposeWorld.Rot[i]));\r\n\r\n            // Root facing: yaw (about +Y) of the normalized root rotation, at the root's\r\n            // ground-plane position.\r\n            var yaw = YawAngle(normWorld[0]);\r\n            facing[t] = (yaw, new Vector3(world.Pos[0].X, 0f, world.Pos[0].Z));\r\n\r\n            // Normalized-skeleton local rotations; root premultiplied by the inverse facing.\r\n            var locals = new Quaternion[j];\r\n            locals[0] = MathQ.Normalize(Quaternion.CreateFromAxisAngle(Vector3.UnitY, -yaw) * normWorld[0]);\r\n            for (var i = 1; i < j; i++)\r\n            {\r\n                locals[i] = MathQ.Normalize(\r\n                    Quaternion.Conjugate(normWorld[nodes.Parent[i]]) * normWorld[i]);\r\n            }\r\n            localRots[t] = locals;\r\n        }\r\n\r\n        // Pass 2: feature rows.\r\n        var x = new float[frames * j * SameModel.InputDim];\r\n        for (var t = 1; t < seq.Count; t++)\r\n        {\r\n            var f = t - 1;\r\n            var (yaw, fpos) = facing[t];\r\n            var invFacing = Quaternion.CreateFromAxisAngle(Vector3.UnitY, -yaw);\r\n            var (yawPrev, fposPrev) = facing[t - 1];\r\n            var invFacingPrev = Quaternion.CreateFromAxisAngle(Vector3.UnitY, -yawPrev);\r\n\r\n            // r: facing delta (d\u03b8, dx, dz) + absolute root height.\r\n            var dTheta = WrapPi(yaw - yawPrev);\r\n            var dPlanar = Vector3.Transform(fpos - fposPrev, invFacingPrev);\r\n            var rootHeight = worlds[t].Pos[0].Y;\r\n\r\n            for (var i = 0; i < j; i++)\r\n            {\r\n                var row = (f * j + i) * SameModel.InputDim;\r\n                var col = 0;\r\n\r\n                // ---- skel: lo, go (tiled per frame) -------------------------------------\r\n                Vector3 lo, go;\r\n                if (i == 0)\r\n                {\r\n                    lo = new Vector3(0f, tposeWorld.Pos[0].Y, 0f);\r\n                    go = lo;\r\n                }\r\n                else\r\n                {\r\n                    lo = tposeWorld.Pos[i] - tposeWorld.Pos[nodes.Parent[i]];\r\n                    go = tposeWorld.Pos[i] - new Vector3(tposeWorld.Pos[0].X, 0f, tposeWorld.Pos[0].Z);\r\n                }\r\n                WriteNorm3(x, row, ref col, lo, stats.LoM, stats.LoS);\r\n                WriteNorm3(x, row, ref col, go, stats.GoM, stats.GoS);\r\n\r\n                // ---- q ------------------------------------------------------------------\r\n                WriteNorm6(x, row, ref col, SixD(localRots[t][i]), stats.QM, stats.QS);\r\n\r\n                // ---- p (facing-frame-relative global position) --------------------------\r\n                var p = Vector3.Transform(worlds[t].Pos[i] - fpos, invFacing);\r\n                WriteNorm3(x, row, ref col, p, stats.PM, stats.PS);\r\n\r\n                // ---- r (root row only; other rows are the mean \u2192 zeros after norm) ------\r\n                if (i == 0)\r\n                {\r\n                    x[row + col++] = (dTheta - stats.RM[0]) / stats.RS[0];\r\n                    x[row + col++] = (dPlanar.X - stats.RM[1]) / stats.RS[1];\r\n                    x[row + col++] = (dPlanar.Z - stats.RM[2]) / stats.RS[2];\r\n                    x[row + col++] = (rootHeight - stats.RM[3]) / stats.RS[3];\r\n                }\r\n                else\r\n                {\r\n                    col += 4; // already zero\r\n                }\r\n\r\n                // ---- pv (facing-frame velocity, \u00d730 fps) ---------------------------------\r\n                var pv = Vector3.Transform(worlds[t].Pos[i] - worlds[t - 1].Pos[i], invFacing) * VelocityFps;\r\n                WriteNorm3(x, row, ref col, pv, stats.PvM, stats.PvS);\r\n\r\n                // ---- qv (local rotation delta) -------------------------------------------\r\n                var qv = MathQ.Normalize(Quaternion.Conjugate(localRots[t - 1][i]) * localRots[t][i]);\r\n                WriteNorm6(x, row, ref col, SixD(qv), stats.QvM, stats.QvS);\r\n\r\n                // ---- pprev (previous position in the CURRENT facing frame) ---------------\r\n                var pprev = Vector3.Transform(worlds[t - 1].Pos[i] - fpos, invFacing);\r\n                WriteNorm3(x, row, ref col, pprev, stats.PprevM, stats.PprevS);\r\n\r\n                // ---- c (ground contact; not normalized) -----------------------------------\r\n                var speedMps = (worlds[t].Pos[i] - worlds[t - 1].Pos[i]).Length() * VelocityFps / 100f;\r\n                x[row + col] = worlds[t].Pos[i].Y < ContactHeightCm && speedMps < ContactSpeedMps ? 1f : 0f;\r\n            }\r\n        }\r\n\r\n        var (edgeSrc, edgeDst) = BuildEdges(nodes.Parent, frames);\r\n        var batch = new int[frames * j];\r\n        for (var f = 0; f < frames; f++)\r\n        {\r\n            for (var i = 0; i < j; i++)\r\n                batch[f * j + i] = f;\r\n        }\r\n\r\n        AssertFinite(x, \"SAME source features\");\r\n        return new SameSourceGraph\r\n        {\r\n            X = x,\r\n            EdgeSrc = edgeSrc,\r\n            EdgeDst = edgeDst,\r\n            Batch = batch,\r\n            FrameCount = frames,\r\n            JointCount = j,\r\n            JointNames = nodes.Names,\r\n        };\r\n    }\r\n\r\n    // ================================================================ graph topology\r\n\r\n    /// <summary>The per-frame graph node set: hips-subtree bones in skeleton order\r\n    /// (hips first) plus synthesized end joints for childless leaves.</summary>\r\n    internal sealed class GraphNodes\r\n    {\r\n        /// <summary>Skeleton bone index per node; -1 for synthesized end joints.</summary>\r\n        public required int[] Bone { get; init; }\r\n\r\n        /// <summary>Graph-parent node index; -1 for the root (node 0).</summary>\r\n        public required int[] Parent { get; init; }\r\n\r\n        /// <summary>For synthesized end joints: the rest-local offset from the leaf bone\r\n        /// (zero vector for real bones).</summary>\r\n        public required Vector3[] EndOffset { get; init; }\r\n\r\n        public required string[] Names { get; init; }\r\n\r\n        public int Count => Bone.Length;\r\n\r\n        public static GraphNodes Build(SkeletonModel skeleton, int hips)\r\n        {\r\n            // Hips subtree, skeleton order (parents precede children, hips first).\r\n            var inSubtree = new bool[skeleton.Count];\r\n            inSubtree[hips] = true;\r\n            var bones = new List<int> { hips };\r\n            for (var i = hips + 1; i < skeleton.Count; i++)\r\n            {\r\n                var parent = skeleton[i].ParentIndex;\r\n                if (parent >= 0 && inSubtree[parent])\r\n                {\r\n                    inSubtree[i] = true;\r\n                    bones.Add(i);\r\n                }\r\n            }\r\n\r\n            var nodeOfBone = new Dictionary<int, int>(bones.Count);\r\n            for (var n = 0; n < bones.Count; n++)\r\n                nodeOfBone[bones[n]] = n;\r\n\r\n            var hasChild = new bool[skeleton.Count];\r\n            foreach (var b in bones)\r\n            {\r\n                var parent = skeleton[b].ParentIndex;\r\n                if (parent >= 0 && inSubtree[parent])\r\n                    hasChild[parent] = true;\r\n            }\r\n\r\n            var bone = new List<int>(bones);\r\n            var parentNode = new List<int>(bones.Count);\r\n            var endOffset = new List<Vector3>(bones.Count);\r\n            var names = new List<string>(bones.Count);\r\n            foreach (var b in bones)\r\n            {\r\n                var p = skeleton[b].ParentIndex;\r\n                parentNode.Add(b == hips ? -1 : nodeOfBone[p]);\r\n                endOffset.Add(Vector3.Zero);\r\n                names.Add(skeleton[b].Name);\r\n            }\r\n\r\n            // Synthesized end joints: leaves with no children anywhere in the skeleton.\r\n            // BVH End Sites already import as real `_end`/`_End` bones and ARE the end\r\n            // joints \u2014 no tip on a tip. The tip continues the parent\u2192leaf segment at half\r\n            // length \u2014 a neutral stand-in for the unknown bone tail (FBX carries none).\r\n            foreach (var b in bones)\r\n            {\r\n                if (hasChild[b]\r\n                    || skeleton[b].Name.EndsWith(\"_end\", StringComparison.OrdinalIgnoreCase))\r\n                    continue;\r\n                var p = skeleton[b].ParentIndex;\r\n                var segment = p >= 0\r\n                    ? skeleton.RestWorld[b].Pos - skeleton.RestWorld[p].Pos\r\n                    : Vector3.Zero;\r\n                var tip = segment.Length() > 1e-4f ? segment * 0.5f : new Vector3(0f, 2f, 0f);\r\n                // Express in the leaf's rest-local frame (applied via the leaf's world rot).\r\n                var local = Vector3.Transform(tip, Quaternion.Conjugate(skeleton.RestWorld[b].Rot));\r\n                bone.Add(-1);\r\n                parentNode.Add(nodeOfBone[b]);\r\n                endOffset.Add(local);\r\n                names.Add(skeleton[b].Name + \"_end\");\r\n            }\r\n\r\n            return new GraphNodes\r\n            {\r\n                Bone = bone.ToArray(),\r\n                Parent = parentNode.ToArray(),\r\n                EndOffset = endOffset.ToArray(),\r\n                Names = names.ToArray(),\r\n            };\r\n        }\r\n    }\r\n\r\n    /// <summary>Aligned world transforms of the graph nodes for one pose.</summary>\r\n    internal readonly struct AlignedFrame\r\n    {\r\n        public required Vector3[] Pos { get; init; }\r\n        public required Quaternion[] Rot { get; init; }\r\n    }\r\n\r\n    private static AlignedFrame AlignedWorld(\r\n        SkeletonModel skeleton, XForm[] locals, Quaternion align, GraphNodes nodes)\r\n    {\r\n        var world = new Pose(locals).ToWorld(skeleton);\r\n        var pos = new Vector3[nodes.Count];\r\n        var rot = new Quaternion[nodes.Count];\r\n        for (var n = 0; n < nodes.Count; n++)\r\n        {\r\n            XForm w;\r\n            if (nodes.Bone[n] >= 0)\r\n            {\r\n                w = world[nodes.Bone[n]];\r\n            }\r\n            else\r\n            {\r\n                // Synthesized end joint: rides its leaf bone (identity local rotation).\r\n                var leaf = world[nodes.Bone[nodes.Parent[n]]];\r\n                w = new XForm(leaf.TransformPoint(nodes.EndOffset[n]), leaf.Rot);\r\n            }\r\n            pos[n] = Vector3.Transform(w.Pos, align);\r\n            rot[n] = MathQ.Normalize(align * w.Rot);\r\n        }\r\n        return new AlignedFrame { Pos = pos, Rot = rot };\r\n    }\r\n\r\n    /// <summary>Bidirectional parent\u2194child pairs plus one self-loop per node, replicated\r\n    /// per frame with node indices offset.</summary>\r\n    internal static (int[] Src, int[] Dst) BuildEdges(int[] parent, int frames)\r\n    {\r\n        var j = parent.Length;\r\n        var nonRoot = 0;\r\n        for (var i = 0; i < j; i++)\r\n        {\r\n            if (parent[i] >= 0)\r\n                nonRoot++;\r\n        }\r\n        var perFrame = nonRoot * 2 + j;\r\n        var src = new int[perFrame * frames];\r\n        var dst = new int[perFrame * frames];\r\n        var e = 0;\r\n        for (var f = 0; f < frames; f++)\r\n        {\r\n            var offset = f * j;\r\n            for (var i = 0; i < j; i++)\r\n            {\r\n                if (parent[i] < 0)\r\n                    continue;\r\n                src[e] = offset + parent[i];\r\n                dst[e] = offset + i;\r\n                e++;\r\n                src[e] = offset + i;\r\n                dst[e] = offset + parent[i];\r\n                e++;\r\n            }\r\n            for (var i = 0; i < j; i++)\r\n            {\r\n                src[e] = offset + i;\r\n                dst[e] = offset + i;\r\n                e++;\r\n            }\r\n        }\r\n        return (src, dst);\r\n    }\r\n\r\n    // ================================================================ alignment + hips\r\n\r\n    /// <summary>Mapped Hips role when available, else the shallowest bone with two or more\r\n    /// children (the hips of any humanoid: the legs/spine branch point).</summary>\r\n    internal static int FindHips(SkeletonModel skeleton, MappingResult? map)\r\n    {\r\n        if (map is not null && map.RoleToBone.TryGetValue(BoneRole.Hips, out var mapped)\r\n            && mapped >= 0 && mapped < skeleton.Count)\r\n            return mapped;\r\n\r\n        var childCount = new int[skeleton.Count];\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n        {\r\n            if (skeleton[i].ParentIndex >= 0)\r\n                childCount[skeleton[i].ParentIndex]++;\r\n        }\r\n\r\n        var best = -1;\r\n        var bestDepth = int.MaxValue;\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n        {\r\n            if (childCount[i] < 2)\r\n                continue;\r\n            var depth = 0;\r\n            for (var a = skeleton[i].ParentIndex; a >= 0; a = skeleton[a].ParentIndex)\r\n                depth++;\r\n            if (depth < bestDepth)\r\n            {\r\n                best = i;\r\n                bestDepth = depth;\r\n            }\r\n        }\r\n        return best >= 0 ? best : 0;\r\n    }\r\n\r\n    /// <summary>\r\n    /// World rotation taking the rig into the canonical SAME frame (X = character left,\r\n    /// Y = up, Z = facing): rest-geometry character frame when computable from the mapping,\r\n    /// else the file's recorded axis conventions.\r\n    /// </summary>\r\n    internal static Quaternion ComputeAlignment(SkeletonModel skeleton, MappingResult? map, SourceScene? scene)\r\n    {\r\n        if (map is not null)\r\n        {\r\n            try\r\n            {\r\n                var frame = CharacterFrame.Compute(skeleton, map, skeleton.RestWorld);\r\n                return AlignFromBasis(frame.Lateral, frame.Up, frame.Forward);\r\n            }\r\n            catch (ArgumentException)\r\n            {\r\n                // fall through to axis metadata\r\n            }\r\n        }\r\n\r\n        if (scene is not null)\r\n        {\r\n            var up = AxisVector(scene.UpAxis, scene.UpAxisSign);\r\n            var forward = AxisVector(scene.FrontAxis, scene.FrontAxisSign);\r\n            if (MathF.Abs(Vector3.Dot(up, forward)) < 0.5f)\r\n                return AlignFromBasis(Vector3.Cross(up, forward), up, forward);\r\n        }\r\n\r\n        return Quaternion.Identity;\r\n    }\r\n\r\n    /// <summary>\r\n    /// Rotation mapping the given (left, up, forward) world directions onto (+X, +Y, +Z),\r\n    /// snapped to the nearest whole axis permutation when one is unambiguous: rigs authored\r\n    /// on exact axes (BVH Y-up/+Z, the s&amp;box rig, Z-up FBX) must map by an exact\r\n    /// quarter-turn \u2014 the few degrees of rest-geometry tilt (shoulders not exactly above\r\n    /// hips) otherwise leak into every feature and measurably cost accuracy.\r\n    /// </summary>\r\n    internal static Quaternion AlignFromBasis(Vector3 left, Vector3 up, Vector3 forward)\r\n    {\r\n        var l = SnapAxis(left);\r\n        var u = SnapAxis(up);\r\n        var f = SnapAxis(forward);\r\n        if (MathF.Abs(Vector3.Dot(l, u)) > 0.5f || MathF.Abs(Vector3.Dot(l, f)) > 0.5f\r\n            || MathF.Abs(Vector3.Dot(u, f)) > 0.5f)\r\n        {\r\n            // Genuinely oblique rig: keep the exact (orthonormalized) directions.\r\n            l = Vector3.Normalize(left);\r\n            u = Vector3.Normalize(up - l * Vector3.Dot(up, l));\r\n            f = Vector3.Cross(l, u);\r\n        }\r\n\r\n        // Row-major with rows = basis images maps +X\u2192left, +Y\u2192up, +Z\u2192forward\r\n        // (System.Numerics row-vector convention); the alignment is its inverse.\r\n        var m = new Matrix4x4(\r\n            l.X, l.Y, l.Z, 0f,\r\n            u.X, u.Y, u.Z, 0f,\r\n            f.X, f.Y, f.Z, 0f,\r\n            0f, 0f, 0f, 1f);\r\n        return Quaternion.Conjugate(MathQ.Normalize(Quaternion.CreateFromRotationMatrix(m)));\r\n    }\r\n\r\n    private static Vector3 SnapAxis(Vector3 v)\r\n    {\r\n        var ax = MathF.Abs(v.X);\r\n        var ay = MathF.Abs(v.Y);\r\n        var az = MathF.Abs(v.Z);\r\n        if (ax >= ay && ax >= az)\r\n            return new Vector3(MathF.Sign(v.X), 0f, 0f);\r\n        if (ay >= az)\r\n            return new Vector3(0f, MathF.Sign(v.Y), 0f);\r\n        return new Vector3(0f, 0f, MathF.Sign(v.Z));\r\n    }\r\n\r\n    private static Vector3 AxisVector(int axis, int sign) => axis switch\r\n    {\r\n        0 => new Vector3(sign, 0f, 0f),\r\n        2 => new Vector3(0f, 0f, sign),\r\n        _ => new Vector3(0f, sign, 0f),\r\n    };\r\n\r\n    // ================================================================ small math\r\n\r\n    /// <summary>The yaw (rotation about +Y) closest to <paramref name=\"q\"/> \u2014 fairmotion's\r\n    /// <c>Q_closest(q, identity, +Y)</c>, reproduced exactly for parity.</summary>\r\n    internal static float YawAngle(Quaternion q)\r\n    {\r\n        var alpha = Math.Atan2(q.W, q.Y);\r\n        var theta1 = -2.0 * alpha + Math.PI;\r\n        var theta2 = -2.0 * alpha - Math.PI;\r\n        var d1 = q.Y * Math.Sin(theta1 * 0.5) + q.W * Math.Cos(theta1 * 0.5);\r\n        var d2 = q.Y * Math.Sin(theta2 * 0.5) + q.W * Math.Cos(theta2 * 0.5);\r\n        return (float)(d1 > d2 ? theta1 : theta2);\r\n    }\r\n\r\n    private static void ShiftToGround(Vector3[] positions)\r\n    {\r\n        var ground = float.PositiveInfinity;\r\n        foreach (var p in positions)\r\n            ground = MathF.Min(ground, p.Y);\r\n        if (!float.IsFinite(ground) || ground == 0f)\r\n            return;\r\n        for (var i = 0; i < positions.Length; i++)\r\n            positions[i].Y -= ground;\r\n    }\r\n\r\n    internal static float WrapPi(float angle)\r\n    {\r\n        while (angle > MathF.PI)\r\n            angle -= 2f * MathF.PI;\r\n        while (angle < -MathF.PI)\r\n            angle += 2f * MathF.PI;\r\n        return angle;\r\n    }\r\n\r\n    /// <summary>6D rotation representation: the first two columns of the rotation matrix\r\n    /// (<c>R\u00b7e_x</c> then <c>R\u00b7e_y</c>).</summary>\r\n    internal static (Vector3 C0, Vector3 C1) SixD(Quaternion q)\r\n        => (Vector3.Transform(Vector3.UnitX, q), Vector3.Transform(Vector3.UnitY, q));\r\n\r\n    private static void WriteNorm3(float[] x, int row, ref int col, Vector3 v, float[] m, float[] s)\r\n    {\r\n        x[row + col++] = (v.X - m[0]) / s[0];\r\n        x[row + col++] = (v.Y - m[1]) / s[1];\r\n        x[row + col++] = (v.Z - m[2]) / s[2];\r\n    }\r\n\r\n    private static void WriteNorm6(float[] x, int row, ref int col, (Vector3 C0, Vector3 C1) sixD, float[] m, float[] s)\r\n    {\r\n        x[row + col++] = (sixD.C0.X - m[0]) / s[0];\r\n        x[row + col++] = (sixD.C0.Y - m[1]) / s[1];\r\n        x[row + col++] = (sixD.C0.Z - m[2]) / s[2];\r\n        x[row + col++] = (sixD.C1.X - m[3]) / s[3];\r\n        x[row + col++] = (sixD.C1.Y - m[4]) / s[4];\r\n        x[row + col++] = (sixD.C1.Z - m[5]) / s[5];\r\n    }\r\n\r\n    internal static void AssertFinite(float[] values, string what)\r\n    {\r\n        foreach (var v in values)\r\n        {\r\n            if (!float.IsFinite(v))\r\n                throw new InvalidOperationException($\"{what} contain non-finite values.\");\r\n        }\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Formats/Fbx/FbxBinaryWriter.cs",
            "FileName": "FbxBinaryWriter.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Buffers.Binary;\r\nusing System.IO;\r\nusing System.Text;\r\n\r\nnamespace HumanoidRetargeter.Formats.Fbx;\r\n\r\n/// <summary>\r\n/// Serializes an <see cref=\"FbxNode\"/> tree back to binary FBX (version 7400 layout \u2014\r\n/// u32 header fields, universally readable). The inverse of\r\n/// <see cref=\"FbxTokenizer.Parse\"/>: a tree parsed from a 7.x binary file and written\r\n/// here re-parses to an identical tree (arrays are written uncompressed; zlib-encoded\r\n/// inputs therefore round-trip by VALUE, not byte-for-byte).\r\n/// </summary>\r\n/// <remarks>\r\n/// Used by <see cref=\"FbxBindPoseFixer\"/> to persist repaired node transforms. The footer\r\n/// is written the way Blender's exporter does: a fixed 16-byte watermark (importers treat\r\n/// it as opaque), zero padding to a 16-byte boundary, the version echo, 120 zero bytes and\r\n/// the closing magic. The FBX SDK computes a content hash here, but every consumer we\r\n/// target (s&amp;box, Blender, assimp) ignores it.\r\n/// </remarks>\r\npublic static class FbxBinaryWriter\r\n{\r\n    private const uint Version = 7400;\r\n\r\n    private static readonly byte[] HeaderMagic =\r\n        \"Kaydara FBX Binary  \\0\\x1a\\0\"u8.ToArray();\r\n\r\n    // Blender's fbx_binary.py FOOT_ID + closing magic bytes.\r\n    private static readonly byte[] FooterWatermark =\r\n    {\r\n        0xfa, 0xbc, 0xab, 0x09, 0xd0, 0xc8, 0xd4, 0x66, 0xb1, 0x76, 0xfb, 0x83, 0x1c, 0xf7, 0x26, 0x7e,\r\n    };\r\n\r\n    private static readonly byte[] FooterMagic =\r\n    {\r\n        0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0x6e, 0x0c, 0xc0, 0x00, 0x00,\r\n    };\r\n\r\n    /// <summary>\r\n    /// Serializes <paramref name=\"root\"/> (a virtual root whose children are the top-level\r\n    /// document nodes, as produced by <see cref=\"FbxTokenizer.Parse\"/>).\r\n    /// </summary>\r\n    public static byte[] Write(FbxNode root)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(root);\r\n\r\n        using var ms = new MemoryStream();\r\n        ms.Write(HeaderMagic);\r\n        WriteU32(ms, Version);\r\n\r\n        foreach (var child in root.Children)\r\n            WriteNode(ms, child);\r\n        WriteNullRecord(ms);\r\n\r\n        WriteFooter(ms);\r\n        return ms.ToArray();\r\n    }\r\n\r\n    // ------------------------------------------------------------------ nodes\r\n\r\n    private static void WriteNode(MemoryStream ms, FbxNode node)\r\n    {\r\n        long headerAt = ms.Position;\r\n        // Placeholder header: endOffset, numProps, propListLen (patched after the body).\r\n        WriteU32(ms, 0);\r\n        WriteU32(ms, (uint)node.Properties.Count);\r\n        WriteU32(ms, 0);\r\n        var nameBytes = Encoding.ASCII.GetBytes(node.Name);\r\n        if (nameBytes.Length > byte.MaxValue)\r\n            throw new FormatException($\"FBX write: node name too long ({node.Name.Length} chars).\");\r\n        ms.WriteByte((byte)nameBytes.Length);\r\n        ms.Write(nameBytes);\r\n\r\n        long propsAt = ms.Position;\r\n        foreach (var p in node.Properties)\r\n            WriteProperty(ms, p, node.Name);\r\n        long propsLen = ms.Position - propsAt;\r\n\r\n        if (node.Children.Count > 0)\r\n        {\r\n            foreach (var child in node.Children)\r\n                WriteNode(ms, child);\r\n            WriteNullRecord(ms);\r\n        }\r\n\r\n        long endAt = ms.Position;\r\n        ms.Position = headerAt;\r\n        WriteU32(ms, checked((uint)endAt));\r\n        WriteU32(ms, (uint)node.Properties.Count);\r\n        WriteU32(ms, checked((uint)propsLen));\r\n        ms.Position = endAt;\r\n    }\r\n\r\n    private static void WriteNullRecord(MemoryStream ms)\r\n    {\r\n        Span<byte> zeros = stackalloc byte[13];\r\n        zeros.Clear();\r\n        ms.Write(zeros);\r\n    }\r\n\r\n    // ------------------------------------------------------------------ properties\r\n\r\n    private static void WriteProperty(MemoryStream ms, object value, string owner)\r\n    {\r\n        switch (value)\r\n        {\r\n            case short y:\r\n                ms.WriteByte((byte)'Y');\r\n                WriteI16(ms, y);\r\n                break;\r\n            case bool c:\r\n                ms.WriteByte((byte)'C');\r\n                ms.WriteByte(c ? (byte)1 : (byte)0);\r\n                break;\r\n            case int i:\r\n                ms.WriteByte((byte)'I');\r\n                WriteI32(ms, i);\r\n                break;\r\n            case float f:\r\n                ms.WriteByte((byte)'F');\r\n                WriteF32(ms, f);\r\n                break;\r\n            case double d:\r\n                ms.WriteByte((byte)'D');\r\n                WriteF64(ms, d);\r\n                break;\r\n            case long l:\r\n                ms.WriteByte((byte)'L');\r\n                WriteI64(ms, l);\r\n                break;\r\n\r\n            case float[] fa:\r\n                WriteArrayHeader(ms, 'f', fa.Length, 4);\r\n                foreach (var x in fa)\r\n                    WriteF32(ms, x);\r\n                break;\r\n            case double[] da:\r\n                WriteArrayHeader(ms, 'd', da.Length, 8);\r\n                foreach (var x in da)\r\n                    WriteF64(ms, x);\r\n                break;\r\n            case long[] la:\r\n                WriteArrayHeader(ms, 'l', la.Length, 8);\r\n                foreach (var x in la)\r\n                    WriteI64(ms, x);\r\n                break;\r\n            case int[] ia:\r\n                WriteArrayHeader(ms, 'i', ia.Length, 4);\r\n                foreach (var x in ia)\r\n                    WriteI32(ms, x);\r\n                break;\r\n            case bool[] ba:\r\n                WriteArrayHeader(ms, 'b', ba.Length, 1);\r\n                foreach (var x in ba)\r\n                    ms.WriteByte(x ? (byte)1 : (byte)0);\r\n                break;\r\n\r\n            case string s:\r\n            {\r\n                ms.WriteByte((byte)'S');\r\n                var bytes = Encoding.UTF8.GetBytes(s);\r\n                WriteU32(ms, (uint)bytes.Length);\r\n                ms.Write(bytes);\r\n                break;\r\n            }\r\n            case byte[] r:\r\n                ms.WriteByte((byte)'R');\r\n                WriteU32(ms, (uint)r.Length);\r\n                ms.Write(r);\r\n                break;\r\n\r\n            default:\r\n                throw new FormatException(\r\n                    $\"FBX write: node '{owner}': unsupported property CLR type {value?.GetType().Name ?? \"null\"}.\");\r\n        }\r\n    }\r\n\r\n    private static void WriteArrayHeader(MemoryStream ms, char code, int count, int elemSize)\r\n    {\r\n        ms.WriteByte((byte)code);\r\n        WriteU32(ms, (uint)count);\r\n        WriteU32(ms, 0); // encoding 0 = uncompressed\r\n        WriteU32(ms, checked((uint)(count * elemSize)));\r\n    }\r\n\r\n    // ------------------------------------------------------------------ footer\r\n\r\n    private static void WriteFooter(MemoryStream ms)\r\n    {\r\n        ms.Write(FooterWatermark);\r\n\r\n        // Zero-pad so the version echo starts 16-aligned (Blender pads at least 1 byte).\r\n        int pad = (int)(16 - ms.Position % 16);\r\n        for (int i = 0; i < pad; i++)\r\n            ms.WriteByte(0);\r\n\r\n        WriteU32(ms, Version);\r\n        Span<byte> zeros = stackalloc byte[120];\r\n        zeros.Clear();\r\n        ms.Write(zeros);\r\n        ms.Write(FooterMagic);\r\n    }\r\n\r\n    // ------------------------------------------------------------------ primitives\r\n\r\n    private static void WriteU32(MemoryStream ms, uint v)\r\n    {\r\n        Span<byte> b = stackalloc byte[4];\r\n        BinaryPrimitives.WriteUInt32LittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n\r\n    private static void WriteI16(MemoryStream ms, short v)\r\n    {\r\n        Span<byte> b = stackalloc byte[2];\r\n        BinaryPrimitives.WriteInt16LittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n\r\n    private static void WriteI32(MemoryStream ms, int v)\r\n    {\r\n        Span<byte> b = stackalloc byte[4];\r\n        BinaryPrimitives.WriteInt32LittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n\r\n    private static void WriteI64(MemoryStream ms, long v)\r\n    {\r\n        Span<byte> b = stackalloc byte[8];\r\n        BinaryPrimitives.WriteInt64LittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n\r\n    private static void WriteF32(MemoryStream ms, float v)\r\n    {\r\n        Span<byte> b = stackalloc byte[4];\r\n        BinaryPrimitives.WriteSingleLittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n\r\n    private static void WriteF64(MemoryStream ms, double v)\r\n    {\r\n        Span<byte> b = stackalloc byte[8];\r\n        BinaryPrimitives.WriteDoubleLittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Mapping/ProfileLibrary.cs",
            "FileName": "ProfileLibrary.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System.Collections.Generic;\r\n\r\nnamespace HumanoidRetargeter.Mapping;\r\n\r\n/// <summary>\r\n/// Built-in preset profiles, embedded as C# data (the same data is written to\r\n/// <c>Assets/humanoid_retargeter/profiles/*.json</c> by a regenerate-and-diff test so the\r\n/// shipped JSON can never drift from the code).\r\n/// </summary>\r\npublic static class ProfileLibrary\r\n{\r\n    /// <summary>Mixamo / Adobe rigs: <c>mixamorig[N]:</c> namespace, <c>LeftArm</c> /\r\n    /// <c>LeftForeArm</c> / <c>LeftHandIndex1..3</c> style names.</summary>\r\n    public static Profile Mixamo { get; } = BuildMixamo();\r\n\r\n    /// <summary>\r\n    /// Reallusion ActorCore / AccuRig / Character Creator rigs (<c>CC_Base_*</c>).\r\n    /// Empirical notes from <c>research/rig_actorcore.json</c>:\r\n    /// <list type=\"bullet\">\r\n    /// <item><c>CC_Base_Hip</c> is the parent of BOTH <c>CC_Base_Pelvis</c> (leg branch) and\r\n    /// <c>CC_Base_Waist</c> (spine branch), i.e. the LCA of legs+spine and the true animated\r\n    /// hips root \u2192 it carries <see cref=\"BoneRole.Hips\"/>; <c>CC_Base_Pelvis</c> is a\r\n    /// leg-branch intermediate and stays unmapped.</item>\r\n    /// <item>The neck chain is <c>CC_Base_NeckTwist01 \u2192 CC_Base_NeckTwist02 \u2192 CC_Base_Head</c>;\r\n    /// despite the name, <c>NeckTwist01</c> IS the neck bone (there is no plain\r\n    /// <c>CC_Base_Neck</c>), so it is the <see cref=\"BoneRole.Neck\"/> alias. NeckTwist02 is\r\n    /// left unmapped. All other Twist/ShareBone helpers are excluded (no aliases).</item>\r\n    /// <item><c>CC_Base_L_ToeBase</c> is the toe role; the co-located\r\n    /// <c>CC_Base_L_ToeBaseShareBone</c> is a helper and must never be mapped.</item>\r\n    /// </list>\r\n    /// </summary>\r\n    public static Profile ActorCoreCc { get; } = BuildActorCoreCc();\r\n\r\n    /// <summary>Unreal Engine mannequin (UE4/UE5): <c>pelvis</c>, <c>spine_01..05</c>,\r\n    /// <c>clavicle_l</c>, <c>thumb_01_l</c>, UE5 <c>*_metacarpal_*</c>; <c>*_twist_*</c>\r\n    /// bones have no aliases and are never mapped.</summary>\r\n    public static Profile UeMannequin { get; } = BuildUeMannequin();\r\n\r\n    /// <summary>Rokoko / Xsens style BVH rigs: plain <c>Hips</c>/<c>Spine..Spine4</c>/<c>\r\n    /// LeftArm|LeftUpperArm</c> name variants, usually no fingers.</summary>\r\n    public static Profile RokokoBvh { get; } = BuildRokokoBvh();\r\n\r\n    /// <summary>\r\n    /// SMPL body model family (AMASS exports, Meshcapade FBX rigs). Joint names per the\r\n    /// published model (vchoutas/smplx <c>joint_names.py</c>, Meshcapade wiki):\r\n    /// <c>pelvis</c>, sided <c>hip\u2192knee\u2192ankle\u2192foot</c> legs (the \"hip\" joint IS the thigh;\r\n    /// \"ankle\" is the foot, \"foot\" is the toe region) and <c>collar\u2192shoulder\u2192elbow\u2192wrist</c>\r\n    /// arms (\"shoulder\" is the upper arm, \"wrist\" is the hand; the <c>hand</c> joint is a\r\n    /// finger stub and stays unmapped). Both spellings occur in the wild: <c>left_hip</c>\r\n    /// (model joints) and <c>L_Hip</c> with gendered FBX prefixes <c>m_avg_</c>/<c>f_avg_</c>\r\n    /// (SMPL Unity/FBX rigs). No fingers \u2014 that is SMPL-X (<see cref=\"SmplX\"/>), kept as a\r\n    /// separate preset so a finger-less SMPL rig still reaches full optional coverage.\r\n    /// </summary>\r\n    public static Profile Smpl { get; } = BuildSmpl(withFingers: false);\r\n\r\n    /// <summary>\r\n    /// SMPL-X: the SMPL body joints (<see cref=\"Smpl\"/>) plus articulated hands \u2014\r\n    /// <c>left_thumb1..3</c>/<c>left_index1..3</c>-style finger joints per\r\n    /// vchoutas/smplx <c>joint_names.py</c> (jaw/eye joints carry no humanoid role).\r\n    /// Evaluated before <see cref=\"Smpl\"/> so it wins the tie on SMPL-X rigs (both score\r\n    /// the body fully; only this one maps the fingers).\r\n    /// </summary>\r\n    public static Profile SmplX { get; } = BuildSmpl(withFingers: true);\r\n\r\n    /// <summary>\r\n    /// NVIDIA SOMA uniform-proportion skeleton (SOMA/SEED BVH exports, e.g.\r\n    /// github.com/NVIDIA/soma-retargeter <c>assets/motions/bvh</c>). Mixamo-like upper-body\r\n    /// names, but: spine is <c>Spine1\u2192Spine2\u2192Chest</c> (no plain \"Spine\"), neck is\r\n    /// <c>Neck1\u2192Neck2</c>, the legs are <c>LeftLeg\u2192LeftShin</c> \u2014 SOMA's <c>LeftLeg</c> is\r\n    /// the THIGH (mixamo's is the calf), which is exactly why the mixamo preset must never\r\n    /// claim these rigs \u2014 and the four fingers have FOUR segments where segment 1 is a\r\n    /// metacarpal (<c>LeftHandIndex1..4</c>; mixamo's 1..3 are the phalanges), so the\r\n    /// phalanx roles map to segments 2/3/4.\r\n    /// </summary>\r\n    public static Profile SomaBvh { get; } = BuildSomaBvh();\r\n\r\n    /// <summary>\r\n    /// Classic BVH / Character-Studio-friendly naming (MotionBuilder \"Export BVH to\r\n    /// Character Studio\" convention, ACCAD-style mocap BVHs): <c>Hips</c>,\r\n    /// <c>Chest[2..4]</c> spine, arms <c>Collar\u2192Shoulder\u2192Elbow\u2192Wrist</c> (the \"Shoulder\"\r\n    /// is the upper arm) and legs <c>Hip\u2192Knee\u2192Ankle\u2192Toe</c> (the sided \"Hip\" is the\r\n    /// thigh). No fingers.\r\n    /// </summary>\r\n    public static Profile ClassicBvh { get; } = BuildClassicBvh();\r\n\r\n    /// <summary>\r\n    /// 3ds Max Character Studio Biped rigs: every bone is \"&lt;BipedName&gt; &lt;Part&gt;\"\r\n    /// where the biped name defaults to <c>Bip01</c> (3ds Max \u22642009) / <c>Bip001</c>\r\n    /// (2010+) per the Autodesk \"Naming the Biped\" documentation; some exporters mangle\r\n    /// the spaces to underscores (<c>Bip01_L_Thigh</c>), hence the <c>^Bip\\d+[ _]</c>\r\n    /// namespace pattern (alias comparison is separator-insensitive, so \"L UpperArm\" and\r\n    /// \"L_UpperArm\" normalize identically). Sided bones use a bare mid-name <c>L/R</c>:\r\n    /// <c>L Clavicle\u2192L UpperArm\u2192L Forearm\u2192L Hand</c> arms,\r\n    /// <c>L Thigh\u2192L Calf\u2192L Foot\u2192L Toe0</c> legs. Fingers are numbered chains\r\n    /// <c>L Finger0..4</c> (0 = thumb) with phalanx segments <c>Finger01/Finger02</c>\r\n    /// etc. (MotionBuilder's \"3ds Max Biped Template\" characterization maps exactly these\r\n    /// names). The COM root <c>Bip01</c> itself, <c>Footsteps</c>, toe segments\r\n    /// <c>Toe01/Toe02</c> and <c>HorseLink</c> carry no aliases and are never mapped.\r\n    /// </summary>\r\n    public static Profile Biped { get; } = BuildBiped();\r\n\r\n    /// <summary>\r\n    /// DAZ/Poser classic naming (Poser 4 era figures, DAZ Generation-4 V4/M4, Genesis 1/2,\r\n    /// MakeHuman's \"Poser/DAZ names\" BVH export \u2014 verified against the local\r\n    /// <c>dev/corpus/unknown_rigs/makehuman_cmu_03_03_dazNames.bvh</c>): camel-case bones\r\n    /// with a lower-case <c>l</c>/<c>r</c> side prefix \u2014 <c>hip</c> (the translating\r\n    /// root), <c>abdomen[\u2192abdomen2]\u2192chest</c> spine, <c>neck</c>, <c>head</c>,\r\n    /// <c>lCollar\u2192lShldr\u2192lForeArm\u2192lHand</c> arms, <c>lThigh\u2192lShin\u2192lFoot\u2192lToe</c> legs and\r\n    /// <c>lThumb1..3/lIndex1..3/lMid1..3/lRing1..3/lPinky1..3</c> fingers. The\r\n    /// <c>l/rButtock</c> thigh helpers and eye bones carry no aliases and stay unmapped.\r\n    /// DAZ Genesis 3/8/9 renamed the skeleton (<c>abdomenLower</c>, <c>lShldrBend</c>, \u2026)\r\n    /// and is NOT covered by this preset.\r\n    /// </summary>\r\n    public static Profile DazPoser { get; } = BuildDazPoser();\r\n\r\n    /// <summary>\r\n    /// Blender Rigify human rigs, per the metarig definition in the rigify add-on\r\n    /// (<c>rigify/metarigs/human.py</c>) and the Blender manual's basic.human reference:\r\n    /// the spine chain is <c>spine\u2192spine.001..spine.006</c> where <c>spine</c> IS the\r\n    /// pelvis/hips bone (it sits at the pelvis and parents the thighs), spine.001\u2013003 are\r\n    /// the torso, spine.004/005 the two neck bones (004 carries <see cref=\"BoneRole.Neck\"/>,\r\n    /// 005 stays unmapped \u2014 same policy as ActorCore's NeckTwist02) and spine.006 is the\r\n    /// head. Limbs: <c>shoulder.L\u2192upper_arm.L\u2192forearm.L\u2192hand.L</c>,\r\n    /// <c>thigh.L\u2192shin.L\u2192foot.L\u2192toe.L</c>; fingers <c>thumb.01.L..03.L</c> and\r\n    /// <c>f_index/f_middle/f_ring/f_pinky.01.L..03.L</c>. The <c>^DEF-</c> namespace\r\n    /// pattern also matches rigify's generated deform skeleton (<c>DEF-spine.001</c>,\r\n    /// <c>DEF-upper_arm.L</c>, \u2026); the segmented deform twins (<c>DEF-upper_arm.L.001</c>),\r\n    /// <c>palm.*</c>, <c>pelvis.L/R</c>, <c>heel.02.L</c>, face bones and the generated\r\n    /// ORG-/MCH-/control bones have no aliases and are never mapped.\r\n    /// </summary>\r\n    public static Profile Rigify { get; } = BuildRigify();\r\n\r\n    /// <summary>\r\n    /// VRoid Studio / VRM avatars (UniVRM exports): <c>J_Bip_&lt;side&gt;_&lt;Part&gt;</c>\r\n    /// bones where side is <c>C</c> (center), <c>L</c> or <c>R</c> \u2014 the standard VRoid\r\n    /// skeleton behind the VRM humanoid spec (vrm-c/vrm-specification, humanoid bone map):\r\n    /// <c>J_Bip_C_Hips/Spine/Chest/UpperChest/Neck/Head</c>,\r\n    /// <c>J_Bip_L_Shoulder\u2192UpperArm\u2192LowerArm\u2192Hand</c>,\r\n    /// <c>J_Bip_L_UpperLeg\u2192LowerLeg\u2192Foot\u2192ToeBase</c>, fingers\r\n    /// <c>J_Bip_L_Thumb1..3/Index1..3/Middle1..3/Ring1..3/Little1..3</c> (\"Little\" is the\r\n    /// pinky, per the VRM littleProximal/Intermediate/Distal humanoid bones). Secondary\r\n    /// physics/adjust bones (<c>J_Sec_*</c>, <c>J_Adj_*</c>) and the <c>Root</c> bone have\r\n    /// no aliases and are never mapped.\r\n    /// </summary>\r\n    public static Profile Vrm { get; } = BuildVrm();\r\n\r\n    /// <summary>\r\n    /// Blender Auto-Rig Pro humanoid FBX exports \u2014 bone names verified empirically against\r\n    /// the local user repro <c>dev/corpus/todo/Defenses.fbx</c> (the PunchPerfect family):\r\n    /// <c>.x</c> suffix marks center bones, <c>.l/.r</c> the sides, and the exported limb\r\n    /// deform bones carry the <c>_stretch</c> twin name \u2014 <c>root.x</c> is the hips\r\n    /// (under a ground bone <c>root</c>), <c>spine_01.x\u2192spine_02.x\u2192spine_03.x</c>,\r\n    /// <c>neck.x</c>, <c>head.x</c>, arms <c>shoulder.l\u2192arm_stretch.l\u2192forearm_stretch.l\u2192\r\n    /// hand.l</c> (plain \"arm\", NOT \"upperarm\"), legs <c>thigh_stretch.l\u2192leg_stretch.l\u2192\r\n    /// foot.l\u2192toes_01.l</c> (\"leg\" is the calf). Fingers keep Auto-Rig Pro's <c>c_</c>\r\n    /// control prefix on the exported deform chain: <c>c_thumb1.l..3.l</c>,\r\n    /// <c>c_index/c_middle/c_ring/c_pinky1.l..3.l</c>. Leftover finger-tip markers\r\n    /// (<c>mixamorig:LeftHandIndex4</c> in the repro) and <c>root</c> have no aliases.\r\n    /// </summary>\r\n    public static Profile AutoRigPro { get; } = BuildAutoRigPro();\r\n\r\n    /// <summary>\r\n    /// Xsens MVN exports (23-segment MVN body model; MVN Animate/Analyze FBX and BVH):\r\n    /// anatomical vertebra names for the spine chain <c>Pelvis\u2192L5\u2192L3\u2192T12\u2192T8</c> (the four\r\n    /// exported lumbar/thoracic segments of the MVN model), <c>Neck\u2192Head</c>, arms\r\n    /// <c>RightShoulder\u2192RightUpperArm\u2192RightForeArm\u2192RightHand</c> and legs\r\n    /// <c>RightUpperLeg\u2192RightLowerLeg\u2192RightFoot\u2192RightToe</c>. Body-suit capture only \u2014 no\r\n    /// finger segments (Xsens gloves ship as separate data), so the hands are chain tips.\r\n    /// </summary>\r\n    public static Profile XsensMvn { get; } = BuildXsensMvn();\r\n\r\n    /// <summary>\r\n    /// Perception Neuron / Axis Neuron BVH exports: mixamo-like limb and finger names\r\n    /// (<c>RightArm\u2192RightForeArm\u2192RightHand</c>, <c>RightUpLeg\u2192RightLeg\u2192RightFoot</c>,\r\n    /// <c>RightHandThumb1..3</c>) but a FOUR-bone spine (<c>Spine\u2192Spine1..Spine3</c>), no\r\n    /// toe joints (the feet are chain tips), and per-finger <c>RightInHandIndex</c>-style\r\n    /// metacarpal helpers between the hand and the <c>RightHandIndex1..3</c> phalanges.\r\n    /// The InHand metacarpals carry no aliases (barely animated palm helpers; mapping them\r\n    /// as phalanges would shift every curl one joint outward \u2014 the SOMA finger bug class).\r\n    /// The extra Spine3 is what lets this preset outscore mixamo on Neuron rigs (and\r\n    /// mixamo's toes keep mixamo ahead on real Mixamo rigs).\r\n    /// </summary>\r\n    public static Profile PerceptionNeuron { get; } = BuildPerceptionNeuron();\r\n\r\n    /// <summary>\r\n    /// Source engine ValveBiped skeletons (HL2/GMod humanoids, playermodels):\r\n    /// <c>ValveBiped.Bip01_*</c> names \u2014 3ds-Max-Biped-derived parts behind the fixed\r\n    /// namespace, with underscores and a spine chain that SKIPS Spine3\r\n    /// (<c>Spine\u2192Spine1\u2192Spine2\u2192Spine4</c>), <c>Neck1</c>/<c>Head1</c>, arms\r\n    /// <c>L_Clavicle\u2192L_UpperArm\u2192L_Forearm\u2192L_Hand</c>, legs\r\n    /// <c>L_Thigh\u2192L_Calf\u2192L_Foot\u2192L_Toe0</c> and numbered finger chains\r\n    /// <c>L_Finger0/01/02</c> (0 = thumb) \u2026 <c>L_Finger4/41/42</c> (pinky). Evaluated\r\n    /// before <see cref=\"Biped\"/> (same Bip01 ancestry; the plain-Biped preset must never\r\n    /// claim a ValveBiped rig). Attachment/weapon helpers (<c>ValveBiped.forward</c>,\r\n    /// <c>ValveBiped.Anim_Attachment_*</c>) have no aliases and are never mapped.\r\n    /// </summary>\r\n    public static Profile ValveBiped { get; } = BuildValveBiped();\r\n\r\n    /// <summary>\r\n    /// DAZ Genesis 3/8(.1) figures: renamed Genesis skeleton (NOT covered by\r\n    /// <see cref=\"DazPoser\"/>) \u2014 <c>hip</c> is the translating root and the LCA of the\r\n    /// <c>pelvis</c> leg branch and the <c>abdomenLower\u2192abdomenUpper\u2192chestLower\u2192chestUpper</c>\r\n    /// spine (so <c>hip</c> carries <see cref=\"BoneRole.Hips\"/> and <c>pelvis</c> stays\r\n    /// unmapped, same policy as ActorCore's Hip/Pelvis pair). Neck chain\r\n    /// <c>neckLower\u2192neckUpper\u2192head</c> (neckUpper unmapped, NeckTwist02 policy). Limbs use\r\n    /// Bend/Twist pairs: the Bend bones (<c>lShldrBend</c>, <c>lForearmBend</c>,\r\n    /// <c>lThighBend</c>) are the primary limb bones; the co-linear Twist roll helpers\r\n    /// (<c>lShldrTwist</c>, <c>lForearmTwist</c>, <c>lThighTwist</c>) carry no aliases and\r\n    /// are never mapped. Legs <c>lThighBend\u2192lShin\u2192lFoot\u2192lToe</c> (<c>lMetatarsals</c> is an\r\n    /// arch helper between foot and toe, unmapped). Fingers are the classic DAZ\r\n    /// <c>lThumb1..3/lIndex1..3/lMid1..3/lRing1..3/lPinky1..3</c>. Genesis 9 renamed the\r\n    /// skeleton again (<c>l_upperarm</c>, \u2026) and is NOT covered by this preset.\r\n    /// </summary>\r\n    public static Profile DazGenesis { get; } = BuildDazGenesis();\r\n\r\n    /// <summary>\r\n    /// The s&amp;box citizen-family skeleton itself (<c>citizen.vmdl</c>,\r\n    /// <c>citizen_human_*.vmdl</c> and every community model re-rigged on their skeleton):\r\n    /// <c>pelvis</c>, <c>spine_0..2</c>, <c>neck_0</c>, <c>head</c>, reversed-word limb\r\n    /// names <c>arm_upper_L\u2192arm_lower_L\u2192hand_L</c> / <c>leg_upper_L\u2192leg_lower_L\u2192\r\n    /// ankle_L\u2192ball_L</c>, <c>clavicle_L/R</c> and fingers\r\n    /// <c>finger_&lt;name&gt;_{meta,0,1,2}_L</c> (meta = metacarpal, 0/1/2 =\r\n    /// proximal/middle/distal). The alias table mirrors\r\n    /// <see cref=\"Target.SboxBoneClassifier\"/>'s curated role table \u2014 kept in sync by a\r\n    /// test. This is what lets a COMPILED s&amp;box model picked as a custom conversion\r\n    /// target (or used as a source) be recognized: the reversed word order\r\n    /// (<c>arm_upper</c>, not <c>upper_arm</c>) defeats generic token matching, which\r\n    /// scored the citizen rig at 5% and made every custom-model pick fail with \"not\r\n    /// recognized as humanoid\". Twist/helper/IK/face bones (<c>*_twist*</c>,\r\n    /// <c>*_helper*</c>, <c>eye_*</c>, \u2026) have no aliases and are never mapped.\r\n    /// </summary>\r\n    public static Profile Sbox { get; } = BuildSbox();\r\n\r\n    /// <summary>\r\n    /// AdvancedSkeleton (Maya auto-rigger, ubiquitous in game rips and mobile-game rigs):\r\n    /// <c>Root_M</c> hips, <c>Spine1_M(\u2192Spine2_M)\u2192Chest_M</c> spine, <c>Neck_M\u2192Head_M</c>\r\n    /// (small rigs parent <c>Head_M</c> straight to the chest with no neck),\r\n    /// <c>Scapula\u2192Shoulder\u2192Elbow\u2192Wrist</c> arms, <c>Hip\u2192Knee\u2192Ankle\u2192Toes</c> legs and\r\n    /// <c>&lt;Name&gt;Finger1..3</c> fingers, all sided <c>_L/_R</c> (center <c>_M</c>).\r\n    /// Twist helpers (<c>ShoulderPart1</c>, <c>HipPart1</c>, \u2026), <c>Cup</c> palm bones and\r\n    /// the face rig carry no aliases. Real case: a Sonic mobile-game rip whose name stage\r\n    /// scored below threshold \u2014 the topology fallback then mapped ARMS AND LEGS ONTO THE\r\n    /// HEAD QUILLS (long symmetric chains), playing every clip as garbage.\r\n    /// </summary>\r\n    public static Profile AdvancedSkeleton { get; } = BuildAdvancedSkeleton();\r\n\r\n    /// <summary>All built-in presets, in detection order (first wins score ties \u2014 see\r\n    /// <see cref=\"SmplX\"/> vs <see cref=\"Smpl\"/>; <see cref=\"ValveBiped\"/> is evaluated\r\n    /// before <see cref=\"Biped\"/> and <see cref=\"DazGenesis\"/> before\r\n    /// <see cref=\"DazPoser\"/> within their families).</summary>\r\n    public static IReadOnlyList<Profile> All { get; } =\r\n        new[]\r\n        {\r\n            Sbox, Mixamo, ActorCoreCc, UeMannequin, XsensMvn, PerceptionNeuron, RokokoBvh,\r\n            SmplX, Smpl, SomaBvh, ClassicBvh, ValveBiped, Biped, DazGenesis, DazPoser,\r\n            Rigify, Vrm, AutoRigPro, AdvancedSkeleton,\r\n        };\r\n\r\n    // ---------------------------------------------------------------- advanced skeleton\r\n\r\n    private static Profile BuildAdvancedSkeleton()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"Root_M\" },\r\n            [BoneRole.Spine0] = new[] { \"Spine1_M\" },\r\n            [BoneRole.Spine1] = new[] { \"Spine2_M\" },\r\n            [BoneRole.Spine2] = new[] { \"Chest_M\" },\r\n            [BoneRole.Neck] = new[] { \"Neck_M\" },\r\n            [BoneRole.Head] = new[] { \"Head_M\" },\r\n        };\r\n        foreach (var side in new[] { \"L\", \"R\" })\r\n        {\r\n            aliases[Role(\"Clavicle\", side)] = new[] { $\"Scapula_{side}\" };\r\n            aliases[Role(\"UpperArm\", side)] = new[] { $\"Shoulder_{side}\" };\r\n            aliases[Role(\"LowerArm\", side)] = new[] { $\"Elbow_{side}\" };\r\n            aliases[Role(\"Hand\", side)] = new[] { $\"Wrist_{side}\" };\r\n            aliases[Role(\"UpperLeg\", side)] = new[] { $\"Hip_{side}\" };\r\n            aliases[Role(\"LowerLeg\", side)] = new[] { $\"Knee_{side}\" };\r\n            aliases[Role(\"Foot\", side)] = new[] { $\"Ankle_{side}\" };\r\n            aliases[Role(\"Toe\", side)] = new[] { $\"Toes_{side}\" };\r\n\r\n            foreach (var finger in new[] { \"Thumb\", \"Index\", \"Middle\", \"Ring\", \"Pinky\" })\r\n            {\r\n                aliases[Role($\"{finger}Prox\", side)] = new[] { $\"{finger}Finger1_{side}\" };\r\n                aliases[Role($\"{finger}Mid\", side)] = new[] { $\"{finger}Finger2_{side}\" };\r\n                aliases[Role($\"{finger}Dist\", side)] = new[] { $\"{finger}Finger3_{side}\" };\r\n            }\r\n        }\r\n\r\n        return new Profile(\"advanced_skeleton\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- sbox\r\n\r\n    private static Profile BuildSbox()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"pelvis\" },\r\n            [BoneRole.Spine0] = new[] { \"spine_0\" },\r\n            [BoneRole.Spine1] = new[] { \"spine_1\" },\r\n            [BoneRole.Spine2] = new[] { \"spine_2\" },\r\n            [BoneRole.Spine3] = new[] { \"spine_3\" },\r\n            [BoneRole.Neck] = new[] { \"neck_0\" },\r\n            [BoneRole.Head] = new[] { \"head\" },\r\n        };\r\n        foreach (var side in new[] { \"L\", \"R\" })\r\n        {\r\n            aliases[Role(\"Clavicle\", side)] = new[] { $\"clavicle_{side}\" };\r\n            aliases[Role(\"UpperArm\", side)] = new[] { $\"arm_upper_{side}\" };\r\n            aliases[Role(\"LowerArm\", side)] = new[] { $\"arm_lower_{side}\" };\r\n            aliases[Role(\"Hand\", side)] = new[] { $\"hand_{side}\" };\r\n            aliases[Role(\"UpperLeg\", side)] = new[] { $\"leg_upper_{side}\" };\r\n            aliases[Role(\"LowerLeg\", side)] = new[] { $\"leg_lower_{side}\" };\r\n            aliases[Role(\"Foot\", side)] = new[] { $\"ankle_{side}\" };\r\n            aliases[Role(\"Toe\", side)] = new[] { $\"ball_{side}\" };\r\n\r\n            foreach (var (finger, rolePrefix) in new[]\r\n            {\r\n                (\"thumb\", \"Thumb\"), (\"index\", \"Index\"), (\"middle\", \"Middle\"),\r\n                (\"ring\", \"Ring\"), (\"pinky\", \"Pinky\"),\r\n            })\r\n            {\r\n                aliases[Role($\"{rolePrefix}Meta\", side)] = new[] { $\"finger_{finger}_meta_{side}\" };\r\n                aliases[Role($\"{rolePrefix}Prox\", side)] = new[] { $\"finger_{finger}_0_{side}\" };\r\n                aliases[Role($\"{rolePrefix}Mid\", side)] = new[] { $\"finger_{finger}_1_{side}\" };\r\n                aliases[Role($\"{rolePrefix}Dist\", side)] = new[] { $\"finger_{finger}_2_{side}\" };\r\n            }\r\n        }\r\n\r\n        return new Profile(\"sbox\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- mixamo\r\n\r\n    private static Profile BuildMixamo()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"Hips\" },\r\n            [BoneRole.Spine0] = new[] { \"Spine\" },\r\n            [BoneRole.Spine1] = new[] { \"Spine1\" },\r\n            [BoneRole.Spine2] = new[] { \"Spine2\" },\r\n            [BoneRole.Neck] = new[] { \"Neck\" },\r\n            [BoneRole.Head] = new[] { \"Head\" },\r\n        };\r\n        foreach (var (roleSide, nameSide) in Sides())\r\n        {\r\n            aliases[Role(\"Clavicle\", roleSide)] = new[] { $\"{nameSide}Shoulder\" };\r\n            aliases[Role(\"UpperArm\", roleSide)] = new[] { $\"{nameSide}Arm\" };\r\n            aliases[Role(\"LowerArm\", roleSide)] = new[] { $\"{nameSide}ForeArm\" };\r\n            aliases[Role(\"Hand\", roleSide)] = new[] { $\"{nameSide}Hand\" };\r\n            aliases[Role(\"UpperLeg\", roleSide)] = new[] { $\"{nameSide}UpLeg\" };\r\n            aliases[Role(\"LowerLeg\", roleSide)] = new[] { $\"{nameSide}Leg\" };\r\n            aliases[Role(\"Foot\", roleSide)] = new[] { $\"{nameSide}Foot\" };\r\n            aliases[Role(\"Toe\", roleSide)] = new[] { $\"{nameSide}ToeBase\" };\r\n\r\n            foreach (var finger in new[] { \"Thumb\", \"Index\", \"Middle\", \"Ring\", \"Pinky\" })\r\n            {\r\n                aliases[Role($\"{finger}Prox\", roleSide)] = new[] { $\"{nameSide}Hand{finger}1\" };\r\n                aliases[Role($\"{finger}Mid\", roleSide)] = new[] { $\"{nameSide}Hand{finger}2\" };\r\n                aliases[Role($\"{finger}Dist\", roleSide)] = new[] { $\"{nameSide}Hand{finger}3\" };\r\n            }\r\n        }\r\n        // Both ':' (FBX namespace) and '_' (namespace mangled by some exporters) forms occur\r\n        // in the wild; some Mixamo downloads ship with no namespace at all, which still\r\n        // matches because the aliases are the bare names.\r\n        return new Profile(\"mixamo\", new[] { \"^mixamorig[0-9]*:\", \"^mixamorig[0-9]*_\" }, aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- actorcore / cc\r\n\r\n    private static Profile BuildActorCoreCc()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"Hip\" },\r\n            [BoneRole.Spine0] = new[] { \"Waist\" },\r\n            [BoneRole.Spine1] = new[] { \"Spine01\" },\r\n            [BoneRole.Spine2] = new[] { \"Spine02\" },\r\n            [BoneRole.Neck] = new[] { \"NeckTwist01\" },\r\n            [BoneRole.Head] = new[] { \"Head\" },\r\n        };\r\n        foreach (var roleSide in new[] { \"L\", \"R\" })\r\n        {\r\n            var nameSide = roleSide; // CC bones use the bare side letter: CC_Base_L_Thigh.\r\n            aliases[Role(\"Clavicle\", roleSide)] = new[] { $\"{nameSide}_Clavicle\" };\r\n            aliases[Role(\"UpperArm\", roleSide)] = new[] { $\"{nameSide}_Upperarm\" };\r\n            aliases[Role(\"LowerArm\", roleSide)] = new[] { $\"{nameSide}_Forearm\" };\r\n            aliases[Role(\"Hand\", roleSide)] = new[] { $\"{nameSide}_Hand\" };\r\n            aliases[Role(\"UpperLeg\", roleSide)] = new[] { $\"{nameSide}_Thigh\" };\r\n            aliases[Role(\"LowerLeg\", roleSide)] = new[] { $\"{nameSide}_Calf\" };\r\n            aliases[Role(\"Foot\", roleSide)] = new[] { $\"{nameSide}_Foot\" };\r\n            aliases[Role(\"Toe\", roleSide)] = new[] { $\"{nameSide}_ToeBase\" };\r\n\r\n            foreach (var (role, cc) in new[]\r\n            {\r\n                (\"Thumb\", \"Thumb\"), (\"Index\", \"Index\"), (\"Middle\", \"Mid\"), (\"Ring\", \"Ring\"), (\"Pinky\", \"Pinky\"),\r\n            })\r\n            {\r\n                aliases[Role($\"{role}Prox\", roleSide)] = new[] { $\"{nameSide}_{cc}1\" };\r\n                aliases[Role($\"{role}Mid\", roleSide)] = new[] { $\"{nameSide}_{cc}2\" };\r\n                aliases[Role($\"{role}Dist\", roleSide)] = new[] { $\"{nameSide}_{cc}3\" };\r\n            }\r\n        }\r\n        return new Profile(\"actorcore_cc\", new[] { \"^CC_Base_\" }, aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- ue mannequin\r\n\r\n    private static Profile BuildUeMannequin()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"pelvis\" },\r\n            [BoneRole.Spine0] = new[] { \"spine_01\" },\r\n            [BoneRole.Spine1] = new[] { \"spine_02\" },\r\n            [BoneRole.Spine2] = new[] { \"spine_03\" },\r\n            [BoneRole.Spine3] = new[] { \"spine_04\" },\r\n            [BoneRole.Spine4] = new[] { \"spine_05\" },\r\n            [BoneRole.Neck] = new[] { \"neck_01\" },\r\n            [BoneRole.Head] = new[] { \"head\" },\r\n        };\r\n        foreach (var (roleSide, s) in new[] { (\"L\", \"l\"), (\"R\", \"r\") })\r\n        {\r\n            aliases[Role(\"Clavicle\", roleSide)] = new[] { $\"clavicle_{s}\" };\r\n            aliases[Role(\"UpperArm\", roleSide)] = new[] { $\"upperarm_{s}\" };\r\n            aliases[Role(\"LowerArm\", roleSide)] = new[] { $\"lowerarm_{s}\" };\r\n            aliases[Role(\"Hand\", roleSide)] = new[] { $\"hand_{s}\" };\r\n            aliases[Role(\"UpperLeg\", roleSide)] = new[] { $\"thigh_{s}\" };\r\n            aliases[Role(\"LowerLeg\", roleSide)] = new[] { $\"calf_{s}\" };\r\n            aliases[Role(\"Foot\", roleSide)] = new[] { $\"foot_{s}\" };\r\n            aliases[Role(\"Toe\", roleSide)] = new[] { $\"ball_{s}\" };\r\n\r\n            foreach (var (role, ue) in new[]\r\n            {\r\n                (\"Thumb\", \"thumb\"), (\"Index\", \"index\"), (\"Middle\", \"middle\"), (\"Ring\", \"ring\"), (\"Pinky\", \"pinky\"),\r\n            })\r\n            {\r\n                // UE5 mannequin adds metacarpals for the four fingers (not the thumb).\r\n                if (role != \"Thumb\")\r\n                    aliases[Role($\"{role}Meta\", roleSide)] = new[] { $\"{ue}_metacarpal_{s}\" };\r\n                aliases[Role($\"{role}Prox\", roleSide)] = new[] { $\"{ue}_01_{s}\" };\r\n                aliases[Role($\"{role}Mid\", roleSide)] = new[] { $\"{ue}_02_{s}\" };\r\n                aliases[Role($\"{role}Dist\", roleSide)] = new[] { $\"{ue}_03_{s}\" };\r\n            }\r\n        }\r\n        return new Profile(\"ue_mannequin\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- rokoko / xsens bvh\r\n\r\n    private static Profile BuildRokokoBvh()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"Hips\" },\r\n            // Spine naming varies (Spine, Spine1..Spine4); ordered alias preference plus the\r\n            // used-bone exclusion in the detector shifts the chain up when \"Spine\" is absent.\r\n            [BoneRole.Spine0] = new[] { \"Spine\", \"Spine1\" },\r\n            [BoneRole.Spine1] = new[] { \"Spine1\", \"Spine2\" },\r\n            [BoneRole.Spine2] = new[] { \"Spine2\", \"Spine3\" },\r\n            [BoneRole.Spine3] = new[] { \"Spine3\", \"Spine4\" },\r\n            [BoneRole.Spine4] = new[] { \"Spine4\" },\r\n            [BoneRole.Neck] = new[] { \"Neck\", \"Neck1\" },\r\n            [BoneRole.Head] = new[] { \"Head\" },\r\n        };\r\n        foreach (var (roleSide, nameSide) in Sides())\r\n        {\r\n            aliases[Role(\"Clavicle\", roleSide)] = new[] { $\"{nameSide}Shoulder\", $\"{nameSide}Collar\" };\r\n            aliases[Role(\"UpperArm\", roleSide)] = new[] { $\"{nameSide}Arm\", $\"{nameSide}UpperArm\" };\r\n            aliases[Role(\"LowerArm\", roleSide)] = new[] { $\"{nameSide}ForeArm\", $\"{nameSide}LowerArm\" };\r\n            aliases[Role(\"Hand\", roleSide)] = new[] { $\"{nameSide}Hand\" };\r\n            aliases[Role(\"UpperLeg\", roleSide)] = new[] { $\"{nameSide}UpLeg\", $\"{nameSide}Thigh\", $\"{nameSide}UpperLeg\" };\r\n            aliases[Role(\"LowerLeg\", roleSide)] = new[] { $\"{nameSide}Leg\", $\"{nameSide}Shin\", $\"{nameSide}LowerLeg\" };\r\n            aliases[Role(\"Foot\", roleSide)] = new[] { $\"{nameSide}Foot\" };\r\n            aliases[Role(\"Toe\", roleSide)] = new[] { $\"{nameSide}Toe\", $\"{nameSide}ToeBase\" };\r\n        }\r\n        return new Profile(\"rokoko_bvh\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- xsens mvn\r\n\r\n    private static Profile BuildXsensMvn()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"Pelvis\" },\r\n            // MVN's exported spine segments are the anatomical vertebra levels L5/L3/T12/T8.\r\n            [BoneRole.Spine0] = new[] { \"L5\" },\r\n            [BoneRole.Spine1] = new[] { \"L3\" },\r\n            [BoneRole.Spine2] = new[] { \"T12\" },\r\n            [BoneRole.Spine3] = new[] { \"T8\" },\r\n            [BoneRole.Neck] = new[] { \"Neck\" },\r\n            [BoneRole.Head] = new[] { \"Head\" },\r\n        };\r\n        foreach (var (roleSide, nameSide) in Sides())\r\n        {\r\n            aliases[Role(\"Clavicle\", roleSide)] = new[] { $\"{nameSide}Shoulder\" };\r\n            aliases[Role(\"UpperArm\", roleSide)] = new[] { $\"{nameSide}UpperArm\" };\r\n            aliases[Role(\"LowerArm\", roleSide)] = new[] { $\"{nameSide}ForeArm\" };\r\n            aliases[Role(\"Hand\", roleSide)] = new[] { $\"{nameSide}Hand\" };\r\n            aliases[Role(\"UpperLeg\", roleSide)] = new[] { $\"{nameSide}UpperLeg\" };\r\n            aliases[Role(\"LowerLeg\", roleSide)] = new[] { $\"{nameSide}LowerLeg\" };\r\n            aliases[Role(\"Foot\", roleSide)] = new[] { $\"{nameSide}Foot\" };\r\n            aliases[Role(\"Toe\", roleSide)] = new[] { $\"{nameSide}Toe\" };\r\n        }\r\n        // Body-suit capture: no finger segments (see the property remarks).\r\n        return new Profile(\"xsens_mvn\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- perception neuron\r\n\r\n    private static Profile BuildPerceptionNeuron()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"Hips\" },\r\n            [BoneRole.Spine0] = new[] { \"Spine\" },\r\n            [BoneRole.Spine1] = new[] { \"Spine1\" },\r\n            [BoneRole.Spine2] = new[] { \"Spine2\" },\r\n            [BoneRole.Spine3] = new[] { \"Spine3\" },\r\n            [BoneRole.Neck] = new[] { \"Neck\" },\r\n            [BoneRole.Head] = new[] { \"Head\" },\r\n        };\r\n        foreach (var (roleSide, nameSide) in Sides())\r\n        {\r\n            aliases[Role(\"Clavicle\", roleSide)] = new[] { $\"{nameSide}Shoulder\" };\r\n            aliases[Role(\"UpperArm\", roleSide)] = new[] { $\"{nameSide}Arm\" };\r\n            aliases[Role(\"LowerArm\", roleSide)] = new[] { $\"{nameSide}ForeArm\" };\r\n            aliases[Role(\"Hand\", roleSide)] = new[] { $\"{nameSide}Hand\" };\r\n            aliases[Role(\"UpperLeg\", roleSide)] = new[] { $\"{nameSide}UpLeg\" };\r\n            aliases[Role(\"LowerLeg\", roleSide)] = new[] { $\"{nameSide}Leg\" };\r\n            aliases[Role(\"Foot\", roleSide)] = new[] { $\"{nameSide}Foot\" };\r\n            // No toe joints in Axis Neuron exports; the feet are chain tips.\r\n\r\n            // Phalanges only: the LeftInHandIndex-style metacarpal helpers between hand\r\n            // and phalanges carry no role (see the property remarks).\r\n            foreach (var finger in new[] { \"Thumb\", \"Index\", \"Middle\", \"Ring\", \"Pinky\" })\r\n            {\r\n                aliases[Role($\"{finger}Prox\", roleSide)] = new[] { $\"{nameSide}Hand{finger}1\" };\r\n                aliases[Role($\"{finger}Mid\", roleSide)] = new[] { $\"{nameSide}Hand{finger}2\" };\r\n                aliases[Role($\"{finger}Dist\", roleSide)] = new[] { $\"{nameSide}Hand{finger}3\" };\r\n            }\r\n        }\r\n        return new Profile(\"perception_neuron\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- valvebiped\r\n\r\n    private static Profile BuildValveBiped()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"Pelvis\" },\r\n            [BoneRole.Spine0] = new[] { \"Spine\" },\r\n            [BoneRole.Spine1] = new[] { \"Spine1\" },\r\n            [BoneRole.Spine2] = new[] { \"Spine2\" },\r\n            // The stock HL2 chain skips Spine3 (Spine2's child IS Spine4, the chest);\r\n            // ordered preference + used-bone exclusion also absorbs a variant that has\r\n            // both: Spine3\u2192Spine3 and Spine4\u2192Spine4.\r\n            [BoneRole.Spine3] = new[] { \"Spine3\", \"Spine4\" },\r\n            [BoneRole.Spine4] = new[] { \"Spine4\" },\r\n            [BoneRole.Neck] = new[] { \"Neck1\" },\r\n            [BoneRole.Head] = new[] { \"Head1\" },\r\n        };\r\n        foreach (var s in new[] { \"L\", \"R\" })\r\n        {\r\n            aliases[Role(\"Clavicle\", s)] = new[] { $\"{s}_Clavicle\" };\r\n            aliases[Role(\"UpperArm\", s)] = new[] { $\"{s}_UpperArm\" };\r\n            aliases[Role(\"LowerArm\", s)] = new[] { $\"{s}_Forearm\" };\r\n            aliases[Role(\"Hand\", s)] = new[] { $\"{s}_Hand\" };\r\n            aliases[Role(\"UpperLeg\", s)] = new[] { $\"{s}_Thigh\" };\r\n            aliases[Role(\"LowerLeg\", s)] = new[] { $\"{s}_Calf\" };\r\n            aliases[Role(\"Foot\", s)] = new[] { $\"{s}_Foot\" };\r\n            aliases[Role(\"Toe\", s)] = new[] { $\"{s}_Toe0\" };\r\n\r\n            // Biped-style numbered finger chains behind the ValveBiped namespace:\r\n            // Finger0 is the thumb; segments append the phalanx digit (Finger0 \u2192\r\n            // Finger01 \u2192 Finger02, Finger1 \u2192 Finger11 \u2192 \u2026).\r\n            foreach (var (finger, n) in new[]\r\n            {\r\n                (\"Thumb\", 0), (\"Index\", 1), (\"Middle\", 2), (\"Ring\", 3), (\"Pinky\", 4),\r\n            })\r\n            {\r\n                aliases[Role($\"{finger}Prox\", s)] = new[] { $\"{s}_Finger{n}\" };\r\n                aliases[Role($\"{finger}Mid\", s)] = new[] { $\"{s}_Finger{n}1\" };\r\n                aliases[Role($\"{finger}Dist\", s)] = new[] { $\"{s}_Finger{n}2\" };\r\n            }\r\n        }\r\n        // The fixed \"ValveBiped.Bip01_\" namespace: anchored, so plain \"Bip01 ...\" Character\r\n        // Studio rigs never strip it (and the Biped preset's \"^Bip\\d+[ _]\" never matches\r\n        // the ValveBiped prefix \u2014 the two families cannot cross-claim).\r\n        return new Profile(\"valvebiped\", new[] { @\"^ValveBiped\\.Bip01_\" }, aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- daz genesis 3/8\r\n\r\n    private static Profile BuildDazGenesis()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            // \"hip\" is the translating root and LCA of the pelvis (leg branch) and the\r\n            // abdomen (spine branch); \"pelvis\" is a leg-branch intermediate and stays\r\n            // unmapped \u2014 same policy as ActorCore's CC_Base_Hip/CC_Base_Pelvis pair.\r\n            [BoneRole.Hips] = new[] { \"hip\" },\r\n            [BoneRole.Spine0] = new[] { \"abdomenLower\" },\r\n            [BoneRole.Spine1] = new[] { \"abdomenUpper\" },\r\n            [BoneRole.Spine2] = new[] { \"chestLower\" },\r\n            [BoneRole.Spine3] = new[] { \"chestUpper\" },\r\n            // neckLower\u2192neckUpper\u2192head: neckLower IS the neck; neckUpper stays unmapped\r\n            // (same policy as ActorCore's NeckTwist02 / rigify's spine.005).\r\n            [BoneRole.Neck] = new[] { \"neckLower\" },\r\n            [BoneRole.Head] = new[] { \"head\" },\r\n        };\r\n        foreach (var s in new[] { \"L\", \"R\" })\r\n        {\r\n            var p = s == \"L\" ? \"l\" : \"r\"; // lower-case side prefix: lShldrBend, rThighBend\r\n            aliases[Role(\"Clavicle\", s)] = new[] { $\"{p}Collar\" };\r\n            // Bend bones are the primary limb bones; the co-linear *Twist roll helpers\r\n            // have no aliases and are never mapped.\r\n            aliases[Role(\"UpperArm\", s)] = new[] { $\"{p}ShldrBend\" };\r\n            aliases[Role(\"LowerArm\", s)] = new[] { $\"{p}ForearmBend\" };\r\n            aliases[Role(\"Hand\", s)] = new[] { $\"{p}Hand\" };\r\n            aliases[Role(\"UpperLeg\", s)] = new[] { $\"{p}ThighBend\" };\r\n            aliases[Role(\"LowerLeg\", s)] = new[] { $\"{p}Shin\" };\r\n            aliases[Role(\"Foot\", s)] = new[] { $\"{p}Foot\" };\r\n            // lMetatarsals sits between foot and toe (arch helper, unmapped).\r\n            aliases[Role(\"Toe\", s)] = new[] { $\"{p}Toe\" };\r\n\r\n            foreach (var (role, daz) in new[]\r\n            {\r\n                (\"Thumb\", \"Thumb\"), (\"Index\", \"Index\"), (\"Middle\", \"Mid\"), (\"Ring\", \"Ring\"), (\"Pinky\", \"Pinky\"),\r\n            })\r\n            {\r\n                aliases[Role($\"{role}Prox\", s)] = new[] { $\"{p}{daz}1\" };\r\n                aliases[Role($\"{role}Mid\", s)] = new[] { $\"{p}{daz}2\" };\r\n                aliases[Role($\"{role}Dist\", s)] = new[] { $\"{p}{daz}3\" };\r\n            }\r\n        }\r\n        return new Profile(\"daz_genesis\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- smpl / smpl-x\r\n\r\n    private static Profile BuildSmpl(bool withFingers)\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"Pelvis\" },\r\n            [BoneRole.Spine0] = new[] { \"Spine1\" },\r\n            [BoneRole.Spine1] = new[] { \"Spine2\" },\r\n            [BoneRole.Spine2] = new[] { \"Spine3\" },\r\n            [BoneRole.Neck] = new[] { \"Neck\" },\r\n            [BoneRole.Head] = new[] { \"Head\" },\r\n        };\r\n        foreach (var (roleSide, abbr, word) in new[] { (\"L\", \"L\", \"left\"), (\"R\", \"R\", \"right\") })\r\n        {\r\n            // Both documented spellings per role: abbreviated FBX-rig names (\"L_Hip\") and\r\n            // spelled model joint names (\"left_hip\"). Comparison is separator-insensitive.\r\n            aliases[Role(\"Clavicle\", roleSide)] = new[] { $\"{abbr}_Collar\", $\"{word}_collar\" };\r\n            aliases[Role(\"UpperArm\", roleSide)] = new[] { $\"{abbr}_Shoulder\", $\"{word}_shoulder\" };\r\n            aliases[Role(\"LowerArm\", roleSide)] = new[] { $\"{abbr}_Elbow\", $\"{word}_elbow\" };\r\n            aliases[Role(\"Hand\", roleSide)] = new[] { $\"{abbr}_Wrist\", $\"{word}_wrist\" };\r\n            aliases[Role(\"UpperLeg\", roleSide)] = new[] { $\"{abbr}_Hip\", $\"{word}_hip\" };\r\n            aliases[Role(\"LowerLeg\", roleSide)] = new[] { $\"{abbr}_Knee\", $\"{word}_knee\" };\r\n            aliases[Role(\"Foot\", roleSide)] = new[] { $\"{abbr}_Ankle\", $\"{word}_ankle\" };\r\n            aliases[Role(\"Toe\", roleSide)] = new[] { $\"{abbr}_Foot\", $\"{word}_foot\" };\r\n\r\n            if (!withFingers)\r\n                continue;\r\n\r\n            // SMPL-X finger joints (left_index1..3 etc., per vchoutas/smplx joint_names.py).\r\n            foreach (var finger in new[] { \"thumb\", \"index\", \"middle\", \"ring\", \"pinky\" })\r\n            {\r\n                var name = char.ToUpperInvariant(finger[0]) + finger[1..];\r\n                aliases[Role($\"{name}Prox\", roleSide)] = new[] { $\"{word}_{finger}1\" };\r\n                aliases[Role($\"{name}Mid\", roleSide)] = new[] { $\"{word}_{finger}2\" };\r\n                aliases[Role($\"{name}Dist\", roleSide)] = new[] { $\"{word}_{finger}3\" };\r\n            }\r\n        }\r\n        // Gendered SMPL FBX rigs prefix every bone (m_avg_L_Hip, f_avg_Pelvis).\r\n        return new Profile(withFingers ? \"smpl_x\" : \"smpl\", new[] { \"^m_avg_\", \"^f_avg_\" }, aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- nvidia soma bvh\r\n\r\n    private static Profile BuildSomaBvh()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"Hips\" },\r\n            [BoneRole.Spine0] = new[] { \"Spine1\" },\r\n            [BoneRole.Spine1] = new[] { \"Spine2\" },\r\n            [BoneRole.Spine2] = new[] { \"Chest\" },\r\n            [BoneRole.Neck] = new[] { \"Neck1\" },\r\n            [BoneRole.Head] = new[] { \"Head\" },\r\n        };\r\n        foreach (var (roleSide, nameSide) in Sides())\r\n        {\r\n            aliases[Role(\"Clavicle\", roleSide)] = new[] { $\"{nameSide}Shoulder\" };\r\n            aliases[Role(\"UpperArm\", roleSide)] = new[] { $\"{nameSide}Arm\" };\r\n            aliases[Role(\"LowerArm\", roleSide)] = new[] { $\"{nameSide}ForeArm\" };\r\n            aliases[Role(\"Hand\", roleSide)] = new[] { $\"{nameSide}Hand\" };\r\n            // SOMA's \"Leg\" is the thigh, \"Shin\" the calf \u2014 the decisive difference from\r\n            // mixamo, where \"Leg\" is the calf under \"UpLeg\".\r\n            aliases[Role(\"UpperLeg\", roleSide)] = new[] { $\"{nameSide}Leg\" };\r\n            aliases[Role(\"LowerLeg\", roleSide)] = new[] { $\"{nameSide}Shin\" };\r\n            aliases[Role(\"Foot\", roleSide)] = new[] { $\"{nameSide}Foot\" };\r\n            aliases[Role(\"Toe\", roleSide)] = new[] { $\"{nameSide}ToeBase\" };\r\n\r\n            // Mixamo-style finger NAMES but not mixamo segmentation: SOMA fingers have four\r\n            // segments where segment 1 is a metacarpal (measured on the repro BVH: Index1\r\n            // sits 3.2 cm from the wrist at the palm base, then a 6.4 cm metacarpal to the\r\n            // Index2 knuckle, then 3.7/2.3 cm phalanges to Index3/Index4) \u2014 so 2/3/4 are the\r\n            // phalanges. Mapping 1..3 as Prox/Mid/Dist (mixamo's segmentation) shifted every\r\n            // curl one joint outward and dropped the distal curl entirely (frozen fingers).\r\n            // The thumb is three segments plus *End, mapped 1..3 like mixamo's; *End tip\r\n            // markers carry no role.\r\n            aliases[Role(\"ThumbProx\", roleSide)] = new[] { $\"{nameSide}HandThumb1\" };\r\n            aliases[Role(\"ThumbMid\", roleSide)] = new[] { $\"{nameSide}HandThumb2\" };\r\n            aliases[Role(\"ThumbDist\", roleSide)] = new[] { $\"{nameSide}HandThumb3\" };\r\n            foreach (var finger in new[] { \"Index\", \"Middle\", \"Ring\", \"Pinky\" })\r\n            {\r\n                aliases[Role($\"{finger}Meta\", roleSide)] = new[] { $\"{nameSide}Hand{finger}1\" };\r\n                aliases[Role($\"{finger}Prox\", roleSide)] = new[] { $\"{nameSide}Hand{finger}2\" };\r\n                aliases[Role($\"{finger}Mid\", roleSide)] = new[] { $\"{nameSide}Hand{finger}3\" };\r\n                aliases[Role($\"{finger}Dist\", roleSide)] = new[] { $\"{nameSide}Hand{finger}4\" };\r\n            }\r\n        }\r\n        return new Profile(\"soma_bvh\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- classic bvh\r\n\r\n    private static Profile BuildClassicBvh()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"Hips\" },\r\n            [BoneRole.Spine0] = new[] { \"Chest\" },\r\n            [BoneRole.Spine1] = new[] { \"Chest2\" },\r\n            [BoneRole.Spine2] = new[] { \"Chest3\" },\r\n            [BoneRole.Spine3] = new[] { \"Chest4\" },\r\n            [BoneRole.Neck] = new[] { \"Neck\" },\r\n            [BoneRole.Head] = new[] { \"Head\" },\r\n        };\r\n        foreach (var (roleSide, nameSide) in Sides())\r\n        {\r\n            aliases[Role(\"Clavicle\", roleSide)] = new[] { $\"{nameSide}Collar\" };\r\n            aliases[Role(\"UpperArm\", roleSide)] = new[] { $\"{nameSide}Shoulder\" };\r\n            aliases[Role(\"LowerArm\", roleSide)] = new[] { $\"{nameSide}Elbow\" };\r\n            aliases[Role(\"Hand\", roleSide)] = new[] { $\"{nameSide}Wrist\" };\r\n            aliases[Role(\"UpperLeg\", roleSide)] = new[] { $\"{nameSide}Hip\" };\r\n            aliases[Role(\"LowerLeg\", roleSide)] = new[] { $\"{nameSide}Knee\" };\r\n            aliases[Role(\"Foot\", roleSide)] = new[] { $\"{nameSide}Ankle\" };\r\n            aliases[Role(\"Toe\", roleSide)] = new[] { $\"{nameSide}Toe\" };\r\n        }\r\n        return new Profile(\"classic_bvh\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- 3ds max biped\r\n\r\n    private static Profile BuildBiped()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"Pelvis\" },\r\n            [BoneRole.Spine0] = new[] { \"Spine\" },\r\n            [BoneRole.Spine1] = new[] { \"Spine1\" },\r\n            [BoneRole.Spine2] = new[] { \"Spine2\" },\r\n            [BoneRole.Spine3] = new[] { \"Spine3\" },\r\n            [BoneRole.Neck] = new[] { \"Neck\" },\r\n            [BoneRole.Head] = new[] { \"Head\" },\r\n        };\r\n        foreach (var s in new[] { \"L\", \"R\" })\r\n        {\r\n            aliases[Role(\"Clavicle\", s)] = new[] { $\"{s} Clavicle\" };\r\n            aliases[Role(\"UpperArm\", s)] = new[] { $\"{s} UpperArm\" };\r\n            aliases[Role(\"LowerArm\", s)] = new[] { $\"{s} Forearm\" };\r\n            aliases[Role(\"Hand\", s)] = new[] { $\"{s} Hand\" };\r\n            aliases[Role(\"UpperLeg\", s)] = new[] { $\"{s} Thigh\" };\r\n            aliases[Role(\"LowerLeg\", s)] = new[] { $\"{s} Calf\" };\r\n            aliases[Role(\"Foot\", s)] = new[] { $\"{s} Foot\" };\r\n            aliases[Role(\"Toe\", s)] = new[] { $\"{s} Toe0\" };\r\n\r\n            // Numbered finger chains: Finger0 is the thumb; segment names append the\r\n            // phalanx digit (Finger0 \u2192 Finger01 \u2192 Finger02, Finger1 \u2192 Finger11 \u2192 ...).\r\n            foreach (var (finger, n) in new[]\r\n            {\r\n                (\"Thumb\", 0), (\"Index\", 1), (\"Middle\", 2), (\"Ring\", 3), (\"Pinky\", 4),\r\n            })\r\n            {\r\n                aliases[Role($\"{finger}Prox\", s)] = new[] { $\"{s} Finger{n}\" };\r\n                aliases[Role($\"{finger}Mid\", s)] = new[] { $\"{s} Finger{n}1\" };\r\n                aliases[Role($\"{finger}Dist\", s)] = new[] { $\"{s} Finger{n}2\" };\r\n            }\r\n        }\r\n        // \"Bip01 \"/\"Bip001 \" biped-name prefix; underscore form covers exporters that\r\n        // mangle the spaces (\"Bip01_L_Thigh\"). The bare COM root \"Bip01\" is untouched by\r\n        // the pattern (no trailing separator) and has no alias.\r\n        return new Profile(\"biped\", new[] { @\"^Bip\\d+[ _]\" }, aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- daz / poser\r\n\r\n    private static Profile BuildDazPoser()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"hip\" },\r\n            [BoneRole.Spine0] = new[] { \"abdomen\" },\r\n            // Poser classic / DAZ Gen4 spine is abdomen\u2192chest; DAZ Genesis 1/2 inserts\r\n            // abdomen2. Ordered preference + used-bone exclusion handles both: without\r\n            // abdomen2 the chest falls back to Spine1 and Spine2 stays unmapped.\r\n            [BoneRole.Spine1] = new[] { \"abdomen2\", \"chest\" },\r\n            [BoneRole.Spine2] = new[] { \"chest\" },\r\n            [BoneRole.Neck] = new[] { \"neck\" },\r\n            [BoneRole.Head] = new[] { \"head\" },\r\n        };\r\n        foreach (var s in new[] { \"L\", \"R\" })\r\n        {\r\n            var p = s == \"L\" ? \"l\" : \"r\"; // lower-case side prefix: lShldr, rThigh\r\n            aliases[Role(\"Clavicle\", s)] = new[] { $\"{p}Collar\" };\r\n            aliases[Role(\"UpperArm\", s)] = new[] { $\"{p}Shldr\" };\r\n            aliases[Role(\"LowerArm\", s)] = new[] { $\"{p}ForeArm\" };\r\n            aliases[Role(\"Hand\", s)] = new[] { $\"{p}Hand\" };\r\n            aliases[Role(\"UpperLeg\", s)] = new[] { $\"{p}Thigh\" };\r\n            aliases[Role(\"LowerLeg\", s)] = new[] { $\"{p}Shin\" };\r\n            aliases[Role(\"Foot\", s)] = new[] { $\"{p}Foot\" };\r\n            aliases[Role(\"Toe\", s)] = new[] { $\"{p}Toe\" };\r\n\r\n            foreach (var (role, daz) in new[]\r\n            {\r\n                (\"Thumb\", \"Thumb\"), (\"Index\", \"Index\"), (\"Middle\", \"Mid\"), (\"Ring\", \"Ring\"), (\"Pinky\", \"Pinky\"),\r\n            })\r\n            {\r\n                aliases[Role($\"{role}Prox\", s)] = new[] { $\"{p}{daz}1\" };\r\n                aliases[Role($\"{role}Mid\", s)] = new[] { $\"{p}{daz}2\" };\r\n                aliases[Role($\"{role}Dist\", s)] = new[] { $\"{p}{daz}3\" };\r\n            }\r\n        }\r\n        return new Profile(\"daz_poser\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- blender rigify\r\n\r\n    private static Profile BuildRigify()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            // rigify's \"spine\" bone sits AT the pelvis and parents both thighs \u2014 it is\r\n            // the hips, not a spine link (rigify/metarigs/human.py).\r\n            [BoneRole.Hips] = new[] { \"spine\" },\r\n            [BoneRole.Spine0] = new[] { \"spine.001\" },\r\n            [BoneRole.Spine1] = new[] { \"spine.002\" },\r\n            [BoneRole.Spine2] = new[] { \"spine.003\" },\r\n            // spine.004 + spine.005 are the two neck bones, spine.006 the head;\r\n            // spine.005 stays unmapped (same policy as ActorCore's NeckTwist02).\r\n            [BoneRole.Neck] = new[] { \"spine.004\" },\r\n            [BoneRole.Head] = new[] { \"spine.006\" },\r\n        };\r\n        foreach (var s in new[] { \"L\", \"R\" })\r\n        {\r\n            aliases[Role(\"Clavicle\", s)] = new[] { $\"shoulder.{s}\" };\r\n            aliases[Role(\"UpperArm\", s)] = new[] { $\"upper_arm.{s}\" };\r\n            aliases[Role(\"LowerArm\", s)] = new[] { $\"forearm.{s}\" };\r\n            aliases[Role(\"Hand\", s)] = new[] { $\"hand.{s}\" };\r\n            aliases[Role(\"UpperLeg\", s)] = new[] { $\"thigh.{s}\" };\r\n            aliases[Role(\"LowerLeg\", s)] = new[] { $\"shin.{s}\" };\r\n            aliases[Role(\"Foot\", s)] = new[] { $\"foot.{s}\" };\r\n            aliases[Role(\"Toe\", s)] = new[] { $\"toe.{s}\" };\r\n\r\n            foreach (var (role, rigify) in new[]\r\n            {\r\n                (\"Thumb\", \"thumb\"), (\"Index\", \"f_index\"), (\"Middle\", \"f_middle\"),\r\n                (\"Ring\", \"f_ring\"), (\"Pinky\", \"f_pinky\"),\r\n            })\r\n            {\r\n                aliases[Role($\"{role}Prox\", s)] = new[] { $\"{rigify}.01.{s}\" };\r\n                aliases[Role($\"{role}Mid\", s)] = new[] { $\"{rigify}.02.{s}\" };\r\n                aliases[Role($\"{role}Dist\", s)] = new[] { $\"{rigify}.03.{s}\" };\r\n            }\r\n        }\r\n        // The generated deform skeleton prefixes every deform bone with \"DEF-\"; its\r\n        // segmented limb twins (\"DEF-upper_arm.L.001\") keep their numeric suffix after\r\n        // stripping and therefore never collide with the whole-bone aliases.\r\n        return new Profile(\"rigify\", new[] { \"^DEF-\" }, aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- vroid / vrm\r\n\r\n    private static Profile BuildVrm()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"J_Bip_C_Hips\" },\r\n            [BoneRole.Spine0] = new[] { \"J_Bip_C_Spine\" },\r\n            [BoneRole.Spine1] = new[] { \"J_Bip_C_Chest\" },\r\n            [BoneRole.Spine2] = new[] { \"J_Bip_C_UpperChest\" },\r\n            [BoneRole.Neck] = new[] { \"J_Bip_C_Neck\" },\r\n            [BoneRole.Head] = new[] { \"J_Bip_C_Head\" },\r\n        };\r\n        foreach (var s in new[] { \"L\", \"R\" })\r\n        {\r\n            aliases[Role(\"Clavicle\", s)] = new[] { $\"J_Bip_{s}_Shoulder\" };\r\n            aliases[Role(\"UpperArm\", s)] = new[] { $\"J_Bip_{s}_UpperArm\" };\r\n            aliases[Role(\"LowerArm\", s)] = new[] { $\"J_Bip_{s}_LowerArm\" };\r\n            aliases[Role(\"Hand\", s)] = new[] { $\"J_Bip_{s}_Hand\" };\r\n            aliases[Role(\"UpperLeg\", s)] = new[] { $\"J_Bip_{s}_UpperLeg\" };\r\n            aliases[Role(\"LowerLeg\", s)] = new[] { $\"J_Bip_{s}_LowerLeg\" };\r\n            aliases[Role(\"Foot\", s)] = new[] { $\"J_Bip_{s}_Foot\" };\r\n            aliases[Role(\"Toe\", s)] = new[] { $\"J_Bip_{s}_ToeBase\" };\r\n\r\n            foreach (var (role, vrm) in new[]\r\n            {\r\n                (\"Thumb\", \"Thumb\"), (\"Index\", \"Index\"), (\"Middle\", \"Middle\"),\r\n                (\"Ring\", \"Ring\"), (\"Pinky\", \"Little\"),\r\n            })\r\n            {\r\n                aliases[Role($\"{role}Prox\", s)] = new[] { $\"J_Bip_{s}_{vrm}1\" };\r\n                aliases[Role($\"{role}Mid\", s)] = new[] { $\"J_Bip_{s}_{vrm}2\" };\r\n                aliases[Role($\"{role}Dist\", s)] = new[] { $\"J_Bip_{s}_{vrm}3\" };\r\n            }\r\n        }\r\n        return new Profile(\"vrm\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- auto-rig pro\r\n\r\n    private static Profile BuildAutoRigPro()\r\n    {\r\n        var aliases = new Dictionary<BoneRole, string[]>\r\n        {\r\n            [BoneRole.Hips] = new[] { \"root.x\" },\r\n            [BoneRole.Spine0] = new[] { \"spine_01.x\" },\r\n            [BoneRole.Spine1] = new[] { \"spine_02.x\" },\r\n            [BoneRole.Spine2] = new[] { \"spine_03.x\" },\r\n            [BoneRole.Neck] = new[] { \"neck.x\" },\r\n            [BoneRole.Head] = new[] { \"head.x\" },\r\n        };\r\n        foreach (var s in new[] { \"L\", \"R\" })\r\n        {\r\n            var p = s == \"L\" ? \"l\" : \"r\";\r\n            aliases[Role(\"Clavicle\", s)] = new[] { $\"shoulder.{p}\" };\r\n            aliases[Role(\"UpperArm\", s)] = new[] { $\"arm_stretch.{p}\" };\r\n            aliases[Role(\"LowerArm\", s)] = new[] { $\"forearm_stretch.{p}\" };\r\n            aliases[Role(\"Hand\", s)] = new[] { $\"hand.{p}\" };\r\n            aliases[Role(\"UpperLeg\", s)] = new[] { $\"thigh_stretch.{p}\" };\r\n            aliases[Role(\"LowerLeg\", s)] = new[] { $\"leg_stretch.{p}\" };\r\n            aliases[Role(\"Foot\", s)] = new[] { $\"foot.{p}\" };\r\n            aliases[Role(\"Toe\", s)] = new[] { $\"toes_01.{p}\" };\r\n\r\n            // Exported finger deform bones keep ARP's c_ control prefix (Defenses.fbx).\r\n            foreach (var finger in new[] { \"thumb\", \"index\", \"middle\", \"ring\", \"pinky\" })\r\n            {\r\n                var role = char.ToUpperInvariant(finger[0]) + finger[1..];\r\n                aliases[Role($\"{role}Prox\", s)] = new[] { $\"c_{finger}1.{p}\" };\r\n                aliases[Role($\"{role}Mid\", s)] = new[] { $\"c_{finger}2.{p}\" };\r\n                aliases[Role($\"{role}Dist\", s)] = new[] { $\"c_{finger}3.{p}\" };\r\n            }\r\n        }\r\n        return new Profile(\"auto_rig_pro\", new string[0], aliases);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- helpers\r\n\r\n    private static IEnumerable<(string RoleSide, string NameSide)> Sides()\r\n    {\r\n        yield return (\"L\", \"Left\");\r\n        yield return (\"R\", \"Right\");\r\n    }\r\n\r\n    private static BoneRole Role(string baseName, string side)\r\n        => System.Enum.Parse<BoneRole>(baseName + side);\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Solve/ClipMirror.cs",
            "FileName": "ClipMirror.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidRetargeter.Mapping;\r\nusing HumanoidRetargeter.Maths;\r\nusing HumanoidRetargeter.Target;\r\n\r\nnamespace HumanoidRetargeter.Solve;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>\r\n/// Mirrors a solved TARGET-space clip across the target character's sagittal plane,\r\n/// producing the left/right-swapped twin of an animation (e.g. a right-foot-lead walk from a\r\n/// left-foot-lead one).\r\n/// </summary>\r\n/// <remarks>\r\n/// <para><b>Mirror plane.</b> The plane through the rig-space origin spanned by the target\r\n/// character's up and forward directions; its normal is the character's LATERAL axis,\r\n/// computed from the target rig's rest geometry via <see cref=\"CharacterFrame\"/> (never\r\n/// hardcoded \u2014 an arbitrary target may be authored in any axis convention). When the\r\n/// computed lateral lies on a coordinate axis up to float dirt (&lt; 1e-3 on the other two\r\n/// components \u2014 true for every axis-aligned authored rig, including the s&amp;box citizen\r\n/// rigs), it is snapped to that exact axis, which makes every reflection below an EXACT\r\n/// sign-flip in IEEE arithmetic and therefore the whole mirror a bit-exact involution\r\n/// (mirror \u2218 mirror == identity, verified by test).</para>\r\n/// <para><b>Math.</b> Let M = I \u2212 2n\u0302n\u0302\u1d40 be the reflection across the plane with unit normal\r\n/// n\u0302. A world transform W = (R, t) maps to its mirror image by conjugation:\r\n/// W\u2032 = M\u0302 \u2218 W \u2218 M\u0302 (M\u0302 is its own inverse), giving rotation R\u2032 = M\u00b7R\u00b7M and translation\r\n/// t\u2032 = M\u00b7t. For a quaternion q = (v, w), M\u00b7R\u00b7M is the rotation by the SAME angle about the\r\n/// REFLECTED axis with REVERSED sense (a reflection flips orientation), i.e.\r\n/// q\u2032 = (2(n\u0302\u00b7v)n\u0302 \u2212 v, w); with n\u0302 = +X that is exactly q\u2032 = (x, \u2212y, \u2212z, w), and positions\r\n/// reflect as p\u2032 = p \u2212 2(n\u0302\u00b7p)n\u0302 = (\u2212p\u2093, p_y, p_z).</para>\r\n/// <para><b>Locals, not worlds.</b> Because conjugation is a homomorphism\r\n/// (M\u0302(AB)M\u0302 = (M\u0302AM\u0302)(M\u0302BM\u0302)) and world transforms are products of locals down the\r\n/// hierarchy, mirroring every LOCAL transform and permuting bones by their L\u2194R partner is\r\n/// exactly equivalent to mirroring the FK worlds \u2014 provided the partner permutation is\r\n/// hierarchy-consistent (the partner's parent is the parent's partner), which is validated\r\n/// and holds on structurally symmetric humanoid rigs. This avoids FK\u2192inverse-FK float drift\r\n/// entirely, which is what makes the double-mirror identity bit-exact.</para>\r\n/// <para><b>Pairing.</b> Left/right bones are paired by the rig's canonical role annotations\r\n/// first (UpperArmL \u2194 UpperArmR, \u2026); role-less bones (twist helpers, IK bones) fall back to\r\n/// <c>_L</c>/<c>_R</c> name-token pairing (<c>arm_upper_L_twist0</c> \u2194\r\n/// <c>arm_upper_R_twist0</c>, <c>foot_L_IK_target</c> \u2194 <c>foot_R_IK_target</c>); anything\r\n/// unpaired (center bones: pelvis, spine, neck, head) mirrors in place, which reflects its\r\n/// rotation across the sagittal plane and negates its lateral translation. IK-baked helper\r\n/// bones are NOT re-baked after mirroring: conjugation is a homomorphism, so the mirrored\r\n/// copies of the primary clip's final helper channels already hang in exactly the mirrored\r\n/// relationship over the mirrored body (re-baking encoded a divergent convention, the Gate 3\r\n/// review's mechanism 2 of the _M render defect; southpaw project, gate3_review.md 3.4).\r\n/// Channel exclusions on mirrored clips go through <see cref=\"MirrorSafeExclusions\"/>.</para>\r\n/// </remarks>\r\npublic static class ClipMirror\r\n{\r\n    /// <summary>Maximum off-axis component magnitude below which the computed lateral axis is\r\n    /// snapped to the exact coordinate axis (authored rigs are axis-aligned; the tiny rest\r\n    /// asymmetries of a real mesh stay far below this).</summary>\r\n    private const float AxisSnapTolerance = 1e-3f;\r\n\r\n    /// <summary>\r\n    /// Returns the mirrored copy of <paramref name=\"frames\"/> (one new list, inputs\r\n    /// untouched): per frame, bone i takes the conjugated local transform of its L\u2194R partner\r\n    /// \u03c3(i). See the class remarks for the math and pairing rules.\r\n    /// </summary>\r\n    /// <param name=\"frames\">Solved per-frame local transforms (target skeleton bone order).</param>\r\n    /// <param name=\"rig\">The target rig (skeleton + roles) the frames belong to.</param>\r\n    /// <exception cref=\"ArgumentException\">Thrown when the rig maps a sided role without its\r\n    /// counterpart, the pairing is not hierarchy-consistent, or the character frame is not\r\n    /// computable \u2014 mirroring would silently produce garbage in those cases.</exception>\r\n    public static List<XForm[]> Mirror(List<XForm[]> frames, TargetRig rig)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(frames);\r\n        ArgumentNullException.ThrowIfNull(rig);\r\n\r\n        var skeleton = rig.Skeleton;\r\n        var lateral = LateralAxis(rig);\r\n        var pair = BuildPairing(rig);\r\n        var fkFix = HierarchyInconsistentBones(rig, pair);\r\n\r\n        var result = new List<XForm[]>(frames.Count);\r\n        var baseWorld = fkFix.Count > 0 ? new XForm[skeleton.Count] : null;\r\n        var mirrorWorld = fkFix.Count > 0 ? new XForm[skeleton.Count] : null;\r\n        foreach (var locals in frames)\r\n        {\r\n            if (locals.Length != skeleton.Count)\r\n                throw new ArgumentException(\r\n                    $\"Frame has {locals.Length} bones but the target skeleton has {skeleton.Count}.\",\r\n                    nameof(frames));\r\n\r\n            var mirrored = new XForm[locals.Length];\r\n            for (var i = 0; i < locals.Length; i++)\r\n            {\r\n                var source = locals[pair[i]];\r\n                mirrored[i] = new XForm(\r\n                    ReflectPoint(source.Pos, lateral),\r\n                    ReflectRotation(source.Rot, lateral));\r\n            }\r\n\r\n            // Hierarchy-inconsistent pairs (the citizen parents arm_elbow_helper_R under\r\n            // arm_lower_R_twist0 while _L hangs under arm_lower_L, the W3a-documented\r\n            // rig quirk): the partner's conjugated LOCAL under a non-mirrored parent\r\n            // chain misplaces the bone by the parent-chain difference (measured ~1 in on\r\n            // the elbow/knee helpers). Solve their locals by FK so the mirrored WORLD is\r\n            // the exact reflection of the partner's world (southpaw G8 mirror fix).\r\n            if (fkFix.Count > 0)\r\n            {\r\n                for (var i = 0; i < skeleton.Count; i++)\r\n                {\r\n                    var parent = skeleton[i].ParentIndex;\r\n                    baseWorld![i] = parent < 0\r\n                        ? locals[i]\r\n                        : XForm.Compose(baseWorld[parent], locals[i]);\r\n                }\r\n                for (var i = 0; i < skeleton.Count; i++)\r\n                {\r\n                    var parent = skeleton[i].ParentIndex;\r\n                    if (fkFix.Contains(i))\r\n                    {\r\n                        var desired = new XForm(\r\n                            ReflectPoint(baseWorld![pair[i]].Pos, lateral),\r\n                            ReflectRotation(baseWorld[pair[i]].Rot, lateral));\r\n                        mirrored[i] = parent < 0\r\n                            ? desired\r\n                            : XForm.ToLocal(mirrorWorld![parent], desired);\r\n                    }\r\n                    mirrorWorld![i] = parent < 0\r\n                        ? mirrored[i]\r\n                        : XForm.Compose(mirrorWorld[parent], mirrored[i]);\r\n                }\r\n            }\r\n            result.Add(mirrored);\r\n        }\r\n        return result;\r\n    }\r\n\r\n    /// <summary>\r\n    /// Bones whose L/R pairing is NOT hierarchy-consistent (the partner hangs under a\r\n    /// non-mirrored parent). Only constraint-driven helpers can reach this state\r\n    /// (<see cref=\"BuildPairing\"/> fails hard for any other bone); their mirrored locals\r\n    /// need the FK solve in <see cref=\"Mirror\"/> and their DMX channels must be written\r\n    /// (<see cref=\"MirrorSafeExclusions\"/>).\r\n    /// </summary>\r\n    private static HashSet<int> HierarchyInconsistentBones(TargetRig rig, int[] pair)\r\n    {\r\n        var skeleton = rig.Skeleton;\r\n        var result = new HashSet<int>();\r\n        for (var i = 0; i < pair.Length; i++)\r\n        {\r\n            var parent = skeleton[i].ParentIndex;\r\n            var partnerParent = skeleton[pair[i]].ParentIndex;\r\n            var consistent = parent < 0\r\n                ? partnerParent < 0\r\n                : partnerParent == pair[parent];\r\n            if (!consistent)\r\n                result.Add(i);\r\n        }\r\n        return result;\r\n    }\r\n\r\n    /// <summary>\r\n    /// Filters a channel-exclusion set for a MIRRORED clip: returns the subset of\r\n    /// <paramref name=\"excluded\"/> that is still safe to leave channel-less after mirroring.\r\n    /// A channel-less bone is rendered/baked at its own REST local under its (mirrored)\r\n    /// parent; the mirrored frames instead carry the conjugated rest local of the bone's\r\n    /// L/R partner. Those agree only when the rig's rest locals are mirror conjugates\r\n    /// (restLocal(i) == conjugate(restLocal(partner(i)))). Bones breaking that symmetry\r\n    /// (measured on the citizen rig: leg/arm *_twist1 chains and neck_clothing, 19 to 37 cm\r\n    /// off) MUST keep explicit mirrored channels or every data consumer (model compiler\r\n    /// sequence bake, render-side helper evaluation) places them wrong: the Gate 3 review's\r\n    /// MECHANISM 1 of the _M render defect (southpaw project, gate3_review.md 3.4).\r\n    /// Truly symmetric helpers stay excluded exactly as on primary clips.\r\n    /// </summary>\r\n    /// <param name=\"rig\">The target rig the exclusion set belongs to.</param>\r\n    /// <param name=\"excluded\">The primary-clip exclusion set (constraint-driven bones).</param>\r\n    /// <returns>The mirror-safe subset, or null when nothing remains excluded.</returns>\r\n    public static IReadOnlySet<int>? MirrorSafeExclusions(TargetRig rig, IReadOnlySet<int>? excluded)\r\n    {\r\n        if (excluded is null || excluded.Count == 0)\r\n            return excluded;\r\n\r\n        var skeleton = rig.Skeleton;\r\n        var lateral = LateralAxis(rig);\r\n        var pair = BuildPairing(rig);\r\n        var inconsistent = HierarchyInconsistentBones(rig, pair);\r\n\r\n        const float posTolCm = 0.1f;\r\n        const float rotTolDeg = 0.5f;\r\n        var cosTol = MathF.Cos(rotTolDeg * MathF.PI / 360f); // half-angle for quat dot\r\n\r\n        var safe = new HashSet<int>();\r\n        foreach (var i in excluded)\r\n        {\r\n            // Hierarchy-inconsistent pairs always need explicit channels: their mirrored\r\n            // locals are FK-solved (see Mirror) and no rest local can stand in for them.\r\n            if (inconsistent.Contains(i))\r\n                continue;\r\n\r\n            var own = skeleton[i].RestLocal;\r\n            var partnerRest = skeleton[pair[i]].RestLocal;\r\n            var needed = new XForm(\r\n                ReflectPoint(partnerRest.Pos, lateral),\r\n                ReflectRotation(partnerRest.Rot, lateral));\r\n\r\n            var posOk = (own.Pos - needed.Pos).Length() <= posTolCm;\r\n            var dot = MathF.Abs(\r\n                own.Rot.X * needed.Rot.X + own.Rot.Y * needed.Rot.Y\r\n                + own.Rot.Z * needed.Rot.Z + own.Rot.W * needed.Rot.W);\r\n            var rotOk = dot >= cosTol;\r\n            if (posOk && rotOk)\r\n                safe.Add(i);\r\n        }\r\n        return safe.Count > 0 ? safe : null;\r\n    }\r\n\r\n    // ================================================================ mirror plane\r\n\r\n    /// <summary>The unit mirror normal: the target character's lateral axis from rest\r\n    /// geometry, snapped to an exact coordinate axis when within tolerance (bit-exact\r\n    /// reflections, see class remarks).</summary>\r\n    private static Vector3 LateralAxis(TargetRig rig)\r\n    {\r\n        Vector3 lateral;\r\n        try\r\n        {\r\n            lateral = CharacterFrame.Compute(\r\n                rig.Skeleton, rig.ToMappingResult(), rig.Skeleton.RestWorld).Lateral;\r\n        }\r\n        catch (ArgumentException e)\r\n        {\r\n            throw new ArgumentException(\r\n                $\"Cannot mirror: target character frame not computable ({e.Message}).\", e);\r\n        }\r\n\r\n        var a = Vector3.Abs(lateral);\r\n        if (a.Y <= AxisSnapTolerance && a.Z <= AxisSnapTolerance)\r\n            return Vector3.UnitX;\r\n        if (a.X <= AxisSnapTolerance && a.Z <= AxisSnapTolerance)\r\n            return Vector3.UnitY;\r\n        if (a.X <= AxisSnapTolerance && a.Y <= AxisSnapTolerance)\r\n            return Vector3.UnitZ;\r\n        return lateral; // general (non-axis-aligned) rig: exact involution is lost, math is not\r\n    }\r\n\r\n    /// <summary>p\u2032 = p \u2212 2(n\u0302\u00b7p)n\u0302. With a snapped axis this is an exact sign flip of one\r\n    /// component (IEEE subtraction of representable values is exact).</summary>\r\n    private static Vector3 ReflectPoint(Vector3 p, Vector3 n)\r\n        => p - 2f * Vector3.Dot(p, n) * n;\r\n\r\n    /// <summary>q\u2032 = (2(n\u0302\u00b7v)n\u0302 \u2212 v, w): the conjugated rotation M\u00b7R\u00b7M \u2014 same angle, axis\r\n    /// reflected, sense reversed. With n\u0302 = +X this is (x, \u2212y, \u2212z, w). Components are\r\n    /// preserved exactly (no renormalization), keeping the double mirror bit-exact.</summary>\r\n    private static Quaternion ReflectRotation(Quaternion q, Vector3 n)\r\n    {\r\n        var v = new Vector3(q.X, q.Y, q.Z);\r\n        var reflected = 2f * Vector3.Dot(v, n) * n - v;\r\n        return new Quaternion(reflected.X, reflected.Y, reflected.Z, q.W);\r\n    }\r\n\r\n    // ================================================================ L\u2194R pairing\r\n\r\n    /// <summary>\r\n    /// \u03c3: bone \u2192 mirror partner (identity for center/unpaired bones). Roles pair first;\r\n    /// role-less bones pair by <c>_L</c>/<c>_R</c> name tokens. Validated to be an involution\r\n    /// consistent with the hierarchy (\u03c3(parent(i)) == parent(\u03c3(i))).\r\n    /// </summary>\r\n    private static int[] BuildPairing(TargetRig rig)\r\n    {\r\n        var skeleton = rig.Skeleton;\r\n        var pair = new int[skeleton.Count];\r\n        for (var i = 0; i < pair.Length; i++)\r\n            pair[i] = i;\r\n\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n        {\r\n            if (rig.RoleOf(i) is { } role)\r\n            {\r\n                if (MirrorRole(role) is not { } mirroredRole)\r\n                    continue; // center role: mirrors in place\r\n                pair[i] = rig.BoneForRole(mirroredRole)\r\n                    ?? throw new ArgumentException(\r\n                        $\"Cannot mirror: target rig maps role {role} ('{skeleton[i].Name}') \"\r\n                        + $\"but not its counterpart {mirroredRole}.\");\r\n            }\r\n            else\r\n            {\r\n                var partnerName = SwapSideTokens(skeleton[i].Name);\r\n                if (partnerName is null)\r\n                    continue; // no side token: center bone\r\n                var partner = skeleton.IndexOf(partnerName);\r\n                if (partner >= 0)\r\n                    pair[i] = partner;\r\n                // No partner bone: leave in place (e.g. an asymmetric prop bone) \u2014 its\r\n                // rotation still mirrors across the sagittal plane.\r\n            }\r\n        }\r\n\r\n        for (var i = 0; i < pair.Length; i++)\r\n        {\r\n            // Constraint-driven helper bones are excluded from the output DMX (the model's\r\n            // AnimConstraintList re-drives them at runtime, see Retargeter.EmitClip\r\n            // ChannelExcludedBones), so their mirrored channels are never written. The shipped\r\n            // s&box citizen rig parents these asymmetrically (arm_elbow_helper_R hangs under\r\n            // arm_lower_R_twist0 while arm_elbow_helper_L hangs under arm_lower_L), a benign\r\n            // data quirk that must not fail the whole mirror. Skip the strict L/R\r\n            // hierarchy-consistency requirement for them: their pairing does not affect any\r\n            // written channel. (W3a fix, southpaw project.)\r\n            if (rig.HelpersAreConstraintDriven && rig.ClassOf(i) == BoneClass.ConstraintDriven)\r\n                continue;\r\n\r\n            if (pair[pair[i]] != i)\r\n                throw new ArgumentException(\r\n                    $\"Cannot mirror: bone pairing is not symmetric ('{skeleton[i].Name}' \u2192 \"\r\n                    + $\"'{skeleton[pair[i]].Name}' \u2192 '{skeleton[pair[pair[i]]].Name}').\");\r\n\r\n            var parent = skeleton[i].ParentIndex;\r\n            var partnerParent = skeleton[pair[i]].ParentIndex;\r\n            var consistent = parent < 0\r\n                ? partnerParent < 0\r\n                : partnerParent == pair[parent];\r\n            if (!consistent)\r\n                throw new ArgumentException(\r\n                    $\"Cannot mirror: left/right pairing is not hierarchy-consistent \u2014 \"\r\n                    + $\"'{skeleton[i].Name}' and partner '{skeleton[pair[i]].Name}' hang under \"\r\n                    + \"non-mirrored parents.\");\r\n        }\r\n\r\n        return pair;\r\n    }\r\n\r\n    /// <summary>UpperArmL \u2192 UpperArmR (and back); null for center roles. Every sided\r\n    /// <see cref=\"BoneRole\"/> ends in <c>L</c>/<c>R</c>; no center role does.</summary>\r\n    private static BoneRole? MirrorRole(BoneRole role)\r\n    {\r\n        var name = role.ToString();\r\n        var mirroredName = name[^1] switch\r\n        {\r\n            'L' => name[..^1] + \"R\",\r\n            'R' => name[..^1] + \"L\",\r\n            _ => null,\r\n        };\r\n        return mirroredName is not null && Enum.TryParse<BoneRole>(mirroredName, out var mirrored)\r\n            ? mirrored\r\n            : null;\r\n    }\r\n\r\n    /// <summary>Swaps <c>L</c>/<c>R</c> underscore-delimited name tokens\r\n    /// (<c>foot_L_IK_target</c> \u2192 <c>foot_R_IK_target</c>); null when the name carries no\r\n    /// side token.</summary>\r\n    private static string? SwapSideTokens(string name)\r\n    {\r\n        var tokens = name.Split('_');\r\n        for (var i = 0; i < tokens.Length; i++)\r\n        {\r\n            tokens[i] = tokens[i] switch\r\n            {\r\n                \"L\" => \"R\",\r\n                \"R\" => \"L\",\r\n                \"l\" => \"r\",\r\n                \"r\" => \"l\",\r\n                _ => tokens[i],\r\n            };\r\n        }\r\n        var result = string.Join('_', tokens);\r\n        return string.Equals(result, name, StringComparison.Ordinal) ? null : result;\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Solve/FingerSolver.cs",
            "FileName": "FingerSolver.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Numerics;\r\nusing HumanoidRetargeter.Mapping;\r\nusing HumanoidRetargeter.Maths;\r\n\r\nnamespace HumanoidRetargeter.Solve;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>\r\n/// Finger retargeting. Picks one of three strategies per finger chain:\r\n/// <list type=\"number\">\r\n/// <item><b>1:1 absolute copy</b> (via the <c>transferOneToOne</c> callback into\r\n/// <see cref=\"GeometricSolver\"/>'s body path) when the source and target chains are\r\n/// <i>geometrically identical</i> \u2014 same mapped role set, same canonical frames, same\r\n/// normalized rest rotations. This is the same-rig round-trip case and is lossless (exact\r\n/// identity, twist included).</item>\r\n/// <item><b>Direction matching</b> when the phalanx counts match ordinally but the rigs\r\n/// differ (the common cross-rig case, e.g. Mixamo Prox/Mid/Dist onto the s&amp;box finger\r\n/// with its extra metacarpal \u2014 which keeps its rest local; a source metacarpal's rotation is\r\n/// implicit in the proximal's absolute direction). Each target phalanx is swung \u2014 shortest\r\n/// arc, rotation axis \u22a5 the finger axis, hence <b>zero twist by construction</b> \u2014 so that its\r\n/// segment direction matches the source phalanx's direction in character-frame coordinates\r\n/// exactly. Curl and splay are both captured by the direction; the source's axial twist is\r\n/// dropped (hinge-joint noise; copying it absolutely would read as roll through the\r\n/// inter-phalanx canonical mismatch between rigs, measured up to ~12\u00b0 on thumbs).</item>\r\n/// <item><b>Proportional redistribution</b> when phalanx counts differ (e.g. a two-phalanx\r\n/// source finger): per-phalanx local curls \u2014 swing-twist about the canonical hinge Y of\r\n/// <c>\u03bb_b = C_b\u207b\u00b9\u00b7(\u0394R_prev\u207b\u00b9\u00b7\u0394R_b)\u00b7C_b</c> \u2014 are summed over the source chain (metacarpal\r\n/// included) and redistributed over the target phalanges proportional to rest segment\r\n/// lengths; splay (metacarpal + proximal, swing-twist about canonical Z) goes 100% to the\r\n/// target proximal; the X-twist residual is dropped.</item>\r\n/// </list>\r\n/// In every mode target world deltas rebuild hierarchically from the solved target hand:\r\n/// <c>\u0394R_i = \u0394R_{i-1} \u00b7 (C_i \u00b7 \u03bb_i \u00b7 C_i\u207b\u00b9)</c>, then <c>W_i = \u0394R_i \u00b7 R_tgtNormRest,i</c>.\r\n/// Instances are per-solve and not thread-safe.\r\n/// </summary>\r\ninternal sealed class FingerSolver\r\n{\r\n    /// <summary>Two canonical frames / rest rotations within this angle count as identical\r\n    /// (same-rig detection for the lossless 1:1 path); cross-rig differences are degrees.</summary>\r\n    private const float SameRigToleranceRad = 1e-3f;\r\n\r\n    private enum ChainMode\r\n    {\r\n        DirectionMatch,\r\n        Proportional,\r\n    }\r\n\r\n    private readonly struct SourcePhalanx\r\n    {\r\n        public required int Slot { get; init; }\r\n        public required Quaternion C { get; init; }\r\n        public required Quaternion CInv { get; init; }\r\n        public required bool TakesSplay { get; init; }\r\n    }\r\n\r\n    private readonly struct Recipient\r\n    {\r\n        public required int TgtBone { get; init; }\r\n        public required Quaternion C { get; init; }\r\n        public required Quaternion CInv { get; init; }\r\n        public required Quaternion RestRot { get; init; }\r\n        public required float Weight { get; init; }\r\n        public required bool Splay { get; init; }\r\n    }\r\n\r\n    private sealed class Chain\r\n    {\r\n        public required ChainMode Mode { get; init; }\r\n        public required int SrcHandSlot { get; init; }\r\n        public required int TgtHandBone { get; init; }\r\n        public required Quaternion TgtHandNormRestRotInv { get; init; }\r\n        public required SourcePhalanx[] Sources { get; init; }\r\n        public required Recipient[] Recipients { get; init; }\r\n    }\r\n\r\n    private readonly List<Chain> _chains;\r\n    private readonly Quaternion _chrSrcInv;\r\n    private readonly Quaternion _chrTgt;\r\n\r\n    private FingerSolver(List<Chain> chains, Quaternion chrSrcInv, Quaternion chrTgt)\r\n    {\r\n        _chains = chains;\r\n        _chrSrcInv = chrSrcInv;\r\n        _chrTgt = chrTgt;\r\n    }\r\n\r\n    // ---------------------------------------------------------------- role tables\r\n\r\n    private static readonly BoneRole[][] ChainRoles = BuildChainRoles();\r\n    private static readonly HashSet<BoneRole> FingerRoleSet = ChainRoles.SelectMany(c => c.Skip(1)).ToHashSet();\r\n\r\n    private static BoneRole[][] BuildChainRoles()\r\n    {\r\n        var chains = new List<BoneRole[]>();\r\n        foreach (var side in new[] { \"L\", \"R\" })\r\n        {\r\n            foreach (var finger in new[] { \"Thumb\", \"Index\", \"Middle\", \"Ring\", \"Pinky\" })\r\n            {\r\n                // Element 0 is the hand the chain hangs off; 1.. are Meta/Prox/Mid/Dist.\r\n                chains.Add(new[]\r\n                {\r\n                    Enum.Parse<BoneRole>(\"Hand\" + side),\r\n                    Enum.Parse<BoneRole>(finger + \"Meta\" + side),\r\n                    Enum.Parse<BoneRole>(finger + \"Prox\" + side),\r\n                    Enum.Parse<BoneRole>(finger + \"Mid\" + side),\r\n                    Enum.Parse<BoneRole>(finger + \"Dist\" + side),\r\n                });\r\n            }\r\n        }\r\n        return chains.ToArray();\r\n    }\r\n\r\n    /// <summary>True for the 40 per-finger segment roles (Meta/Prox/Mid/Dist \u00d7 finger \u00d7 side).</summary>\r\n    public static bool IsFingerRole(BoneRole role) => FingerRoleSet.Contains(role);\r\n\r\n    // ---------------------------------------------------------------- build\r\n\r\n    /// <summary>\r\n    /// Builds the per-chain plans. Geometrically identical chains are reported through\r\n    /// <paramref name=\"transferOneToOne\"/> instead of being planned here. Returns null when\r\n    /// every mapped chain took that path (or none is mapped).\r\n    /// </summary>\r\n    public static FingerSolver? Build(\r\n        MappingResult sourceMap,\r\n        CanonicalFrames srcCanon,\r\n        IReadOnlyList<XForm> srcNormRest,\r\n        Func<BoneRole, int?> tgtBoneForRole,\r\n        CanonicalFrames tgtCanon,\r\n        IReadOnlyList<XForm> tgtNormRest,\r\n        Quaternion chrSrcInv,\r\n        Quaternion chrTgt,\r\n        Func<int, int> registerSlot,\r\n        Action<BoneRole> transferOneToOne)\r\n    {\r\n        var chains = new List<Chain>();\r\n        foreach (var chainRoles in ChainRoles)\r\n        {\r\n            var handRole = chainRoles[0];\r\n            var metaRole = chainRoles[1];\r\n            var proxRole = chainRoles[2];\r\n            var segments = chainRoles.Skip(1).ToArray();\r\n\r\n            var srcRoles = segments\r\n                .Where(r => sourceMap.RoleToBone.ContainsKey(r) && srcCanon.Has(r))\r\n                .ToArray();\r\n            var tgtRoles = segments\r\n                .Where(r => tgtBoneForRole(r) is not null && tgtCanon.Has(r))\r\n                .ToArray();\r\n            if (srcRoles.Length == 0 || tgtRoles.Length == 0)\r\n                continue;\r\n\r\n            if (srcRoles.SequenceEqual(tgtRoles) && ChainsCoincide(\r\n                srcRoles, sourceMap, srcCanon, srcNormRest, tgtBoneForRole, tgtCanon, tgtNormRest))\r\n            {\r\n                foreach (var role in srcRoles)\r\n                    transferOneToOne(role);\r\n                continue;\r\n            }\r\n\r\n            var srcPhalanges = srcRoles.Where(r => r != metaRole).ToArray();\r\n            var tgtPhalanges = tgtRoles.Where(r => r != metaRole).ToArray();\r\n            var recipientRoles = tgtPhalanges.Length > 0 ? tgtPhalanges : tgtRoles;\r\n            var mode = srcPhalanges.Length == recipientRoles.Length && srcPhalanges.Length > 0\r\n                ? ChainMode.DirectionMatch\r\n                : ChainMode.Proportional;\r\n\r\n            // Direction matching consumes only the non-meta phalanges (the metacarpal's\r\n            // motion is implicit in the proximal's absolute direction); redistribution\r\n            // decomposes every mapped source segment including the metacarpal.\r\n            var sourceRolesUsed = mode == ChainMode.DirectionMatch ? srcPhalanges : srcRoles;\r\n            var sources = sourceRolesUsed.Select(r =>\r\n            {\r\n                var c = srcCanon.WorldFrameOf(r);\r\n                return new SourcePhalanx\r\n                {\r\n                    Slot = registerSlot(sourceMap.RoleToBone[r]),\r\n                    C = c,\r\n                    CInv = Quaternion.Conjugate(c),\r\n                    TakesSplay = r == metaRole || r == proxRole,\r\n                };\r\n            }).ToArray();\r\n\r\n            var weights = SegmentWeights(tgtRoles, recipientRoles, tgtBoneForRole, tgtNormRest);\r\n            var recipients = recipientRoles.Select((r, i) =>\r\n            {\r\n                var bone = tgtBoneForRole(r)!.Value;\r\n                var c = tgtCanon.WorldFrameOf(r);\r\n                return new Recipient\r\n                {\r\n                    TgtBone = bone,\r\n                    C = c,\r\n                    CInv = Quaternion.Conjugate(c),\r\n                    RestRot = tgtNormRest[bone].Rot,\r\n                    Weight = weights[i],\r\n                    Splay = i == 0,\r\n                };\r\n            }).ToArray();\r\n\r\n            var tgtHand = tgtBoneForRole(handRole);\r\n            chains.Add(new Chain\r\n            {\r\n                Mode = mode,\r\n                SrcHandSlot = sourceMap.RoleToBone.TryGetValue(handRole, out var srcHand)\r\n                    ? registerSlot(srcHand)\r\n                    : -1,\r\n                TgtHandBone = tgtHand ?? -1,\r\n                TgtHandNormRestRotInv = tgtHand is int h\r\n                    ? Quaternion.Conjugate(tgtNormRest[h].Rot)\r\n                    : Quaternion.Identity,\r\n                Sources = sources,\r\n                Recipients = recipients,\r\n            });\r\n        }\r\n\r\n        return chains.Count > 0 ? new FingerSolver(chains, chrSrcInv, chrTgt) : null;\r\n    }\r\n\r\n    /// <summary>Same-rig detection: every chain member's canonical frame and normalized rest\r\n    /// rotation agree between source and target (within float noise). Only then is the 1:1\r\n    /// absolute copy lossless.</summary>\r\n    private static bool ChainsCoincide(\r\n        BoneRole[] roles, MappingResult sourceMap, CanonicalFrames srcCanon,\r\n        IReadOnlyList<XForm> srcNormRest, Func<BoneRole, int?> tgtBoneForRole,\r\n        CanonicalFrames tgtCanon, IReadOnlyList<XForm> tgtNormRest)\r\n    {\r\n        foreach (var role in roles)\r\n        {\r\n            var srcBone = sourceMap.RoleToBone[role];\r\n            var tgtBone = tgtBoneForRole(role)!.Value;\r\n            if (MathQ.AngleBetween(srcCanon.WorldFrameOf(role), tgtCanon.WorldFrameOf(role)) > SameRigToleranceRad\r\n                || MathQ.AngleBetween(srcNormRest[srcBone].Rot, tgtNormRest[tgtBone].Rot) > SameRigToleranceRad)\r\n            {\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    }\r\n\r\n    /// <summary>Normalized rest segment lengths of the recipient phalanges (the proportional\r\n    /// curl weights). The distal segment, having no chain child, is estimated as 0.8\u00d7 its\r\n    /// preceding segment.</summary>\r\n    private static float[] SegmentWeights(\r\n        BoneRole[] tgtRoles, BoneRole[] recipientRoles,\r\n        Func<BoneRole, int?> tgtBoneForRole, IReadOnlyList<XForm> tgtNormRest)\r\n    {\r\n        var positions = tgtRoles.Select(r => tgtNormRest[tgtBoneForRole(r)!.Value].Pos).ToArray();\r\n        var weights = new float[recipientRoles.Length];\r\n        for (var i = 0; i < recipientRoles.Length; i++)\r\n        {\r\n            var j = Array.IndexOf(tgtRoles, recipientRoles[i]);\r\n            weights[i] = j + 1 < positions.Length\r\n                ? (positions[j + 1] - positions[j]).Length()\r\n                : j > 0 ? 0.8f * (positions[j] - positions[j - 1]).Length() : 1f;\r\n        }\r\n\r\n        var sum = weights.Sum();\r\n        if (sum <= 1e-6f)\r\n            return Enumerable.Repeat(1f / weights.Length, weights.Length).ToArray();\r\n        for (var i = 0; i < weights.Length; i++)\r\n            weights[i] /= sum;\r\n        return weights;\r\n    }\r\n\r\n    // ---------------------------------------------------------------- per frame\r\n\r\n    /// <summary>\r\n    /// Solves the planned chains for one frame. <paramref name=\"srcDeltas\"/> holds the\r\n    /// registered source world rotation deltas (from normalized rest); solved target world\r\n    /// rotations are written into <paramref name=\"rot\"/>/<paramref name=\"solved\"/>. The target\r\n    /// hands must already be solved (body pass runs first).\r\n    /// </summary>\r\n    public void Apply(Quaternion[] srcDeltas, bool[] solved, Quaternion[] rot)\r\n    {\r\n        foreach (var chain in _chains)\r\n        {\r\n            var acc = chain.TgtHandBone >= 0 && solved[chain.TgtHandBone]\r\n                ? MathQ.Normalize(rot[chain.TgtHandBone] * chain.TgtHandNormRestRotInv)\r\n                : Quaternion.Identity;\r\n\r\n            if (chain.Mode == ChainMode.DirectionMatch)\r\n                ApplyDirectionMatch(chain, srcDeltas, acc, solved, rot);\r\n            else\r\n                ApplyProportional(chain, srcDeltas, acc, solved, rot);\r\n        }\r\n    }\r\n\r\n    private void ApplyDirectionMatch(\r\n        Chain chain, Quaternion[] srcDeltas, Quaternion acc, bool[] solved, Quaternion[] rot)\r\n    {\r\n        for (var i = 0; i < chain.Recipients.Length; i++)\r\n        {\r\n            var sp = chain.Sources[i];\r\n            var rc = chain.Recipients[i];\r\n\r\n            // Source phalanx direction in character coords; re-expressed in the target world,\r\n            // then relative to the already-reconstructed parent delta, then in the phalanx's\r\n            // canonical frame \u2014 where the rest direction is unit X.\r\n            var srcAbs = MathQ.Normalize(_chrSrcInv * srcDeltas[sp.Slot] * sp.C);\r\n            var dirChr = Vector3.Transform(Vector3.UnitX, srcAbs);\r\n            var dirTgtWorld = Vector3.Transform(dirChr, _chrTgt);\r\n            var dirLocal = Vector3.Transform(dirTgtWorld, Quaternion.Conjugate(acc));\r\n            var dirCanon = Vector3.Transform(dirLocal, rc.CInv);\r\n\r\n            // Shortest-arc swing X -> dir: rotation axis \u22a5 X, so it carries zero finger-axis\r\n            // twist by construction.\r\n            var swing = MathQ.FromTo(Vector3.UnitX, dirCanon);\r\n\r\n            acc = MathQ.Normalize(acc * (rc.C * swing * rc.CInv));\r\n            rot[rc.TgtBone] = MathQ.Normalize(acc * rc.RestRot);\r\n            solved[rc.TgtBone] = true;\r\n        }\r\n    }\r\n\r\n    private static void ApplyProportional(\r\n        Chain chain, Quaternion[] srcDeltas, Quaternion acc, bool[] solved, Quaternion[] rot)\r\n    {\r\n        // Decompose: total local curl over the chain, splay from metacarpal + proximal.\r\n        var prev = chain.SrcHandSlot >= 0 ? srcDeltas[chain.SrcHandSlot] : Quaternion.Identity;\r\n        float totalCurl = 0f, splay = 0f;\r\n        foreach (var sp in chain.Sources)\r\n        {\r\n            var dr = srcDeltas[sp.Slot];\r\n            var local = MathQ.Normalize(Quaternion.Conjugate(prev) * dr);\r\n            var canon = MathQ.Normalize(sp.CInv * local * sp.C);\r\n\r\n            MathQ.SwingTwist(canon, Vector3.UnitY, out var swing, out var curlQ);\r\n            totalCurl += SignedAngle(curlQ, Vector3.UnitY);\r\n\r\n            if (sp.TakesSplay)\r\n            {\r\n                MathQ.SwingTwist(swing, Vector3.UnitZ, out _, out var splayQ);\r\n                splay += SignedAngle(splayQ, Vector3.UnitZ);\r\n            }\r\n\r\n            prev = dr;\r\n        }\r\n\r\n        foreach (var rc in chain.Recipients)\r\n        {\r\n            var mu = Quaternion.CreateFromAxisAngle(Vector3.UnitY, totalCurl * rc.Weight);\r\n            if (rc.Splay)\r\n                mu = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, splay) * mu;\r\n\r\n            acc = MathQ.Normalize(acc * (rc.C * mu * rc.CInv));\r\n            rot[rc.TgtBone] = MathQ.Normalize(acc * rc.RestRot);\r\n            solved[rc.TgtBone] = true;\r\n        }\r\n    }\r\n\r\n    /// <summary>Signed rotation angle of an axis-aligned twist quaternion about\r\n    /// <paramref name=\"axis\"/>, wrapped to (\u2212\u03c0, \u03c0].</summary>\r\n    private static float SignedAngle(Quaternion twist, Vector3 axis)\r\n    {\r\n        var s = twist.X * axis.X + twist.Y * axis.Y + twist.Z * axis.Z;\r\n        var angle = 2f * MathF.Atan2(s, twist.W);\r\n        if (angle > MathF.PI)\r\n            angle -= 2f * MathF.PI;\r\n        else if (angle < -MathF.PI)\r\n            angle += 2f * MathF.PI;\r\n        return angle;\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Solve/SolveOptions.cs",
            "FileName": "SolveOptions.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System.Collections.Generic;\r\nusing HumanoidRetargeter.Mapping;\r\n\r\nnamespace HumanoidRetargeter.Solve;\r\n\r\n/// <summary>How a mapped role's rotation is transferred by the <see cref=\"GeometricSolver\"/>.</summary>\r\npublic enum RoleTransferMode\r\n{\r\n    /// <summary>\r\n    /// Absolute canonical-orientation matching: the target's animated chain direction is\r\n    /// driven to <b>equal</b> the source's (in character-frame coordinates). Right for limbs\r\n    /// and the spine \u2014 the pose IS the direction \u2014 but it also imposes the source rig's rest\r\n    /// proportions/posture on roles whose rest directions legitimately differ between rigs.\r\n    /// </summary>\r\n    AbsoluteDirection,\r\n\r\n    /// <summary>\r\n    /// Rest-relative delta: the source's canonical-space rotation <i>delta from its own\r\n    /// normalized rest</i> is replayed onto the <b>target's</b> normalized rest\r\n    /// (<c>W_t(f) = C_t\u00b7\u0394C(f)\u00b7C_t\u207b\u00b9\u00b7R_tgtNormRest</c> with\r\n    /// <c>\u0394C(f) = C_s\u207b\u00b9\u00b7\u0394R(f)\u00b7C_s</c>). The target keeps its own rest carriage (shoulder\r\n    /// line height, neck-base angle) and moves with the source. Identical to\r\n    /// <see cref=\"AbsoluteDirection\"/> when source and target rigs coincide.\r\n    /// </summary>\r\n    DeltaFromRest,\r\n\r\n    /// <summary>\r\n    /// Character-space delta: the source's world-rotation delta from its normalized rest is\r\n    /// re-expressed in character coordinates and applied to the <b>target's</b> normalized\r\n    /// rest (<c>W_t(f) = M\u00b7\u0394R(f)\u00b7M\u207b\u00b9\u00b7R_tgtNormRest</c> with <c>M = Q_tgt\u00b7Q_src\u207b\u00b9</c>, the\r\n    /// same character basis change <see cref=\"AbsoluteDirection\"/> premultiplies). Like\r\n    /// <see cref=\"DeltaFromRest\"/> the target keeps its own rest carriage, but the delta\r\n    /// keeps its <i>world</i> rotation axes instead of being remapped through the per-role\r\n    /// canonical frames \u2014 the faithful replay when the rigs' rest chain directions diverge\r\n    /// so far that canonical-axis remapping would tilt every rotation axis by that\r\n    /// divergence (measured 23\u201344\u00b0 on feet: CMU/ARP ankle anatomy vs the s&amp;box rig's\r\n    /// steep ankle, where canonical remapping mis-pitched planted feet by up to 47\u00b0).\r\n    /// Identical to the other modes when source and target rigs coincide.\r\n    /// </summary>\r\n    CharacterDeltaFromRest,\r\n}\r\n\r\n/// <summary>Options controlling a single retarget solve (one clip \u2192 one output clip).</summary>\r\npublic sealed class SolveOptions\r\n{\r\n    /// <summary>\r\n    /// Default per-role transfer modes: shoulder girdle and neck carriage are\r\n    /// <see cref=\"RoleTransferMode.DeltaFromRest\"/> (each rig's clavicle line / neck-base\r\n    /// direction is rig anatomy, not pose \u2014 absolute matching was measured to drag the\r\n    /// s&amp;box shoulders 6\u201328\u00b0 toward the source's flatter/lower clavicle line and is the\r\n    /// \"low shoulders, hunched neck\" artifact), and feet are\r\n    /// <see cref=\"RoleTransferMode.CharacterDeltaFromRest\"/> (a rest foot\u2192toe direction is\r\n    /// ankle anatomy too \u2014 rigs diverge 11\u201344\u00b0 from the s&amp;box rig's steep ankle, so\r\n    /// absolute matching pitched planted feet up to 25\u00b0 off flat, the \"feet bent\r\n    /// upward/inward\" artifact; the character-space delta keeps the rotation's world axes,\r\n    /// which canonical-frame remapping would tilt by that same divergence). The head is\r\n    /// <see cref=\"RoleTransferMode.CharacterDeltaFromRest\"/> for the same reason: the rest\r\n    /// neck\u2192head direction is head-joint-placement anatomy (measured 0\u201327\u00b0 forward lean\r\n    /// across neutral-rest rigs vs the s&amp;box rig's 25.5\u00b0), so the target keeps its own\r\n    /// neutral skull attitude and replays the source's attitude <i>changes</i> \u2014 for the\r\n    /// head this computes exactly what the previous virtual-frame absolute matching did.\r\n    /// Two solver fallbacks adjust these defaults per rig pair: on a toe-less source the\r\n    /// foot entries become <see cref=\"RoleTransferMode.DeltaFromRest\"/> (virtual-foot\r\n    /// fallback), and a source whose normalized rest head attitude is implausible as a\r\n    /// neutral carriage (a posed bind \u2014 e.g. a chin-down/tilted fighting-stance rest,\r\n    /// measured 40.7\u00b0 forward / 16.9\u00b0 lateral on such a rig where the delta replay read\r\n    /// ~12\u00b0 \"looking up at an angle\") switches the head to\r\n    /// <see cref=\"RoleTransferMode.AbsoluteDirection\"/> so the gaze follows the source\r\n    /// absolutely instead of replaying deltas from a posed reference (see the\r\n    /// <see cref=\"GeometricSolver\"/> remarks for both). Everything else (limbs, spine,\r\n    /// toes, fingers) stays absolute: there the worldspace direction IS the pose.\r\n    /// </summary>\r\n    public static IReadOnlyDictionary<BoneRole, RoleTransferMode> DefaultTransferModes { get; } =\r\n        new Dictionary<BoneRole, RoleTransferMode>\r\n        {\r\n            [BoneRole.ClavicleL] = RoleTransferMode.DeltaFromRest,\r\n            [BoneRole.ClavicleR] = RoleTransferMode.DeltaFromRest,\r\n            [BoneRole.Neck] = RoleTransferMode.DeltaFromRest,\r\n            [BoneRole.Head] = RoleTransferMode.CharacterDeltaFromRest,\r\n            [BoneRole.FootL] = RoleTransferMode.CharacterDeltaFromRest,\r\n            [BoneRole.FootR] = RoleTransferMode.CharacterDeltaFromRest,\r\n        };\r\n\r\n    /// <summary>\r\n    /// Per-role transfer modes. Null (default) = <see cref=\"DefaultTransferModes\"/> plus the\r\n    /// solver's fallback heuristics (a toe-less source's virtual foot direction overrides\r\n    /// the foot default to <see cref=\"RoleTransferMode.DeltaFromRest\"/>, and a posed-rest\r\n    /// source head overrides the head default to\r\n    /// <see cref=\"RoleTransferMode.AbsoluteDirection\"/> \u2014 see the\r\n    /// <see cref=\"GeometricSolver\"/> remarks). A non-null map REPLACES the defaults entirely\r\n    /// and disables every fallback heuristic: each role uses exactly the mode in the map, and\r\n    /// roles absent from it are <see cref=\"RoleTransferMode.AbsoluteDirection\"/>. Pass an\r\n    /// empty dictionary for fully absolute (legacy) behavior \u2014 API callers supplying a map\r\n    /// opt out of all heuristics.\r\n    /// </summary>\r\n    public IReadOnlyDictionary<BoneRole, RoleTransferMode>? TransferModes { get; init; }\r\n\r\n    /// <summary>\r\n    /// Scale applied to the pelvis translation components perpendicular to the character up\r\n    /// direction. Null (default) = automatic: target hip height / source hip height, both\r\n    /// measured on the normalized rests.\r\n    /// </summary>\r\n    public float? HipScaleHorizontal { get; init; }\r\n\r\n    /// <summary>\r\n    /// Scale applied to the pelvis translation component along the character up direction.\r\n    /// Null (default) = the same automatic hip-height ratio as <see cref=\"HipScaleHorizontal\"/>.\r\n    /// </summary>\r\n    public float? HipScaleVertical { get; init; }\r\n\r\n    /// <summary>Whether finger roles are transferred; when false, target finger bones keep\r\n    /// their rest locals.</summary>\r\n    public bool TransferFingers { get; init; } = true;\r\n\r\n    /// <summary>Output clip name; null = the source clip's name.</summary>\r\n    public string? ClipName { get; init; }\r\n\r\n    /// <summary>Index of the source clip to retarget (<c>SourceScene.Clips</c>).</summary>\r\n    public int ClipIndex { get; init; }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "HumanoidRetargeter/Cleanup/FootGroundAlign.cs",
            "FileName": "FootGroundAlign.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidRetargeter.Maths;\r\nusing SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;\r\n\r\nnamespace HumanoidRetargeter.Cleanup;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>Tunables for the grounded-foot stance recalibration pass.</summary>\r\npublic sealed class FootGroundAlignOptions\r\n{\r\n    /// <summary>\r\n    /// Dead zone (degrees): measured stance offsets at or below this are genuine planted\r\n    /// articulation (heel-roll bias, natural lean \u2014 measured 2\u20134\u00b0 on well-rested rigs and\r\n    /// on citizen clips) and are left untouched, keeping the transfer byte-faithful there.\r\n    /// Only offsets beyond it are clearly rest-pose artifacts (measured 12\u201325\u00b0 on the\r\n    /// repro rig) and get recalibrated.\r\n    /// </summary>\r\n    public float MinCorrectionDeg { get; set; } = 8f;\r\n\r\n    /// <summary>\r\n    /// Maximum mean sole deviation (degrees) a plant may show and still count as a STANCE\r\n    /// for the offset measurement. Plants beyond this are not standing on the sole (crawls,\r\n    /// kneels, prone contact \u2014 measured 60\u201390\u00b0 there) and are excluded; genuine rest-pose\r\n    /// stance artifacts measure well below it (largest seen: 27\u00b0).\r\n    /// </summary>\r\n    public float MaxStanceDeviationDeg { get; set; } = 35f;\r\n}\r\n\r\n/// <summary>Per-foot results of a <see cref=\"FootGroundAlign.Apply\"/> run.</summary>\r\npublic sealed class FootGroundAlignFootReport\r\n{\r\n    /// <summary>Plants that contributed to the stance measurement.</summary>\r\n    public int StancePlants { get; set; }\r\n\r\n    /// <summary>Plants excluded as non-stance (mean sole deviation beyond\r\n    /// <see cref=\"FootGroundAlignOptions.MaxStanceDeviationDeg\"/>).</summary>\r\n    public int SkippedPlants { get; set; }\r\n\r\n    /// <summary>Measured planted sole offset from the ground plane, degrees (0 when no\r\n    /// stance plants exist).</summary>\r\n    public float MeasuredOffsetDeg { get; set; }\r\n\r\n    /// <summary>Foot correction applied to every frame, degrees (0 = inside the dead zone,\r\n    /// nothing changed).</summary>\r\n    public float AppliedFootDeg { get; set; }\r\n\r\n    /// <summary>Toe correction applied to every frame, degrees.</summary>\r\n    public float AppliedToeDeg { get; set; }\r\n}\r\n\r\n/// <summary>Results of a <see cref=\"FootGroundAlign.Apply\"/> run.</summary>\r\npublic sealed class FootGroundAlignReport\r\n{\r\n    /// <summary>Left-foot results.</summary>\r\n    public required FootGroundAlignFootReport Left { get; init; }\r\n\r\n    /// <summary>Right-foot results.</summary>\r\n    public required FootGroundAlignFootReport Right { get; init; }\r\n}\r\n\r\n/// <summary>\r\n/// Grounded-foot stance recalibration: measures how far the foot's SOLE sits from the ground\r\n/// plane while planted, and \u2014 when that offset is clearly a rest-pose artifact \u2014 rotates it\r\n/// out with one constant per foot, applied to every frame of the clip.\r\n/// </summary>\r\n/// <remarks>\r\n/// <para><b>Why a cleanup pass.</b> The solver transfers feet as rest-relative deltas\r\n/// (<see cref=\"Solve.RoleTransferMode.CharacterDeltaFromRest\"/>), so the target keeps its own\r\n/// ankle anatomy \u2014 correct whenever the source's rest pose is a flat-footed stance (the delta\r\n/// is then \"deviation from standing\"). Some rigs ship a NON-stance rest (measured: an\r\n/// Auto-Rig-Pro export whose rest foot sits 12\u201325\u00b0 from its planted stance), and that constant\r\n/// offset rides into every frame of the replay \u2014 planted feet hover toe-down/heel-up. What a\r\n/// stance actually looks like is animation evidence (planted phases), which a per-frame\r\n/// solver cannot see, so the recalibration lives here.</para>\r\n/// <para><b>Measurement.</b> Per foot: over every planted frame, the sole normal = rest up\r\n/// carried by the foot's world delta from the target bind rest (whose feet stand on the\r\n/// ground by construction); plants whose own mean normal sits beyond\r\n/// <see cref=\"FootGroundAlignOptions.MaxStanceDeviationDeg\"/> are excluded (crawl/kneel/prone\r\n/// contact is not a stance). The pooled mean normal's deviation from up is the stance\r\n/// offset.</para>\r\n/// <para><b>Correction.</b> Offsets inside <see cref=\"FootGroundAlignOptions.MinCorrectionDeg\"/>\r\n/// are genuine articulation \u2014 nothing is changed (well-rested rigs and same-rig round trips\r\n/// stay byte-identical through this pass). Beyond it, the shortest-arc rotation taking the\r\n/// pooled normal back to up (pitch+roll only \u2014 yaw/toe-out is pose and follows the source)\r\n/// premultiplies the foot's world rotation on EVERY frame: a rest artifact is constant, so\r\n/// the fix is too \u2014 within-plant heel-roll, swing styling and frame-to-frame continuity are\r\n/// preserved exactly, and no blending is needed. The toe then receives its own residual\r\n/// constant measured on top of the corrected foot (it neither double-rotates with the foot\r\n/// fix nor inherits the source toe's own rest artifact). Corrections rotate bones about\r\n/// their own joints: ankle positions are untouched, so the pass composes freely with the\r\n/// <see cref=\"FootPlant\"/> position pinning (which preserves foot world rotations).</para>\r\n/// <para><b>Plant intervals come from the caller</b> (the pipeline detects them on the\r\n/// SOURCE clip via <see cref=\"FootPlant.DetectPlantIntervals\"/> \u2014 ground truth, immune to\r\n/// the hip-height rescaling that can push target-side trajectories outside the cm-tuned\r\n/// Kovar thresholds). So does the decision to run at all: the pipeline invokes this pass\r\n/// only when the source's normalized rest is implausible as a flat stance (toe at/above\r\n/// ankle level or asymmetric feet \u2014 see <c>Retargeter.GroundAlignFeet</c>); on plausible\r\n/// stance rests the solver's rest-relative transfer is already faithful and planted-sole\r\n/// deviations are genuine articulation (boxing stances, heel rolls) that must not be\r\n/// flattened.</para>\r\n/// </remarks>\r\npublic static class FootGroundAlign\r\n{\r\n    /// <summary>Measures planted stance offsets and recalibrates feet whose offset is a\r\n    /// rest-pose artifact; returns what was measured and done.</summary>\r\n    /// <param name=\"frames\">Per-frame local transforms (skeleton bone order); modified in place.</param>\r\n    /// <param name=\"skeleton\">Bone hierarchy the frames are expressed against; its bind rest\r\n    /// is the flat-stance reference.</param>\r\n    /// <param name=\"left\">Left leg chain bone indices.</param>\r\n    /// <param name=\"right\">Right leg chain bone indices.</param>\r\n    /// <param name=\"up\">World up direction of the clip's space.</param>\r\n    /// <param name=\"leftPlants\">Left-foot plant intervals (frame indices into\r\n    /// <paramref name=\"frames\"/>; out-of-range parts are clamped/ignored).</param>\r\n    /// <param name=\"rightPlants\">Right-foot plant intervals.</param>\r\n    /// <param name=\"options\">Tunables; defaults used when null.</param>\r\n    public static FootGroundAlignReport Apply(\r\n        List<XForm[]> frames,\r\n        SkeletonModel skeleton,\r\n        FootChain left,\r\n        FootChain right,\r\n        Vector3 up,\r\n        IReadOnlyList<FrameRange> leftPlants,\r\n        IReadOnlyList<FrameRange> rightPlants,\r\n        FootGroundAlignOptions? options = null)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(frames);\r\n        ArgumentNullException.ThrowIfNull(skeleton);\r\n        ArgumentNullException.ThrowIfNull(left);\r\n        ArgumentNullException.ThrowIfNull(right);\r\n        ArgumentNullException.ThrowIfNull(leftPlants);\r\n        ArgumentNullException.ThrowIfNull(rightPlants);\r\n\r\n        options ??= new FootGroundAlignOptions();\r\n        var report = new FootGroundAlignReport\r\n        {\r\n            Left = new FootGroundAlignFootReport(),\r\n            Right = new FootGroundAlignFootReport(),\r\n        };\r\n        if (frames.Count == 0 || up.LengthSquared() < 1e-12f)\r\n            return report;\r\n        up = Vector3.Normalize(up);\r\n\r\n        RecalibrateFoot(frames, skeleton, left, up, leftPlants, options, report.Left);\r\n        RecalibrateFoot(frames, skeleton, right, up, rightPlants, options, report.Right);\r\n        return report;\r\n    }\r\n\r\n    private static void RecalibrateFoot(\r\n        List<XForm[]> frames, SkeletonModel skeleton, FootChain chain, Vector3 up,\r\n        IReadOnlyList<FrameRange> plants, FootGroundAlignOptions options,\r\n        FootGroundAlignFootReport report)\r\n    {\r\n        int n = frames.Count;\r\n        var foot = chain.Ankle;\r\n        var restFootRotInv = Quaternion.Conjugate(skeleton.RestWorld[foot].Rot);\r\n        var maxStanceCos = MathF.Cos(options.MaxStanceDeviationDeg * MathF.PI / 180f);\r\n\r\n        // ---- measurement: pooled planted sole normal over the stance plants ----\r\n        var pooled = Vector3.Zero;\r\n        foreach (var plant in plants)\r\n        {\r\n            int start = Math.Max(plant.Start, 0);\r\n            int end = Math.Min(plant.End, n - 1);\r\n            if (start > end)\r\n                continue;\r\n\r\n            var plantSum = Vector3.Zero;\r\n            for (int f = start; f <= end; f++)\r\n            {\r\n                var footRot = FkUtil.BoneWorld(frames[f], skeleton, foot).Rot;\r\n                plantSum += Vector3.Transform(up, MathQ.Normalize(footRot * restFootRotInv));\r\n            }\r\n            if (plantSum.LengthSquared() < 1e-8f\r\n                || Vector3.Dot(Vector3.Normalize(plantSum), up) < maxStanceCos)\r\n            {\r\n                report.SkippedPlants++; // not standing on the sole \u2014 crawl/kneel/toe contact\r\n                continue;\r\n            }\r\n            report.StancePlants++;\r\n            pooled += plantSum; // frame-count-weighted: longer stances dominate\r\n        }\r\n        if (pooled.LengthSquared() < 1e-8f)\r\n            return;\r\n        pooled = Vector3.Normalize(pooled);\r\n\r\n        var offsetDeg = MathQ.AngleBetween(pooled, up) * (180f / MathF.PI);\r\n        report.MeasuredOffsetDeg = offsetDeg;\r\n        if (offsetDeg <= options.MinCorrectionDeg)\r\n            return; // genuine planted articulation \u2014 leave the transfer byte-faithful\r\n\r\n        // ---- correction: one constant per foot, every frame ----\r\n        var footFix = MathQ.FromTo(pooled, up);\r\n        report.AppliedFootDeg = offsetDeg;\r\n\r\n        // Toe residual measured on top of the corrected foot, same dead zone.\r\n        var toeFix = Quaternion.Identity;\r\n        if (chain.Toe is { } toe && skeleton[toe].ParentIndex == foot)\r\n        {\r\n            var restToeRotInv = Quaternion.Conjugate(skeleton.RestWorld[toe].Rot);\r\n            var toePooled = Vector3.Zero;\r\n            foreach (var plant in plants)\r\n            {\r\n                int start = Math.Max(plant.Start, 0);\r\n                int end = Math.Min(plant.End, n - 1);\r\n                for (int f = start; f <= end && f >= 0; f++)\r\n                {\r\n                    var toeRot = FkUtil.BoneWorld(frames[f], skeleton, toe).Rot;\r\n                    toePooled += Vector3.Transform(\r\n                        up, MathQ.Normalize(footFix * toeRot * restToeRotInv));\r\n                }\r\n            }\r\n            if (toePooled.LengthSquared() > 1e-8f)\r\n            {\r\n                toePooled = Vector3.Normalize(toePooled);\r\n                var toeDeg = MathQ.AngleBetween(toePooled, up) * (180f / MathF.PI);\r\n                if (toeDeg > options.MinCorrectionDeg && Vector3.Dot(toePooled, up) >= maxStanceCos)\r\n                {\r\n                    toeFix = MathQ.FromTo(toePooled, up);\r\n                    report.AppliedToeDeg = toeDeg;\r\n                }\r\n            }\r\n        }\r\n\r\n        for (int f = 0; f < n; f++)\r\n            CorrectFrame(frames[f], skeleton, chain, footFix, toeFix);\r\n    }\r\n\r\n    /// <summary>Premultiplies the foot's world rotation by the constant fix (the joint\r\n    /// position is untouched \u2014 the rotation pivots the foot about its own head), then gives\r\n    /// the toe its own residual on top of the corrected foot.</summary>\r\n    private static void CorrectFrame(\r\n        XForm[] locals, SkeletonModel skeleton, FootChain chain,\r\n        Quaternion footFix, Quaternion toeFix)\r\n    {\r\n        var foot = chain.Ankle;\r\n        var parent = skeleton[foot].ParentIndex;\r\n        var parentRot = parent < 0\r\n            ? Quaternion.Identity\r\n            : FkUtil.BoneWorld(locals, skeleton, parent).Rot;\r\n\r\n        var footWorld = MathQ.Normalize(parentRot * locals[foot].Rot);\r\n        var newFootWorld = MathQ.Normalize(footFix * footWorld);\r\n        locals[foot] = new XForm(\r\n            locals[foot].Pos, MathQ.Normalize(Quaternion.Conjugate(parentRot) * newFootWorld));\r\n\r\n        if (chain.Toe is { } toe && skeleton[toe].ParentIndex == foot)\r\n        {\r\n            // Desired toe world = toeFix \u2218 footFix \u2218 original world; re-derive its local\r\n            // against the corrected foot so it does not double-rotate with the foot fix.\r\n            var toeWorldOld = MathQ.Normalize(footWorld * locals[toe].Rot);\r\n            var desired = MathQ.Normalize(toeFix * footFix * toeWorldOld);\r\n            locals[toe] = new XForm(\r\n                locals[toe].Pos, MathQ.Normalize(Quaternion.Conjugate(newFootWorld) * desired));\r\n        }\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "HumanoidRetargeter/Formats/Bvh/BvhImporter.cs",
            "FileName": "BvhImporter.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Globalization;\r\nusing System.Numerics;\r\nusing System.Text;\r\nusing HumanoidRetargeter.Maths;\r\nusing HumanoidRetargeter.Skeleton;\r\n\r\nnamespace HumanoidRetargeter.Formats.Bvh;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>Options for <see cref=\"BvhImporter.Import\"/>.</summary>\r\npublic sealed class BvhImportOptions\r\n{\r\n    /// <summary>Fixed resampling rate for the motion data, frames per second.</summary>\r\n    public float SampleFps { get; init; } = 30f;\r\n}\r\n\r\n/// <summary>\r\n/// BVH (Biovision Hierarchy) \u2192 <see cref=\"SourceScene\"/> importer.\r\n/// </summary>\r\n/// <remarks>\r\n/// <para><b>Format conventions implemented</b> (verified against Blender's\r\n/// <c>io_anim_bvh</c> importer, which is the project's ground-truth extractor):</para>\r\n/// <list type=\"bullet\">\r\n/// <item><b>Rest pose:</b> each joint's rest local translation is its <c>OFFSET</c>; rest\r\n/// rotation is identity (BVH stores no rest orientation).</item>\r\n/// <item><b>Rotation channels:</b> the channel list order IS the rotation order. The listed\r\n/// rotations apply left-to-right as intrinsic rotations, which in this library's\r\n/// column-vector convention (<c>a * b</c> applies <c>b</c> first) is the product\r\n/// <c>R = R_chan1 * R_chan2 * R_chan3</c> \u2014 e.g. <c>Zrotation Yrotation Xrotation</c> gives\r\n/// <c>R = Rz * Ry * Rx</c>. This matches Blender, which builds\r\n/// <c>Euler((x,y,z), reversed(channelOrder))</c> for the same matrix. Angles are degrees.</item>\r\n/// <item><b>Position channels:</b> when a joint has any position channel, the channel values\r\n/// REPLACE the joint's local translation (missing components are 0) \u2014 they are not added to\r\n/// the <c>OFFSET</c>. This is Blender's behavior; in practice roots have OFFSET 0 so the two\r\n/// readings only diverge on non-root position channels (e.g. Bandai-Namco exports).</item>\r\n/// <item><b>End Sites:</b> synthesized as a channel-less leaf bone named\r\n/// <c>\"&lt;parent&gt;_end\"</c> so chain tips keep their direction information (Blender instead\r\n/// folds them into the parent bone's tail).</item>\r\n/// </list>\r\n/// <para><b>Units</b>: BVH files carry no unit declaration. Heuristic: compute the rest\r\n/// skeleton height (max\u2212min world Y over all joints); if it is &lt; 10 the file is assumed\r\n/// to be in meters and all translations (offsets AND position channels, root included) are\r\n/// scaled \u00d7100 to centimeters, otherwise it is assumed to already be centimeters (\u00d71).\r\n/// Millimeter-scale files (height &gt; 400) are not special-cased \u2014 they are rare and\r\n/// ambiguous against cm mocap of long ranges; <see cref=\"SourceScene.UnitScaleCm\"/> records\r\n/// whichever factor was applied for diagnostics.</para>\r\n/// <para><b>Calibration rest-frame trim</b>: mocap exports (CMU asf/amc conversions among\r\n/// them) often prepend a skeleton-calibration segment \u2014 the rest pose itself (all rotation\r\n/// channels \u2248 0), hard-cut (or blend-ramped over 2\u20133 frames) into the real motion. Played\r\n/// back it reads as a T-pose flash at t = 0. The importer drops such a segment from either\r\n/// clip end when ALL of (measured margins in parentheses, over the corpus + repro files):\r\n/// every segment frame is rest-like (max joint rotation vs the identity rest \u2264 40\u00b0;\r\n/// calibration frames/ramps measure \u2264 24\u00b0, real clip edges \u2265 80\u00b0); the segment is short\r\n/// (\u2264 4 rest-like frames \u2014 hard cuts measure 1, blend ramps 2; longer rest-like leads are\r\n/// content); the clip beyond it is NOT rest-like; and the discontinuity where the segment\r\n/// exits into the motion is both large in absolute terms (\u2265 20\u00b0; measured 25\u2013177\u00b0) and\r\n/// large versus the clip's own typical inter-frame delta (\u2265 4\u00d7 the median; real clip edges\r\n/// measure \u2264 8\u00b0 at \u2264 ~1\u00d7 the median). A qualifying segment that exits through a multi-frame\r\n/// blend RAMP (measured 22\u201325\u00b0/frame for 3 frames on a makehuman-retarget export) has the\r\n/// ramp trimmed too, until the motion settles \u2014 8 frames total per end at most. A clip that\r\n/// legitimately starts near rest (an idle) is continuous into the motion and never trips\r\n/// the discontinuity gates. Note the trim can never remove\r\n/// the reference frame a non-anatomical stick bind needs for its rest rebuild (see\r\n/// <c>RestNormalizer</c>): it only removes frames that MATCH the identity-rotation bind,\r\n/// and a frame matching a stick bind carries no rest information the bind itself lacks \u2014\r\n/// the next (real) frame is then strictly the better reference.</para>\r\n/// <para><b>Resampling</b>: motion frames are resampled from the file's <c>Frame Time</c>\r\n/// grid onto <see cref=\"BvhImportOptions.SampleFps\"/>. Each native frame's euler channels are\r\n/// converted to a quaternion FIRST and bracketing frames are then slerped (positions lerped).\r\n/// Interpolating raw euler angles across frames would mostly work at mocap densities\r\n/// (30\u2013120 fps, small per-frame deltas) but breaks down when an angle wraps \u00b1180\u00b0 between\r\n/// frames; per-frame quaternion + slerp has no such failure mode, so that is what we do.</para>\r\n/// <para><b>Axes</b>: BVH is conventionally Y-up / Z-forward / X-right. Native axes are\r\n/// preserved (no conversion), matching the FBX importer's policy; the conventional axes are\r\n/// recorded on the <see cref=\"SourceScene\"/> (up = Y, front = Z, coord = X).</para>\r\n/// </remarks>\r\npublic static class BvhImporter\r\n{\r\n    private const float MeterHeightThreshold = 10f;\r\n\r\n    /// <summary>Parses BVH bytes and builds the source scene.</summary>\r\n    /// <exception cref=\"FormatException\">Malformed or truncated BVH.</exception>\r\n    public static SourceScene Import(byte[] data, BvhImportOptions? options = null)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(data);\r\n        options ??= new BvhImportOptions();\r\n        if (!(options.SampleFps > 0f) || !float.IsFinite(options.SampleFps))\r\n            throw new ArgumentOutOfRangeException(nameof(options), \"SampleFps must be positive.\");\r\n\r\n        var cursor = new TokenCursor(Encoding.UTF8.GetString(data));\r\n\r\n        // ---- HIERARCHY -----------------------------------------------------------------\r\n        cursor.ExpectKeyword(\"HIERARCHY\");\r\n        var joints = new List<Joint>();\r\n        int channelCount = 0;\r\n        if (!cursor.PeekIs(\"ROOT\"))\r\n            throw new FormatException(\"BVH: expected ROOT after HIERARCHY.\");\r\n        while (cursor.PeekIs(\"ROOT\")) // multiple roots are out of spec but harmless to accept\r\n        {\r\n            cursor.Next();\r\n            ParseJoint(cursor, joints, parent: -1, ref channelCount);\r\n        }\r\n\r\n        // ---- MOTION ---------------------------------------------------------------------\r\n        cursor.ExpectKeyword(\"MOTION\");\r\n        cursor.ExpectKeyword(\"FRAMES:\");\r\n        int frameCount = cursor.NextInt();\r\n        if (frameCount < 0)\r\n            throw new FormatException($\"BVH: negative frame count {frameCount}.\");\r\n        cursor.ExpectKeyword(\"FRAME\");\r\n        cursor.ExpectKeyword(\"TIME:\");\r\n        float frameTime = cursor.NextFloat();\r\n        if (!(frameTime > 0f) || !float.IsFinite(frameTime))\r\n            throw new FormatException($\"BVH: invalid Frame Time {frameTime}.\");\r\n\r\n        var motion = new float[frameCount][];\r\n        for (int f = 0; f < frameCount; f++)\r\n        {\r\n            var row = new float[channelCount];\r\n            for (int c = 0; c < channelCount; c++)\r\n                row[c] = cursor.NextFloat();\r\n            motion[f] = row;\r\n        }\r\n\r\n        // ---- units heuristic --------------------------------------------------------------\r\n        float unitScale = HeuristicUnitScale(joints);\r\n\r\n        // ---- skeleton ----------------------------------------------------------------------\r\n        var defs = new List<BoneDefinition>(joints.Count);\r\n        foreach (var j in joints)\r\n        {\r\n            defs.Add(new BoneDefinition(\r\n                j.Name,\r\n                j.Parent < 0 ? null : joints[j.Parent].Name,\r\n                new XForm(j.Offset * unitScale, Quaternion.Identity)));\r\n        }\r\n        var skeleton = Skeleton.Skeleton.Create(defs);\r\n\r\n        // ---- clip ----------------------------------------------------------------------------\r\n        var clips = new List<Clip>();\r\n        if (frameCount > 0)\r\n            clips.Add(ResampleClip(joints, skeleton, motion, frameTime, unitScale, options.SampleFps));\r\n\r\n        // BVH conventional axes: Y-up (1), Z-front (2), X-coord (0) \u2014 recorded, not converted.\r\n        // RestPlacementAuthored = false: the BVH rest skeleton is OFFSETs only (root at the\r\n        // file origin, no authored world placement), while MOTION root positions live in\r\n        // absolute capture-volume coordinates \u2014 the two share no common ground/origin, so\r\n        // the solver must normalize clip placement against the rest skeleton\r\n        // (see SourceScene.RestPlacementAuthored and GeometricSolver remarks).\r\n        return new SourceScene(\r\n            skeleton, clips, unitScale,\r\n            upAxis: 1, upAxisSign: 1,\r\n            frontAxis: 2, frontAxisSign: 1,\r\n            coordAxis: 0, coordAxisSign: 1,\r\n            originalUpAxis: -1)\r\n        {\r\n            RestPlacementAuthored = false,\r\n        };\r\n    }\r\n\r\n    // =====================================================================================\r\n    // hierarchy parsing\r\n    // =====================================================================================\r\n\r\n    private sealed class Joint\r\n    {\r\n        public required string Name;\r\n        public required int Parent;          // index into the joint list, -1 for roots\r\n        public Vector3 Offset;               // raw file units\r\n        public int PosX = -1, PosY = -1, PosZ = -1;            // motion column per position axis\r\n        public List<(int Axis, int Column)> Rot = new();        // rotation channels in file order\r\n        public bool HasPos => PosX >= 0 || PosY >= 0 || PosZ >= 0;\r\n    }\r\n\r\n    private static void ParseJoint(TokenCursor cursor, List<Joint> joints, int parent, ref int channelCount)\r\n    {\r\n        // Joint name: tokens up to '{', joined with '_' (mirrors Blender's handling of\r\n        // names containing spaces).\r\n        var nameParts = new List<string>();\r\n        while (!cursor.PeekIs(\"{\"))\r\n        {\r\n            if (cursor.AtEnd)\r\n                throw new FormatException(\"BVH: unexpected end of file in joint name.\");\r\n            nameParts.Add(cursor.Next());\r\n        }\r\n        if (nameParts.Count == 0)\r\n            throw new FormatException(\"BVH: joint with no name.\");\r\n        string name = UniqueName(string.Join('_', nameParts), joints);\r\n\r\n        cursor.ExpectKeyword(\"{\");\r\n        cursor.ExpectKeyword(\"OFFSET\");\r\n        var joint = new Joint { Name = name, Parent = parent };\r\n        joint.Offset = new Vector3(cursor.NextFloat(), cursor.NextFloat(), cursor.NextFloat());\r\n        int index = joints.Count;\r\n        joints.Add(joint);\r\n\r\n        if (cursor.PeekIs(\"CHANNELS\"))\r\n        {\r\n            cursor.Next();\r\n            int n = cursor.NextInt();\r\n            if (n < 0 || n > 6)\r\n                throw new FormatException($\"BVH: joint '{name}' has invalid channel count {n}.\");\r\n            for (int i = 0; i < n; i++)\r\n            {\r\n                string channel = cursor.Next();\r\n                int column = channelCount++;\r\n                switch (channel.ToUpperInvariant())\r\n                {\r\n                    case \"XPOSITION\": joint.PosX = column; break;\r\n                    case \"YPOSITION\": joint.PosY = column; break;\r\n                    case \"ZPOSITION\": joint.PosZ = column; break;\r\n                    case \"XROTATION\": joint.Rot.Add((0, column)); break;\r\n                    case \"YROTATION\": joint.Rot.Add((1, column)); break;\r\n                    case \"ZROTATION\": joint.Rot.Add((2, column)); break;\r\n                    default:\r\n                        throw new FormatException($\"BVH: unknown channel '{channel}' on joint '{name}'.\");\r\n                }\r\n            }\r\n        }\r\n\r\n        while (!cursor.PeekIs(\"}\"))\r\n        {\r\n            if (cursor.AtEnd)\r\n                throw new FormatException($\"BVH: unexpected end of file inside joint '{name}'.\");\r\n            if (cursor.PeekIs(\"JOINT\"))\r\n            {\r\n                cursor.Next();\r\n                ParseJoint(cursor, joints, index, ref channelCount);\r\n            }\r\n            else if (cursor.PeekIs(\"END\"))\r\n            {\r\n                cursor.Next();\r\n                cursor.ExpectKeyword(\"SITE\");\r\n                while (!cursor.PeekIs(\"{\")) // a name after \"End Site\" is out of spec; skip it\r\n                {\r\n                    if (cursor.AtEnd)\r\n                        throw new FormatException(\"BVH: unexpected end of file in End Site.\");\r\n                    cursor.Next();\r\n                }\r\n                cursor.ExpectKeyword(\"{\");\r\n                cursor.ExpectKeyword(\"OFFSET\");\r\n                var endOffset = new Vector3(cursor.NextFloat(), cursor.NextFloat(), cursor.NextFloat());\r\n                cursor.ExpectKeyword(\"}\");\r\n\r\n                // Synthesize a channel-less leaf so the chain tip's direction is kept.\r\n                joints.Add(new Joint\r\n                {\r\n                    Name = UniqueName(name + \"_end\", joints),\r\n                    Parent = index,\r\n                    Offset = endOffset,\r\n                });\r\n            }\r\n            else\r\n            {\r\n                throw new FormatException(\r\n                    $\"BVH: unexpected token '{cursor.Next()}' inside joint '{name}'.\");\r\n            }\r\n        }\r\n        cursor.ExpectKeyword(\"}\");\r\n    }\r\n\r\n    private static string UniqueName(string name, List<Joint> joints)\r\n    {\r\n        bool Taken(string candidate)\r\n        {\r\n            foreach (var j in joints)\r\n                if (string.Equals(j.Name, candidate, StringComparison.Ordinal))\r\n                    return true;\r\n            return false;\r\n        }\r\n\r\n        if (!Taken(name))\r\n            return name;\r\n        for (int i = 1; ; i++)\r\n        {\r\n            string candidate = $\"{name}#{i}\";\r\n            if (!Taken(candidate))\r\n                return candidate;\r\n        }\r\n    }\r\n\r\n    // =====================================================================================\r\n    // units\r\n    // =====================================================================================\r\n\r\n    /// <summary>\r\n    /// Meters-vs-centimeters heuristic: rest skeleton height (max\u2212min world Y over all\r\n    /// joints, end sites included) &lt; 10 \u2192 meters \u2192 \u00d7100; otherwise centimeters \u2192 \u00d71.\r\n    /// </summary>\r\n    private static float HeuristicUnitScale(List<Joint> joints)\r\n    {\r\n        Span<float> worldY = joints.Count <= 256 ? stackalloc float[joints.Count] : new float[joints.Count];\r\n        float min = float.MaxValue, max = float.MinValue;\r\n        for (int i = 0; i < joints.Count; i++)\r\n        {\r\n            worldY[i] = (joints[i].Parent < 0 ? 0f : worldY[joints[i].Parent]) + joints[i].Offset.Y;\r\n            min = MathF.Min(min, worldY[i]);\r\n            max = MathF.Max(max, worldY[i]);\r\n        }\r\n        float height = max - min;\r\n        return height > 0f && height < MeterHeightThreshold ? 100f : 1f;\r\n    }\r\n\r\n    // =====================================================================================\r\n    // motion sampling\r\n    // =====================================================================================\r\n\r\n    /// <summary>\r\n    /// Decodes every native frame to per-joint local transforms (quaternions built per frame\r\n    /// from the joint's channel order), drops leading/trailing calibration rest frames (see\r\n    /// class remarks), then resamples onto the <paramref name=\"fps\"/> grid \u2014 positions\r\n    /// lerped, rotations slerped between the bracketing native frames.\r\n    /// </summary>\r\n    private static Clip ResampleClip(\r\n        List<Joint> joints, Skeleton.Skeleton skeleton, float[][] motion,\r\n        float frameTime, float unitScale, float fps)\r\n    {\r\n        int jointCount = joints.Count;\r\n        int nativeCount = motion.Length;\r\n\r\n        // Joint order may differ from skeleton bone order (topological sort) \u2014 map.\r\n        var toSkeleton = new int[jointCount];\r\n        for (int i = 0; i < jointCount; i++)\r\n            toSkeleton[i] = skeleton.IndexOf(joints[i].Name);\r\n\r\n        // Native-frame locals.\r\n        var native = new XForm[nativeCount][];\r\n        for (int f = 0; f < nativeCount; f++)\r\n        {\r\n            var row = motion[f];\r\n            var locals = new XForm[jointCount];\r\n            for (int i = 0; i < jointCount; i++)\r\n                locals[i] = EvaluateLocal(joints[i], row, unitScale);\r\n            native[f] = locals;\r\n        }\r\n\r\n        // Calibration rest-frame trim: a short rest-like segment per clip end (class remarks).\r\n        int first = 0;\r\n        int last = nativeCount - 1;\r\n        if (nativeCount >= 3)\r\n        {\r\n            float typicalDeltaDeg = TypicalNeighborRotDeltaDeg(native);\r\n            first += CalibrationSegmentLength(native, first, last, step: +1, typicalDeltaDeg);\r\n            last -= CalibrationSegmentLength(native, last, first, step: -1, typicalDeltaDeg);\r\n        }\r\n        int trimmedCount = last - first + 1;\r\n\r\n        double duration = (trimmedCount - 1) * (double)frameTime;\r\n        int outCount = Math.Max(1, (int)Math.Round(duration * fps) + 1);\r\n\r\n        var frames = new List<XForm[]>(outCount);\r\n        for (int f = 0; f < outCount; f++)\r\n        {\r\n            double s = f / (double)fps / frameTime; // position on the trimmed native frame grid\r\n            int i0 = first + Math.Clamp((int)Math.Floor(s), 0, trimmedCount - 1);\r\n            int i1 = Math.Min(i0 + 1, last);\r\n            float u = Math.Clamp((float)(s - (i0 - first)), 0f, 1f);\r\n\r\n            var frame = new XForm[skeleton.Count];\r\n            var a = native[i0];\r\n            var b = native[i1];\r\n            for (int i = 0; i < jointCount; i++)\r\n            {\r\n                frame[toSkeleton[i]] = new XForm(\r\n                    Vector3.Lerp(a[i].Pos, b[i].Pos, u),\r\n                    MathQ.Normalize(Quaternion.Slerp(a[i].Rot, b[i].Rot, u)));\r\n            }\r\n            frames.Add(frame);\r\n        }\r\n\r\n        // NativeFps records the file's authored frame rate (1 / FrameTime): external frame\r\n        // ranges (Unity .meta clipAnimations) are expressed in it.\r\n        float nativeFps = frameTime > 0f ? (float)(1.0 / frameTime) : fps;\r\n        return new Clip(\"motion\", fps, looping: false, frames, nativeFps);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- calibration trim\r\n\r\n    /// <summary>A frame counts as rest-like only below this max-joint rotation angle vs the\r\n    /// identity-rotation bind (measured: calibration frames/ramps \u2264 24\u00b0, real edges \u2265 80\u00b0).</summary>\r\n    private const float CalibrationRestMaxDeg = 40f;\r\n\r\n    /// <summary>Absolute floor on the discontinuity out of the rest-like segment (measured:\r\n    /// calibration exits 25\u2013177\u00b0, continuous real clip edges \u2264 8\u00b0).</summary>\r\n    private const float CalibrationJumpMinDeg = 20f;\r\n\r\n    /// <summary>The segment-exit discontinuity must also exceed this multiple of the clip's\r\n    /// median inter-frame delta \u2014 a clip idling near rest never trips this.</summary>\r\n    private const float CalibrationJumpTypicalRatio = 4f;\r\n\r\n    /// <summary>Longest rest-like calibration segment trimmed per clip end. Hard cuts are\r\n    /// 1 frame (CMU asf/amc exports); rest\u2192motion blend ramps measure 2 rest-like frames\r\n    /// (a makehuman-retarget export). Longer rest-like leads are content, left alone.</summary>\r\n    private const int CalibrationMaxSegmentFrames = 4;\r\n\r\n    /// <summary>Absolute floor on a blend-ramp frame's delta for the ramp extension\r\n    /// (measured ramp deltas 15\u201325\u00b0/frame; settled motion \u2264 6\u00b0).</summary>\r\n    private const float CalibrationRampMinDeg = 10f;\r\n\r\n    /// <summary>Hard cap on the total trim per clip end (rest-like segment + blend ramp;\r\n    /// measured worst case 5 frames on the makehuman-retarget export).</summary>\r\n    private const int CalibrationMaxTrimFrames = 8;\r\n\r\n    /// <summary>\r\n    /// Length of the prepended (<paramref name=\"step\"/> = +1, scanning from\r\n    /// <paramref name=\"edge\"/> toward <paramref name=\"stop\"/>) or appended (\u22121)\r\n    /// skeleton-calibration segment, 0 when there is none. The segment is a short run of\r\n    /// rest-like frames (\u2264 <see cref=\"CalibrationMaxSegmentFrames\"/>) that exits into\r\n    /// NON-rest-like motion (\u2265 2 frames of which must remain) through a discontinuity that\r\n    /// is large both absolutely and against the clip's typical inter-frame delta. When the\r\n    /// exit is a multi-frame blend RAMP rather than a hard cut (measured: 2 rest-like frames\r\n    /// then 22\u201325\u00b0/frame for 3 more), the ramp frames are consumed too \u2014 until the motion\r\n    /// settles to ordinary deltas \u2014 bounded by <see cref=\"CalibrationMaxTrimFrames\"/> total.\r\n    /// See class remarks for the measured margins.\r\n    /// </summary>\r\n    private static int CalibrationSegmentLength(\r\n        XForm[][] native, int edge, int stop, int step, float typicalDeltaDeg)\r\n    {\r\n        int length = 0;\r\n        int f = edge;\r\n        while (f != stop && length <= CalibrationMaxSegmentFrames\r\n               && MaxRotDeltaDeg(native[f], null) <= CalibrationRestMaxDeg)\r\n        {\r\n            length++;\r\n            f += step;\r\n        }\r\n        if (length is 0 or > CalibrationMaxSegmentFrames)\r\n            return 0;\r\n\r\n        // f = first frame past the rest-like segment; require a real (non-rest-like) clip\r\n        // of \u2265 2 frames beyond it and a calibration-grade cut between segment and motion.\r\n        if (f == stop || MaxRotDeltaDeg(native[f], null) <= CalibrationRestMaxDeg)\r\n            return 0;\r\n        float jump = MaxRotDeltaDeg(native[f - step], native[f]);\r\n        if (jump < CalibrationJumpMinDeg || jump < CalibrationJumpTypicalRatio * typicalDeltaDeg)\r\n            return 0;\r\n\r\n        // Blend-ramp extension: consume frames still moving at calibration-ramp speed until\r\n        // the motion settles (leaving \u2265 2 frames past the trim).\r\n        float rampFloor = MathF.Max(\r\n            CalibrationRampMinDeg, CalibrationJumpTypicalRatio * typicalDeltaDeg);\r\n        while (length < CalibrationMaxTrimFrames\r\n               && f != stop && f + step != stop\r\n               && MaxRotDeltaDeg(native[f], native[f + step]) >= rampFloor)\r\n        {\r\n            length++;\r\n            f += step;\r\n        }\r\n        return length;\r\n    }\r\n\r\n    /// <summary>Max joint rotation angle (degrees) between two decoded frames, or \u2014 when\r\n    /// <paramref name=\"b\"/> is null \u2014 against the identity-rotation BVH bind rest.</summary>\r\n    private static float MaxRotDeltaDeg(XForm[] a, XForm[]? b)\r\n    {\r\n        float max = 0f;\r\n        for (int i = 0; i < a.Length; i++)\r\n        {\r\n            float angle = MathQ.AngleBetween(a[i].Rot, b is null ? Quaternion.Identity : b[i].Rot);\r\n            max = MathF.Max(max, angle);\r\n        }\r\n        return max * (180f / MathF.PI);\r\n    }\r\n\r\n    /// <summary>Median of the per-pair max joint rotation deltas over the clip's INTERIOR\r\n    /// consecutive frame pairs (both edge pairs excluded \u2014 they are the trim candidates).</summary>\r\n    private static float TypicalNeighborRotDeltaDeg(XForm[][] native)\r\n    {\r\n        int pairCount = native.Length - 3; // pairs (1,2) \u2026 (n-3, n-2)\r\n        if (pairCount <= 0)\r\n            return 0f;\r\n        var deltas = new float[pairCount];\r\n        for (int f = 0; f < pairCount; f++)\r\n            deltas[f] = MaxRotDeltaDeg(native[f + 1], native[f + 2]);\r\n        Array.Sort(deltas);\r\n        return deltas[pairCount / 2];\r\n    }\r\n\r\n    /// <summary>One joint's local transform from one motion row (see class remarks).</summary>\r\n    private static XForm EvaluateLocal(Joint joint, float[] row, float unitScale)\r\n    {\r\n        // Position channels replace the OFFSET; absent channels (or no position channels at\r\n        // all) fall back per Blender's semantics described in the class remarks.\r\n        Vector3 pos = joint.HasPos\r\n            ? new Vector3(\r\n                joint.PosX >= 0 ? row[joint.PosX] : 0f,\r\n                joint.PosY >= 0 ? row[joint.PosY] : 0f,\r\n                joint.PosZ >= 0 ? row[joint.PosZ] : 0f)\r\n            : joint.Offset;\r\n\r\n        // R = R_chan1 * R_chan2 * R_chan3 (column-vector convention; degrees in the file).\r\n        var rot = Quaternion.Identity;\r\n        foreach (var (axis, column) in joint.Rot)\r\n        {\r\n            float radians = row[column] * (MathF.PI / 180f);\r\n            var axisVector = axis switch\r\n            {\r\n                0 => Vector3.UnitX,\r\n                1 => Vector3.UnitY,\r\n                _ => Vector3.UnitZ,\r\n            };\r\n            rot *= Quaternion.CreateFromAxisAngle(axisVector, radians);\r\n        }\r\n\r\n        return new XForm(pos * unitScale, MathQ.Normalize(rot));\r\n    }\r\n\r\n    // =====================================================================================\r\n    // tokenizer\r\n    // =====================================================================================\r\n\r\n    /// <summary>Whitespace token stream over the BVH text (BVH is line-format agnostic).</summary>\r\n    private sealed class TokenCursor\r\n    {\r\n        private readonly string[] _tokens;\r\n        private int _pos;\r\n\r\n        public TokenCursor(string text)\r\n            => _tokens = text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);\r\n\r\n        public bool AtEnd => _pos >= _tokens.Length;\r\n\r\n        public bool PeekIs(string keywordUpper)\r\n            => _pos < _tokens.Length &&\r\n               string.Equals(_tokens[_pos], keywordUpper, StringComparison.OrdinalIgnoreCase);\r\n\r\n        public string Next()\r\n        {\r\n            if (AtEnd)\r\n                throw new FormatException(\"BVH: unexpected end of file.\");\r\n            return _tokens[_pos++];\r\n        }\r\n\r\n        public void ExpectKeyword(string keywordUpper)\r\n        {\r\n            string token = Next();\r\n            if (!string.Equals(token, keywordUpper, StringComparison.OrdinalIgnoreCase))\r\n                throw new FormatException($\"BVH: expected '{keywordUpper}', found '{token}'.\");\r\n        }\r\n\r\n        public int NextInt()\r\n        {\r\n            string token = Next();\r\n            if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out int value))\r\n                throw new FormatException($\"BVH: expected an integer, found '{token}'.\");\r\n            return value;\r\n        }\r\n\r\n        public float NextFloat()\r\n        {\r\n            string token = Next();\r\n            if (!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out float value) ||\r\n                !float.IsFinite(value))\r\n                throw new FormatException($\"BVH: expected a number, found '{token}'.\");\r\n            return value;\r\n        }\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "HumanoidRetargeter/Formats/Dmx/DmxWriter.cs",
            "FileName": "DmxWriter.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Globalization;\r\nusing System.Security.Cryptography;\r\nusing System.Text;\r\nusing HumanoidRetargeter.Skeleton;\r\nusing SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;\r\n\r\nnamespace HumanoidRetargeter.Formats.Dmx;\r\n\r\n/// <summary>Options for <see cref=\"DmxWriter.Write\"/>.</summary>\r\npublic sealed class DmxWriteOptions\r\n{\r\n    /// <summary>Model/clip name written into the DmeModel element (e.g. the sequence name).</summary>\r\n    public string Name { get; set; } = \"\";\r\n\r\n    /// <summary>Free-form provenance note written as the DmeDCCMakefile source name\r\n    /// (fbx2dmx writes the source .fbx path here).</summary>\r\n    public string SourceNote { get; set; } = \"\";\r\n\r\n    /// <summary>When true (default, matching fbx2dmx output) the file declares a Y-up axis\r\n    /// system; when false it declares Z-up. Data is written as-is either way.</summary>\r\n    public bool UpAxisY { get; set; } = true;\r\n\r\n    /// <summary>\r\n    /// Skeleton bone indices that get NO DmeChannel pair: the bones keep their DmeJoint and\r\n    /// bind (rest) transform, but no animation channels are written for them \u2014 the engine then\r\n    /// drives them itself (e.g. ConstraintDriven twist/helper bones, design \u00a73). Null (default)\r\n    /// writes channels for every bone.\r\n    /// </summary>\r\n    public IReadOnlySet<int>? ChannelExcludedBones { get; set; }\r\n}\r\n\r\n/// <summary>\r\n/// Writes an animation DMX in <c>keyvalues2_noids</c> text encoding, replicating the exact\r\n/// element/attribute shape of fbx2dmx output (authoritative reference:\r\n/// <c>dev/m0/ref_idlepose.dmx</c>): a root DmElement holding an inline DmeModel (joint GUID\r\n/// refs + bind base state), a top-level DmeAnimationList with one DmeChannelsClip carrying a\r\n/// position and an orientation channel per bone, and top-level DmeTransform/DmeJoint elements\r\n/// the channels and joint lists reference by GUID. Output is fully deterministic: GUIDs are\r\n/// MD5-derived from the options name and an element path, and export tags use fixed\r\n/// placeholder strings.\r\n/// </summary>\r\npublic static class DmxWriter\r\n{\r\n    private const string Header = \"<!-- dmx encoding keyvalues2_noids 4 format model 22 -->\";\r\n\r\n    /// <summary>\r\n    /// Serializes <paramref name=\"clip\"/> on <paramref name=\"skeleton\"/> to DMX text.\r\n    /// Frames must contain one local transform per bone in skeleton order.\r\n    /// </summary>\r\n    /// <exception cref=\"ArgumentException\">Thrown when the clip is empty or a frame's bone\r\n    /// count does not match the skeleton.</exception>\r\n    public static string Write(SkeletonModel skeleton, Clip clip, DmxWriteOptions options)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(skeleton);\r\n        ArgumentNullException.ThrowIfNull(clip);\r\n        ArgumentNullException.ThrowIfNull(options);\r\n\r\n        if (clip.FrameCount == 0)\r\n            throw new ArgumentException(\"Clip has no frames.\", nameof(clip));\r\n        for (var f = 0; f < clip.FrameCount; f++)\r\n        {\r\n            if (clip.Frames[f].Length != skeleton.Count)\r\n                throw new ArgumentException(\r\n                    $\"Frame {f} has {clip.Frames[f].Length} bone transforms, skeleton has {skeleton.Count}.\",\r\n                    nameof(clip));\r\n        }\r\n\r\n        var w = new Emitter();\r\n        var animListGuid = GuidString(options.Name, \"animationList\");\r\n        var jointGuids = new string[skeleton.Count];\r\n        var transformGuids = new string[skeleton.Count];\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n        {\r\n            jointGuids[i] = GuidString(options.Name, \"joint:\" + skeleton[i].Name);\r\n            transformGuids[i] = GuidString(options.Name, \"transform:\" + skeleton[i].Name);\r\n        }\r\n\r\n        w.Raw(Header);\r\n\r\n        // ---- root DmElement -------------------------------------------------\r\n        w.BeginTopLevel(\"DmElement\");\r\n        w.Attr(\"name\", \"string\", \"root\");\r\n\r\n        w.BeginInlineAttr(\"skeleton\", \"DmeModel\");\r\n        w.Attr(\"name\", \"string\", options.Name);\r\n        w.BeginInlineAttr(\"transform\", \"DmeTransform\");\r\n        w.Attr(\"position\", \"vector3\", \"0 0 0\");\r\n        w.Attr(\"orientation\", \"quaternion\", \"0 0 0 1\");\r\n        w.EndInlineAttr();\r\n        w.Attr(\"shape\", \"element\", \"\");\r\n        w.Attr(\"visible\", \"bool\", \"1\");\r\n\r\n        w.BeginArray(\"children\");\r\n        var roots = new List<int>();\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n        {\r\n            if (skeleton[i].ParentIndex < 0)\r\n                roots.Add(i);\r\n        }\r\n        for (var r = 0; r < roots.Count; r++)\r\n            w.ElementRef(jointGuids[roots[r]], last: r == roots.Count - 1);\r\n        w.EndArray();\r\n\r\n        w.BeginArray(\"jointList\");\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n            w.ElementRef(jointGuids[i], last: i == skeleton.Count - 1);\r\n        w.EndArray();\r\n\r\n        w.BeginArray(\"baseStates\");\r\n        w.BeginArrayElement(\"DmeTransformList\");\r\n        w.Attr(\"name\", \"string\", \"bind\");\r\n        w.BeginArray(\"transforms\");\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n        {\r\n            w.BeginArrayElement(\"DmeTransform\");\r\n            w.Attr(\"name\", \"string\", skeleton[i].Name);\r\n            w.Attr(\"position\", \"vector3\", Vec(skeleton[i].RestLocal));\r\n            w.Attr(\"orientation\", \"quaternion\", Quat(skeleton[i].RestLocal));\r\n            w.EndArrayElement(last: i == skeleton.Count - 1);\r\n        }\r\n        w.EndArray();\r\n        w.EndArrayElement(last: true);\r\n        w.EndArray();\r\n\r\n        w.Attr(\"upAxis\", \"string\", options.UpAxisY ? \"Y\" : \"Z\");\r\n        w.BeginInlineAttr(\"axisSystem\", \"DmeAxisSystem\");\r\n        w.Attr(\"upAxis\", \"int\", options.UpAxisY ? \"2\" : \"3\");\r\n        w.Attr(\"forwardParity\", \"int\", \"2\");\r\n        w.Attr(\"coordSys\", \"int\", \"0\");\r\n        w.EndInlineAttr();\r\n        w.Attr(\"animationList\", \"element\", animListGuid);\r\n        w.EndInlineAttr(); // skeleton DmeModel\r\n\r\n        w.BeginInlineAttr(\"makefile\", \"DmeDCCMakefile\");\r\n        w.Attr(\"name\", \"string\", \"makefile\");\r\n        w.BeginArray(\"sources\");\r\n        w.BeginArrayElement(\"DmeSource\");\r\n        w.Attr(\"name\", \"string\", options.SourceNote);\r\n        w.EndArrayElement(last: true);\r\n        w.EndArray();\r\n        w.EndInlineAttr();\r\n\r\n        // Deterministic placeholders \u2014 never wall-clock/user data, so output is reproducible.\r\n        w.BeginInlineAttr(\"exportTags\", \"DmeExportTags\");\r\n        w.Attr(\"name\", \"string\", \"exportTags\");\r\n        w.Attr(\"date\", \"string\", \"2026/01/01\");\r\n        w.Attr(\"time\", \"string\", \"12:00:00 am\");\r\n        w.Attr(\"user\", \"string\", \"retargeter\");\r\n        w.Attr(\"machine\", \"string\", \"retargeter\");\r\n        w.Attr(\"app\", \"string\", \"humanoid-retargeter\");\r\n        w.Attr(\"appVersion\", \"string\", \"1.0\");\r\n        w.Attr(\"cmdLine\", \"string\", \"humanoid-retargeter\");\r\n        w.Attr(\"pwd\", \"string\", \"\");\r\n        w.EndInlineAttr();\r\n\r\n        w.Attr(\"animationList\", \"element\", animListGuid);\r\n        w.EndTopLevel();\r\n\r\n        // ---- DmeAnimationList ----------------------------------------------\r\n        w.BeginTopLevel(\"DmeAnimationList\");\r\n        w.Attr(\"id\", \"elementid\", animListGuid);\r\n        w.Attr(\"name\", \"string\", \"anim\");\r\n        w.BeginArray(\"animations\");\r\n        w.BeginArrayElement(\"DmeChannelsClip\");\r\n        w.Attr(\"name\", \"string\", \"anim\");\r\n\r\n        w.BeginInlineAttr(\"timeFrame\", \"DmeTimeFrame\");\r\n        w.Attr(\"start\", \"time\", Time(0.0));\r\n        w.Attr(\"duration\", \"time\", Time((clip.FrameCount - 1) / (double)clip.Fps));\r\n        w.Attr(\"offset\", \"time\", Time(0.0));\r\n        w.Attr(\"scale\", \"float\", \"1\");\r\n        w.EndInlineAttr();\r\n\r\n        w.Attr(\"color\", \"color\", \"0 0 0 0\");\r\n        w.Attr(\"text\", \"string\", \"\");\r\n        w.Attr(\"mute\", \"bool\", \"0\");\r\n        w.BeginArray(\"trackGroups\");\r\n        w.EndArray();\r\n        w.Attr(\"displayScale\", \"float\", \"1\");\r\n\r\n        var channelBones = new List<int>(skeleton.Count);\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n        {\r\n            if (options.ChannelExcludedBones is null || !options.ChannelExcludedBones.Contains(i))\r\n                channelBones.Add(i);\r\n        }\r\n\r\n        w.BeginArray(\"channels\");\r\n        for (var n = 0; n < channelBones.Count; n++)\r\n        {\r\n            var i = channelBones[n];\r\n            WriteChannel(w, skeleton, clip, i, transformGuids[i], position: true, last: false);\r\n            WriteChannel(w, skeleton, clip, i, transformGuids[i], position: false,\r\n                last: n == channelBones.Count - 1);\r\n        }\r\n        w.EndArray();\r\n\r\n        w.Attr(\"frameRate\", \"int\",\r\n            ((int)MathF.Round(clip.Fps)).ToString(CultureInfo.InvariantCulture));\r\n        w.EndArrayElement(last: true);\r\n        w.EndArray();\r\n        w.EndTopLevel();\r\n\r\n        // ---- top-level channel-target DmeTransforms (rest values) -----------\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n        {\r\n            w.BeginTopLevel(\"DmeTransform\");\r\n            w.Attr(\"id\", \"elementid\", transformGuids[i]);\r\n            w.Attr(\"name\", \"string\", skeleton[i].Name);\r\n            w.Attr(\"position\", \"vector3\", Vec(skeleton[i].RestLocal));\r\n            w.Attr(\"orientation\", \"quaternion\", Quat(skeleton[i].RestLocal));\r\n            w.EndTopLevel();\r\n        }\r\n\r\n        // ---- top-level DmeJoints --------------------------------------------\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n        {\r\n            w.BeginTopLevel(\"DmeJoint\");\r\n            w.Attr(\"id\", \"elementid\", jointGuids[i]);\r\n            w.Attr(\"name\", \"string\", skeleton[i].Name);\r\n            w.Attr(\"transform\", \"element\", transformGuids[i]);\r\n            w.Attr(\"shape\", \"element\", \"\");\r\n            w.Attr(\"visible\", \"bool\", \"1\");\r\n            w.BeginArray(\"children\");\r\n            var children = new List<int>();\r\n            for (var c = 0; c < skeleton.Count; c++)\r\n            {\r\n                if (skeleton[c].ParentIndex == i)\r\n                    children.Add(c);\r\n            }\r\n            for (var c = 0; c < children.Count; c++)\r\n                w.ElementRef(jointGuids[children[c]], last: c == children.Count - 1);\r\n            w.EndArray();\r\n            w.EndTopLevel();\r\n        }\r\n\r\n        return w.ToString();\r\n    }\r\n\r\n    /// <summary>\r\n    /// Deterministic element GUID: MD5 over <c>\"&lt;name&gt;\\n&lt;path&gt;\"</c> (UTF-8)\r\n    /// interpreted as <see cref=\"Guid\"/> bytes. Exposed so tests can verify the scheme.\r\n    /// </summary>\r\n    public static Guid ElementGuid(string name, string path)\r\n        => new(MD5.HashData(Encoding.UTF8.GetBytes(name + \"\\n\" + path)));\r\n\r\n    private static string GuidString(string name, string path)\r\n        => ElementGuid(name, path).ToString(\"D\", CultureInfo.InvariantCulture);\r\n\r\n    // ---------------------------------------------------------------- channels\r\n\r\n    private static void WriteChannel(Emitter w, SkeletonModel skeleton, Clip clip, int bone,\r\n        string transformGuid, bool position, bool last)\r\n    {\r\n        var logClass = position ? \"DmeVector3Log\" : \"DmeQuaternionLog\";\r\n        var layerClass = position ? \"DmeVector3LogLayer\" : \"DmeQuaternionLogLayer\";\r\n        var logName = position ? \"vector3 log\" : \"quaternion log\";\r\n\r\n        w.BeginArrayElement(\"DmeChannel\");\r\n        w.Attr(\"name\", \"string\", skeleton[bone].Name + (position ? \"_p\" : \"_o\"));\r\n        w.Attr(\"fromElement\", \"element\", \"\");\r\n        w.Attr(\"fromAttribute\", \"string\", \"\");\r\n        w.Attr(\"fromIndex\", \"int\", \"0\");\r\n        w.Attr(\"toElement\", \"element\", transformGuid);\r\n        w.Attr(\"toAttribute\", \"string\", position ? \"position\" : \"orientation\");\r\n        w.Attr(\"toIndex\", \"int\", \"0\");\r\n        w.Attr(\"mode\", \"int\", \"3\");\r\n\r\n        w.BeginInlineAttr(\"log\", logClass);\r\n        w.Attr(\"name\", \"string\", logName);\r\n        w.BeginArray(\"layers\");\r\n        w.BeginArrayElement(layerClass);\r\n        w.Attr(\"name\", \"string\", logName);\r\n\r\n        w.BeginArray(\"times\", \"time_array\");\r\n        for (var f = 0; f < clip.FrameCount; f++)\r\n            w.ArrayValue(Time(f / (double)clip.Fps), last: f == clip.FrameCount - 1);\r\n        w.EndArray();\r\n\r\n        w.BeginArray(\"curvetypes\", \"int_array\");\r\n        w.EndArray();\r\n\r\n        w.BeginArray(\"values\", position ? \"vector3_array\" : \"quaternion_array\");\r\n        // Orientation values are hemisphere-aligned on the fly (q and -q are the same\r\n        // rotation, but the engine interpolates between DMX samples numerically \u2014 see\r\n        // QuaternionContinuity). The clip itself is never mutated.\r\n        var prev = System.Numerics.Quaternion.Identity;\r\n        for (var f = 0; f < clip.FrameCount; f++)\r\n        {\r\n            var x = clip.Frames[f][bone];\r\n            string value;\r\n            if (position)\r\n            {\r\n                value = Vec(x);\r\n            }\r\n            else\r\n            {\r\n                var q = x.Rot;\r\n                if (f > 0 && System.Numerics.Quaternion.Dot(prev, q) < 0f)\r\n                    q = System.Numerics.Quaternion.Negate(q);\r\n                prev = q;\r\n                value = Quat(q);\r\n            }\r\n            w.ArrayValue(value, last: f == clip.FrameCount - 1);\r\n        }\r\n        w.EndArray();\r\n\r\n        w.EmptyBinaryAttr(\"compressed\");\r\n        w.EndArrayElement(last: true);\r\n        w.EndArray(); // layers\r\n\r\n        w.Attr(\"curveinfo\", \"element\", \"\");\r\n        w.Attr(\"usedefaultvalue\", \"bool\", \"0\");\r\n        w.Attr(\"defaultvalue\", position ? \"vector3\" : \"quaternion\", position ? \"0 0 0\" : \"0 0 0 1\");\r\n        w.BeginArray(\"bookmarksX\", \"time_array\");\r\n        w.EndArray();\r\n        w.BeginArray(\"bookmarksY\", \"time_array\");\r\n        w.EndArray();\r\n        w.BeginArray(\"bookmarksZ\", \"time_array\");\r\n        w.EndArray();\r\n        w.EndInlineAttr(); // log\r\n\r\n        w.EndArrayElement(last);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- formatting\r\n\r\n    /// <summary>fbx2dmx float style: up to 10 decimal places, trailing zeros stripped,\r\n    /// invariant culture, negative zero normalized.</summary>\r\n    private static string F(float value)\r\n    {\r\n        if (value == 0f)\r\n            return \"0\";\r\n        return ((double)value).ToString(\"0.##########\", CultureInfo.InvariantCulture);\r\n    }\r\n\r\n    private static string Time(double seconds)\r\n        => seconds.ToString(\"0.0000\", CultureInfo.InvariantCulture);\r\n\r\n    private static string Vec(in Maths.XForm x)\r\n        => $\"{F(x.Pos.X)} {F(x.Pos.Y)} {F(x.Pos.Z)}\";\r\n\r\n    private static string Quat(in Maths.XForm x) => Quat(x.Rot);\r\n\r\n    private static string Quat(in System.Numerics.Quaternion q)\r\n        => $\"{F(q.X)} {F(q.Y)} {F(q.Z)} {F(q.W)}\";\r\n\r\n    // ---------------------------------------------------------------- emitter\r\n\r\n    /// <summary>\r\n    /// Low-level keyvalues2 text emitter reproducing fbx2dmx layout quirks: CRLF endings,\r\n    /// tab indentation, a trailing space after array-typed attribute names, and an\r\n    /// indentation-only line after every inline element attribute closes.\r\n    /// </summary>\r\n    private sealed class Emitter\r\n    {\r\n        private readonly StringBuilder _sb = new();\r\n        private int _indent;\r\n\r\n        public void Raw(string text)\r\n        {\r\n            _sb.Append(text).Append(\"\\r\\n\");\r\n        }\r\n\r\n        private void Line(string text)\r\n        {\r\n            _sb.Append('\\t', _indent).Append(text).Append(\"\\r\\n\");\r\n        }\r\n\r\n        public void Attr(string name, string type, string value)\r\n            => Line($\"\\\"{name}\\\" \\\"{type}\\\" \\\"{value}\\\"\");\r\n\r\n        public void BeginTopLevel(string className)\r\n        {\r\n            Line($\"\\\"{className}\\\"\");\r\n            Line(\"{\");\r\n            _indent++;\r\n        }\r\n\r\n        public void EndTopLevel()\r\n        {\r\n            _indent--;\r\n            Line(\"}\");\r\n            _sb.Append(\"\\r\\n\"); // blank separator after every top-level element (incl. the last)\r\n        }\r\n\r\n        public void BeginInlineAttr(string name, string className)\r\n        {\r\n            Line($\"\\\"{name}\\\" \\\"{className}\\\"\");\r\n            Line(\"{\");\r\n            _indent++;\r\n        }\r\n\r\n        public void EndInlineAttr()\r\n        {\r\n            _indent--;\r\n            Line(\"}\");\r\n            Line(\"\"); // indentation-only line, as fbx2dmx emits\r\n        }\r\n\r\n        public void BeginArrayElement(string className)\r\n        {\r\n            Line($\"\\\"{className}\\\"\");\r\n            Line(\"{\");\r\n            _indent++;\r\n        }\r\n\r\n        public void EndArrayElement(bool last)\r\n        {\r\n            _indent--;\r\n            Line(last ? \"}\" : \"},\");\r\n        }\r\n\r\n        public void BeginArray(string name, string type = \"element_array\")\r\n        {\r\n            Line($\"\\\"{name}\\\" \\\"{type}\\\" \");\r\n            Line(\"[\");\r\n            _indent++;\r\n        }\r\n\r\n        public void EndArray()\r\n        {\r\n            _indent--;\r\n            Line(\"]\");\r\n        }\r\n\r\n        public void ElementRef(string guid, bool last)\r\n            => Line($\"\\\"element\\\" \\\"{guid}\\\"\" + (last ? \"\" : \",\"));\r\n\r\n        public void ArrayValue(string value, bool last)\r\n            => Line($\"\\\"{value}\\\"\" + (last ? \"\" : \",\"));\r\n\r\n        public void EmptyBinaryAttr(string name)\r\n        {\r\n            Line($\"\\\"{name}\\\" \\\"binary\\\" \");\r\n            Line(\"\\\"\");\r\n            Line(\"\\\"\");\r\n        }\r\n\r\n        public override string ToString() => _sb.ToString();\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "HumanoidRetargeter/Formats/Fbx/FbxBindPoseFixer.cs",
            "FileName": "FbxBindPoseFixer.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidRetargeter.Maths;\r\n\r\nnamespace HumanoidRetargeter.Formats.Fbx;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>\r\n/// Repairs FBX files that were exported MID-POSE: their node transforms (Lcl\r\n/// Translation/Rotation) hold an animation snapshot while the true skeleton bind lives in\r\n/// the file's Pose/BindPose section. Engines that build the skeleton from node transforms\r\n/// (s&amp;box does; the FBX SDK's own samples do) then import a posed \"bind\" \u2014 the skin\r\n/// stays self-consistent so the model LOOKS fine at rest, but every anatomical assumption\r\n/// about the skeleton (leg chains point down, hands mirror) is silently wrong and\r\n/// retargeted motion comes out mangled on exactly the posed bones. Found in the wild on\r\n/// Auto-Rig Pro exports whose IK'd hands/feet were left posed (one leg at hip height).\r\n/// </summary>\r\npublic static class FbxBindPoseFixer\r\n{\r\n    /// <summary>Bones whose node transform is further than this (native units, as a\r\n    /// fraction of skeleton height) from their BindPose matrix count as posed.</summary>\r\n    private const float PositionToleranceOfHeight = 0.01f;\r\n\r\n    /// <summary>Rotation disagreement (degrees) that counts as posed.</summary>\r\n    private const float RotationToleranceDeg = 2.0f;\r\n\r\n    /// <summary>\r\n    /// Detects the mid-pose condition and rewrites node transforms to the BindPose. Returns\r\n    /// null when the file needs no repair (no BindPose section, or node transforms already\r\n    /// agree with it); otherwise the repaired file bytes.\r\n    /// <paramref name=\"report\"/> always describes what was found.\r\n    /// </summary>\r\n    public static byte[]? TryFix(byte[] fbx, out string report)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(fbx);\r\n\r\n        FbxNode root;\r\n        FbxScene scene;\r\n        try\r\n        {\r\n            root = FbxTokenizer.Parse(fbx);\r\n            scene = FbxScene.Build(root);\r\n        }\r\n        catch (FormatException e)\r\n        {\r\n            report = $\"not parseable ({e.Message})\";\r\n            return null;\r\n        }\r\n\r\n        if (scene.BindPose.Count == 0)\r\n        {\r\n            report = \"no BindPose section\";\r\n            return null;\r\n        }\r\n\r\n        // Evaluate the ORIGINAL node-transform FK, roots first.\r\n        var originalWorld = new Dictionary<long, Matrix4x4>();\r\n        var order = new List<FbxObject>();\r\n        foreach (var model in scene.Models)\r\n            VisitModel(model, scene, originalWorld, order);\r\n\r\n        // Skeleton height (native units) for the position tolerance.\r\n        float minUp = float.MaxValue, maxUp = float.MinValue;\r\n        foreach (var w in originalWorld.Values)\r\n        {\r\n            var t = w.Translation;\r\n            float up = MathF.Max(MathF.Abs(t.Y), MathF.Abs(t.Z));\r\n            minUp = MathF.Min(minUp, up);\r\n            maxUp = MathF.Max(maxUp, up);\r\n        }\r\n        float posTolerance = MathF.Max(0.0001f, (maxUp - minUp) * PositionToleranceOfHeight);\r\n\r\n        // Any bone posed away from its bind?\r\n        int posedCount = 0;\r\n        foreach (var model in order)\r\n        {\r\n            if (!scene.BindPose.TryGetValue(model.Id, out var bind))\r\n                continue;\r\n            var fk = FbxTransform.ToRigid(originalWorld[model.Id]);\r\n            var target = FbxTransform.ToRigid(bind);\r\n            if ((fk.Pos - target.Pos).Length() > posTolerance\r\n                || MathQ.AngleBetween(fk.Rot, target.Rot) > RotationToleranceDeg * MathF.PI / 180f)\r\n            {\r\n                posedCount++;\r\n            }\r\n        }\r\n        if (posedCount == 0)\r\n        {\r\n            report = $\"node transforms match the BindPose ({scene.BindPose.Count} entries)\";\r\n            return null;\r\n        }\r\n\r\n        // Rewrite every BindPose-backed model's local transform so FK lands on the bind.\r\n        // correctedWorld carries the repair down the hierarchy for bones WITHOUT a\r\n        // BindPose entry (helpers keep their original locals under corrected parents).\r\n        var correctedWorld = new Dictionary<long, Matrix4x4>();\r\n        int patched = 0, skipped = 0;\r\n        foreach (var model in order)\r\n        {\r\n            var parent = model.ModelParent;\r\n            Matrix4x4 parentWorld = parent is not null && correctedWorld.TryGetValue(parent.Id, out var pw)\r\n                ? pw\r\n                : Matrix4x4.Identity;\r\n\r\n            if (!scene.BindPose.TryGetValue(model.Id, out var bindWorld))\r\n            {\r\n                // No bind info: keep the original local under the (possibly corrected) parent.\r\n                var transform = FbxTransform.FromModel(scene, model);\r\n                correctedWorld[model.Id] = transform.LocalMatrixDefault() * parentWorld;\r\n                continue;\r\n            }\r\n\r\n            if (!Matrix4x4.Invert(parentWorld, out var invParent))\r\n            {\r\n                correctedWorld[model.Id] = bindWorld;\r\n                skipped++;\r\n                continue;\r\n            }\r\n\r\n            // Row-vector: World = Local \u00b7 ParentWorld  \u21d2  Local = World \u00b7 ParentWorld\u207b\u00b9.\r\n            var desiredLocal = FbxTransform.ToRigid(bindWorld * invParent);\r\n            if (PatchModelLocal(scene, model, desiredLocal))\r\n                patched++;\r\n            else\r\n                skipped++;\r\n\r\n            // Children FK from the ACTUAL bind either way (unpatchable bones are rare and\r\n            // their children still deserve correct parent frames).\r\n            correctedWorld[model.Id] = bindWorld;\r\n        }\r\n\r\n        report = $\"{posedCount} bones were exported mid-pose; repaired {patched}\"\r\n            + (skipped > 0 ? $\", {skipped} left as-is (pivots/scale beyond the safe rewrite)\" : \"\");\r\n        if (patched == 0)\r\n            return null;\r\n        return FbxBinaryWriter.Write(root);\r\n    }\r\n\r\n    private static void VisitModel(\r\n        FbxObject model, FbxScene scene,\r\n        Dictionary<long, Matrix4x4> world, List<FbxObject> order)\r\n    {\r\n        if (world.ContainsKey(model.Id))\r\n            return;\r\n        Matrix4x4 parentWorld = Matrix4x4.Identity;\r\n        if (model.ModelParent is { } parent)\r\n        {\r\n            VisitModel(parent, scene, world, order);\r\n            parentWorld = world[parent.Id];\r\n        }\r\n        var transform = FbxTransform.FromModel(scene, model);\r\n        world[model.Id] = transform.LocalMatrixDefault() * parentWorld;\r\n        order.Add(model);\r\n    }\r\n\r\n    // ------------------------------------------------------------------ patching\r\n\r\n    /// <summary>\r\n    /// Rewrites one Model's Lcl Translation/Rotation so its local evaluates to\r\n    /// <paramref name=\"desiredLocal\"/>. Verified by re-evaluating through the full FBX\r\n    /// transform formula \u2014 models using pivots/offsets/scale that the rewrite cannot\r\n    /// express are left untouched (returns false).\r\n    /// </summary>\r\n    private static bool PatchModelLocal(FbxScene scene, FbxObject model, XForm desiredLocal)\r\n    {\r\n        var transform = FbxTransform.FromModel(scene, model);\r\n\r\n        // R_total = Pre \u00b7 R \u00b7 Post\u207b\u00b9  \u21d2  R = Pre\u207b\u00b9 \u00b7 R_total \u00b7 Post\r\n        var r = MathQ.Normalize(\r\n            Quaternion.Conjugate(transform.PreRotation)\r\n            * desiredLocal.Rot\r\n            * transform.PostRotation);\r\n\r\n        var eulerDeg = QuaternionToEulerDegrees(r, transform.RotationOrder);\r\n\r\n        // Full-formula verification (catches pivots, scale, decomposition branches).\r\n        var check = new FbxTransform\r\n        {\r\n            LclTranslation = desiredLocal.Pos,\r\n            LclRotationDeg = eulerDeg,\r\n            LclScaling = transform.LclScaling,\r\n            PreRotation = transform.PreRotation,\r\n            PostRotation = transform.PostRotation,\r\n            RotationOffset = transform.RotationOffset,\r\n            RotationPivot = transform.RotationPivot,\r\n            ScalingOffset = transform.ScalingOffset,\r\n            ScalingPivot = transform.ScalingPivot,\r\n            RotationOrder = transform.RotationOrder,\r\n        };\r\n        var evaluated = FbxTransform.ToRigid(check.LocalMatrixDefault());\r\n        float posScale = MathF.Max(1f, desiredLocal.Pos.Length());\r\n        if ((evaluated.Pos - desiredLocal.Pos).Length() > 0.001f * posScale\r\n            || MathQ.AngleBetween(evaluated.Rot, desiredLocal.Rot) > 0.1f * MathF.PI / 180f)\r\n        {\r\n            return false;\r\n        }\r\n\r\n        SetProperty70(model.Node, \"Lcl Translation\", \"Lcl Translation\", \"A\",\r\n            desiredLocal.Pos.X, desiredLocal.Pos.Y, desiredLocal.Pos.Z);\r\n        SetProperty70(model.Node, \"Lcl Rotation\", \"Lcl Rotation\", \"A\",\r\n            eulerDeg.X, eulerDeg.Y, eulerDeg.Z);\r\n        return true;\r\n    }\r\n\r\n    /// <summary>Sets (or adds) a 3-double P entry in the node's Properties70 block.</summary>\r\n    private static void SetProperty70(\r\n        FbxNode modelNode, string name, string type, string flags,\r\n        double x, double y, double z)\r\n    {\r\n        var block = modelNode.Child(\"Properties70\");\r\n        if (block is null)\r\n        {\r\n            block = new FbxNode(\"Properties70\");\r\n            modelNode.Children.Insert(0, block);\r\n        }\r\n\r\n        foreach (var p in block.ChildrenNamed(\"P\"))\r\n        {\r\n            if (p.Properties.Count >= 1 && p.Properties[0] is string n && n == name)\r\n            {\r\n                // Values live at indices 4.. \u2014 replace, extending if the entry was short.\r\n                while (p.Properties.Count < 7)\r\n                    p.Properties.Add(0.0);\r\n                p.Properties[4] = x;\r\n                p.Properties[5] = y;\r\n                p.Properties[6] = z;\r\n                return;\r\n            }\r\n        }\r\n\r\n        var entry = new FbxNode(\"P\");\r\n        entry.Properties.Add(name);\r\n        entry.Properties.Add(type);\r\n        entry.Properties.Add(\"\");\r\n        entry.Properties.Add(flags);\r\n        entry.Properties.Add(x);\r\n        entry.Properties.Add(y);\r\n        entry.Properties.Add(z);\r\n        block.Children.Add(entry);\r\n    }\r\n\r\n    // ------------------------------------------------------------------ euler decomposition\r\n\r\n    /// <summary>\r\n    /// Decomposes a quaternion into FBX euler degrees for the given RotationOrder, the\r\n    /// exact inverse of <see cref=\"FbxTransform.EulerDegreesToQuaternion\"/>. Tait-Bryan\r\n    /// extraction on the column-convention rotation matrix.\r\n    /// </summary>\r\n    public static Vector3 QuaternionToEulerDegrees(Quaternion q, int order)\r\n    {\r\n        // Column-convention matrix C (v' = C\u00b7v): C = transpose of System.Numerics' row form.\r\n        var m = Matrix4x4.CreateFromQuaternion(q);\r\n        // C[r,c]: row r, column c.\r\n        float c00 = m.M11, c01 = m.M21, c02 = m.M31;\r\n        float c10 = m.M12, c11 = m.M22, c12 = m.M32;\r\n        float c20 = m.M13, c21 = m.M23, c22 = m.M33;\r\n\r\n        const float radToDeg = 180f / MathF.PI;\r\n        float a, b, c;\r\n        switch (order)\r\n        {\r\n            case 0: // XYZ: C = Rz\u00b7Ry\u00b7Rx\r\n                b = MathF.Asin(Math.Clamp(-c20, -1f, 1f));\r\n                a = MathF.Atan2(c21, c22);\r\n                c = MathF.Atan2(c10, c00);\r\n                return new Vector3(a * radToDeg, b * radToDeg, c * radToDeg);\r\n            case 1: // XZY: C = Ry\u00b7Rz\u00b7Rx\r\n                b = MathF.Asin(Math.Clamp(c10, -1f, 1f));\r\n                a = MathF.Atan2(-c12, c11);\r\n                c = MathF.Atan2(-c20, c00);\r\n                return new Vector3(a * radToDeg, c * radToDeg, b * radToDeg);\r\n            case 2: // YZX: C = Rx\u00b7Rz\u00b7Ry\r\n                b = MathF.Asin(Math.Clamp(-c01, -1f, 1f));\r\n                a = MathF.Atan2(c02, c00);\r\n                c = MathF.Atan2(c21, c11);\r\n                return new Vector3(c * radToDeg, a * radToDeg, b * radToDeg);\r\n            case 3: // YXZ: C = Rz\u00b7Rx\u00b7Ry\r\n                b = MathF.Asin(Math.Clamp(c21, -1f, 1f));\r\n                a = MathF.Atan2(-c20, c22);\r\n                c = MathF.Atan2(-c01, c11);\r\n                return new Vector3(b * radToDeg, a * radToDeg, c * radToDeg);\r\n            case 4: // ZXY: C = Ry\u00b7Rx\u00b7Rz\r\n                b = MathF.Asin(Math.Clamp(-c12, -1f, 1f));\r\n                a = MathF.Atan2(c02, c22);\r\n                c = MathF.Atan2(c10, c11);\r\n                return new Vector3(b * radToDeg, a * radToDeg, c * radToDeg);\r\n            case 5: // ZYX: C = Rx\u00b7Ry\u00b7Rz\r\n            case 6: // eSphericXYZ treated as XYZ on read; mirror that here\r\n            default:\r\n                if (order == 5)\r\n                {\r\n                    b = MathF.Asin(Math.Clamp(c02, -1f, 1f));\r\n                    a = MathF.Atan2(-c01, c00);\r\n                    c = MathF.Atan2(-c12, c22);\r\n                    return new Vector3(c * radToDeg, b * radToDeg, a * radToDeg);\r\n                }\r\n                goto case 0;\r\n        }\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "HumanoidRetargeter/RetargetRequest.cs",
            "FileName": "RetargetRequest.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing HumanoidRetargeter.Cleanup;\r\nusing HumanoidRetargeter.Formats;\r\nusing HumanoidRetargeter.Mapping;\r\nusing HumanoidRetargeter.Solve;\r\nusing HumanoidRetargeter.Target;\r\n\r\nnamespace HumanoidRetargeter;\r\n\r\n/// <summary>Which solver retargets a request's clips (design \u00a710).</summary>\r\npublic enum SolverKind\r\n{\r\n    /// <summary>The deterministic <see cref=\"Solve.GeometricSolver\"/> (default; better\r\n    /// wherever a role mapping exists).</summary>\r\n    Geometric,\r\n\r\n    /// <summary>The experimental skeleton-agnostic deep-learning solver\r\n    /// (<see cref=\"Dl.DlSolver\"/>, SAME pretrained checkpoint) \u2014 the no-profile fallback.\r\n    /// Requires <see cref=\"RetargetTargetSpec.DlWeights\"/>; ignores per-role mapping\r\n    /// (only hips/alignment heuristics consult it) and leaves fingers at rest.</summary>\r\n    DeepLearning,\r\n}\r\n\r\n/// <summary>\r\n/// One source animation file to retarget (engine-agnostic: bytes in, no file IO). Every\r\n/// request runs its OWN profile detection, so a single batch may mix Mixamo + ActorCore +\r\n/// BVH sources \u2014 unless <see cref=\"MappingOverride\"/> supplies a mapping explicitly.\r\n/// </summary>\r\npublic sealed class RetargetRequest\r\n{\r\n    /// <summary>Solver choice for this request's clips. <see cref=\"SolverKind.DeepLearning\"/>\r\n    /// requires the batch's <see cref=\"RetargetTargetSpec.DlWeights\"/> to be set; the\r\n    /// conversion fails per-clip with a clear error otherwise.</summary>\r\n    public SolverKind Solver { get; init; } = SolverKind.Geometric;\r\n\r\n    /// <summary>Raw bytes of the source file (.fbx, .bvh, .glb, .gltf, .vrm, .anm or .an5).</summary>\r\n    public required byte[] SourceData { get; init; }\r\n\r\n    /// <summary>\r\n    /// Source file name (used for the report and DMX provenance). The extension drives the\r\n    /// format choice (<c>.fbx</c> / <c>.bvh</c> / <c>.glb</c> / <c>.gltf</c> / <c>.vrm</c> \u2014\r\n    /// a VRM is a glTF container whose authored humanoid bone map becomes the mapping \u2014 /\r\n    /// <c>.anm</c> / <c>.an5</c> RenderWare animations, which additionally need\r\n    /// <see cref=\"SkeletonData\"/>); when the extension is unknown the content is sniffed\r\n    /// (FBX binary magic / \"FBXHeaderExtension\" / BVH \"HIERARCHY\" / GLB 'glTF' magic /\r\n    /// glTF JSON / RenderWare 0x1B animation chunk).\r\n    /// </summary>\r\n    public required string SourceFileName { get; init; }\r\n\r\n    /// <summary>\r\n    /// Raw bytes of a companion SKELETON file for formats whose animation files carry no\r\n    /// skeleton of their own: RenderWare <c>.anm</c>/<c>.an5</c> sources require the\r\n    /// character model's <c>.dff</c> here (callers resolve the file \u2014 e.g. a .dff sitting\r\n    /// next to the animation; the facade does no file IO). Ignored by self-contained\r\n    /// formats. A RenderWare request without it fails with an instructive error.\r\n    /// </summary>\r\n    public byte[]? SkeletonData { get; init; }\r\n\r\n    /// <summary>\r\n    /// Caller-supplied identity of this request, echoed verbatim on every produced\r\n    /// <see cref=\"ClipResult.SourceId\"/> so callers can join results back to their own\r\n    /// entries unambiguously (e.g. the editor window passes the FULL file path here, since\r\n    /// two files in different folders may share the same <see cref=\"SourceFileName\"/>).\r\n    /// Null = <see cref=\"SourceFileName\"/>.\r\n    /// </summary>\r\n    public string? SourceId { get; init; }\r\n\r\n    /// <summary>\r\n    /// Import sample rate the source clips are resampled to (BVH native frames / FBX curves\r\n    /// are evaluated on this grid). Null = the importer default (30 fps).\r\n    /// </summary>\r\n    public float? SampleFps { get; init; }\r\n\r\n    /// <summary>\r\n    /// Restricts the conversion to ONE take of the source file (0-based index into the\r\n    /// imported scene's clips). Null = convert all takes. Out of range fails the request's\r\n    /// clip result with a clear error (the batch continues). UI listings that expand a\r\n    /// multi-take file into one entry per take submit one request per selected take.\r\n    /// When <see cref=\"ClipDefinitions\"/> is set this index addresses the DEFINITIONS\r\n    /// instead (each definition is what a UI row represents then).\r\n    /// </summary>\r\n    public int? TakeIndex { get; init; }\r\n\r\n    /// <summary>\r\n    /// Optional external clip definitions, parsed from a Unity <c>&lt;file&gt;.fbx.meta</c>\r\n    /// sidecar (<see cref=\"UnityMeta.ParseClipAnimations\"/>): Unity animation packs ship FBX\r\n    /// files whose clips are sub-ranges of ONE source timeline. When set (non-empty), the\r\n    /// conversion produces one output clip per definition instead of one per take: the\r\n    /// definition's take (matched by <see cref=\"ExternalClipDef.TakeName\"/>, falling back to\r\n    /// the file's first take) is sliced to the definition's native-frame range\r\n    /// (<see cref=\"UnityMeta.Slice\"/>), named <see cref=\"ExternalClipDef.Name\"/> (sanitized\r\n    /// like take names, collision-suffixed across the batch) and looped per\r\n    /// <see cref=\"ExternalClipDef.Loop\"/> unless <see cref=\"LoopingOverride\"/> is set.\r\n    /// <see cref=\"TakeIndex\"/> then indexes INTO this list. Null = no definitions.\r\n    /// </summary>\r\n    public IReadOnlyList<ExternalClipDef>? ClipDefinitions { get; init; }\r\n\r\n    /// <summary>\r\n    /// UI-supplied mapping (manual mapping table or a user preset loaded Editor-side).\r\n    /// Null = auto-detect per request: preset profiles via <see cref=\"ProfileDetector\"/>,\r\n    /// then the <see cref=\"AutoMapper\"/> as best-effort fallback.\r\n    /// </summary>\r\n    public MappingResult? MappingOverride { get; init; }\r\n\r\n    /// <summary>Solver tunables (hip scales, finger transfer). ClipIndex/ClipName are managed\r\n    /// by the pipeline per take and ignored here. Null = defaults.</summary>\r\n    public SolveOptions? Solve { get; init; }\r\n\r\n    /// <summary>\r\n    /// Root-motion handling. <see cref=\"RootMotionMode.Extract\"/> on a target without a\r\n    /// dedicated animated root bone (the s&amp;box rig: pelvis is parentless, root_IK is\r\n    /// IkBaked) leaves the frames untouched and instead sets the ExtractMotion flag on the\r\n    /// clip's vmdl AnimFile entry \u2014 Source 2's compile-time extraction replaces the missing\r\n    /// bone-level extraction. <see cref=\"RootMotionMode.InPlace\"/> always operates on the\r\n    /// hips directly.\r\n    /// </summary>\r\n    public RootMotionMode RootMotion { get; init; } = RootMotionMode.Off;\r\n\r\n    /// <summary>Run the Kovar foot-plant cleanup pass on the solved frames (default on).</summary>\r\n    public bool FootPlantCleanup { get; init; } = true;\r\n\r\n    /// <summary>\r\n    /// Copy the source clip's per-frame LOCAL translations onto same-named target bones\r\n    /// (hips and its ancestors excluded \u2014 trajectory stays solver-owned). For SAME-RIG\r\n    /// conversions of authored takes (a target FBX's own embedded animations): the solver\r\n    /// pins every non-hips bone to its rest translation, silently dropping a Biped take's\r\n    /// animated spine/thigh translations (~19cm of authored body sway on a death fall).\r\n    /// Meaningless across different rigs \u2014 leave off (default) for real retargets.\r\n    /// </summary>\r\n    public bool PreserveSourceTranslations { get; init; }\r\n\r\n    /// <summary>\r\n    /// Optional arm end-effector IK pass pulling the wrists onto limb-length-normalized\r\n    /// source hand positions. Default OFF: the geometric solver already matches anatomical\r\n    /// directions, so arm IK only helps reach-critical work (props, contact poses) and can\r\n    /// otherwise disturb elbow styling.\r\n    /// </summary>\r\n    public bool ArmEffectorIk { get; init; }\r\n\r\n    /// <summary>\r\n    /// Generate <c>AE_FOOTSTEP</c> AnimEvent nodes on each produced clip's vmdl AnimFile\r\n    /// entry (default OFF). After solving and cleanup, foot-plant intervals are detected on\r\n    /// the SOLVED target clip (<see cref=\"Cleanup.FootPlant.DetectPlantIntervals\"/>); each\r\n    /// plant's start frame is a touchdown and becomes one footstep event, in the exact node\r\n    /// shape the shipped citizen data uses (see <see cref=\"Target.FootstepEvents\"/>).\r\n    /// Skipped (with a report note) when the target rig lacks complete leg chains.\r\n    /// </summary>\r\n    public bool GenerateFootstepEvents { get; init; }\r\n\r\n    /// <summary>\r\n    /// Additionally produce a mirrored twin of every converted clip (default OFF), named\r\n    /// <c>&lt;clip&gt;_M</c> (collision-suffixed across the batch as usual). Mirroring runs\r\n    /// in TARGET space on the solved clip (<see cref=\"Solve.ClipMirror\"/>): left/right role\r\n    /// bone channels swap and everything is reflected across the target character's sagittal\r\n    /// plane; IK-baked helper bones are re-baked from the mirrored body afterwards.\r\n    /// </summary>\r\n    public bool CreateMirroredVariant { get; init; }\r\n\r\n    /// <summary>\r\n    /// Additionally register an additive (delta) twin of every converted clip in the\r\n    /// generated/augmented vmdl (default OFF), named <c>&lt;clip&gt;_delta</c> (the shipped\r\n    /// citizen naming; collision-suffixed across the batch as usual). The twin is a second\r\n    /// AnimFile entry REUSING the clip's DMX with an <c>AnimSubtract</c> child\r\n    /// (<c>anim_name</c> = the base sequence, <c>frame</c> = 0) \u2014 exactly the shipped\r\n    /// <c>IdleLayer_01</c>/<c>IdleLayer_01_delta</c> pattern, where resourcecompiler\r\n    /// subtracts the reference frame at compile time (no frame math happens here). The\r\n    /// resulting <c>_delta</c> sequence is what s&amp;box layered animation additively\r\n    /// blends on top of a base pose.\r\n    /// </summary>\r\n    public bool CreateAdditiveVariant { get; init; }\r\n\r\n    /// <summary>Output clip name override; with multiple takes an index suffix is appended.\r\n    /// Null = the source take name.</summary>\r\n    public string? ClipNameOverride { get; init; }\r\n\r\n    /// <summary>Force the looping flag on the output sequence(s); null = the source clip's flag.</summary>\r\n    public bool? LoopingOverride { get; init; }\r\n}\r\n\r\n/// <summary>\r\n/// Axis/unit convention of a <see cref=\"RetargetTargetSpec\"/>'s rig data \u2014 drives the DMX\r\n/// axis-system declaration, foot-plant threshold units, and the editor preview's\r\n/// rig-space \u2192 engine-space conversion.\r\n/// </summary>\r\npublic enum TargetUpAxis\r\n{\r\n    /// <summary>\r\n    /// The s&amp;box source convention: rig authored in centimeters, Y-up (the shipped\r\n    /// citizen rig, FBX targets). The vmdl's ScaleAndMirror 0.3937 + resourcecompiler's\r\n    /// Y-up\u2192Z-up conversion take it to engine space at compile time. Default.\r\n    /// </summary>\r\n    YUpCm,\r\n\r\n    /// <summary>\r\n    /// Engine space already: rig read from a compiled model's <c>Model.Bones</c>\r\n    /// (inches, Z-up). The DMX declares a Z-up axis system so the compiler performs no\r\n    /// further axis conversion.\r\n    /// </summary>\r\n    ZUpEngine,\r\n\r\n    /// <summary>\r\n    /// A Z-up rig authored in centimeters: FBX targets whose GlobalSettings declare a Z\r\n    /// up-axis (UE and 3ds Max exports; Maya/Blender exports are Y-up). The DMX declares\r\n    /// Z-up (no compile-time rotation \u2014 the mesh source is in the same Z-up space) while\r\n    /// the vmdl's ScaleAndMirror 0.3937 still converts cm\u2192inches. Without this, a Z-up\r\n    /// FBX target compiles lying on its back.\r\n    /// </summary>\r\n    ZUpCm,\r\n}\r\n\r\n/// <summary>\r\n/// The conversion target shared by all requests of one <see cref=\"Retargeter.Convert\"/> /\r\n/// <see cref=\"Retargeter.ConvertBatch\"/> call: the rig plus the vmdl generation parameters.\r\n/// </summary>\r\npublic sealed class RetargetTargetSpec\r\n{\r\n    /// <summary>The s&amp;box-source \u2192 engine-units vmdl scale (cm rigs like the citizen).</summary>\r\n    public const float SboxSourceScale = 0.3937f;\r\n\r\n    /// <summary>The committed asset path of the s&amp;box human male model.</summary>\r\n    public const string SboxHumanMalePath = \"models/citizen_human/citizen_human_male.vmdl\";\r\n\r\n    /// <summary>The committed asset path of the classic (4-finger) s&amp;box citizen model.</summary>\r\n    public const string SboxCitizenPath = \"models/citizen/citizen.vmdl\";\r\n\r\n    /// <summary>Target rig (skeleton + bone classes + roles).</summary>\r\n    public required TargetRig Rig { get; set; }\r\n\r\n    /// <summary>ModelModifier_ScaleAndMirror scale written into standalone vmdls:\r\n    /// <c>0.3937</c> for cm-authored s&amp;box-source rigs, <c>1.0</c> for engine-unit rigs\r\n    /// (the modifier node is omitted at 1.0).</summary>\r\n    public required float VmdlScale { get; init; }\r\n\r\n    /// <summary>base_model_name of generated standalone vmdls (the model that owns the mesh).</summary>\r\n    public string BaseModelPath { get; init; } = \"\";\r\n\r\n    /// <summary>\r\n    /// Assets-relative mesh source file (e.g. an <c>.fbx</c>) embedded in generated\r\n    /// standalone vmdls as a <c>RenderMeshList/RenderMeshFile</c> node. Custom FBX targets\r\n    /// have no compiled base model to point <see cref=\"BaseModelPath\"/> at \u2014 without a mesh\r\n    /// source their standalone vmdl compiles into an EMPTY model (0 bones, 0 sequences) and\r\n    /// playing it does nothing. Callers own copying the file into the project (this type\r\n    /// does no IO); settable so the editor can fill it at convert time once the output\r\n    /// folder is known. Empty (default) = no mesh node.\r\n    /// </summary>\r\n    public string MeshFilePath { get; set; } = \"\";\r\n\r\n    /// <summary>\r\n    /// Import scale of <see cref=\"MeshFilePath\"/> (raw mesh-file units \u2192 the target\r\n    /// skeleton's units). resourcecompiler reads mesh files' raw values ignoring their unit\r\n    /// metadata, while the importer normalizes the target skeleton to centimeters \u2014 a\r\n    /// meters-authored FBX therefore needs 100 here (the importer's recorded\r\n    /// source-unit\u2192cm factor) for the mesh to match the animation skeleton.\r\n    /// </summary>\r\n    public float MeshImportScale { get; set; } = 1.0f;\r\n\r\n    /// <summary>\r\n    /// Material remaps written into generated standalone vmdls as a\r\n    /// MaterialGroupList/DefaultMaterialGroup (bare mesh material reference \u2192 assets-relative\r\n    /// vmat path, e.g. <c>\"mi_dante_head.vmat\" \u2192 \"animations/retargeted/mi_dante_head.vmat\"</c>).\r\n    /// FBX materials carry bare names the compiler cannot resolve as resource paths\r\n    /// (\"Trying to load an illegal resource name X.vmat\"); this remap table \u2014 the same\r\n    /// mechanism the shipped citizen vmdl uses \u2014 points them at real files. Null/empty =\r\n    /// no material group node (default).\r\n    /// </summary>\r\n    public IReadOnlyDictionary<string, string>? MaterialRemaps { get; set; }\r\n\r\n    /// <summary>\r\n    /// Additional AnimFile entries appended to generated/augmented vmdls verbatim \u2014\r\n    /// the target FBX's OWN embedded animations (an FBX with an animation on it must keep\r\n    /// that animation when new ones are retargeted onto it; the AnimFile references the\r\n    /// FBX directly, exactly like the shipped citizen animation list references its\r\n    /// Citizen@*.fbx files, so the import is lossless). Augmentation skips entries the\r\n    /// existing vmdl already carries (idempotent re-runs). Null/empty = none (default).\r\n    /// </summary>\r\n    public IReadOnlyList<Target.AnimEntry>? ExtraAnimFiles { get; set; }\r\n\r\n    /// <summary>default_root_bone_name of the generated AnimationList (also the bone vmdl\r\n    /// ExtractMotion nodes operate on).</summary>\r\n    public string DefaultRootBone { get; set; } = \"pelvis\";\r\n\r\n    /// <summary>\r\n    /// Axis/unit convention of <see cref=\"Rig\"/>. <see cref=\"TargetUpAxis.YUpCm\"/> (default)\r\n    /// for cm Y-up source-space rigs (DMX declares Y-up, compiler converts);\r\n    /// <see cref=\"TargetUpAxis.ZUpEngine\"/> for rigs read from compiled engine models\r\n    /// (DMX declares Z-up so no double conversion happens at compile, and cm-tuned cleanup\r\n    /// thresholds are rescaled to inches).\r\n    /// </summary>\r\n    public TargetUpAxis UpAxis { get; init; } = TargetUpAxis.YUpCm;\r\n\r\n    /// <summary>\r\n    /// Raw bytes of the committed SAME weight blob\r\n    /// (<c>Assets/humanoid_retargeter/dl/same_v1.weights</c>; callers do the file IO).\r\n    /// Required only when a request selects <see cref=\"SolverKind.DeepLearning\"/>; the\r\n    /// solver instance is built once per batch from these bytes.\r\n    /// </summary>\r\n    public byte[]? DlWeights { get; init; }\r\n\r\n    /// <summary>\r\n    /// The shipped s&amp;box default target: rig parsed from the committed\r\n    /// <c>Assets/humanoid_retargeter/target_rig_sbox.json</c> text (callers do the file IO),\r\n    /// 0.3937 vmdl scale, citizen human male base model, pelvis root. Pass the committed\r\n    /// SAME weight bytes as <paramref name=\"dlWeights\"/> to enable the deep-learning solver.\r\n    /// </summary>\r\n    public static RetargetTargetSpec SboxDefault(string targetRigJson, byte[]? dlWeights = null) => new()\r\n    {\r\n        Rig = TargetRig.SboxDefault(targetRigJson),\r\n        VmdlScale = SboxSourceScale,\r\n        BaseModelPath = SboxHumanMalePath,\r\n        DefaultRootBone = \"pelvis\",\r\n        DlWeights = dlWeights,\r\n    };\r\n\r\n    /// <summary>\r\n    /// The classic (4-finger) s&amp;box citizen target: rig parsed from the committed\r\n    /// <c>Assets/humanoid_retargeter/target_rig_sbox_citizen.json</c> text (callers do the\r\n    /// file IO), 0.3937 vmdl scale, citizen base model, pelvis root, Y-up cm. The rig has no\r\n    /// pinky bones, so pinky roles stay unassigned \u2014 the engine's own constraints handle the\r\n    /// pinky at runtime for models that have one. Pass the committed SAME weight bytes as\r\n    /// <paramref name=\"dlWeights\"/> to enable the deep-learning solver.\r\n    /// </summary>\r\n    public static RetargetTargetSpec SboxCitizen(string targetRigJson, byte[]? dlWeights = null) => new()\r\n    {\r\n        Rig = TargetRig.Load(targetRigJson),\r\n        VmdlScale = SboxSourceScale,\r\n        BaseModelPath = SboxCitizenPath,\r\n        DefaultRootBone = \"pelvis\",\r\n        UpAxis = TargetUpAxis.YUpCm,\r\n        DlWeights = dlWeights,\r\n    };\r\n}\r\n\r\n/// <summary>Options for <see cref=\"Retargeter.ConvertBatch\"/> output assembly.</summary>\r\npublic sealed class BatchOptions\r\n{\r\n    /// <summary>\r\n    /// When set, the batch additionally augments this existing vmdl text (all successful\r\n    /// clips spliced into its AnimationList via <see cref=\"VmdlAugmenter\"/>) and returns the\r\n    /// result in <see cref=\"RetargetBatchResult.AugmentedVmdl\"/>.\r\n    /// </summary>\r\n    public string? AugmentVmdlText { get; init; }\r\n\r\n    /// <summary>Assets-relative folder the DMX files will be written to by the caller; used\r\n    /// to build each AnimFile's <c>source_filename</c>.</summary>\r\n    public string DmxFolderRelative { get; init; } = \"animations/retargeted\";\r\n\r\n    /// <summary>\r\n    /// Animation source paths (<c>source_filename</c> values of the augment target's existing\r\n    /// AnimFile nodes, assets-relative) that the IO-owning caller has determined NO LONGER\r\n    /// EXIST on disk. Stale AnimFile entries referencing them are REMOVED from the augmented\r\n    /// vmdl (reported on <see cref=\"RetargetBatchResult.Warnings\"/>) \u2014 one unresolvable\r\n    /// source otherwise fails the ENTIRE vmdl recompile (\"Node 'X' resolve failure\"), taking\r\n    /// every newly added animation down with it. Entries this batch overwrites (their DMX is\r\n    /// about to be written) are never pruned. The facade itself never touches the filesystem:\r\n    /// callers probe <see cref=\"Target.VmdlAugmenter.CollectAnimSourcePaths\"/> results against\r\n    /// their content roots and pass the missing ones here. Null/empty = keep everything.\r\n    /// </summary>\r\n    public IReadOnlyCollection<string>? MissingAnimSources { get; init; }\r\n\r\n    /// <summary>Auto-suffix colliding clip names (<c>_2</c>, <c>_3</c>, \u2026) across the whole\r\n    /// batch (default on). When off, duplicate names are kept as-is.</summary>\r\n    public bool AutoSuffixCollisions { get; init; } = true;\r\n\r\n    /// <summary>\r\n    /// After conversion, scan the batch's successful clip names for directional locomotion\r\n    /// families (default OFF): <c>_N</c>/<c>_NE</c>/\u2026/<c>_NW</c> compass suffixes and\r\n    /// <c>_Forward</c>/<c>_Backward</c>(/<c>_Back</c>)/<c>_Left</c>/<c>_Right</c> word forms\r\n    /// sharing a stem. Each complete family (all four cardinals) is grouped under a Folder\r\n    /// node with a <c>2DBlend</c> wired to the citizen <c>move_x</c>/<c>move_y</c> pose\r\n    /// parameters, replicating the shipped citizen locomotion layout (see\r\n    /// <see cref=\"Target.LocomotionSetDetector\"/>); detection results land on\r\n    /// <see cref=\"RetargetBatchResult.LocomotionSets\"/>. Custom (non-citizen) base models\r\n    /// must declare <c>move_x</c>/<c>move_y</c> pose parameters themselves for the blends to\r\n    /// be drivable.\r\n    /// </summary>\r\n    public bool DetectLocomotionSets { get; init; }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "HumanoidRetargeter/Solve/HandGeometry.cs",
            "FileName": "HandGeometry.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidRetargeter.Mapping;\r\nusing HumanoidRetargeter.Maths;\r\n\r\nnamespace HumanoidRetargeter.Solve;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>\r\n/// Hand rest-geometry helpers shared by <see cref=\"CanonicalFrames\"/> (finger secondary axes)\r\n/// and <see cref=\"RestNormalizer\"/> (palm-down roll correction). Everything derives from joint\r\n/// positions only \u2014 bone local axes carry no anatomical meaning on the s&amp;box rig.\r\n/// </summary>\r\ninternal static class HandGeometry\r\n{\r\n    private static readonly BoneRole[] LeftProximals =\r\n    {\r\n        BoneRole.ThumbProxL, BoneRole.IndexProxL, BoneRole.MiddleProxL, BoneRole.RingProxL, BoneRole.PinkyProxL,\r\n    };\r\n\r\n    private static readonly BoneRole[] RightProximals =\r\n    {\r\n        BoneRole.ThumbProxR, BoneRole.IndexProxR, BoneRole.MiddleProxR, BoneRole.RingProxR, BoneRole.PinkyProxR,\r\n    };\r\n\r\n    // Index \u2192 pinky order; the knuckle line is taken from the first and last mapped of these.\r\n    private static readonly BoneRole[] LeftNonThumbProximals =\r\n    {\r\n        BoneRole.IndexProxL, BoneRole.MiddleProxL, BoneRole.RingProxL, BoneRole.PinkyProxL,\r\n    };\r\n\r\n    private static readonly BoneRole[] RightNonThumbProximals =\r\n    {\r\n        BoneRole.IndexProxR, BoneRole.MiddleProxR, BoneRole.RingProxR, BoneRole.PinkyProxR,\r\n    };\r\n\r\n    /// <summary>\r\n    /// Midpoint of all mapped finger proximal heads of one hand (the hand's anatomical\r\n    /// \"chain child\" point), or null when no finger proximal is mapped.\r\n    /// </summary>\r\n    public static Vector3? FingerProximalMidpoint(MappingResult map, IReadOnlyList<XForm> worldRest, bool left)\r\n    {\r\n        var sum = Vector3.Zero;\r\n        var count = 0;\r\n        foreach (var role in left ? LeftProximals : RightProximals)\r\n        {\r\n            if (map.RoleToBone.TryGetValue(role, out var index))\r\n            {\r\n                sum += worldRest[index].Pos;\r\n                count++;\r\n            }\r\n        }\r\n        return count > 0 ? sum / count : null;\r\n    }\r\n\r\n    /// <summary>\r\n    /// Dorsal palm normal of one hand: the unit vector pointing out of the <b>back</b> of the\r\n    /// hand (away from the palm), or null when the hand/finger geometry is unmapped or\r\n    /// degenerate.\r\n    /// </summary>\r\n    /// <remarks>\r\n    /// Formula (mirror-consistent by construction, verified on the ActorCore fixture by the\r\n    /// finger-curl test): <c>dorsal = sideSign \u00b7 cross(knuckle, fingerDir)</c> with\r\n    /// <c>sideSign = +1</c> left / <c>\u22121</c> right, <c>knuckle = IndexProx.head \u2212\r\n    /// PinkyProx.head</c> (first/last mapped non-thumb proximal), and <c>fingerDir =\r\n    /// FingerProximalMidpoint \u2212 Hand.head</c>. On every fixture rig the thumb proximal lies on\r\n    /// the \u2212dorsal (palmar) side of the hand plane, grounding the sign anatomically. A positive\r\n    /// rotation about a finger frame's hinge axis (frame Y = cross(dorsal, fingerChainDir))\r\n    /// curls the fingertip toward the palm on <b>both</b> hands.\r\n    /// </remarks>\r\n    public static Vector3? Dorsal(MappingResult map, IReadOnlyList<XForm> worldRest, bool left)\r\n    {\r\n        if (!map.RoleToBone.TryGetValue(left ? BoneRole.HandL : BoneRole.HandR, out var handIndex))\r\n            return null;\r\n        var hand = worldRest[handIndex].Pos;\r\n\r\n        var nonThumb = left ? LeftNonThumbProximals : RightNonThumbProximals;\r\n        Vector3? first = null, last = null;\r\n        foreach (var role in nonThumb)\r\n        {\r\n            if (!map.RoleToBone.TryGetValue(role, out var index))\r\n                continue;\r\n            first ??= worldRest[index].Pos;\r\n            last = worldRest[index].Pos;\r\n        }\r\n        if (first is null || last is null || (first.Value - last.Value).LengthSquared() < 1e-8f)\r\n            return null;\r\n\r\n        var midpoint = FingerProximalMidpoint(map, worldRest, left);\r\n        if (midpoint is null)\r\n            return null;\r\n\r\n        var knuckle = first.Value - last.Value;\r\n        var fingerDir = midpoint.Value - hand;\r\n        var raw = Vector3.Cross(knuckle, fingerDir) * (left ? 1f : -1f);\r\n        return raw.LengthSquared() < 1e-8f ? null : Vector3.Normalize(raw);\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/AssemblyInfo.cs",
            "FileName": "AssemblyInfo.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\n// Engine-agnostic retargeting core. No Sandbox/Editor references allowed in this tree:\r\n// these sources also compile in the plain net8.0 dev harness (dev/HumanoidRetargeter.Dev.csproj).\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Formats/Gltf/GltfDocument.cs",
            "FileName": "GltfDocument.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing System.Text;\r\nusing System.Text.Json;\r\n\r\nnamespace HumanoidRetargeter.Formats.Gltf;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>One glTF node, reduced to what skeleton import needs (TRS rest + hierarchy).</summary>\r\ninternal sealed class GltfNode\r\n{\r\n    public string? Name;\r\n    public int[] Children = Array.Empty<int>();\r\n    public int Parent = -1;\r\n    public bool HasMesh;\r\n\r\n    // Rest local transform: TRS properties, or the decomposed \"matrix\" property (the spec\r\n    // makes them exclusive; animated nodes must use TRS). Shear is not representable.\r\n    public Vector3 Translation;                       // meters\r\n    public Quaternion Rotation = Quaternion.Identity; // xyzw\r\n    public Vector3 Scale = Vector3.One;\r\n}\r\n\r\n/// <summary>One decoded animation channel: keyframe times + values for one node property.</summary>\r\ninternal sealed class GltfChannel\r\n{\r\n    public required int NodeIndex;\r\n    public required bool IsRotation;     // true = rotation (VEC4 quat), false = translation (VEC3)\r\n    public required float[] Times;       // seconds, ascending\r\n    public required float[] Values;      // flattened; 4 (or 3) floats per element\r\n    public required string Interpolation; // LINEAR / STEP / CUBICSPLINE\r\n\r\n    /// <summary>Floats per element (3 translation / 4 rotation).</summary>\r\n    public int Comps => IsRotation ? 4 : 3;\r\n\r\n    /// <summary>Elements stored per key: CUBICSPLINE keys carry in-tangent/value/out-tangent.</summary>\r\n    public int ElementsPerKey => Interpolation == \"CUBICSPLINE\" ? 3 : 1;\r\n\r\n    /// <summary>Number of keys.</summary>\r\n    public int KeyCount => Times.Length;\r\n}\r\n\r\n/// <summary>One glTF animation with its decoded rotation/translation channels.</summary>\r\ninternal sealed class GltfAnimation\r\n{\r\n    public string? Name;\r\n    public List<GltfChannel> Channels { get; } = new();\r\n}\r\n\r\n/// <summary>\r\n/// Container + JSON layer of the glTF importer: parses a GLB binary container or a plain\r\n/// .gltf JSON document, resolves buffers (GLB BIN chunk and base64 <c>data:</c> URIs \u2014 file\r\n/// IO is banned in Code/, so external file URIs throw), and decodes nodes, skin joints and\r\n/// animation samplers into plain arrays. Throws <see cref=\"FormatException\"/> on anything\r\n/// malformed or unsupported.\r\n/// </summary>\r\ninternal sealed class GltfDocument\r\n{\r\n    private const uint GlbMagic = 0x46546C67;     // 'glTF' little-endian\r\n    private const uint ChunkJson = 0x4E4F534A;    // 'JSON'\r\n    private const uint ChunkBin = 0x004E4942;     // 'BIN\\0'\r\n\r\n    /// <summary>All nodes, indexed as in the file, with parents resolved from children lists.</summary>\r\n    public List<GltfNode> Nodes { get; } = new();\r\n\r\n    /// <summary>Union of all skins' joint node indices.</summary>\r\n    public HashSet<int> SkinJoints { get; } = new();\r\n\r\n    /// <summary>All animations with decoded rotation/translation channels (scale/weights ignored).</summary>\r\n    public List<GltfAnimation> Animations { get; } = new();\r\n\r\n    /// <summary>\r\n    /// The VRM humanoid bone map authored in the file, when present: VRM bone name\r\n    /// (<c>hips</c>, <c>leftUpperArm</c>, \u2026) \u2192 node index. Read from BOTH extension layouts:\r\n    /// VRM 0.x <c>extensions.VRM.humanoid.humanBones</c> (an ARRAY of\r\n    /// <c>{ \"bone\": \"hips\", \"node\": 14 }</c> entries) and VRM 1.0\r\n    /// <c>extensions.VRMC_vrm.humanoid.humanBones</c> (an OBJECT\r\n    /// <c>{ \"hips\": { \"node\": 14 }, \u2026 }</c>). Null when the file carries neither.\r\n    /// </summary>\r\n    public Dictionary<string, int>? VrmHumanBones { get; private set; }\r\n\r\n    /// <summary>Which VRM extension supplied <see cref=\"VrmHumanBones\"/>: <c>0</c> for the\r\n    /// 0.x <c>VRM</c> extension, <c>1</c> for the 1.0 <c>VRMC_vrm</c> extension, <c>-1</c>\r\n    /// when none.</summary>\r\n    public int VrmVersion { get; private set; } = -1;\r\n\r\n    private GltfDocument()\r\n    {\r\n    }\r\n\r\n    /// <summary>Parses GLB or plain-JSON glTF bytes.</summary>\r\n    /// <exception cref=\"FormatException\">Truncated/malformed container, invalid JSON,\r\n    /// unresolvable buffers, or unsupported accessor layouts.</exception>\r\n    public static GltfDocument Parse(byte[] data)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(data);\r\n\r\n        byte[] json;\r\n        byte[]? bin = null;\r\n        if (data.Length >= 4 && ReadU32(data, 0) == GlbMagic)\r\n            (json, bin) = ParseGlbContainer(data);\r\n        else\r\n            json = data;\r\n\r\n        JsonElement root;\r\n        try\r\n        {\r\n            // Parse via string: Memory<T>/ReadOnlyMemory<T> are not on the s&box runtime\r\n            // whitelist (SB1000), and the string path also lets us strip a UTF-8 BOM\r\n            // (Utf8JsonReader rejects raw BOM bytes). Clone detaches from the disposed\r\n            // JsonDocument.\r\n            var text = System.Text.Encoding.UTF8.GetString(json).TrimStart('\\uFEFF');\r\n            using var doc = JsonDocument.Parse(text);\r\n            root = doc.RootElement.Clone();\r\n        }\r\n        catch (JsonException e)\r\n        {\r\n            throw new FormatException($\"glTF: invalid JSON ({e.Message})\");\r\n        }\r\n\r\n        if (root.ValueKind != JsonValueKind.Object || !root.TryGetProperty(\"asset\", out _))\r\n            throw new FormatException(\"glTF: missing required 'asset' object (not a glTF file?).\");\r\n\r\n        var document = new GltfDocument();\r\n        var buffers = ResolveBuffers(root, bin);\r\n        document.ReadNodes(root);\r\n        document.ReadSkins(root);\r\n        document.ReadAnimations(root, buffers);\r\n        document.ReadVrmHumanoid(root);\r\n        return document;\r\n    }\r\n\r\n    // ================================================================== VRM humanoid\r\n\r\n    /// <summary>\r\n    /// Reads the authored humanoid bone map of a VRM file (a .vrm is a regular glTF 2.0/GLB\r\n    /// container plus a VRM extension). VRM 1.0's <c>VRMC_vrm</c> wins when both extensions\r\n    /// are present. Defensive throughout: malformed entries and out-of-range node indices\r\n    /// are skipped (a broken bone map degrades to the regular detection cascade rather than\r\n    /// failing the import).\r\n    /// </summary>\r\n    private void ReadVrmHumanoid(JsonElement root)\r\n    {\r\n        if (!root.TryGetProperty(\"extensions\", out var extensions)\r\n            || extensions.ValueKind != JsonValueKind.Object)\r\n            return;\r\n\r\n        // ---- VRM 1.0: extensions.VRMC_vrm.humanoid.humanBones = { \"<bone>\": { \"node\": n } } ----\r\n        if (TryGetHumanBones(extensions, \"VRMC_vrm\", out var humanBones1)\r\n            && humanBones1.ValueKind == JsonValueKind.Object)\r\n        {\r\n            var map = new Dictionary<string, int>(StringComparer.Ordinal);\r\n            foreach (var property in humanBones1.EnumerateObject())\r\n            {\r\n                if (property.Value.ValueKind == JsonValueKind.Object\r\n                    && property.Value.TryGetProperty(\"node\", out var node)\r\n                    && node.ValueKind == JsonValueKind.Number\r\n                    && node.TryGetInt32(out var index)\r\n                    && index >= 0 && index < Nodes.Count)\r\n                {\r\n                    map[property.Name] = index;\r\n                }\r\n            }\r\n            if (map.Count > 0)\r\n            {\r\n                VrmHumanBones = map;\r\n                VrmVersion = 1;\r\n                return;\r\n            }\r\n        }\r\n\r\n        // ---- VRM 0.x: extensions.VRM.humanoid.humanBones = [ { \"bone\": \"...\", \"node\": n } ] ----\r\n        if (TryGetHumanBones(extensions, \"VRM\", out var humanBones0)\r\n            && humanBones0.ValueKind == JsonValueKind.Array)\r\n        {\r\n            var map = new Dictionary<string, int>(StringComparer.Ordinal);\r\n            foreach (var entry in humanBones0.EnumerateArray())\r\n            {\r\n                if (entry.ValueKind == JsonValueKind.Object\r\n                    && entry.TryGetProperty(\"bone\", out var bone)\r\n                    && bone.ValueKind == JsonValueKind.String\r\n                    && entry.TryGetProperty(\"node\", out var node)\r\n                    && node.ValueKind == JsonValueKind.Number\r\n                    && node.TryGetInt32(out var index)\r\n                    && index >= 0 && index < Nodes.Count)\r\n                {\r\n                    map[bone.GetString()!] = index;\r\n                }\r\n            }\r\n            if (map.Count > 0)\r\n            {\r\n                VrmHumanBones = map;\r\n                VrmVersion = 0;\r\n            }\r\n        }\r\n    }\r\n\r\n    private static bool TryGetHumanBones(JsonElement extensions, string extensionName, out JsonElement humanBones)\r\n    {\r\n        humanBones = default;\r\n        return extensions.TryGetProperty(extensionName, out var vrm)\r\n            && vrm.ValueKind == JsonValueKind.Object\r\n            && vrm.TryGetProperty(\"humanoid\", out var humanoid)\r\n            && humanoid.ValueKind == JsonValueKind.Object\r\n            && humanoid.TryGetProperty(\"humanBones\", out humanBones);\r\n    }\r\n\r\n    // ================================================================== GLB container\r\n\r\n    /// <summary>GLB layout: 12-byte header (magic 'glTF', u32 version = 2, u32 length),\r\n    /// then chunks of (u32 length, u32 type, bytes): one JSON chunk, optionally one BIN.</summary>\r\n    private static (byte[] Json, byte[]? Bin) ParseGlbContainer(byte[] data)\r\n    {\r\n        if (data.Length < 12)\r\n            throw new FormatException(\"GLB: truncated header (need 12 bytes).\");\r\n\r\n        uint version = ReadU32(data, 4);\r\n        if (version != 2)\r\n            throw new FormatException($\"GLB: unsupported container version {version} (expected 2).\");\r\n\r\n        long declared = ReadU32(data, 8);\r\n        if (declared > data.Length)\r\n            throw new FormatException(\r\n                $\"GLB: truncated file (header declares {declared} bytes, got {data.Length}).\");\r\n\r\n        byte[]? json = null, bin = null;\r\n        long offset = 12;\r\n        while (offset + 8 <= declared)\r\n        {\r\n            long length = ReadU32(data, (int)offset);\r\n            uint type = ReadU32(data, (int)offset + 4);\r\n            offset += 8;\r\n            if (offset + length > data.Length)\r\n                throw new FormatException(\"GLB: truncated chunk (declared length exceeds the file).\");\r\n\r\n            if (type == ChunkJson && json is null)\r\n                json = data.AsSpan((int)offset, (int)length).ToArray();\r\n            else if (type == ChunkBin && bin is null)\r\n                bin = data.AsSpan((int)offset, (int)length).ToArray();\r\n            // Unknown chunk types are skipped per spec.\r\n\r\n            offset += length + (length % 4 == 0 ? 0 : 4 - length % 4); // chunks are 4-aligned\r\n        }\r\n\r\n        if (json is null)\r\n            throw new FormatException(\"GLB: no JSON chunk found.\");\r\n        return (json, bin);\r\n    }\r\n\r\n    private static uint ReadU32(byte[] data, int offset)\r\n        => (uint)(data[offset] | data[offset + 1] << 8 | data[offset + 2] << 16 | data[offset + 3] << 24);\r\n\r\n    // ================================================================== buffers\r\n\r\n    /// <summary>\r\n    /// Resolves every entry of <c>buffers</c>: no <c>uri</c> = the GLB BIN chunk (spec: only\r\n    /// buffer 0 may do this), <c>data:</c> URIs are base64-decoded inline. External file\r\n    /// URIs are NOT supported \u2014 this library does no file IO; users should export .glb.\r\n    /// </summary>\r\n    private static List<byte[]> ResolveBuffers(JsonElement root, byte[]? bin)\r\n    {\r\n        var buffers = new List<byte[]>();\r\n        if (!root.TryGetProperty(\"buffers\", out var array) || array.ValueKind != JsonValueKind.Array)\r\n            return buffers;\r\n\r\n        foreach (var buffer in array.EnumerateArray())\r\n        {\r\n            if (!buffer.TryGetProperty(\"uri\", out var uriProp))\r\n            {\r\n                buffers.Add(bin ?? throw new FormatException(\r\n                    \"glTF: buffer has no uri but the file has no GLB BIN chunk.\"));\r\n                continue;\r\n            }\r\n\r\n            var uri = uriProp.GetString() ?? \"\";\r\n            if (uri.StartsWith(\"data:\", StringComparison.OrdinalIgnoreCase))\r\n            {\r\n                int comma = uri.IndexOf(',');\r\n                if (comma < 0 || !uri[..comma].EndsWith(\";base64\", StringComparison.OrdinalIgnoreCase))\r\n                    throw new FormatException(\"glTF: only base64 data: URIs are supported for buffers.\");\r\n                try\r\n                {\r\n                    buffers.Add(Convert.FromBase64String(uri[(comma + 1)..]));\r\n                }\r\n                catch (Exception e) when (e is FormatException or ArgumentException)\r\n                {\r\n                    throw new FormatException(\"glTF: invalid base64 in buffer data: URI.\");\r\n                }\r\n            }\r\n            else\r\n            {\r\n                throw new FormatException(\r\n                    $\"glTF: buffer references an external file ('{uri}') which this importer cannot \"\r\n                    + \"read (no file IO). Export as .glb (binary, self-contained) instead.\");\r\n            }\r\n        }\r\n        return buffers;\r\n    }\r\n\r\n    // ================================================================== nodes + skins\r\n\r\n    private void ReadNodes(JsonElement root)\r\n    {\r\n        if (!root.TryGetProperty(\"nodes\", out var array) || array.ValueKind != JsonValueKind.Array)\r\n            return;\r\n\r\n        Span<float> m = stackalloc float[16]; // matrix scratch (outside the loop: CA2014)\r\n        foreach (var n in array.EnumerateArray())\r\n        {\r\n            var node = new GltfNode\r\n            {\r\n                Name = n.TryGetProperty(\"name\", out var name) ? name.GetString() : null,\r\n                HasMesh = n.TryGetProperty(\"mesh\", out _),\r\n            };\r\n\r\n            if (n.TryGetProperty(\"children\", out var children) && children.ValueKind == JsonValueKind.Array)\r\n            {\r\n                var list = new List<int>();\r\n                foreach (var c in children.EnumerateArray())\r\n                    list.Add(c.GetInt32());\r\n                node.Children = list.ToArray();\r\n            }\r\n\r\n            if (n.TryGetProperty(\"matrix\", out var matrix) && matrix.ValueKind == JsonValueKind.Array)\r\n            {\r\n                // Column-major 16 floats; the element order maps 1:1 onto System.Numerics'\r\n                // row-vector matrices (translation in elements 12..14 either way).\r\n                int i = 0;\r\n                foreach (var v in matrix.EnumerateArray())\r\n                {\r\n                    if (i >= 16)\r\n                        break;\r\n                    m[i++] = v.GetSingle();\r\n                }\r\n                if (i < 16)\r\n                    throw new FormatException(\"glTF: node matrix has fewer than 16 elements.\");\r\n                var local = new Matrix4x4(\r\n                    m[0], m[1], m[2], m[3],\r\n                    m[4], m[5], m[6], m[7],\r\n                    m[8], m[9], m[10], m[11],\r\n                    m[12], m[13], m[14], m[15]);\r\n                if (Matrix4x4.Decompose(local, out var scale, out var rot, out var pos))\r\n                {\r\n                    node.Translation = pos;\r\n                    node.Rotation = rot;\r\n                    node.Scale = scale;\r\n                }\r\n                else\r\n                {\r\n                    node.Translation = local.Translation; // degenerate: keep position at least\r\n                }\r\n            }\r\n            else\r\n            {\r\n                node.Translation = ReadVec3(n, \"translation\", Vector3.Zero);\r\n                node.Scale = ReadVec3(n, \"scale\", Vector3.One);\r\n                if (n.TryGetProperty(\"rotation\", out var r) && r.ValueKind == JsonValueKind.Array\r\n                    && r.GetArrayLength() >= 4)\r\n                {\r\n                    node.Rotation = new Quaternion(\r\n                        r[0].GetSingle(), r[1].GetSingle(), r[2].GetSingle(), r[3].GetSingle());\r\n                }\r\n            }\r\n\r\n            Nodes.Add(node);\r\n        }\r\n\r\n        // Resolve parents (per spec a node is referenced by at most one other node's children).\r\n        for (int i = 0; i < Nodes.Count; i++)\r\n        {\r\n            foreach (var child in Nodes[i].Children)\r\n            {\r\n                if (child < 0 || child >= Nodes.Count)\r\n                    throw new FormatException($\"glTF: node {i} references nonexistent child {child}.\");\r\n                if (Nodes[child].Parent < 0)\r\n                    Nodes[child].Parent = i;\r\n            }\r\n        }\r\n    }\r\n\r\n    private static Vector3 ReadVec3(JsonElement element, string property, Vector3 fallback)\r\n    {\r\n        if (!element.TryGetProperty(property, out var v) || v.ValueKind != JsonValueKind.Array\r\n            || v.GetArrayLength() < 3)\r\n            return fallback;\r\n        return new Vector3(v[0].GetSingle(), v[1].GetSingle(), v[2].GetSingle());\r\n    }\r\n\r\n    private void ReadSkins(JsonElement root)\r\n    {\r\n        if (!root.TryGetProperty(\"skins\", out var array) || array.ValueKind != JsonValueKind.Array)\r\n            return;\r\n\r\n        foreach (var skin in array.EnumerateArray())\r\n        {\r\n            if (!skin.TryGetProperty(\"joints\", out var joints) || joints.ValueKind != JsonValueKind.Array)\r\n                continue;\r\n            foreach (var j in joints.EnumerateArray())\r\n            {\r\n                int index = j.GetInt32();\r\n                if (index >= 0 && index < Nodes.Count)\r\n                    SkinJoints.Add(index);\r\n            }\r\n        }\r\n    }\r\n\r\n    // ================================================================== animations\r\n\r\n    private void ReadAnimations(JsonElement root, List<byte[]> buffers)\r\n    {\r\n        if (!root.TryGetProperty(\"animations\", out var array) || array.ValueKind != JsonValueKind.Array)\r\n            return;\r\n\r\n        root.TryGetProperty(\"accessors\", out var accessors);\r\n        root.TryGetProperty(\"bufferViews\", out var views);\r\n\r\n        foreach (var a in array.EnumerateArray())\r\n        {\r\n            var animation = new GltfAnimation\r\n            {\r\n                Name = a.TryGetProperty(\"name\", out var name) ? name.GetString() : null,\r\n            };\r\n\r\n            if (!a.TryGetProperty(\"channels\", out var channels) || !a.TryGetProperty(\"samplers\", out var samplers))\r\n            {\r\n                Animations.Add(animation);\r\n                continue;\r\n            }\r\n\r\n            foreach (var channel in channels.EnumerateArray())\r\n            {\r\n                if (!channel.TryGetProperty(\"target\", out var target)\r\n                    || !target.TryGetProperty(\"node\", out var nodeProp)\r\n                    || !target.TryGetProperty(\"path\", out var pathProp))\r\n                    continue; // extension targets (e.g. KHR_animation_pointer) are ignored\r\n\r\n                var path = pathProp.GetString();\r\n                if (path is not (\"rotation\" or \"translation\"))\r\n                    continue; // scale / weights channels are ignored by design\r\n\r\n                int node = nodeProp.GetInt32();\r\n                if (node < 0 || node >= Nodes.Count)\r\n                    continue;\r\n\r\n                int samplerIndex = channel.TryGetProperty(\"sampler\", out var s) ? s.GetInt32() : -1;\r\n                if (samplerIndex < 0 || samplerIndex >= samplers.GetArrayLength())\r\n                    throw new FormatException(\"glTF: animation channel references a nonexistent sampler.\");\r\n                var sampler = samplers[samplerIndex];\r\n\r\n                var interpolation = sampler.TryGetProperty(\"interpolation\", out var interp)\r\n                    ? interp.GetString() ?? \"LINEAR\"\r\n                    : \"LINEAR\";\r\n\r\n                bool isRotation = path == \"rotation\";\r\n                int comps = isRotation ? 4 : 3;\r\n\r\n                var times = ReadAccessor(accessors, views, buffers,\r\n                    RequiredInt(sampler, \"input\", \"animation sampler\"), 1, normalizedAllowed: false);\r\n                var values = ReadAccessor(accessors, views, buffers,\r\n                    RequiredInt(sampler, \"output\", \"animation sampler\"), comps, normalizedAllowed: isRotation);\r\n\r\n                int elementsPerKey = interpolation == \"CUBICSPLINE\" ? 3 : 1;\r\n                if (times.Length == 0 || values.Length < times.Length * elementsPerKey * comps)\r\n                    continue; // empty or under-filled sampler: nothing usable\r\n\r\n                animation.Channels.Add(new GltfChannel\r\n                {\r\n                    NodeIndex = node,\r\n                    IsRotation = isRotation,\r\n                    Times = times,\r\n                    Values = values,\r\n                    Interpolation = interpolation,\r\n                });\r\n            }\r\n\r\n            Animations.Add(animation);\r\n        }\r\n    }\r\n\r\n    private static int RequiredInt(JsonElement element, string property, string context)\r\n    {\r\n        if (!element.TryGetProperty(property, out var v))\r\n            throw new FormatException($\"glTF: {context} is missing '{property}'.\");\r\n        return v.GetInt32();\r\n    }\r\n\r\n    // ================================================================== accessors\r\n\r\n    /// <summary>\r\n    /// Decodes an accessor to floats. Component types: f32 directly; normalized i8/u8/i16/u16\r\n    /// per the spec's normalization rules when <paramref name=\"normalizedAllowed\"/> (rotation\r\n    /// outputs); anything else throws. Honors accessor/bufferView byte offsets and an\r\n    /// explicit byteStride. Sparse accessors are not supported.\r\n    /// </summary>\r\n    private static float[] ReadAccessor(\r\n        JsonElement accessors, JsonElement views, List<byte[]> buffers,\r\n        int accessorIndex, int expectedComps, bool normalizedAllowed)\r\n    {\r\n        if (accessors.ValueKind != JsonValueKind.Array || accessorIndex < 0\r\n            || accessorIndex >= accessors.GetArrayLength())\r\n            throw new FormatException($\"glTF: accessor {accessorIndex} does not exist.\");\r\n        var accessor = accessors[accessorIndex];\r\n\r\n        if (accessor.TryGetProperty(\"sparse\", out _))\r\n            throw new FormatException(\"glTF: sparse accessors are not supported.\");\r\n\r\n        var type = accessor.TryGetProperty(\"type\", out var t) ? t.GetString() : null;\r\n        int comps = type switch\r\n        {\r\n            \"SCALAR\" => 1,\r\n            \"VEC3\" => 3,\r\n            \"VEC4\" => 4,\r\n            _ => throw new FormatException($\"glTF: unsupported accessor type '{type}'.\"),\r\n        };\r\n        if (comps != expectedComps)\r\n            throw new FormatException(\r\n                $\"glTF: accessor {accessorIndex} is {type}, expected {expectedComps} component(s).\");\r\n\r\n        int count = RequiredInt(accessor, \"count\", \"accessor\");\r\n        int componentType = RequiredInt(accessor, \"componentType\", \"accessor\");\r\n        bool normalized = accessor.TryGetProperty(\"normalized\", out var n) && n.GetBoolean();\r\n\r\n        // The count is attacker-controlled: validate it BEFORE any allocation sized by it.\r\n        // Negative would throw OverflowException from the array allocation (breaking the\r\n        // FormatException malformed-file contract); huge would OOM; count * comps can wrap.\r\n        if (count < 0)\r\n            throw new FormatException($\"glTF: accessor {accessorIndex} has a negative count ({count}).\");\r\n\r\n        int compSize = componentType switch\r\n        {\r\n            5126 => 4,            // FLOAT\r\n            5120 or 5121 => 1,    // BYTE / UNSIGNED_BYTE\r\n            5122 or 5123 => 2,    // SHORT / UNSIGNED_SHORT\r\n            _ => throw new FormatException(\r\n                $\"glTF: unsupported accessor componentType {componentType}.\"),\r\n        };\r\n        if (componentType != 5126 && !(normalized && normalizedAllowed))\r\n            throw new FormatException(\r\n                $\"glTF: accessor {accessorIndex} must be float (or a normalized integer \"\r\n                + \"rotation output).\");\r\n\r\n        int elementSize = comps * compSize;\r\n\r\n        if (!accessor.TryGetProperty(\"bufferView\", out var viewIndexProp))\r\n        {\r\n            // Zero-filled when no bufferView (legal per spec) \u2014 but then nothing backs the\r\n            // count, so cap it by the file's total decoded buffer bytes (a real file's\r\n            // accessors never outgrow its payload; a small floor keeps tiny legitimate\r\n            // zero-filled accessors working in buffer-less documents).\r\n            long totalBufferBytes = 0;\r\n            foreach (var b in buffers)\r\n                totalBufferBytes += b.Length;\r\n            long capacity = Math.Min(\r\n                Math.Max(totalBufferBytes / elementSize, 65536),\r\n                int.MaxValue / comps); // keeps count * comps int-representable\r\n            if (count > capacity)\r\n                throw new FormatException(\r\n                    $\"glTF: accessor {accessorIndex} count {count} exceeds what the file's \"\r\n                    + \"buffers could back (malformed or hostile file).\");\r\n            return new float[checked(count * comps)];\r\n        }\r\n\r\n        int viewIndex = viewIndexProp.GetInt32();\r\n        if (views.ValueKind != JsonValueKind.Array || viewIndex < 0 || viewIndex >= views.GetArrayLength())\r\n            throw new FormatException($\"glTF: bufferView {viewIndex} does not exist.\");\r\n        var view = views[viewIndex];\r\n\r\n        int bufferIndex = RequiredInt(view, \"buffer\", \"bufferView\");\r\n        if (bufferIndex < 0 || bufferIndex >= buffers.Count)\r\n            throw new FormatException($\"glTF: buffer {bufferIndex} does not exist.\");\r\n        var buffer = buffers[bufferIndex];\r\n\r\n        int viewOffset = view.TryGetProperty(\"byteOffset\", out var vo) ? vo.GetInt32() : 0;\r\n        int accessorOffset = accessor.TryGetProperty(\"byteOffset\", out var ao) ? ao.GetInt32() : 0;\r\n        int stride = view.TryGetProperty(\"byteStride\", out var st) ? st.GetInt32() : elementSize;\r\n        if (stride < elementSize)\r\n            throw new FormatException(\"glTF: bufferView byteStride is smaller than the element size.\");\r\n\r\n        // Bounds check in long arithmetic BEFORE allocating: the backing range must fit the\r\n        // buffer, which also caps count at buffer.Length / stride (+1) \u2014 so the allocation\r\n        // below is bounded by the actual file size and checked() can no longer overflow.\r\n        long start = (long)viewOffset + accessorOffset;\r\n        long end = start + (long)(count - 1) * stride + elementSize;\r\n        if (count > 0 && (start < 0 || end > buffer.Length))\r\n            throw new FormatException(\r\n                $\"glTF: accessor {accessorIndex} reads past the end of its buffer (truncated file?).\");\r\n\r\n        var result = new float[checked(count * comps)];\r\n        for (int element = 0; element < count; element++)\r\n        {\r\n            int offset = (int)(start + (long)element * stride);\r\n            for (int c = 0; c < comps; c++)\r\n            {\r\n                int at = offset + c * compSize;\r\n                result[element * comps + c] = componentType switch\r\n                {\r\n                    5126 => BitConverter.ToSingle(buffer, at),\r\n                    5120 => MathF.Max((sbyte)buffer[at] / 127f, -1f),\r\n                    5121 => buffer[at] / 255f,\r\n                    5122 => MathF.Max(BitConverter.ToInt16(buffer, at) / 32767f, -1f),\r\n                    _ => BitConverter.ToUInt16(buffer, at) / 65535f,\r\n                };\r\n            }\r\n        }\r\n        return result;\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Maths/XForm.cs",
            "FileName": "XForm.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Numerics;\r\n\r\nnamespace HumanoidRetargeter.Maths;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>\r\n/// A rigid transform: rotation followed by translation (no scale or shear).\r\n/// </summary>\r\n/// <remarks>\r\n/// Project conventions (fixed for the whole library):\r\n/// <list type=\"bullet\">\r\n/// <item>Positions are centimeters.</item>\r\n/// <item>Quaternions are XYZW unit quaternions (<see cref=\"System.Numerics.Quaternion\"/> native layout).</item>\r\n/// <item>Column-vector convention: a local-space point maps to outer space as\r\n/// <c>p' = rotate(Rot, p) + Pos</c>, and <c>a * b</c> on quaternions applies <c>b</c> first.</item>\r\n/// </list>\r\n/// </remarks>\r\npublic struct XForm : IEquatable<XForm>\r\n{\r\n    /// <summary>Translation component, in centimeters.</summary>\r\n    public Vector3 Pos;\r\n\r\n    /// <summary>Rotation component, XYZW unit quaternion.</summary>\r\n    public Quaternion Rot;\r\n\r\n    /// <summary>Creates a transform from a translation and a rotation.</summary>\r\n    public XForm(Vector3 pos, Quaternion rot)\r\n    {\r\n        Pos = pos;\r\n        Rot = rot;\r\n    }\r\n\r\n    /// <summary>The identity transform (zero translation, identity rotation).</summary>\r\n    public static XForm Identity => new(Vector3.Zero, Quaternion.Identity);\r\n\r\n    /// <summary>\r\n    /// Composes a parent transform with a child-local transform, producing the child's\r\n    /// transform in the parent's outer space (world = parent \u2218 local):\r\n    /// <c>pos = parent.Pos + rotate(parent.Rot, local.Pos)</c>, <c>rot = parent.Rot * local.Rot</c>.\r\n    /// The resulting rotation is re-normalized to suppress floating-point drift.\r\n    /// </summary>\r\n    public static XForm Compose(in XForm parent, in XForm local)\r\n        => new(\r\n            parent.Pos + Vector3.Transform(local.Pos, parent.Rot),\r\n            MathQ.Normalize(parent.Rot * local.Rot));\r\n\r\n    /// <summary>\r\n    /// Returns the inverse transform, such that <c>Compose(x, x.Inverse())</c> and\r\n    /// <c>Compose(x.Inverse(), x)</c> are both identity.\r\n    /// </summary>\r\n    public readonly XForm Inverse()\r\n    {\r\n        var invRot = Quaternion.Conjugate(MathQ.Normalize(Rot));\r\n        return new XForm(-Vector3.Transform(Pos, invRot), invRot);\r\n    }\r\n\r\n    /// <summary>\r\n    /// Re-expresses a world transform relative to a parent world transform; the inverse of\r\n    /// <see cref=\"Compose\"/>: <c>ToLocal(p, Compose(p, l)) == l</c>.\r\n    /// </summary>\r\n    public static XForm ToLocal(in XForm parentWorld, in XForm world)\r\n        => Compose(parentWorld.Inverse(), world);\r\n\r\n    /// <summary>Transforms a point from this transform's local space to its outer space.</summary>\r\n    public readonly Vector3 TransformPoint(Vector3 point) => Pos + Vector3.Transform(point, Rot);\r\n\r\n    /// <summary>Rotates a direction vector by this transform's rotation (translation ignored).</summary>\r\n    public readonly Vector3 TransformVector(Vector3 vector) => Vector3.Transform(vector, Rot);\r\n\r\n    /// <inheritdoc />\r\n    public readonly bool Equals(XForm other) => Pos.Equals(other.Pos) && Rot.Equals(other.Rot);\r\n\r\n    /// <inheritdoc />\r\n    public override readonly bool Equals(object? obj) => obj is XForm other && Equals(other);\r\n\r\n    /// <inheritdoc />\r\n    public override readonly int GetHashCode() => HashCode.Combine(Pos, Rot);\r\n\r\n    /// <summary>Componentwise equality (no tolerance).</summary>\r\n    public static bool operator ==(XForm left, XForm right) => left.Equals(right);\r\n\r\n    /// <summary>Componentwise inequality (no tolerance).</summary>\r\n    public static bool operator !=(XForm left, XForm right) => !left.Equals(right);\r\n\r\n    /// <inheritdoc />\r\n    public override readonly string ToString() => $\"XForm(Pos={Pos}, Rot={Rot})\";\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Solve/CanonicalFrames.cs",
            "FileName": "CanonicalFrames.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidRetargeter.Mapping;\r\nusing HumanoidRetargeter.Maths;\r\nusing SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;\r\n\r\nnamespace HumanoidRetargeter.Solve;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>\r\n/// Canonical anatomical frames: one world-space rest basis per mapped <see cref=\"BoneRole\"/>,\r\n/// derived from rest <b>geometry</b> (joint head positions) of any rig plus its mapping.\r\n/// Built with the same deterministic convention on source and target, so world-rotation deltas\r\n/// conjugated through these frames transfer between rigs with different bone local axes\r\n/// (the s&amp;box Citizen rig's local axes encode no anatomy \u2014 bone-Y points chest-forward).\r\n/// </summary>\r\n/// <remarks>\r\n/// <para><b>Frame convention</b> \u2014 for each role the frame quaternion <c>F</c> rotates unit\r\n/// axes onto: <c>X = P</c> (primary), <c>Z</c> = the secondary hint <c>S</c> orthonormalized\r\n/// against <c>P</c>, <c>Y = cross(Z, X)</c> (right-handed; for fingers Y is the curl hinge).</para>\r\n/// <para><b>Primary axis P</b> = normalize(chain-child head \u2212 bone head), where the chain\r\n/// child is the next <i>mapped</i> role down the bone's anatomical chain\r\n/// (Hips\u2192Spine0..4\u2192Neck\u2192Head; Clavicle\u2192UpperArm\u2192LowerArm\u2192Hand; UpperLeg\u2192LowerLeg\u2192Foot\u2192Toe;\r\n/// per-finger Meta\u2192Prox\u2192Mid\u2192Dist). Tips: the Head inherits its previous chain segment\r\n/// (neck\u2192head \u2014 the skull-base axis, real anatomy; measured 0\u201327\u00b0 forward of character up\r\n/// across neutral-rest rigs), falling back to a virtual character-up extension only when\r\n/// that segment is absent or degenerate; Hand points at the midpoint of its mapped finger\r\n/// proximals (else along the forearm); Foot without a toe and Toe extend along character\r\n/// forward; finger distals extend along their previous segment. Other bones with nothing\r\n/// mapped below inherit the previous chain segment's direction.</para>\r\n/// <para><b>Secondary axis S</b> by bone class: spine/neck/head/hips and legs use character\r\n/// forward (knee hinge lateral); clavicle/arms/hands use <c>cross(P, characterUp)</c>\r\n/// (elbow hinge \u22a5 limb in the character's horizontal plane at T-pose), falling back to\r\n/// character forward when P is vertical; feet/toes use character up; fingers use the hand's\r\n/// dorsal palm normal (see <see cref=\"HandGeometry.Dorsal\"/>) so a positive rotation about\r\n/// frame Y curls fingertips toward the palm on both hands.</para>\r\n/// <para>When used by the solver, build the frames on the <see cref=\"RestNormalizer\"/>-\r\n/// normalized rest via <see cref=\"Build(SkeletonModel, MappingResult, IReadOnlyList{XForm})\"/>;\r\n/// this class itself just measures whatever rest it is given.</para>\r\n/// </remarks>\r\npublic sealed class CanonicalFrames\r\n{\r\n    private readonly Dictionary<BoneRole, Quaternion> _frames;\r\n    private readonly HashSet<BoneRole> _virtualPrimary;\r\n\r\n    /// <summary>Character forward (the direction the toes point at rest), unit length.</summary>\r\n    public Vector3 CharacterForward { get; }\r\n\r\n    /// <summary>Character up (hips toward shoulders at rest), unit length.</summary>\r\n    public Vector3 CharacterUp { get; }\r\n\r\n    /// <summary>Rest hip height above the lowest foot/toe point, along character up, cm.</summary>\r\n    public float HipHeight { get; }\r\n\r\n    private CanonicalFrames(\r\n        Dictionary<BoneRole, Quaternion> frames, HashSet<BoneRole> virtualPrimary,\r\n        Vector3 forward, Vector3 up, float hipHeight)\r\n    {\r\n        _frames = frames;\r\n        _virtualPrimary = virtualPrimary;\r\n        CharacterForward = forward;\r\n        CharacterUp = up;\r\n        HipHeight = hipHeight;\r\n    }\r\n\r\n    /// <summary>True when a canonical frame exists for <paramref name=\"role\"/> (the role is\r\n    /// mapped and its chain geometry is resolvable).</summary>\r\n    public bool Has(BoneRole role) => _frames.ContainsKey(role);\r\n\r\n    /// <summary>\r\n    /// True when the role's primary axis is a <b>virtual</b> character-axis extension rather\r\n    /// than real joint geometry (e.g. a Foot with no mapped Toe extends along character\r\n    /// forward; mapped Toes extend along character forward by convention; a Head whose\r\n    /// neck\u2192head segment is degenerate extends along character up). Absolute direction\r\n    /// matching against a virtual primary imposes an arbitrary direction, so the solver\r\n    /// falls back to delta transfer when the source is virtual but the target is real\r\n    /// (see <see cref=\"GeometricSolver\"/> remarks).\r\n    /// </summary>\r\n    public bool HasVirtualPrimary(BoneRole role) => _virtualPrimary.Contains(role);\r\n\r\n    /// <summary>The world-space canonical rest frame of <paramref name=\"role\"/>.</summary>\r\n    /// <exception cref=\"InvalidOperationException\">Thrown when <see cref=\"Has\"/> is false for\r\n    /// the role.</exception>\r\n    public Quaternion WorldFrameOf(BoneRole role)\r\n        => _frames.TryGetValue(role, out var frame)\r\n            ? frame\r\n            : throw new InvalidOperationException($\"No canonical frame for role {role} (not mapped or unresolvable).\");\r\n\r\n    /// <summary>Builds frames from the skeleton's bind rest (<c>skeleton.RestWorld</c>).</summary>\r\n    public static CanonicalFrames Build(SkeletonModel skeleton, MappingResult map)\r\n        => Build(skeleton, map, (skeleton ?? throw new ArgumentNullException(nameof(skeleton))).RestWorld);\r\n\r\n    /// <summary>\r\n    /// Builds frames from explicit rest world transforms (e.g. a <see cref=\"RestPose\"/>\r\n    /// produced by <see cref=\"RestNormalizer\"/>), indexed like <c>skeleton.Bones</c>.\r\n    /// </summary>\r\n    public static CanonicalFrames Build(\r\n        SkeletonModel skeleton, MappingResult map, IReadOnlyList<XForm> worldRest)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(skeleton);\r\n        ArgumentNullException.ThrowIfNull(map);\r\n        ArgumentNullException.ThrowIfNull(worldRest);\r\n        if (worldRest.Count != skeleton.Count)\r\n            throw new ArgumentException(\r\n                $\"worldRest has {worldRest.Count} entries for a {skeleton.Count}-bone skeleton.\");\r\n\r\n        var cf = CharacterFrame.Compute(skeleton, map, worldRest);\r\n        var frames = new Dictionary<BoneRole, Quaternion>();\r\n        var virtualPrimary = new HashSet<BoneRole>();\r\n\r\n        foreach (var (chain, kind, left) in Chains())\r\n            BuildChainFrames(chain, kind, left, map, worldRest, cf, frames, virtualPrimary);\r\n\r\n        return new CanonicalFrames(frames, virtualPrimary, cf.Forward, cf.Up, cf.HipHeight);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- chain construction\r\n\r\n    private enum ChainKind\r\n    {\r\n        Body,\r\n        Arm,\r\n        Leg,\r\n        Finger,\r\n    }\r\n\r\n    private static IEnumerable<(BoneRole[] Chain, ChainKind Kind, bool Left)> Chains()\r\n    {\r\n        yield return (new[]\r\n        {\r\n            BoneRole.Hips, BoneRole.Spine0, BoneRole.Spine1, BoneRole.Spine2, BoneRole.Spine3,\r\n            BoneRole.Spine4, BoneRole.Neck, BoneRole.Head,\r\n        }, ChainKind.Body, false);\r\n\r\n        foreach (var left in new[] { true, false })\r\n        {\r\n            var s = left ? \"L\" : \"R\";\r\n            yield return (new[]\r\n            {\r\n                Role(\"Clavicle\", s), Role(\"UpperArm\", s), Role(\"LowerArm\", s), Role(\"Hand\", s),\r\n            }, ChainKind.Arm, left);\r\n            yield return (new[]\r\n            {\r\n                Role(\"UpperLeg\", s), Role(\"LowerLeg\", s), Role(\"Foot\", s), Role(\"Toe\", s),\r\n            }, ChainKind.Leg, left);\r\n\r\n            foreach (var finger in new[] { \"Thumb\", \"Index\", \"Middle\", \"Ring\", \"Pinky\" })\r\n            {\r\n                yield return (new[]\r\n                {\r\n                    Role(finger + \"Meta\", s), Role(finger + \"Prox\", s),\r\n                    Role(finger + \"Mid\", s), Role(finger + \"Dist\", s),\r\n                }, ChainKind.Finger, left);\r\n            }\r\n        }\r\n    }\r\n\r\n    private static BoneRole Role(string baseName, string side) => Enum.Parse<BoneRole>(baseName + side);\r\n\r\n    private static void BuildChainFrames(\r\n        BoneRole[] chain, ChainKind kind, bool left, MappingResult map,\r\n        IReadOnlyList<XForm> worldRest, CharacterFrame cf, Dictionary<BoneRole, Quaternion> frames,\r\n        HashSet<BoneRole> virtualPrimary)\r\n    {\r\n        // Collapse to the mapped chain members; gaps are skipped so e.g. a missing Spine1\r\n        // makes Spine0 point straight at Spine2.\r\n        var mapped = new List<(BoneRole Role, Vector3 Pos)>(chain.Length);\r\n        foreach (var role in chain)\r\n        {\r\n            if (map.RoleToBone.TryGetValue(role, out var index))\r\n                mapped.Add((role, worldRest[index].Pos));\r\n        }\r\n\r\n        Vector3? dorsal = kind == ChainKind.Finger ? HandGeometry.Dorsal(map, worldRest, left) : null;\r\n\r\n        for (var i = 0; i < mapped.Count; i++)\r\n        {\r\n            var (role, pos) = mapped[i];\r\n            Vector3? prevDir = i > 0 ? pos - mapped[i - 1].Pos : null;\r\n\r\n            var (primary, isVirtual) = i + 1 < mapped.Count\r\n                ? ((Vector3?)(mapped[i + 1].Pos - pos), false)\r\n                : TipPrimary(kind, role, pos, prevDir, left, map, worldRest, cf);\r\n            if (primary is null || primary.Value.LengthSquared() < 1e-8f)\r\n                continue;\r\n\r\n            var secondary = Secondary(kind, role, primary.Value, dorsal, cf);\r\n            frames[role] = BasisFromPrimarySecondary(primary.Value, secondary, cf);\r\n            if (isVirtual)\r\n                virtualPrimary.Add(role);\r\n        }\r\n    }\r\n\r\n    /// <summary>Primary direction for the last mapped bone of a chain. <c>Virtual</c> is true\r\n    /// when the direction is a character-axis convention rather than this rig's real joint\r\n    /// geometry (see <see cref=\"HasVirtualPrimary\"/>).</summary>\r\n    private static (Vector3? Dir, bool Virtual) TipPrimary(\r\n        ChainKind kind, BoneRole role, Vector3 pos, Vector3? prevDir, bool left,\r\n        MappingResult map, IReadOnlyList<XForm> worldRest, CharacterFrame cf)\r\n    {\r\n        switch (kind)\r\n        {\r\n            case ChainKind.Body:\r\n                // Head: its primary is the REAL previous chain segment (neck\u2192head \u2014 the\r\n                // skull-base axis; the rest lean of that segment is head-joint-placement\r\n                // anatomy the delta transfer modes reference, and the posed-rest gaze\r\n                // fallback measures \u2014 see GeometricSolver remarks). Only a degenerate or\r\n                // absent segment falls back to the virtual character-up extension (e.g. a\r\n                // head stacked on the neck). A body chain that ends early keeps its\r\n                // previous segment direction, defaulting to up.\r\n                if (role == BoneRole.Head)\r\n                    return prevDir is { } seg && seg.LengthSquared() >= 1e-8f ? (seg, false) : (cf.Up, true);\r\n                return prevDir is not null ? (prevDir, false) : (cf.Up, true);\r\n\r\n            case ChainKind.Arm:\r\n                if (role is BoneRole.HandL or BoneRole.HandR)\r\n                {\r\n                    var knuckles = HandGeometry.FingerProximalMidpoint(map, worldRest, left);\r\n                    if (knuckles is not null)\r\n                        return (knuckles.Value - pos, false);\r\n                }\r\n                return (prevDir, false); // along the forearm / previous segment; null \u2192 no frame\r\n\r\n            case ChainKind.Leg:\r\n                // Foot without a mapped toe, and the toe itself, extend along character\r\n                // forward (toes point forward by the character-frame convention).\r\n                if (role is BoneRole.FootL or BoneRole.FootR or BoneRole.ToeL or BoneRole.ToeR)\r\n                    return (cf.Forward, true);\r\n                return (prevDir, false);\r\n\r\n            case ChainKind.Finger:\r\n                if (prevDir is not null)\r\n                    return (prevDir, false); // distal tip extrapolates its previous segment\r\n                // Single mapped finger bone: point away from the hand when possible.\r\n                var handRole = left ? BoneRole.HandL : BoneRole.HandR;\r\n                if (map.RoleToBone.TryGetValue(handRole, out var handIndex))\r\n                    return (pos - worldRest[handIndex].Pos, false);\r\n                return (null, false);\r\n\r\n            default:\r\n                return (null, false);\r\n        }\r\n    }\r\n\r\n    /// <summary>Secondary (Z) hint by bone class; see the class remarks for rationale.</summary>\r\n    private static Vector3 Secondary(ChainKind kind, BoneRole role, Vector3 primary, Vector3? dorsal, CharacterFrame cf)\r\n    {\r\n        switch (kind)\r\n        {\r\n            case ChainKind.Body:\r\n                return cf.Forward;\r\n\r\n            case ChainKind.Arm:\r\n            {\r\n                var hinge = Vector3.Cross(Vector3.Normalize(primary), cf.Up);\r\n                return hinge.LengthSquared() < 1e-6f ? cf.Forward : hinge;\r\n            }\r\n\r\n            case ChainKind.Leg:\r\n                // Feet and toes lie near the character-forward direction, so they use up as\r\n                // the secondary; thigh/calf use forward (knee hinge lateral).\r\n                if (role is BoneRole.FootL or BoneRole.FootR or BoneRole.ToeL or BoneRole.ToeR)\r\n                    return cf.Up;\r\n                return cf.Forward;\r\n\r\n            case ChainKind.Finger:\r\n                if (dorsal is not null)\r\n                    return dorsal.Value;\r\n                var fallback = Vector3.Cross(Vector3.Normalize(primary), cf.Up);\r\n                return fallback.LengthSquared() < 1e-6f ? cf.Forward : fallback;\r\n\r\n            default:\r\n                return cf.Forward;\r\n        }\r\n    }\r\n\r\n    /// <summary>\r\n    /// Orthonormal right-handed basis: <c>X = normalize(primary)</c>, <c>Z = secondary</c>\r\n    /// Gram-Schmidt-orthonormalized against X (falling back to character forward, then up,\r\n    /// then world axes when degenerate), <c>Y = cross(Z, X)</c>.\r\n    /// </summary>\r\n    private static Quaternion BasisFromPrimarySecondary(Vector3 primary, Vector3 secondary, CharacterFrame cf)\r\n    {\r\n        var x = Vector3.Normalize(primary);\r\n\r\n        var z = Orthonormalized(secondary, x)\r\n            ?? Orthonormalized(cf.Forward, x)\r\n            ?? Orthonormalized(cf.Up, x)\r\n            ?? Orthonormalized(Vector3.UnitZ, x)\r\n            ?? Orthonormalized(Vector3.UnitX, x)!.Value;\r\n\r\n        var y = Vector3.Cross(z, x);\r\n\r\n        // System.Numerics matrices act on row vectors: the rows are the images of the unit\r\n        // axes under the rotation (row1 = R*X, row2 = R*Y, row3 = R*Z).\r\n        var m = new Matrix4x4(\r\n            x.X, x.Y, x.Z, 0f,\r\n            y.X, y.Y, y.Z, 0f,\r\n            z.X, z.Y, z.Z, 0f,\r\n            0f, 0f, 0f, 1f);\r\n\r\n        return MathQ.Normalize(Quaternion.CreateFromRotationMatrix(m));\r\n    }\r\n\r\n    private static Vector3? Orthonormalized(Vector3 hint, Vector3 x)\r\n    {\r\n        var z = hint - x * Vector3.Dot(hint, x);\r\n        return z.LengthSquared() < 1e-6f ? null : Vector3.Normalize(z);\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Solve/HandGeometry.cs",
            "FileName": "HandGeometry.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidRetargeter.Mapping;\r\nusing HumanoidRetargeter.Maths;\r\n\r\nnamespace HumanoidRetargeter.Solve;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)\r\n\r\n/// <summary>\r\n/// Hand rest-geometry helpers shared by <see cref=\"CanonicalFrames\"/> (finger secondary axes)\r\n/// and <see cref=\"RestNormalizer\"/> (palm-down roll correction). Everything derives from joint\r\n/// positions only \u2014 bone local axes carry no anatomical meaning on the s&amp;box rig.\r\n/// </summary>\r\ninternal static class HandGeometry\r\n{\r\n    private static readonly BoneRole[] LeftProximals =\r\n    {\r\n        BoneRole.ThumbProxL, BoneRole.IndexProxL, BoneRole.MiddleProxL, BoneRole.RingProxL, BoneRole.PinkyProxL,\r\n    };\r\n\r\n    private static readonly BoneRole[] RightProximals =\r\n    {\r\n        BoneRole.ThumbProxR, BoneRole.IndexProxR, BoneRole.MiddleProxR, BoneRole.RingProxR, BoneRole.PinkyProxR,\r\n    };\r\n\r\n    // Index \u2192 pinky order; the knuckle line is taken from the first and last mapped of these.\r\n    private static readonly BoneRole[] LeftNonThumbProximals =\r\n    {\r\n        BoneRole.IndexProxL, BoneRole.MiddleProxL, BoneRole.RingProxL, BoneRole.PinkyProxL,\r\n    };\r\n\r\n    private static readonly BoneRole[] RightNonThumbProximals =\r\n    {\r\n        BoneRole.IndexProxR, BoneRole.MiddleProxR, BoneRole.RingProxR, BoneRole.PinkyProxR,\r\n    };\r\n\r\n    /// <summary>\r\n    /// Midpoint of all mapped finger proximal heads of one hand (the hand's anatomical\r\n    /// \"chain child\" point), or null when no finger proximal is mapped.\r\n    /// </summary>\r\n    public static Vector3? FingerProximalMidpoint(MappingResult map, IReadOnlyList<XForm> worldRest, bool left)\r\n    {\r\n        var sum = Vector3.Zero;\r\n        var count = 0;\r\n        foreach (var role in left ? LeftProximals : RightProximals)\r\n        {\r\n            if (map.RoleToBone.TryGetValue(role, out var index))\r\n            {\r\n                sum += worldRest[index].Pos;\r\n                count++;\r\n            }\r\n        }\r\n        return count > 0 ? sum / count : null;\r\n    }\r\n\r\n    /// <summary>\r\n    /// Dorsal palm normal of one hand: the unit vector pointing out of the <b>back</b> of the\r\n    /// hand (away from the palm), or null when the hand/finger geometry is unmapped or\r\n    /// degenerate.\r\n    /// </summary>\r\n    /// <remarks>\r\n    /// Formula (mirror-consistent by construction, verified on the ActorCore fixture by the\r\n    /// finger-curl test): <c>dorsal = sideSign \u00b7 cross(knuckle, fingerDir)</c> with\r\n    /// <c>sideSign = +1</c> left / <c>\u22121</c> right, <c>knuckle = IndexProx.head \u2212\r\n    /// PinkyProx.head</c> (first/last mapped non-thumb proximal), and <c>fingerDir =\r\n    /// FingerProximalMidpoint \u2212 Hand.head</c>. On every fixture rig the thumb proximal lies on\r\n    /// the \u2212dorsal (palmar) side of the hand plane, grounding the sign anatomically. A positive\r\n    /// rotation about a finger frame's hinge axis (frame Y = cross(dorsal, fingerChainDir))\r\n    /// curls the fingertip toward the palm on <b>both</b> hands.\r\n    /// </remarks>\r\n    public static Vector3? Dorsal(MappingResult map, IReadOnlyList<XForm> worldRest, bool left)\r\n    {\r\n        if (!map.RoleToBone.TryGetValue(left ? BoneRole.HandL : BoneRole.HandR, out var handIndex))\r\n            return null;\r\n        var hand = worldRest[handIndex].Pos;\r\n\r\n        var nonThumb = left ? LeftNonThumbProximals : RightNonThumbProximals;\r\n        Vector3? first = null, last = null;\r\n        foreach (var role in nonThumb)\r\n        {\r\n            if (!map.RoleToBone.TryGetValue(role, out var index))\r\n                continue;\r\n            first ??= worldRest[index].Pos;\r\n            last = worldRest[index].Pos;\r\n        }\r\n        if (first is null || last is null || (first.Value - last.Value).LengthSquared() < 1e-8f)\r\n            return null;\r\n\r\n        var midpoint = FingerProximalMidpoint(map, worldRest, left);\r\n        if (midpoint is null)\r\n            return null;\r\n\r\n        var knuckle = first.Value - last.Value;\r\n        var fingerDir = midpoint.Value - hand;\r\n        var raw = Vector3.Cross(knuckle, fingerDir) * (left ? 1f : -1f);\r\n        return raw.LengthSquared() < 1e-8f ? null : Vector3.Normalize(raw);\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "Code/HumanoidRetargeter/Solve/TargetRigMapping.cs",
            "FileName": "TargetRigMapping.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\nusing HumanoidRetargeter.Mapping;\r\nusing HumanoidRetargeter.Target;\r\n\r\nnamespace HumanoidRetargeter.Solve;\r\n\r\n/// <summary>\r\n/// Bridges a <see cref=\"TargetRig\"/>'s role annotations into the <see cref=\"MappingResult\"/>\r\n/// shape shared with source mappings, so target-side machinery (rest normalization, canonical\r\n/// frames) can run on the exact same code paths as the source side.\r\n/// </summary>\r\npublic static class TargetRigMappingExtensions\r\n{\r\n    /// <summary>Role \u2192 target bone index mapping of the rig's annotated animated bones.</summary>\r\n    public static MappingResult ToMappingResult(this TargetRig rig)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(rig);\r\n\r\n        var map = new MappingResult(rig.Name, MappingSource.Preset) { Confidence = 1f };\r\n        for (var i = 0; i < rig.Skeleton.Count; i++)\r\n        {\r\n            if (rig.RoleOf(i) is BoneRole role)\r\n                map.RoleToBone[role] = i;\r\n        }\r\n        return map;\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_retargeter",
            "Path": "HumanoidRetargeter/Assembly.cs",
            "FileName": "Assembly.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 311783,
            "Code": "#nullable enable annotations\r\n\r\n// Global usings for the s&box in-engine compiler.\r\n//\r\n// The plain net8.0 dev harness gets these automatically via <ImplicitUsings>,\r\n// but s&box's compiler injects no BCL usings at all - without this file the\r\n// library fails to compile inside the editor (CS0246 on List<>, IEnumerable<>,\r\n// FormatException, ...). Duplicating the SDK's implicit set is harmless there\r\n// (verified: no warnings).\r\n\r\nglobal using System;\r\nglobal using System.Collections.Generic;\r\nglobal using System.IO;\r\nglobal using System.Linq;\r\nglobal using System.Threading;\r\nglobal using System.Threading.Tasks;\r\n\r\n// NOTE on Vector3: s&box declares its own Vector3 in the *global namespace*,\r\n// which wins over `using System.Numerics;` imports during name lookup and\r\n// breaks this System.Numerics-based core (.X/.Y/.Z, static helpers, delegate\r\n// signatures). A global using-alias does NOT fix this (CS0576: alias conflicts\r\n// with the global-namespace type at every use site). The working fix is a\r\n// *namespace-scoped* alias, declared after the file-scoped namespace line:\r\n//\r\n//     namespace HumanoidRetargeter.Xyz;\r\n//     using Vector3 = System.Numerics.Vector3;\r\n//\r\n// Every file in this tree that uses the simple name Vector3 carries that line.\r\n// (Quaternion and Matrix4x4 are not global-namespace types in s&box and need\r\n// no alias.)\r\n"
        }
    ]
}