Skip to main content

animsmith_engine/
types.rs

1use animsmith_core::{InputIdentity, SourceFormatV1};
2use std::collections::BTreeMap;
3
4/// Exact four-field key selecting one revisioned engine profile.
5#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
6pub struct ProfileSelection {
7    family: String,
8    profile_revision: u32,
9    engine_version: String,
10    importer: String,
11}
12
13impl ProfileSelection {
14    /// Construct an exact profile selection without aliases or version ranges.
15    pub fn new(
16        family: impl Into<String>,
17        profile_revision: u32,
18        engine_version: impl Into<String>,
19        importer: impl Into<String>,
20    ) -> Self {
21        Self {
22            family: family.into(),
23            profile_revision,
24            engine_version: engine_version.into(),
25            importer: importer.into(),
26        }
27    }
28
29    /// Stable profile family id.
30    pub fn family(&self) -> &str {
31        &self.family
32    }
33
34    /// Exact profile revision.
35    pub const fn profile_revision(&self) -> u32 {
36        self.profile_revision
37    }
38
39    /// Exact target engine version.
40    pub fn engine_version(&self) -> &str {
41        &self.engine_version
42    }
43
44    /// Exact importer id.
45    pub fn importer(&self) -> &str {
46        &self.importer
47    }
48}
49
50/// Stable id for one immutable fact in every V1 profile record.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
52pub enum FactId {
53    /// Bounded AnimSmith input containers accepted by the profile.
54    AcceptedInputs,
55    /// How imported animation assets are addressed.
56    AnimationAddressability,
57    /// Target coordinate basis.
58    TargetCoordinateBasis,
59    /// Target linear unit.
60    TargetLinearUnit,
61    /// Importer's source-to-target unit-conversion control.
62    UnitConversionControl,
63    /// Importer's source-to-target axis-conversion control.
64    AxisConversionControl,
65    /// Exact axis-conversion transform.
66    ExactAxisConversion,
67    /// Resulting imported hierarchy scale.
68    ResultingHierarchyScale,
69    /// Whether a whole end frame is required at clip boundaries.
70    WholeEndFrameRequired,
71    /// Import handling of animation channels.
72    AnimationChannelHandling,
73    /// Import handling of source extensions.
74    ExtensionHandling,
75    /// Import handling of source constructs.
76    ConstructHandling,
77    /// How imported animation targets are addressed.
78    AnimationTargetAddressability,
79    /// How root-motion sources are addressed.
80    RootMotionAddressability,
81}
82
83impl FactId {
84    /// Stable digest/source-reference spelling.
85    pub const fn as_str(self) -> &'static str {
86        match self {
87            Self::AcceptedInputs => "accepted_inputs",
88            Self::AnimationAddressability => "animation_addressability",
89            Self::TargetCoordinateBasis => "target_coordinate_basis",
90            Self::TargetLinearUnit => "target_linear_unit",
91            Self::UnitConversionControl => "unit_conversion_control",
92            Self::AxisConversionControl => "axis_conversion_control",
93            Self::ExactAxisConversion => "exact_axis_conversion",
94            Self::ResultingHierarchyScale => "resulting_hierarchy_scale",
95            Self::WholeEndFrameRequired => "whole_end_frame_required",
96            Self::AnimationChannelHandling => "animation_channel_handling",
97            Self::ExtensionHandling => "extension_handling",
98            Self::ConstructHandling => "construct_handling",
99            Self::AnimationTargetAddressability => "animation_target_addressability",
100            Self::RootMotionAddressability => "root_motion_addressability",
101        }
102    }
103}
104
105/// Coordinate-system handedness.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
107pub enum Handedness {
108    /// Left-handed coordinates.
109    Left,
110    /// Right-handed coordinates.
111    Right,
112}
113
114/// Positive world axis used as up.
115#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
116pub enum UpAxis {
117    /// Positive X is up.
118    X,
119    /// Positive Y is up.
120    Y,
121    /// Positive Z is up.
122    Z,
123}
124
125/// Known target coordinate basis.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
127pub struct CoordinateBasis {
128    /// Coordinate-system handedness.
129    pub handedness: Handedness,
130    /// Positive world up axis.
131    pub up_axis: UpAxis,
132    /// Signed target forward axis.
133    pub forward_axis: ForwardAxis,
134}
135
136/// Signed world axis used as forward.
137#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
138pub enum ForwardAxis {
139    /// Positive X is forward.
140    PositiveX,
141    /// Negative X is forward.
142    NegativeX,
143    /// Positive Y is forward.
144    PositiveY,
145    /// Negative Y is forward.
146    NegativeY,
147    /// Positive Z is forward.
148    PositiveZ,
149    /// Negative Z is forward.
150    NegativeZ,
151}
152
153/// Known target linear unit.
154#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
155pub enum LinearUnit {
156    /// Metre.
157    Metre,
158    /// Centimetre.
159    Centimetre,
160}
161
162/// Stable setting id in the closed V1 vocabulary.
163#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
164pub enum SettingId {
165    /// Unity document-level unit-conversion toggle.
166    ConvertUnits,
167    /// Unity document-level axis-baking toggle.
168    BakeAxisConversion,
169    /// Unity Generic document-level exact source-transform path.
170    RootMotionSource,
171    /// Unity per-clip root-rotation policy.
172    RootRotation,
173    /// Unity per-clip vertical root-position policy.
174    RootPositionY,
175    /// Unity per-clip horizontal root-position policy.
176    RootPositionXz,
177}
178
179impl SettingId {
180    /// Stable public configuration spelling.
181    pub const fn as_str(self) -> &'static str {
182        match self {
183            Self::ConvertUnits => "convert_units",
184            Self::BakeAxisConversion => "bake_axis_conversion",
185            Self::RootMotionSource => "root_motion_source",
186            Self::RootRotation => "root_rotation",
187            Self::RootPositionY => "root_position_y",
188            Self::RootPositionXz => "root_position_xz",
189        }
190    }
191
192    pub(crate) fn from_str(value: &str) -> Option<Self> {
193        match value {
194            "convert_units" => Some(Self::ConvertUnits),
195            "bake_axis_conversion" => Some(Self::BakeAxisConversion),
196            "root_motion_source" => Some(Self::RootMotionSource),
197            "root_rotation" => Some(Self::RootRotation),
198            "root_position_y" => Some(Self::RootPositionY),
199            "root_position_xz" => Some(Self::RootPositionXz),
200            _ => None,
201        }
202    }
203}
204
205impl std::fmt::Display for SettingId {
206    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        formatter.write_str(self.as_str())
208    }
209}
210
211/// A known importer control relevant to source-to-target conversion.
212#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
213pub enum ConversionControl {
214    /// Behavior is controlled by one declared profile setting.
215    ProfileSetting(SettingId),
216    /// Behavior is exposed by the importer but is not a V1 profile setting.
217    ImporterOption,
218}
219
220/// Known importer treatment for a fact domain.
221#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
222pub enum ImportHandling {
223    /// The importer retains the domain.
224    Preserved,
225    /// The importer converts the domain.
226    Converted,
227    /// The importer discards the domain.
228    Discarded,
229    /// The importer does not support the domain.
230    Unsupported,
231}
232
233/// Known animation-target addressability behavior.
234#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
235pub enum TargetAddressability {
236    /// Targets are addressed by a stable id derived from their name path.
237    NamePathDerivedId,
238}
239
240/// Known animation-asset addressability behavior.
241#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
242pub enum AnimationAddressability {
243    /// Bevy addresses each glTF animation by its source-array index through
244    /// `GltfAssetLabel::Animation(index)`; animation names populate Bevy's
245    /// separate named-animation map rather than this typed label.
246    GltfAssetLabel,
247}
248
249/// Known root-motion addressability behavior.
250#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
251pub enum RootMotionAddressability {
252    /// A bounded exact source-transform path selects the motion node.
253    ExactSourceTransformPath,
254    /// Humanoid Avatar/body semantics determine root motion.
255    HumanoidAvatarBody,
256}
257
258/// Typed value of a known immutable profile fact.
259#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
260pub enum FactValue {
261    /// Exact accepted AnimSmith input formats.
262    AcceptedFormats(Vec<SourceFormatV1>),
263    /// Animation-asset addressability.
264    AnimationAddressability(AnimationAddressability),
265    /// Target coordinate basis.
266    CoordinateBasis(CoordinateBasis),
267    /// Target linear unit.
268    LinearUnit(LinearUnit),
269    /// Source-to-target conversion control.
270    ConversionControl(ConversionControl),
271    /// Boolean predicate.
272    Boolean(bool),
273    /// Import handling of a domain.
274    ImportHandling(ImportHandling),
275    /// Animation-target addressability.
276    TargetAddressability(TargetAddressability),
277    /// Root-motion addressability.
278    RootMotionAddressability(RootMotionAddressability),
279}
280
281/// Evidence state of one profile fact.
282#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
283pub enum FactState {
284    /// Supported known value.
285    Known(FactValue),
286    /// Primary evidence does not establish a value.
287    Unknown,
288    /// The fact domain genuinely does not apply.
289    NotApplicable,
290}
291
292/// One stable fact and its explicit evidence state.
293#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
294pub struct ProfileFact {
295    id: FactId,
296    state: FactState,
297}
298
299impl ProfileFact {
300    pub(crate) fn new(id: FactId, state: FactState) -> Self {
301        Self { id, state }
302    }
303
304    /// Stable fact id.
305    pub const fn id(&self) -> FactId {
306        self.id
307    }
308
309    /// Explicit known, unknown, or not-applicable state.
310    pub const fn state(&self) -> &FactState {
311        &self.state
312    }
313}
314
315/// Configuration scope of a setting.
316#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
317pub enum SettingScope {
318    /// One value governs the imported document and all clips.
319    Document,
320    /// One materialized value is required for each real clip.
321    Clip,
322}
323
324/// Closed value domain of a setting.
325#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
326pub enum SettingDomain {
327    /// Boolean value.
328    Boolean,
329    /// `bake` or `extract`.
330    BakeOrExtract,
331    /// Bounded exact source-transform path.
332    SourceTransformPath,
333}
334
335/// Whether a descriptor applies to a profile revision.
336#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
337pub enum SettingApplicability {
338    /// The setting applies.
339    Applicable,
340    /// The setting genuinely does not apply.
341    NotApplicable,
342}
343
344/// Verified default status of a setting.
345#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
346pub enum DefaultStatus {
347    /// The caller must declare a value because no default is verified.
348    RequiredWithoutDefault,
349    /// Default behavior is irrelevant because the setting does not apply.
350    NotApplicable,
351}
352
353/// Immutable descriptor for one stable setting id.
354#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
355pub struct SettingDescriptor {
356    id: SettingId,
357    scope: SettingScope,
358    domain: SettingDomain,
359    applicability: SettingApplicability,
360    default_status: DefaultStatus,
361}
362
363impl SettingDescriptor {
364    pub(crate) const fn new(
365        id: SettingId,
366        scope: SettingScope,
367        domain: SettingDomain,
368        applicability: SettingApplicability,
369        default_status: DefaultStatus,
370    ) -> Self {
371        Self {
372            id,
373            scope,
374            domain,
375            applicability,
376            default_status,
377        }
378    }
379
380    /// Stable setting id.
381    pub const fn id(&self) -> SettingId {
382        self.id
383    }
384
385    /// Required declaration scope.
386    pub const fn scope(&self) -> SettingScope {
387        self.scope
388    }
389
390    /// Closed value domain.
391    pub const fn domain(&self) -> SettingDomain {
392        self.domain
393    }
394
395    /// Applicability to this exact profile revision.
396    pub const fn applicability(&self) -> SettingApplicability {
397        self.applicability
398    }
399
400    /// Verified default status.
401    pub const fn default_status(&self) -> DefaultStatus {
402        self.default_status
403    }
404}
405
406/// Exact policy for a Unity root transform component.
407#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
408pub enum BakeOrExtract {
409    /// Bake the component into the pose.
410    Bake,
411    /// Extract the component as root motion.
412    Extract,
413}
414
415/// Closed public value vocabulary for engine settings.
416#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
417pub enum SettingValue {
418    /// Boolean setting value.
419    Boolean(bool),
420    /// Root-component bake/extract policy.
421    BakeOrExtract(BakeOrExtract),
422    /// Exact source-transform path, validated during static resolution.
423    SourceTransformPath(String),
424}
425
426/// String-keyed setting declarations used at the TOML-free public boundary.
427pub type SettingMap = BTreeMap<String, SettingValue>;
428
429/// Full public input to phase-one static resolution.
430#[derive(Debug, Clone, Default, PartialEq, Eq)]
431pub struct EngineDeclaration {
432    /// Exact profile selection, or none for engine-neutral behavior.
433    pub selection: Option<ProfileSelection>,
434    /// Declared document settings. `Some` retains an explicitly empty table.
435    pub document_settings: Option<SettingMap>,
436    /// Selector-keyed per-clip setting declarations.
437    pub clip_settings: BTreeMap<String, SettingMap>,
438}
439
440/// One primary source retained by an immutable profile record.
441#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
442pub struct PrimarySource {
443    id: &'static str,
444    target_version: &'static str,
445    url: &'static str,
446    verified_on: &'static str,
447    supported_facts: Vec<FactId>,
448    supported_settings: Vec<SettingId>,
449}
450
451impl PrimarySource {
452    pub(crate) fn new(
453        id: &'static str,
454        target_version: &'static str,
455        url: &'static str,
456        verified_on: &'static str,
457        mut supported_facts: Vec<FactId>,
458        mut supported_settings: Vec<SettingId>,
459    ) -> Self {
460        supported_facts.sort_by_key(|id| id.as_str());
461        supported_settings.sort_by_key(|id| id.as_str());
462        Self {
463            id,
464            target_version,
465            url,
466            verified_on,
467            supported_facts,
468            supported_settings,
469        }
470    }
471
472    /// Stable source id.
473    pub const fn id(&self) -> &'static str {
474        self.id
475    }
476
477    /// Version of the source's target product/specification.
478    pub const fn target_version(&self) -> &'static str {
479        self.target_version
480    }
481
482    /// Primary-source URL.
483    pub const fn url(&self) -> &'static str {
484        self.url
485    }
486
487    /// ISO date on which this source was verified.
488    pub const fn verified_on(&self) -> &'static str {
489        self.verified_on
490    }
491
492    /// Stable fact ids supported by the source.
493    pub fn supported_facts(&self) -> &[FactId] {
494        &self.supported_facts
495    }
496
497    /// Stable setting ids supported by the source.
498    pub fn supported_settings(&self) -> &[SettingId] {
499        &self.supported_settings
500    }
501
502    #[cfg(test)]
503    pub(crate) fn reverse_supported_ids_for_test(&mut self) {
504        self.supported_facts.reverse();
505        self.supported_settings.reverse();
506    }
507}
508
509/// One immutable, revisioned registry profile.
510#[derive(Debug, Clone, PartialEq, Eq)]
511pub struct EngineProfile {
512    pub(crate) selection: ProfileSelection,
513    pub(crate) fact_bundle_urn: &'static str,
514    pub(crate) facts: Vec<ProfileFact>,
515    pub(crate) settings: Vec<SettingDescriptor>,
516    pub(crate) sources: Vec<PrimarySource>,
517    pub(crate) facts_identity: InputIdentity,
518}
519
520impl EngineProfile {
521    /// Full exact selection tuple retained by this profile.
522    pub const fn selection(&self) -> &ProfileSelection {
523        &self.selection
524    }
525
526    /// Revisioned fact-bundle URN.
527    pub const fn fact_bundle_urn(&self) -> &'static str {
528        self.fact_bundle_urn
529    }
530
531    /// Bounded AnimSmith V1 input formats accepted by the profile.
532    pub fn accepted_inputs(&self) -> &[SourceFormatV1] {
533        match self.fact(FactId::AcceptedInputs).map(ProfileFact::state) {
534            Some(FactState::Known(FactValue::AcceptedFormats(formats))) => formats,
535            _ => &[],
536        }
537    }
538
539    /// Complete typed fact inventory in stable-id order.
540    pub fn facts(&self) -> &[ProfileFact] {
541        &self.facts
542    }
543
544    /// Complete descriptor inventory in stable-id order.
545    pub fn setting_descriptors(&self) -> &[SettingDescriptor] {
546        &self.settings
547    }
548
549    /// Primary sources in stable-id order.
550    pub fn sources(&self) -> &[PrimarySource] {
551        &self.sources
552    }
553
554    /// SHA-256 plus byte count of the canonical immutable record encoding.
555    pub const fn facts_identity(&self) -> &InputIdentity {
556        &self.facts_identity
557    }
558
559    /// Look up one fact by stable id.
560    pub fn fact(&self, id: FactId) -> Option<&ProfileFact> {
561        self.facts.iter().find(|fact| fact.id == id)
562    }
563
564    /// Look up one setting descriptor, including not-applicable descriptors.
565    pub fn setting_descriptor(&self, id: SettingId) -> Option<&SettingDescriptor> {
566        self.settings.iter().find(|descriptor| descriptor.id == id)
567    }
568}
569
570/// Stable setting id in the revision-2 engine-profile vocabulary.
571///
572/// This is intentionally distinct from [`SettingId`]: the V1 ids and their
573/// core wire projection are immutable and cannot represent verified defaults,
574/// Unity's closed import modes, or Bevy's closed load-time environment.
575#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
576pub enum SettingIdV2 {
577    /// Unity animation import mode.
578    AnimationType,
579    /// Unity avatar construction policy.
580    AvatarSetup,
581    /// Unity animation import toggle.
582    ImportAnimation,
583    /// Unity Generic exact source-transform path.
584    RootMotionSource,
585    /// Unity per-clip root-rotation policy.
586    RootRotation,
587    /// Unity per-clip vertical root-position policy.
588    RootPositionY,
589    /// Unity per-clip horizontal root-position policy.
590    RootPositionXz,
591    /// Per-load coordinate conversion of the loader-created scene entity.
592    RotateSceneEntity,
593    /// Per-load coordinate conversion of mesh assets and primitive entities.
594    RotateMeshes,
595    /// Per-load mesh usage flags, represented by their empty/nonempty state.
596    LoadMeshes,
597    /// Exact glTF extension-handler registry installed at load time.
598    ExtensionHandlerEnvironment,
599    /// Whether Bevy was compiled with its `bevy_animation` feature.
600    BevyAnimationFeature,
601    /// Per-load animation loading toggle.
602    LoadAnimations,
603    /// Godot scene-import animation bake frequency.
604    AnimationFps,
605    /// Godot scene-import animation trimming toggle.
606    AnimationTrimming,
607    /// Unreal FBX importer sample-rate policy.
608    SampleRate,
609}
610
611impl SettingIdV2 {
612    /// Stable public configuration spelling.
613    pub const fn as_str(self) -> &'static str {
614        match self {
615            Self::AnimationType => "animation_type",
616            Self::AvatarSetup => "avatar_setup",
617            Self::ImportAnimation => "import_animation",
618            Self::RootMotionSource => "root_motion_source",
619            Self::RootRotation => "root_rotation",
620            Self::RootPositionY => "root_position_y",
621            Self::RootPositionXz => "root_position_xz",
622            Self::RotateSceneEntity => "rotate_scene_entity",
623            Self::RotateMeshes => "rotate_meshes",
624            Self::LoadMeshes => "load_meshes",
625            Self::ExtensionHandlerEnvironment => "extension_handler_environment",
626            Self::BevyAnimationFeature => "bevy_animation_feature",
627            Self::LoadAnimations => "load_animations",
628            Self::AnimationFps => "animation_fps",
629            Self::AnimationTrimming => "animation_trimming",
630            Self::SampleRate => "sample_rate",
631        }
632    }
633
634    pub(crate) fn from_str_v2(value: &str) -> Option<Self> {
635        match value {
636            "animation_type" => Some(Self::AnimationType),
637            "avatar_setup" => Some(Self::AvatarSetup),
638            "import_animation" => Some(Self::ImportAnimation),
639            "root_motion_source" => Some(Self::RootMotionSource),
640            "root_rotation" => Some(Self::RootRotation),
641            "root_position_y" => Some(Self::RootPositionY),
642            "root_position_xz" => Some(Self::RootPositionXz),
643            "rotate_scene_entity" => Some(Self::RotateSceneEntity),
644            "rotate_meshes" => Some(Self::RotateMeshes),
645            "load_meshes" => Some(Self::LoadMeshes),
646            "extension_handler_environment" => Some(Self::ExtensionHandlerEnvironment),
647            "bevy_animation_feature" => Some(Self::BevyAnimationFeature),
648            "load_animations" => Some(Self::LoadAnimations),
649            "animation_fps" => Some(Self::AnimationFps),
650            "animation_trimming" => Some(Self::AnimationTrimming),
651            "sample_rate" => Some(Self::SampleRate),
652            _ => None,
653        }
654    }
655}
656
657/// Exact Unreal FBX animation sample-rate policy.
658#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
659pub enum UnrealSampleRateV2 {
660    /// `bUseDefaultSampleRate=true` with no custom-rate value.
661    Default30,
662    /// `bUseDefaultSampleRate=false, CustomSampleRate=0`.
663    SourceDetermined,
664    /// `bUseDefaultSampleRate=false, CustomSampleRate=<hertz>`.
665    CustomHz(u32),
666}
667
668/// Closed Unity animation-type values supported by the revision-2 profile.
669#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
670pub enum UnityAnimationTypeV2 {
671    /// Unity's generic transform animation mode.
672    Generic,
673    /// Unity's humanoid avatar animation mode.
674    Humanoid,
675    /// Unity's legacy animation mode.
676    Legacy,
677}
678
679impl UnityAnimationTypeV2 {
680    /// Stable public value spelling.
681    pub const fn as_str(self) -> &'static str {
682        match self {
683            Self::Generic => "generic",
684            Self::Humanoid => "humanoid",
685            Self::Legacy => "legacy",
686        }
687    }
688}
689
690/// Closed Unity avatar-setup values supported by the revision-2 profile.
691#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
692pub enum UnityAvatarSetupV2 {
693    /// Build an avatar from the imported model.
694    CreateFromThisModel,
695    /// Reuse a separately supplied avatar.
696    CopyFromOtherAvatar,
697}
698
699impl UnityAvatarSetupV2 {
700    /// Stable public value spelling.
701    pub const fn as_str(self) -> &'static str {
702        match self {
703            Self::CreateFromThisModel => "create_from_this_model",
704            Self::CopyFromOtherAvatar => "copy_from_other_avatar",
705        }
706    }
707}
708
709impl std::fmt::Display for SettingIdV2 {
710    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
711        formatter.write_str(self.as_str())
712    }
713}
714
715/// Exact state of Bevy's `RenderAssetUsages` mesh-loading flags.
716#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
717pub enum BevyLoadMeshesStateV2 {
718    /// No usage flag is enabled, so mesh primitive child entities are skipped;
719    /// the source-node entity is still created when its scene is loaded.
720    Empty,
721    /// At least one usage flag is enabled, so mesh primitive child entities
722    /// are created beneath their source-node entity.
723    Nonempty,
724}
725
726/// Supported version-pinned glTF extension-handler environment.
727#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
728pub enum BevyGltfHandlerEnvironmentV2 {
729    /// `GltfExtensionHandlers` is empty.
730    BareEmpty,
731    /// The registry contains only Bevy 0.19's stock PBR handler.
732    BevyPbrStock019,
733}
734
735impl BevyGltfHandlerEnvironmentV2 {
736    /// Stable public value spelling.
737    pub const fn as_str(self) -> &'static str {
738        match self {
739            Self::BareEmpty => "bare_empty",
740            Self::BevyPbrStock019 => "bevy_pbr_stock_0_19",
741        }
742    }
743}
744
745/// Closed value domain for revision-2 settings.
746#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
747pub enum SettingDomainV2 {
748    /// Boolean value.
749    Boolean,
750    /// Bake or extract value.
751    BakeOrExtract,
752    /// Exact source-transform path.
753    SourceTransformPath,
754    /// Unity animation type.
755    AnimationType,
756    /// Unity avatar setup policy.
757    AvatarSetup,
758    /// Empty/nonempty mesh usage state.
759    LoadMeshesState,
760    /// Exact supported glTF extension-handler environment.
761    HandlerEnvironment,
762    /// Positive bounded integer.
763    PositiveInteger,
764    /// Exact Unreal FBX sample-rate policy.
765    SampleRate,
766}
767
768/// Closed public value vocabulary for revision-2 settings.
769#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
770pub enum SettingValueV2 {
771    /// Boolean setting value.
772    Boolean(bool),
773    /// Root-component bake/extract policy.
774    BakeOrExtract(BakeOrExtract),
775    /// Exact source-transform path.
776    SourceTransformPath(String),
777    /// Unity animation type.
778    AnimationType(UnityAnimationTypeV2),
779    /// Unity avatar setup policy.
780    AvatarSetup(UnityAvatarSetupV2),
781    /// Empty/nonempty mesh usage state.
782    LoadMeshesState(BevyLoadMeshesStateV2),
783    /// Exact supported extension-handler environment.
784    HandlerEnvironment(BevyGltfHandlerEnvironmentV2),
785    /// Positive bounded integer.
786    PositiveInteger(u32),
787    /// Exact Unreal FBX sample-rate policy.
788    SampleRate(UnrealSampleRateV2),
789}
790
791impl SettingValueV2 {
792    pub(crate) const fn matches_domain(&self, domain: SettingDomainV2) -> bool {
793        matches!(
794            (self, domain),
795            (Self::Boolean(_), SettingDomainV2::Boolean)
796                | (Self::BakeOrExtract(_), SettingDomainV2::BakeOrExtract)
797                | (
798                    Self::SourceTransformPath(_),
799                    SettingDomainV2::SourceTransformPath
800                )
801                | (Self::AnimationType(_), SettingDomainV2::AnimationType)
802                | (Self::AvatarSetup(_), SettingDomainV2::AvatarSetup)
803                | (Self::LoadMeshesState(_), SettingDomainV2::LoadMeshesState)
804                | (
805                    Self::HandlerEnvironment(_),
806                    SettingDomainV2::HandlerEnvironment
807                )
808                | (
809                    Self::PositiveInteger(1..=120),
810                    SettingDomainV2::PositiveInteger
811                )
812                | (
813                    Self::SampleRate(
814                        UnrealSampleRateV2::Default30 | UnrealSampleRateV2::SourceDetermined
815                    ),
816                    SettingDomainV2::SampleRate
817                )
818                | (
819                    Self::SampleRate(UnrealSampleRateV2::CustomHz(1..=48_000)),
820                    SettingDomainV2::SampleRate
821                )
822        )
823    }
824}
825
826/// Declaration/default policy for one revision-2 setting.
827#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
828pub enum SettingDefaultV2 {
829    /// The caller must declare a value; no default is claimed.
830    RequiredExplicit,
831    /// The exact profile has a version-pinned verified default.
832    Verified(SettingValueV2),
833}
834
835/// Immutable descriptor for one revision-2 setting.
836#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
837pub struct SettingDescriptorV2 {
838    pub(crate) id: SettingIdV2,
839    pub(crate) scope: SettingScope,
840    pub(crate) domain: SettingDomainV2,
841    pub(crate) default: SettingDefaultV2,
842}
843
844impl SettingDescriptorV2 {
845    pub(crate) const fn new(
846        id: SettingIdV2,
847        scope: SettingScope,
848        domain: SettingDomainV2,
849        default: SettingDefaultV2,
850    ) -> Self {
851        Self {
852            id,
853            scope,
854            domain,
855            default,
856        }
857    }
858
859    /// Stable setting id.
860    pub const fn id(&self) -> SettingIdV2 {
861        self.id
862    }
863
864    /// Required declaration scope.
865    pub const fn scope(&self) -> SettingScope {
866        self.scope
867    }
868
869    /// Closed value domain.
870    pub const fn domain(&self) -> SettingDomainV2 {
871        self.domain
872    }
873
874    /// Exact required/default policy.
875    pub const fn default(&self) -> &SettingDefaultV2 {
876        &self.default
877    }
878}
879
880/// Primary source retained by an immutable revision-2 profile record.
881#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
882pub struct PrimarySourceV2 {
883    pub(crate) id: &'static str,
884    pub(crate) target_version: &'static str,
885    pub(crate) url: &'static str,
886    pub(crate) verified_on: &'static str,
887    pub(crate) supported_facts: Vec<animsmith_core::engine_contract::EngineFactIdV2>,
888    pub(crate) supported_settings: Vec<SettingIdV2>,
889}
890
891impl PrimarySourceV2 {
892    /// Stable source id.
893    pub const fn id(&self) -> &'static str {
894        self.id
895    }
896
897    /// Version of the source's target product/specification.
898    pub const fn target_version(&self) -> &'static str {
899        self.target_version
900    }
901
902    /// Version-pinned primary-source URL.
903    pub const fn url(&self) -> &'static str {
904        self.url
905    }
906
907    /// ISO date on which the source was verified.
908    pub const fn verified_on(&self) -> &'static str {
909        self.verified_on
910    }
911
912    /// Whether this source establishes the accepted input containers.
913    pub fn supports_accepted_inputs(&self) -> bool {
914        self.supported_facts
915            .contains(&animsmith_core::engine_contract::EngineFactIdV2::AcceptedInputs)
916    }
917
918    /// Stable fact ids supported by this source.
919    pub fn supported_facts(&self) -> &[animsmith_core::engine_contract::EngineFactIdV2] {
920        &self.supported_facts
921    }
922
923    /// Stable setting ids supported by this source.
924    pub fn supported_settings(&self) -> &[SettingIdV2] {
925        &self.supported_settings
926    }
927}
928
929/// One immutable revision-2 engine-profile registry record.
930#[derive(Debug, Clone, PartialEq, Eq)]
931pub struct EngineProfileV2 {
932    pub(crate) selection: ProfileSelection,
933    pub(crate) profile_urn: &'static str,
934    pub(crate) accepted_inputs: Vec<SourceFormatV1>,
935    pub(crate) facts: Vec<animsmith_core::engine_contract::EngineProfileFactV2>,
936    pub(crate) settings: Vec<SettingDescriptorV2>,
937    pub(crate) sources: Vec<PrimarySourceV2>,
938}
939
940impl EngineProfileV2 {
941    /// Full exact selection tuple retained by this record.
942    pub const fn selection(&self) -> &ProfileSelection {
943        &self.selection
944    }
945
946    /// Stable revisioned profile-record URN.
947    pub const fn profile_urn(&self) -> &'static str {
948        self.profile_urn
949    }
950
951    /// Exact accepted input containers.
952    pub fn accepted_inputs(&self) -> &[SourceFormatV1] {
953        &self.accepted_inputs
954    }
955
956    /// Complete profile-facts V2 inventory in stable-id order.
957    pub fn facts(&self) -> &[animsmith_core::engine_contract::EngineProfileFactV2] {
958        &self.facts
959    }
960
961    /// Complete descriptor inventory in stable-id order.
962    pub fn setting_descriptors(&self) -> &[SettingDescriptorV2] {
963        &self.settings
964    }
965
966    /// Version-pinned primary sources in stable-id order.
967    pub fn sources(&self) -> &[PrimarySourceV2] {
968        &self.sources
969    }
970
971    /// Look up one setting descriptor.
972    pub fn setting_descriptor(&self, id: SettingIdV2) -> Option<&SettingDescriptorV2> {
973        self.settings.iter().find(|descriptor| descriptor.id == id)
974    }
975}
976
977/// String-keyed setting declarations for revision-2 profiles.
978pub type SettingMapV2 = BTreeMap<String, SettingValueV2>;
979
980/// Public input to revision-2 static settings resolution.
981#[derive(Debug, Clone, Default, PartialEq, Eq)]
982pub struct EngineDeclarationV2 {
983    /// Exact profile selection, or none for engine-neutral behavior.
984    pub selection: Option<ProfileSelection>,
985    /// Declared document settings. `Some` retains an explicitly empty table.
986    pub document_settings: Option<SettingMapV2>,
987    /// Selector-keyed clip settings reserved for descriptors that use clip scope.
988    pub clip_settings: BTreeMap<String, SettingMapV2>,
989}
990
991/// Origin of one fully materialized revision-2 setting value.
992#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
993pub enum ResolvedSettingOriginV2 {
994    /// Supplied explicitly by the caller.
995    ExplicitConfig,
996    /// Materialized from a version-pinned profile default.
997    ProfileDefault,
998}
999
1000/// One fully materialized revision-2 setting and its value origin.
1001#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
1002pub struct ResolvedSettingV2 {
1003    pub(crate) value: SettingValueV2,
1004    pub(crate) origin: ResolvedSettingOriginV2,
1005}
1006
1007impl ResolvedSettingV2 {
1008    /// Closed resolved value.
1009    pub const fn value(&self) -> &SettingValueV2 {
1010        &self.value
1011    }
1012
1013    /// Explicit/default value origin.
1014    pub const fn origin(&self) -> ResolvedSettingOriginV2 {
1015        self.origin
1016    }
1017}