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}