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
205/// A known importer control relevant to source-to-target conversion.
206#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
207pub enum ConversionControl {
208    /// Behavior is controlled by one declared profile setting.
209    ProfileSetting(SettingId),
210    /// Behavior is exposed by the importer but is not a V1 profile setting.
211    ImporterOption,
212}
213
214/// Known importer treatment for a fact domain.
215#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
216pub enum ImportHandling {
217    /// The importer retains the domain.
218    Preserved,
219    /// The importer converts the domain.
220    Converted,
221    /// The importer discards the domain.
222    Discarded,
223    /// The importer does not support the domain.
224    Unsupported,
225}
226
227/// Known animation-target addressability behavior.
228#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
229pub enum TargetAddressability {
230    /// Targets are addressed by a stable id derived from their name path.
231    NamePathDerivedId,
232}
233
234/// Known animation-asset addressability behavior.
235#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
236pub enum AnimationAddressability {
237    /// Bevy addresses each glTF animation by its source-array index through
238    /// `GltfAssetLabel::Animation(index)`; animation names populate Bevy's
239    /// separate named-animation map rather than this typed label.
240    GltfAssetLabel,
241}
242
243/// Known root-motion addressability behavior.
244#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
245pub enum RootMotionAddressability {
246    /// A bounded exact source-transform path selects the motion node.
247    ExactSourceTransformPath,
248    /// Humanoid Avatar/body semantics determine root motion.
249    HumanoidAvatarBody,
250}
251
252/// Typed value of a known immutable profile fact.
253#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
254pub enum FactValue {
255    /// Exact accepted AnimSmith input formats.
256    AcceptedFormats(Vec<SourceFormatV1>),
257    /// Animation-asset addressability.
258    AnimationAddressability(AnimationAddressability),
259    /// Target coordinate basis.
260    CoordinateBasis(CoordinateBasis),
261    /// Target linear unit.
262    LinearUnit(LinearUnit),
263    /// Source-to-target conversion control.
264    ConversionControl(ConversionControl),
265    /// Boolean predicate.
266    Boolean(bool),
267    /// Import handling of a domain.
268    ImportHandling(ImportHandling),
269    /// Animation-target addressability.
270    TargetAddressability(TargetAddressability),
271    /// Root-motion addressability.
272    RootMotionAddressability(RootMotionAddressability),
273}
274
275/// Evidence state of one profile fact.
276#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
277pub enum FactState {
278    /// Supported known value.
279    Known(FactValue),
280    /// Primary evidence does not establish a value.
281    Unknown,
282    /// The fact domain genuinely does not apply.
283    NotApplicable,
284}
285
286/// One stable fact and its explicit evidence state.
287#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
288pub struct ProfileFact {
289    id: FactId,
290    state: FactState,
291}
292
293impl ProfileFact {
294    pub(crate) fn new(id: FactId, state: FactState) -> Self {
295        Self { id, state }
296    }
297
298    /// Stable fact id.
299    pub const fn id(&self) -> FactId {
300        self.id
301    }
302
303    /// Explicit known, unknown, or not-applicable state.
304    pub const fn state(&self) -> &FactState {
305        &self.state
306    }
307}
308
309/// Configuration scope of a setting.
310#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
311pub enum SettingScope {
312    /// One value governs the imported document and all clips.
313    Document,
314    /// One materialized value is required for each real clip.
315    Clip,
316}
317
318/// Closed value domain of a setting.
319#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
320pub enum SettingDomain {
321    /// Boolean value.
322    Boolean,
323    /// `bake` or `extract`.
324    BakeOrExtract,
325    /// Bounded exact source-transform path.
326    SourceTransformPath,
327}
328
329/// Whether a descriptor applies to a profile revision.
330#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
331pub enum SettingApplicability {
332    /// The setting applies.
333    Applicable,
334    /// The setting genuinely does not apply.
335    NotApplicable,
336}
337
338/// Verified default status of a setting.
339#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
340pub enum DefaultStatus {
341    /// The caller must declare a value because no default is verified.
342    RequiredWithoutDefault,
343    /// Default behavior is irrelevant because the setting does not apply.
344    NotApplicable,
345}
346
347/// Immutable descriptor for one stable setting id.
348#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
349pub struct SettingDescriptor {
350    id: SettingId,
351    scope: SettingScope,
352    domain: SettingDomain,
353    applicability: SettingApplicability,
354    default_status: DefaultStatus,
355}
356
357impl SettingDescriptor {
358    pub(crate) const fn new(
359        id: SettingId,
360        scope: SettingScope,
361        domain: SettingDomain,
362        applicability: SettingApplicability,
363        default_status: DefaultStatus,
364    ) -> Self {
365        Self {
366            id,
367            scope,
368            domain,
369            applicability,
370            default_status,
371        }
372    }
373
374    /// Stable setting id.
375    pub const fn id(&self) -> SettingId {
376        self.id
377    }
378
379    /// Required declaration scope.
380    pub const fn scope(&self) -> SettingScope {
381        self.scope
382    }
383
384    /// Closed value domain.
385    pub const fn domain(&self) -> SettingDomain {
386        self.domain
387    }
388
389    /// Applicability to this exact profile revision.
390    pub const fn applicability(&self) -> SettingApplicability {
391        self.applicability
392    }
393
394    /// Verified default status.
395    pub const fn default_status(&self) -> DefaultStatus {
396        self.default_status
397    }
398}
399
400/// Exact policy for a Unity root transform component.
401#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
402pub enum BakeOrExtract {
403    /// Bake the component into the pose.
404    Bake,
405    /// Extract the component as root motion.
406    Extract,
407}
408
409/// Closed public value vocabulary for engine settings.
410#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
411pub enum SettingValue {
412    /// Boolean setting value.
413    Boolean(bool),
414    /// Root-component bake/extract policy.
415    BakeOrExtract(BakeOrExtract),
416    /// Exact source-transform path, validated during static resolution.
417    SourceTransformPath(String),
418}
419
420/// String-keyed setting declarations used at the TOML-free public boundary.
421pub type SettingMap = BTreeMap<String, SettingValue>;
422
423/// Full public input to phase-one static resolution.
424#[derive(Debug, Clone, Default, PartialEq, Eq)]
425pub struct EngineDeclaration {
426    /// Exact profile selection, or none for engine-neutral behavior.
427    pub selection: Option<ProfileSelection>,
428    /// Declared document settings. `Some` retains an explicitly empty table.
429    pub document_settings: Option<SettingMap>,
430    /// Selector-keyed per-clip setting declarations.
431    pub clip_settings: BTreeMap<String, SettingMap>,
432}
433
434/// One primary source retained by an immutable profile record.
435#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
436pub struct PrimarySource {
437    id: &'static str,
438    target_version: &'static str,
439    url: &'static str,
440    verified_on: &'static str,
441    supported_facts: Vec<FactId>,
442    supported_settings: Vec<SettingId>,
443}
444
445impl PrimarySource {
446    pub(crate) fn new(
447        id: &'static str,
448        target_version: &'static str,
449        url: &'static str,
450        verified_on: &'static str,
451        mut supported_facts: Vec<FactId>,
452        mut supported_settings: Vec<SettingId>,
453    ) -> Self {
454        supported_facts.sort_by_key(|id| id.as_str());
455        supported_settings.sort_by_key(|id| id.as_str());
456        Self {
457            id,
458            target_version,
459            url,
460            verified_on,
461            supported_facts,
462            supported_settings,
463        }
464    }
465
466    /// Stable source id.
467    pub const fn id(&self) -> &'static str {
468        self.id
469    }
470
471    /// Version of the source's target product/specification.
472    pub const fn target_version(&self) -> &'static str {
473        self.target_version
474    }
475
476    /// Primary-source URL.
477    pub const fn url(&self) -> &'static str {
478        self.url
479    }
480
481    /// ISO date on which this source was verified.
482    pub const fn verified_on(&self) -> &'static str {
483        self.verified_on
484    }
485
486    /// Stable fact ids supported by the source.
487    pub fn supported_facts(&self) -> &[FactId] {
488        &self.supported_facts
489    }
490
491    /// Stable setting ids supported by the source.
492    pub fn supported_settings(&self) -> &[SettingId] {
493        &self.supported_settings
494    }
495
496    #[cfg(test)]
497    pub(crate) fn reverse_supported_ids_for_test(&mut self) {
498        self.supported_facts.reverse();
499        self.supported_settings.reverse();
500    }
501}
502
503/// One immutable, revisioned registry profile.
504#[derive(Debug, Clone, PartialEq, Eq)]
505pub struct EngineProfile {
506    pub(crate) selection: ProfileSelection,
507    pub(crate) fact_bundle_urn: &'static str,
508    pub(crate) facts: Vec<ProfileFact>,
509    pub(crate) settings: Vec<SettingDescriptor>,
510    pub(crate) sources: Vec<PrimarySource>,
511    pub(crate) facts_identity: InputIdentity,
512}
513
514impl EngineProfile {
515    /// Full exact selection tuple retained by this profile.
516    pub const fn selection(&self) -> &ProfileSelection {
517        &self.selection
518    }
519
520    /// Revisioned fact-bundle URN.
521    pub const fn fact_bundle_urn(&self) -> &'static str {
522        self.fact_bundle_urn
523    }
524
525    /// Bounded AnimSmith V1 input formats accepted by the profile.
526    pub fn accepted_inputs(&self) -> &[SourceFormatV1] {
527        match self.fact(FactId::AcceptedInputs).map(ProfileFact::state) {
528            Some(FactState::Known(FactValue::AcceptedFormats(formats))) => formats,
529            _ => &[],
530        }
531    }
532
533    /// Complete typed fact inventory in stable-id order.
534    pub fn facts(&self) -> &[ProfileFact] {
535        &self.facts
536    }
537
538    /// Complete descriptor inventory in stable-id order.
539    pub fn setting_descriptors(&self) -> &[SettingDescriptor] {
540        &self.settings
541    }
542
543    /// Primary sources in stable-id order.
544    pub fn sources(&self) -> &[PrimarySource] {
545        &self.sources
546    }
547
548    /// SHA-256 plus byte count of the canonical immutable record encoding.
549    pub const fn facts_identity(&self) -> &InputIdentity {
550        &self.facts_identity
551    }
552
553    /// Look up one fact by stable id.
554    pub fn fact(&self, id: FactId) -> Option<&ProfileFact> {
555        self.facts.iter().find(|fact| fact.id == id)
556    }
557
558    /// Look up one setting descriptor, including not-applicable descriptors.
559    pub fn setting_descriptor(&self, id: SettingId) -> Option<&SettingDescriptor> {
560        self.settings.iter().find(|descriptor| descriptor.id == id)
561    }
562}