Skip to main content

animsmith_engine/
provenance.rs

1use crate::canonical::{project_resolved_profile, project_resolved_profile_v2};
2use crate::{ResolvedProfile, ResolvedProfileV2};
3use animsmith_core::engine_contract::EngineContractError;
4use animsmith_core::prediction::{
5    PredictionContractError, PredictionProvenanceV1, PredictionProvenanceV2,
6    PredictionProvenanceV3, PredictionProvenanceV4, PredictionRuleInputsV1,
7    RawSceneAttachmentBindingV1, RawSceneAttachmentUnavailableReasonV1, RawSourceBindingV1,
8    RawSourceBindingV2,
9};
10use animsmith_core::{
11    EngineRootMotionProjectIntentV1, LoadedSource, PredictionProvenanceV5, PredictionProvenanceV6,
12    RawAnimationChannelInventoryV1, RawTransformPathInventoryV1, SourceFormatV1,
13};
14
15/// Failure to project already-resolved engine and same-load source evidence.
16#[derive(Debug, thiserror::Error)]
17#[non_exhaustive]
18pub enum PredictionProvenanceProjectionError {
19    /// The frozen engine profile or its materialized settings contradicted the
20    /// core-owned V1 engine contract.
21    #[error("resolved engine evidence is invalid: {0}")]
22    EngineContract(#[from] EngineContractError),
23    /// The same-load engine, raw-source, or dependency-closure evidence could
24    /// not form one valid prediction-provenance record.
25    #[error("prediction provenance is invalid: {0}")]
26    PredictionContract(#[from] PredictionContractError),
27}
28
29/// Project one resolved engine profile and its same-load source evidence into
30/// the self-contained prediction-provenance V1 contract.
31///
32/// This is a one-way, allocation-only adapter. It does not read source bytes,
33/// resolve a profile or selector again, consult the registry, apply defaults,
34/// or recompute the dependency-closure identity. The caller must pass the
35/// [`LoadedSource`] retained from the same load used to resolve `profile`.
36///
37/// # Errors
38///
39/// Returns [`PredictionProvenanceProjectionError`] if the already-resolved
40/// engine evidence no longer satisfies its core contract or if the source
41/// format and primary identity do not agree across the supplied same-load
42/// evidence.
43pub fn project_prediction_provenance_v1(
44    profile: &ResolvedProfile,
45    source: &LoadedSource,
46) -> Result<PredictionProvenanceV1, PredictionProvenanceProjectionError> {
47    let (profile_contract, settings_contract) = project_resolved_profile(profile)?;
48    let raw_source = RawSourceBindingV1::from_source(source.source_facts());
49    Ok(PredictionProvenanceV1::new(
50        profile_contract,
51        profile.source_format(),
52        settings_contract,
53        raw_source,
54        source.dependency_closure().clone(),
55    )?)
56}
57
58/// Project one bounded V2 engine resolution into V2 prediction provenance.
59///
60/// This adapter consumes only the canonical retained prefix plus its explicit
61/// N+1 coverage/work evidence. It does not revisit the tail inventory.
62pub fn project_prediction_provenance_v2(
63    profile: &ResolvedProfileV2,
64    source: &LoadedSource,
65) -> Result<PredictionProvenanceV2, PredictionProvenanceProjectionError> {
66    let (profile_contract, settings_contract) = project_resolved_profile_v2(profile)?;
67    let raw_source = RawSourceBindingV1::from_source(source.source_facts());
68    Ok(PredictionProvenanceV2::new(
69        profile_contract,
70        profile.source_format(),
71        settings_contract,
72        raw_source,
73        source.dependency_closure().clone(),
74    )?)
75}
76
77/// Project one bounded V2 engine resolution and same-load exact source
78/// evidence into V3 prediction provenance.
79///
80/// The engine profile and settings remain the immutable V2 contracts. V3
81/// widens only the raw-source binding so engine-owned rules can cite exact source
82/// timing observations without copying or reconstructing them.
83pub fn project_prediction_provenance_v3(
84    profile: &ResolvedProfileV2,
85    source: &LoadedSource,
86) -> Result<PredictionProvenanceV3, PredictionProvenanceProjectionError> {
87    let (profile_contract, settings_contract) = project_resolved_profile_v2(profile)?;
88    let raw_source =
89        RawSourceBindingV2::from_source(source.source_facts(), source.exact_source_timing())?;
90    Ok(PredictionProvenanceV3::new(
91        profile_contract,
92        profile.source_format(),
93        settings_contract,
94        raw_source,
95        source.dependency_closure().clone(),
96    )?)
97}
98
99/// Project one revision-2 profile resolution, normalized rule inputs, and all
100/// same-load V4 source evidence.
101///
102/// The adapter does not infer scene or primitive presence from normalized
103/// assets. A glTF load that did not retain the raw inventory is represented by
104/// the contract's typed loader-evidence absence.
105///
106/// # Errors
107///
108/// `runtime_node_selectors` must be the complete alias-normalized declaration
109/// used for this lint invocation. Returns [`PredictionProvenanceProjectionError`]
110/// if the profile/settings projection is invalid or the rule inputs, source
111/// identities, format, raw inventory, or dependency closure cannot form one
112/// V4 provenance record.
113pub fn project_prediction_provenance_v4(
114    profile: &crate::ResolvedProfileSettingsV2,
115    source: &LoadedSource,
116    runtime_node_selectors: Vec<String>,
117) -> Result<PredictionProvenanceV4, PredictionProvenanceProjectionError> {
118    let (profile_contract, settings_contract) =
119        crate::canonical::project_resolved_engine_settings_v3(profile)?;
120    let raw_source =
121        RawSourceBindingV2::from_source(source.source_facts(), source.exact_source_timing())?;
122    let raw_scene_attachment = source
123        .raw_scene_attachment_inventory()
124        .cloned()
125        .map_or_else(
126            || {
127                RawSceneAttachmentBindingV1::unavailable(match source.source_facts().format() {
128                    SourceFormatV1::Glb | SourceFormatV1::GltfJson => {
129                        RawSceneAttachmentUnavailableReasonV1::LoaderEvidenceUnavailable
130                    }
131                    SourceFormatV1::Fbx => {
132                        RawSceneAttachmentUnavailableReasonV1::UnsupportedSourceFormat
133                    }
134                })
135            },
136            RawSceneAttachmentBindingV1::available,
137        );
138    Ok(PredictionProvenanceV4::new(
139        profile_contract,
140        profile.source_format(),
141        settings_contract,
142        raw_source,
143        raw_scene_attachment,
144        source.dependency_closure().clone(),
145        PredictionRuleInputsV1::new(runtime_node_selectors)?,
146    )?)
147}
148
149/// Project V5 provenance by binding immutable V4 authority to the bounded
150/// same-load raw animation/channel inventory required by engine-track-support.
151pub fn project_prediction_provenance_v5(
152    profile: &crate::ResolvedProfileSettingsV2,
153    source: &LoadedSource,
154    runtime_node_selectors: Vec<String>,
155) -> Result<PredictionProvenanceV5, PredictionProvenanceProjectionError> {
156    let base = project_prediction_provenance_v4(profile, source, runtime_node_selectors)?;
157    Ok(PredictionProvenanceV5::new(
158        base,
159        RawAnimationChannelInventoryV1::from_source(source.source_facts()),
160    )?)
161}
162
163/// Project V6 provenance for the exact Unity Generic root-motion slice.
164///
165/// The raw transform inventory is consumed only from the supplied same-load
166/// source. A loader without that optional sidecar retains a typed unavailable
167/// inventory; this adapter never reopens or reparses the input.
168pub fn project_prediction_provenance_v6(
169    profile: &crate::ResolvedProfileSettingsV2,
170    source: &LoadedSource,
171    runtime_node_selectors: Vec<String>,
172    root_motion_project_intent: EngineRootMotionProjectIntentV1,
173) -> Result<PredictionProvenanceV6, PredictionProvenanceProjectionError> {
174    let base = project_prediction_provenance_v5(profile, source, runtime_node_selectors)?;
175    let raw_transform_paths = source
176        .raw_transform_path_inventory()
177        .cloned()
178        .unwrap_or_else(|| {
179            RawTransformPathInventoryV1::unavailable(
180                source.source_facts().primary_identity().clone(),
181                source.source_facts().format(),
182                source.document().skeleton.bones.len() as u64,
183            )
184        });
185    Ok(PredictionProvenanceV6::new(
186        base,
187        raw_transform_paths,
188        root_motion_project_intent,
189    )?)
190}