Skip to main content

animsmith_core/
lib.rs

1//! Engine-agnostic animation linting primitives for Rust pipelines.
2//!
3//! This crate is the embedding boundary for animsmith. It owns the core
4//! data model ([`Document`], [`Skeleton`], [`Clip`], [`Track`]), rig-role
5//! resolution ([`detect_profile`], [`ResolvedRoles::from_names`]),
6//! typed configuration ([`Config`]), measurement generation
7//! ([`measure::measure_document`]), versioned result envelopes
8//! ([`contract::MeasureEnvelope`], [`contract::LintEnvelope`]), measurement diffs
9//! ([`diff::diff_measurements`]), structured findings ([`Finding`]), and
10//! check execution ([`CheckCtx`], [`all_checks`], [`evaluate_checks`]).
11//! The [`source_facts`] module owns the bounded, format-neutral V1 vocabulary
12//! that format loaders bind to the exact primary bytes in an immutable
13//! [`LoadedSource`]. A mutable normalized [`Document`] does not reconstruct
14//! importer-sensitive source declarations; consuming the wrapper as a document
15//! deliberately discards those facts. The separate [`dependency_closure`]
16//! sidecar records bounded, same-load primary/external content identities over
17//! the raw resource-declaration domain; format crates own rooted I/O while core
18//! owns its validated value and canonical digest contract. The borrowing facts
19//! view reuses the canonical [`model::SourceSkeletonAssets`] table and remains
20//! separate from scale's operation-specific capability and proof ledgers.
21//! The opt-in [`bake_static_mesh_transforms`] operation canonicalizes supported
22//! unanimated, unskinned mesh scenes into identity-root geometry and returns
23//! deterministic producer evidence.
24//! The opt-in [`transform::prune_constant_tracks`] helper removes only
25//! interpolation-aware constant-track candidates whose sampled local and
26//! model-space pose evidence remains within its documented tolerances.
27//! The [`scale`] module owns the format-neutral plan/proof contracts for the
28//! two distinct DESIGN.md Appendix D scale operations —
29//! [`scale::ScaleOperation::WholeDocumentLinearUnits`] and
30//! [`scale::ScaleOperation::RestBindUniformScale`] — through pure, fail-closed
31//! [`scale::plan_scale`] and independent [`scale::prove_scale`]. A format
32//! frontend owns exact source rewriting and hands the reloaded emitted
33//! document back through [`scale::ScaleCandidate::from_document`]; core does
34//! not expose a production candidate builder, decide selectors, publish
35//! artifacts, or write files.
36//! The [`animsmith-gltf`] and [`animsmith-fbx`] loader crates translate file
37//! formats into this model; their docs.rs pages continue the library path for
38//! format-specific loading and, for glTF, writing.
39//!
40//! The [embedding guide] explains crate selection and integration
41//! boundaries. The [pipeline scenario guide] shows where an embedded gate
42//! fits in marketplace intake, mocap cleanup, outsourced acceptance, and CI.
43//! A [runnable example] exercises the complete library flow.
44//!
45//! [embedding guide]: https://github.com/mmannerm/animsmith/blob/main/docs/embedding.md
46//! [pipeline scenario guide]: https://github.com/mmannerm/animsmith/blob/main/docs/pipeline-scenarios.md
47//! [runnable example]: https://github.com/mmannerm/animsmith/blob/main/crates/animsmith/examples/embed.rs
48//! [`animsmith-gltf`]: https://docs.rs/animsmith-gltf
49//! [`animsmith-fbx`]: https://docs.rs/animsmith-fbx
50//!
51//! # Quick start
52//!
53//! After a format crate has loaded a [`Document`], resolve rig roles, build
54//! a [`Config`] from the host pipeline's contract, and share one
55//! [`MetricGrids`] between measurements, checks, and optional report
56//! generation:
57//!
58//! ```
59//! use animsmith_core::{
60//!     CheckCtx, CheckSelection, Config, Document, MetricGrids, all_checks,
61//!     evaluate_checks, resolve_configured_roles,
62//! };
63//! use animsmith_core::measure::measure_document;
64//!
65//! let doc = Document::default();
66//! let config = Config::default();
67//! config.validate()?;
68//! let roles = resolve_configured_roles(&doc.skeleton, &config.rig);
69//! let grids = MetricGrids::new(&doc);
70//!
71//! let measurements = measure_document(&grids, &roles, &config);
72//! let ctx = CheckCtx::new(&grids, &roles, &config);
73//! let results = evaluate_checks(&ctx, &all_checks(), CheckSelection::All)?;
74//!
75//! assert!(measurements.is_empty());
76//! assert!(results.iter().all(|result| result.findings().is_empty()));
77//! # Ok::<(), animsmith_core::EvaluationError>(())
78//! ```
79//!
80//! [`CheckCtx::new`] consumes already-resolved roles; it does not interpret
81//! [`Config::rig`] automatically. Frontends may use [`detect_profile`],
82//! [`resolve_configured_roles`] for the same named-profile plus inline-override
83//! policy as the CLI. Missing prerequisites are represented as typed coverage
84//! gaps rather than false findings.
85//!
86//! # API status
87//!
88//! The Rust API is pre-1.0 and may still change before the first stable
89//! release. The intended extension points are the data model,
90//! configuration types, measurement and diff APIs, rig-profile APIs, the
91//! [`Check`] trait for custom checks, and the check catalog functions
92//! re-exported from this crate root. Built-in check ids, CLI exit-code
93//! semantics, and the shared versioned JSON envelope/schema ids are treated
94//! as the most stable automation contracts. The [`contract`] module owns the
95//! same envelope types and immutable identities for CLI and embedded
96//! producers. The scene-asset
97//! structs in [`model`] and the pipeline-mechanical helpers in
98//! [`transform`] and [`static_bake`] are public so the loader, writer, and CLI crates can
99//! share the same model, but they are less settled than the
100//! measurement/check embedding flow while the crate is pre-1.0. Metric
101//! formulas and individual Rust symbols are still subject to pre-1.0
102//! refinement.
103//!
104//! Public APIs that return [`Result`] document their `# Errors` cases.
105//! Index-based accessors and transform helpers that rely on
106//! loader-established invariants document their `# Panics` contracts.
107//! Loader-valid documents from the format crates should flow through
108//! checking, sampling, and measurement without panicking on untrusted
109//! input.
110
111#![warn(missing_docs)]
112
113pub mod assembly;
114mod bounded_deserialize;
115pub mod check;
116mod checks;
117pub mod config;
118pub mod contract;
119pub mod dependency_closure;
120pub mod diff;
121pub mod engine_contract;
122pub mod evaluation;
123pub mod finding;
124#[cfg(feature = "fixtures")]
125pub mod fixtures;
126pub mod measure;
127pub mod metrics;
128pub mod model;
129pub mod prediction;
130pub mod profile;
131pub mod sample;
132pub mod scale;
133pub mod skinned_canonical;
134pub mod source_facts;
135pub mod static_bake;
136pub mod transform;
137
138pub use check::{Check, CheckCtx, all_checks, mechanical_checks};
139pub use config::{
140    ClipExpectations, Config, ConfigValidationError, GaitGroup, MovementOwner, Pinned,
141    RuntimeNodeSelectorResolution, RuntimeNodeSelectors, RuntimeNodesConfig, SeveritySetting,
142    SyncGroup, TimeComplementSettings,
143};
144pub use contract::{
145    DiffEnvelope, InputIdentity, LintEnvelope, LintFileReport, MEASUREMENTS_SCHEMA_ID,
146    MEASUREMENTS_SCHEMA_VERSION, MeasureEnvelope, MeasureFileReport, MeasurementContract,
147    MeasurementContractError, MeasurementFileError, MeasurementReportError, MeasurementReportFile,
148    MeasurementReportInput, MeasurementReportReadError, OUTPUT_SCHEMA_ID, OUTPUT_SCHEMA_VERSION,
149    OUTPUT_V10_MAX_CHECKS_PER_FILE, OUTPUT_V10_MAX_FILES, OUTPUT_V10_MAX_REPORT_BYTES,
150    OutputContractError, RigInfo, RigInfoError, ToolInfo, ToolSource, sha256_hex,
151};
152pub use dependency_closure::{
153    DEPENDENCY_CLOSURE_BUDGET_V1_ID, DEPENDENCY_CLOSURE_V1_ID,
154    DEPENDENCY_CLOSURE_V1_MAX_DEDUP_PROBES, DEPENDENCY_CLOSURE_V1_MAX_EXTERNAL_RESOURCES,
155    DEPENDENCY_CLOSURE_V1_MAX_KEY_BYTES, DEPENDENCY_CLOSURE_V1_MAX_NORMALIZATION_BYTES,
156    DEPENDENCY_CLOSURE_V1_MAX_PATH_COMPONENTS, DEPENDENCY_CLOSURE_V1_MAX_REFERENCES,
157    DEPENDENCY_CLOSURE_V1_MAX_RESOURCE_BYTES, DEPENDENCY_CLOSURE_V1_MAX_TOTAL_RESOURCE_BYTES,
158    DependencyClosureBuilderV1, DependencyClosureCoverageReasonV1, DependencyClosureCoverageV1,
159    DependencyClosureError, DependencyClosureIdentityV1, DependencyClosureReferenceV1,
160    DependencyClosureV1, DependencyClosureWorkV1, DependencyReferenceTargetV1,
161    DependencyResourceKeyV1, DependencyResourcePurposeV1, DependencyResourceRefusalReasonV1,
162    DependencyResourceUnavailableReasonV1, ExternalResourceIdentityV1, ResourceClosureBudgetV1,
163    ResourceKeySyntaxV1,
164};
165pub use engine_contract::{
166    ENGINE_CONTRACT_V1_MAX_AGGREGATE_ROWS, ENGINE_CONTRACT_V1_MAX_COLLECTION_ROWS,
167    ENGINE_CONTRACT_V1_MAX_TEXT_BYTES, ENGINE_CONTRACT_V1_MAX_TOTAL_TEXT_BYTES,
168    ENGINE_PROFILE_FACTS_V1_ID, EngineAnimationAddressabilityV1, EngineBakeOrExtractV1,
169    EngineClipSettingsV1, EngineContractError, EngineConversionControlV1, EngineCoordinateBasisV1,
170    EngineDefaultStatusV1, EngineFactIdV1, EngineFactStateV1, EngineFactValueV1,
171    EngineForwardAxisV1, EngineHandednessV1, EngineImportHandlingV1, EngineLinearUnitV1,
172    EnginePrimarySourceV1, EngineProfileFactV1, EngineProfileSelectionV1,
173    EngineRootMotionAddressabilityV1, EngineSettingApplicabilityV1, EngineSettingDescriptorV1,
174    EngineSettingDomainV1, EngineSettingIdV1, EngineSettingRowV1, EngineSettingScopeV1,
175    EngineSettingValueV1, EngineTargetAddressabilityV1, EngineUpAxisV1,
176    RESOLVED_ENGINE_SETTINGS_V1_ID, ResolvedEngineProfileV1, ResolvedEngineSettingsV1,
177};
178pub use evaluation::{
179    Applicability, BUILTIN_COVERAGE_GAP_CODES, BUILTIN_EVALUATION_SCOPE_CODES, CheckEvaluation,
180    CheckOutput, CheckSelection, ConfigurationState, CoverageGap, CoverageGapCode, EvaluationError,
181    EvaluationScope, EvaluationScopeCode, EvaluationState, SelectionState, evaluate_checks,
182    lint_requires_failure,
183};
184pub use finding::{Finding, MemberMeasurement, Severity, Value};
185/// Re-export of the exact `glam` version used by animsmith's public math
186/// types, so embedders can construct [`Transform`] values without a
187/// cross-version type mismatch.
188pub use glam;
189pub use metrics::MetricGrids;
190pub use model::{
191    AdditionalInfluenceSet, AffineDomainViolation, Bone, BoneId, Clip, DecodedImageColorType,
192    Document, DocumentShapeError, ImageContainerFormat, ImageSourceKind, ImageUnavailableReason,
193    Interpolation, MaterialResourceAssets, MaterialResourceCoverage, MaterialTextureSlot,
194    MeshInstanceShapeViolation, Property, Skeleton, SourceImageAsset, SourceImageInspection,
195    SourceInfo, SourceInverseBindAccessor, SourceInverseBindAccessorStatus, SourceMaterialAsset,
196    SourceMaterialTextureBinding, SourceNodeAsset, SourceNodeLocalRest, SourceProjectionViolation,
197    SourceSkeletonAssets, SourceSkeletonCoverage, SourceSkinAsset, SourceSkinAttachment,
198    SourceTextureAsset, Track, TrackShapeViolation, TrackValues, Transform,
199    validate_document_shape,
200};
201pub use prediction::{
202    ENGINE_PREDICTION_V1_ID, EnginePredictionBasisV1, EnginePredictionFacetStateV1,
203    EnginePredictionFacetV1, EnginePredictionV1, FinitePredictionNumberV1, MeasurementPointerV1,
204    PREDICTION_PROVENANCE_V1_ID, PREDICTION_V1_MAX_AGGREGATE_PROVENANCE_ROWS,
205    PREDICTION_V1_MAX_BASIS_REFERENCES_PER_FACET, PREDICTION_V1_MAX_BASIS_REFERENCES_PER_FILE,
206    PREDICTION_V1_MAX_FACETS_PER_FILE, PREDICTION_V1_MAX_MEASUREMENT_POINTER_COMPONENTS,
207    PREDICTION_V1_MAX_REASONS_PER_FACET, PREDICTION_V1_MAX_TEXT_BYTES,
208    PREDICTION_V1_MAX_TOTAL_TEXT_BYTES_PER_FILE, PredictionBasisIdentityV1,
209    PredictionBasisReferenceV1, PredictionContractError, PredictionProvenanceIdentityV1,
210    PredictionProvenanceV1, PredictionScalarV1, PredictionUnavailableReasonV1, RawSourceAxisV1,
211    RawSourceBasisReferenceV1, RawSourceBindingV1, RawSourceCoordinateBasisV1,
212    RawSourceDispositionV1, RawSourceDomainV1, RawSourceFieldIdV1, RawSourceKeyV1,
213    RawSourceObservationStateWireV1, RawSourceObservationWireV1, RawSourceProjectionWorkWireV1,
214    RawSourceProvenanceKindV1, RawSourceProvenanceV1, RawSourceSetCoverageStateV1,
215    RawSourceSetCoverageV1, RawSourceUnavailableReasonV1, ResolvedSettingLocationV1,
216    SourceSkeletonRowKindV1,
217};
218pub use profile::{
219    ResolvedRoles, RigProfile, Role, builtin_profiles, detect_profile, resolve_configured_roles,
220};
221pub use sample::{PoseGrid, TrackSample, default_frame_count, sample_clip, sample_track};
222pub use scale::{
223    ProofResidualKind, ScaleBoneRestField, ScaleCandidate, ScaleCapabilityCoverage,
224    ScaleCapabilityFacts, ScaleError, ScaleFieldDisposition, ScaleFieldPlan, ScaleFieldTarget,
225    ScaleOperation, ScalePayloadShapeRow, ScalePlan, ScalePlanLedger, ScaleProjectedRole,
226    ScaleProof, ScaleProofObligation, ScaleProofResidual, ScaleRequest, ScaleRewriteRule,
227    ScaleSourceNodeKind, ScaleSourceRestField, ScaleSourceTopologyRow, ScaleTolerancePolicy,
228    plan_scale, prove_scale,
229};
230pub use skinned_canonical::{
231    SkinnedBindPoseCanonicalization, SkinnedBindPoseCanonicalizationError,
232    SkinnedBindPoseCanonicalizationOptions, SkinnedBindPosePlacement,
233    canonicalize_skinned_bind_pose,
234};
235pub use source_facts::{
236    LoadedSource, RAW_SOURCE_FACTS_V1_ID, RAW_SOURCE_V1_MAX_CLIPS, RAW_SOURCE_V1_MAX_OBSERVATIONS,
237    RAW_SOURCE_V1_MAX_RESOURCE_REFERENCES, RAW_SOURCE_V1_MAX_TEXT_BYTES,
238    RAW_SOURCE_V1_MAX_TOTAL_TEXT_BYTES, RAW_SOURCE_V1_MAX_TRAVERSAL_DEPTH, RawSourceFactsBuilderV1,
239    RawSourceFactsV1, SourceAxisV1, SourceChannelFactV1, SourceChannelPropertyV1, SourceClipFactV1,
240    SourceComponentMaskV1, SourceConstructFactV1, SourceConstructKindV1, SourceCoordinateBasisV1,
241    SourceFactDomainV1, SourceFactSetV1, SourceFactsError, SourceFactsViewV1, SourceFormatV1,
242    SourceFramesPerSecondV1, SourceHandednessV1, SourceInterpolationV1, SourceLinearUnitV1,
243    SourceLoaderDispositionV1, SourceLogicalLocatorV1, SourceObservationStateV1,
244    SourceObservationV1, SourceProjectionWorkV1, SourceProvenanceKindV1, SourceProvenanceV1,
245    SourceRelativeLocatorV1, SourceResourceKindV1, SourceResourceLocatorV1,
246    SourceResourceReferenceV1, SourceSetCoverageStateV1, SourceSetCoverageV1, SourceTargetKindV1,
247    SourceTargetV1, SourceTextV1, SourceTimeRangeV1, SourceUnavailableReasonV1,
248};
249pub use static_bake::{
250    StaticMeshBake, StaticMeshBakeError, StaticMeshBakeEvidence, StaticMeshBakeInstanceEvidence,
251    bake_static_mesh_transforms,
252};