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