Code/HumanoidRetargeter/Core/Target/AugmentOptions.cs

Data container for options passed to a Vmdl augmenter. It stores settings like DefaultRootBone, NeutralizePinkyConstraints, grouping specs (LocomotionSets), the DMX folder path (DmxFolderRelative), and a list of animation source paths known to be missing (MissingSourceFiles).

#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Linq;

namespace HumanoidRetargeter.Core.Target;

/// <summary>Options for <see cref="VmdlAugmenter.Augment"/>.</summary>
public sealed class AugmentOptions
{
    /// <summary>
    /// <c>default_root_bone_name</c> used when the vmdl has NO AnimationList yet (the value
    /// also seeds ExtractMotion nodes' <c>root_bone_name</c>) — same value the standalone
    /// writer uses (<see cref="RetargetTargetSpec.DefaultRootBone"/>). An existing
    /// AnimationList's own non-empty value always wins.
    /// </summary>
    public string DefaultRootBone { get; init; } = "";

    /// <summary>
    /// When true, every CopyPinky ring→pinky constraint in the vmdl (the citizen base
    /// model's transitional <c>AnimConstraintOrient</c> folder, or any constraint whose
    /// driven bone is a <c>finger_pinky_*</c>) gets its weights set to 0, so the exported
    /// pinky channels are no longer overridden at runtime. Idempotent; everything else in
    /// the document is untouched.
    /// </summary>
    public bool NeutralizePinkyConstraints { get; init; }

    /// <summary>
    /// Detected locomotion families to splice as Folder + 2DBlend groups (see
    /// <see cref="LocomotionSetDetector"/>). Each set's member entries are grouped under a
    /// Folder named <see cref="LocomotionSetSpec.FolderName"/> instead of being spliced at
    /// the AnimationList top level. Pipeline-owned locomotion folders are rebuilt AS A UNIT:
    /// a previous run's same-named folder is replaced wholesale when every AnimFile inside
    /// it is pipeline-owned per <see cref="DmxFolderRelative"/> — even when the new batch is
    /// a SHRUNKEN family (e.g. 8-way re-run as 4-way), whose stale members are simply
    /// dropped with the old folder. A hand-edited or foreign folder is never destroyed (it
    /// throws <see cref="VmdlAugmentException"/> instead). Null/empty = no grouping.
    /// </summary>
    public IReadOnlyList<LocomotionSetSpec>? LocomotionSets { get; init; }

    /// <summary>
    /// Assets-relative folder this batch's DMX files live in (back- or forward slashes,
    /// trailing slash ignored). Decides which existing nodes are PIPELINE-OWNED — an
    /// AnimFile whose <c>source_filename</c> sits under this folder — mirroring the
    /// name-seeding ownership rules in <c>Retargeter.ConvertBatch</c>, so a folder the
    /// batch's collision seeding treated as replaceable is also replaceable here. Empty
    /// (default) = only sources with no directory component count as ours.
    /// </summary>
    public string DmxFolderRelative { get; init; } = "";

    /// <summary>
    /// Animation source paths (<c>source_filename</c> values, assets-relative, either slash
    /// direction) the IO-owning caller knows are MISSING on disk. Existing AnimFile nodes
    /// referencing them are removed from the augmented output — a single unresolvable source
    /// fails the whole vmdl recompile (<c>Node 'X' resolve failure</c>), so a stale entry
    /// left over from an earlier batch (its DMX deleted or moved) would take every animation
    /// in the model down with it. Nodes whose name this batch (re)writes are exempt — their
    /// DMX is about to exist. 2DBlend nodes referencing a pruned sequence and Folder nodes
    /// emptied by the pruning are removed with it. Null/empty = keep everything (default;
    /// the augmenter itself never touches the filesystem).
    /// </summary>
    public IReadOnlyCollection<string>? MissingSourceFiles { get; init; }
}