Skip to main content

animsmith_engine/
unit_scale.rs

1//! Exact Bevy 0.19 glTF unit and static/default-rest scale prediction.
2
3use crate::SettingIdV2;
4use crate::canonical::matches_frozen_registry_projection_v2;
5use crate::error::PredictionRuleError;
6use animsmith_core::engine_contract::{EngineSettingIdV2, EngineSettingValueV2};
7use animsmith_core::measure::{
8    AssetMeasurements, LinearTransformClassification, SkeletonNodeMeasurements, measure_assets,
9};
10use animsmith_core::prediction::{
11    EnginePredictionBasisV4, PredictionBasisReferenceV4, RawSceneAttachmentBasisDomainV1,
12    RawSceneAttachmentBasisReferenceV1,
13};
14use animsmith_core::{
15    Applicability, Check, CheckCtx, CheckOutput, DependencyClosureCoverageV1,
16    EngineMachineResultV1, EnginePredictionFacetV4, EnginePredictionV4, EvaluationScope,
17    EvaluationScopeCode, ImporterSubjectCreationV1, InventoryCoverageResultV1, LoadedSource,
18    MeasurementPointerV1, PREDICTION_V2_MAX_CANDIDATE_FACETS_PER_RULE, PredictionBasisReferenceV1,
19    PredictionBasisReferenceV2, PredictionFacetDemandV2, PredictionInventoryCoverageStateV1,
20    PredictionInventoryDomainV1, PredictionProvenanceV4, PredictionProvenanceV5,
21    PredictionRuleAllocationV2, PredictionScalarV1, PredictionUnavailableReasonV2,
22    RawSceneAttachmentCoverageV1, RawSceneAttachmentInventoryV1, RawSourceBasisReferenceV1,
23    RawSourceDomainV1, RawSourceFieldIdV1, RawSourceKeyV1, ResolvedSettingLocationV1,
24    SourceFormatV1, SourceNodeLocalRest, SourceSkeletonCoverage, SourceSkeletonRowKindV1,
25    TransformScaleDomainV1, TransformScaleResultV1, TransformScaleSubjectKindV1,
26    UnitMappingResultV1,
27};
28use std::collections::{BTreeMap, BTreeSet};
29
30/// Stable id for Bevy's exact glTF unit/static-scale prediction.
31pub const ENGINE_UNIT_SCALE_CHECK_ID: &str = "engine-unit-scale";
32
33const BEVY_FAMILY: &str = "bevy";
34const BEVY_PROFILE_REVISION: u32 = 2;
35const BEVY_ENGINE_VERSION: &str = "0.19.0";
36const BEVY_IMPORTER: &str = "gltf-asset-loader";
37
38const BEVY_LOADER_SOURCE: &str = "bevy-gltf-loader-0.19.0-c6f634ca";
39const BEVY_COORDINATE_SOURCE: &str = "bevy-gltf-coordinate-conversion-0.19.0-c6f634ca";
40const BEVY_RENDER_ASSET_SOURCE: &str = "bevy-render-asset-usages-0.19.0-c6f634ca";
41const GLTF_UNIT_SOURCE: &str = "khronos-gltf-2.0-coordinate-units";
42
43const FILE_SCOPE: &str = "engine-unit-scale:file-unit";
44const SCENE_SCOPE: &str = "engine-unit-scale:loader-scene-root";
45const SCENE_INVENTORY_SCOPE: &str = "engine-unit-scale:scene-inventory";
46const MESH_SCOPE: &str = "engine-unit-scale:loader-mesh-primitive";
47const MESH_INVENTORY_SCOPE: &str = "engine-unit-scale:mesh-inventory";
48const SELECTED_NODE_SCOPE: &str = "engine-unit-scale:selected-source-node";
49const SELECTED_REACHABILITY_UNAVAILABLE_REASON: &str =
50    "animsmith:selected_node_scene_reachability_unavailable";
51
52// Bound malformed or adversarial parent walks independently of the source
53// inventory size. Ordinary production rigs fit within 128 retained rows;
54// deeper ancestry is a typed unavailable result instead of an unbounded walk.
55const SELECTED_ANCESTRY_MAX_NODES: usize = 128;
56
57/// Engine-owned check for the exact Bevy revision-2 glTF/GLB profile.
58pub struct EngineUnitScaleCheck<'a> {
59    source: &'a LoadedSource,
60    provenance: Option<&'a PredictionProvenanceV4>,
61    successor_provenance: Option<&'a PredictionProvenanceV5>,
62}
63
64impl<'a> EngineUnitScaleCheck<'a> {
65    /// Bind one optional exact-profile V4 provenance record to its same load.
66    ///
67    /// # Errors
68    ///
69    /// Returns [`PredictionRuleError`] when provenance is invalid, does not
70    /// reproduce the source's raw facts, inventory, or dependency closure, or
71    /// claims the frozen Bevy tuple with a non-registry profile record.
72    pub fn new(
73        source: &'a LoadedSource,
74        provenance: Option<&'a PredictionProvenanceV4>,
75    ) -> Result<Self, PredictionRuleError> {
76        if let Some(provenance) = provenance {
77            provenance
78                .validate()
79                .map_err(|_| PredictionRuleError::SourceProvenanceMismatch)?;
80            let raw_source = animsmith_core::RawSourceBindingV2::from_source(
81                source.source_facts(),
82                source.exact_source_timing(),
83            )
84            .map_err(|_| PredictionRuleError::SourceProvenanceMismatch)?;
85            let raw_inventory_matches = match (
86                provenance.raw_scene_attachment().inventory(),
87                source.raw_scene_attachment_inventory(),
88            ) {
89                (Some(left), Some(right)) => left == right,
90                (None, None) => true,
91                _ => false,
92            };
93            if &raw_source != provenance.raw_source()
94                || !raw_inventory_matches
95                || source.dependency_closure() != provenance.dependency_closure()
96            {
97                return Err(PredictionRuleError::SourceProvenanceMismatch);
98            }
99            if is_bevy_tuple(provenance)
100                && !matches_frozen_registry_projection_v2(provenance.profile())
101            {
102                return Err(PredictionRuleError::FrozenProfileMismatch);
103            }
104        }
105        Ok(Self {
106            source,
107            provenance,
108            successor_provenance: None,
109        })
110    }
111
112    /// Bind optional V5 provenance while retaining this rule's immutable V4
113    /// inputs and machine results inside an EnginePredictionV5 attachment.
114    pub fn new_v5(
115        source: &'a LoadedSource,
116        provenance: Option<&'a PredictionProvenanceV5>,
117    ) -> Result<Self, PredictionRuleError> {
118        let mut check = Self::new(source, provenance.map(PredictionProvenanceV5::base))?;
119        if let Some(provenance) = provenance {
120            provenance
121                .validate()
122                .map_err(|_| PredictionRuleError::SourceProvenanceMismatch)?;
123            if is_bevy_tuple_revision(provenance.base(), 3)
124                && !matches_frozen_registry_projection_v2(provenance.base().profile())
125            {
126                return Err(PredictionRuleError::FrozenProfileMismatch);
127            }
128        }
129        check.successor_provenance = provenance;
130        Ok(check)
131    }
132
133    fn exact_applicable(&self) -> bool {
134        match (self.provenance, self.successor_provenance) {
135            (Some(provenance), Some(_)) => {
136                is_exact_bevy_gltf_revision(self.source, provenance, 2)
137                    || is_exact_bevy_gltf_revision(self.source, provenance, 3)
138            }
139            (Some(provenance), None) => is_exact_bevy_gltf(self.source, provenance),
140            (None, _) => false,
141        }
142    }
143}
144
145impl Check for EngineUnitScaleCheck<'_> {
146    fn id(&self) -> &'static str {
147        ENGINE_UNIT_SCALE_CHECK_ID
148    }
149
150    fn applicability(&self, _ctx: &CheckCtx<'_>) -> Applicability {
151        if self.exact_applicable() {
152            Applicability::Applicable
153        } else {
154            Applicability::NotApplicable
155        }
156    }
157
158    fn evaluate(&self, _ctx: &CheckCtx<'_>) -> CheckOutput {
159        let Some(provenance) = self.provenance else {
160            return empty_output();
161        };
162        if !self.exact_applicable() {
163            return empty_output();
164        }
165        let plan = plan(self.source, provenance);
166        let (capacity, summary_required) = match plan.demand {
167            PredictionFacetDemandV2::Exact(count) => (count, false),
168            PredictionFacetDemandV2::NPlusOne => (
169                PREDICTION_V2_MAX_CANDIDATE_FACETS_PER_RULE.saturating_sub(1),
170                true,
171            ),
172        };
173        evaluate_allocated(
174            self.source,
175            provenance,
176            self.successor_provenance,
177            &plan,
178            capacity,
179            summary_required,
180        )
181    }
182
183    fn prediction_facet_demand_v2(&self, _ctx: &CheckCtx<'_>) -> PredictionFacetDemandV2 {
184        match self.provenance {
185            Some(provenance) if self.exact_applicable() => plan(self.source, provenance).demand,
186            _ => PredictionFacetDemandV2::Exact(0),
187        }
188    }
189
190    fn evaluate_with_prediction_allocation_v2(
191        &self,
192        _ctx: &CheckCtx<'_>,
193        allocation: PredictionRuleAllocationV2<'_>,
194    ) -> CheckOutput {
195        let Some(provenance) = self.provenance else {
196            return empty_output();
197        };
198        if !self.exact_applicable() {
199            return empty_output();
200        }
201        let plan = plan(self.source, provenance);
202        evaluate_allocated(
203            self.source,
204            provenance,
205            self.successor_provenance,
206            &plan,
207            allocation.candidate_capacity(),
208            allocation.summary_required(),
209        )
210    }
211}
212
213#[derive(Debug, Clone, Copy)]
214enum ScenePlan {
215    Detailed(usize),
216    Incomplete,
217}
218
219#[derive(Debug, Clone)]
220enum MeshPlan {
221    Detailed(Vec<MeshJoinRow>),
222    CompleteEmpty,
223    Incomplete,
224    JoinOverflow,
225}
226
227/// One deterministic atomic row of the bounded scene/attachment/primitive join.
228#[derive(Debug, Clone, Copy)]
229struct MeshJoinRow {
230    source_scene_index: u64,
231    source_root_ordinal: u64,
232    root_node_index: u64,
233    source_node_index: u64,
234    source_mesh_index: u64,
235    source_primitive_index: u64,
236}
237
238#[derive(Debug, Clone)]
239struct RulePlan {
240    demand: PredictionFacetDemandV2,
241    scenes: ScenePlan,
242    meshes: MeshPlan,
243    selectors: usize,
244}
245
246fn plan(source: &LoadedSource, provenance: &PredictionProvenanceV4) -> RulePlan {
247    let inventory = provenance.raw_scene_attachment().inventory();
248    let scenes = match inventory {
249        Some(inventory)
250            if inventory.scenes().coverage() == RawSceneAttachmentCoverageV1::Complete =>
251        {
252            ScenePlan::Detailed(inventory.scenes().rows().len())
253        }
254        _ => ScenePlan::Incomplete,
255    };
256    let meshes = plan_meshes(source, inventory);
257    let selectors = selected_facet_count(source, inventory, provenance);
258
259    let scene_facets = match scenes {
260        ScenePlan::Detailed(count) => count,
261        ScenePlan::Incomplete => 1,
262    };
263    let mesh_facets = match &meshes {
264        MeshPlan::Detailed(rows) => rows.len(),
265        MeshPlan::CompleteEmpty | MeshPlan::Incomplete | MeshPlan::JoinOverflow => 1,
266    };
267    let exact = 1usize
268        .checked_add(scene_facets)
269        .and_then(|count| count.checked_add(mesh_facets))
270        .and_then(|count| count.checked_add(selectors));
271    let demand = match exact {
272        Some(count) if count <= PREDICTION_V2_MAX_CANDIDATE_FACETS_PER_RULE => {
273            PredictionFacetDemandV2::Exact(count)
274        }
275        _ => PredictionFacetDemandV2::NPlusOne,
276    };
277    RulePlan {
278        demand,
279        scenes,
280        meshes,
281        selectors,
282    }
283}
284
285fn plan_meshes(
286    source: &LoadedSource,
287    inventory: Option<&RawSceneAttachmentInventoryV1>,
288) -> MeshPlan {
289    let Some(inventory) = inventory else {
290        return MeshPlan::Incomplete;
291    };
292    if inventory.scenes().coverage() != RawSceneAttachmentCoverageV1::Complete
293        || inventory.node_mesh_attachments().coverage() != RawSceneAttachmentCoverageV1::Complete
294        || inventory.mesh_primitives().coverage() != RawSceneAttachmentCoverageV1::Complete
295        || inventory.source_skeleton().coverage() != RawSceneAttachmentCoverageV1::Complete
296        || source.document().assets.source_skeleton.coverage != SourceSkeletonCoverage::Complete
297    {
298        return MeshPlan::Incomplete;
299    }
300    checked_mesh_join(source, inventory)
301}
302
303fn selected_facet_count(
304    source: &LoadedSource,
305    inventory: Option<&RawSceneAttachmentInventoryV1>,
306    provenance: &PredictionProvenanceV4,
307) -> usize {
308    let selectors = provenance.rule_inputs().runtime_node_selectors();
309    if selectors.is_empty() {
310        return 0;
311    }
312    if source.document().assets.source_skeleton.coverage != SourceSkeletonCoverage::Complete {
313        return selectors.len();
314    }
315    let nodes = &source.document().assets.source_skeleton.nodes;
316    let parents = selected_parent_index(source);
317    let complete_scenes = inventory.filter(|inventory| {
318        inventory.scenes().coverage() == RawSceneAttachmentCoverageV1::Complete
319            && inventory.source_skeleton().coverage() == RawSceneAttachmentCoverageV1::Complete
320    });
321    let mut count = 0usize;
322    for selector in selectors {
323        let mut matches = nodes.iter().filter(|node| {
324            node.name
325                .as_deref()
326                .is_some_and(|name| animsmith_core::config::glob_match(selector, name))
327        });
328        let first = matches.next();
329        let ambiguous = matches.next().is_some();
330        let facets = match (first, ambiguous, complete_scenes) {
331            (Some(node), false, Some(inventory)) => {
332                let mut work = 0usize;
333                let mut reachable = 0usize;
334                for scene in inventory.scenes().rows() {
335                    match selected_scene_reachability(
336                        node.source_node_index as u64,
337                        scene.root_node_indices(),
338                        &parents,
339                        &mut work,
340                    ) {
341                        SelectedSceneReachability::Reachable(_, _) => {
342                            reachable = reachable.saturating_add(1);
343                        }
344                        SelectedSceneReachability::Unreachable => {}
345                        SelectedSceneReachability::Unavailable
346                        | SelectedSceneReachability::WorkBudgetExceeded => {
347                            reachable = 0;
348                            break;
349                        }
350                    }
351                }
352                reachable.max(1)
353            }
354            _ => 1,
355        };
356        count = count.saturating_add(facets);
357        if count > PREDICTION_V2_MAX_CANDIDATE_FACETS_PER_RULE {
358            return count;
359        }
360    }
361    count
362}
363
364/// Materialize the atomic scene×reachable-attachment×primitive join to N+1.
365///
366/// Each scene/attachment probe, parent step, and matching primitive expansion
367/// spends one deterministic work unit. That bounds adversarial unmatched
368/// cross-products and deep walks as well as emitted facets; an incomplete probe
369/// is a budget result, never a complete-empty inventory claim.
370fn checked_mesh_join(source: &LoadedSource, inventory: &RawSceneAttachmentInventoryV1) -> MeshPlan {
371    let parents = source
372        .document()
373        .assets
374        .source_skeleton
375        .nodes
376        .iter()
377        .map(|node| {
378            (
379                node.source_node_index as u64,
380                node.parent_source_node_index.map(|parent| parent as u64),
381            )
382        })
383        .collect::<BTreeMap<_, _>>();
384    let scene_roots = inventory
385        .scenes()
386        .rows()
387        .iter()
388        .map(|scene| {
389            (
390                scene.source_scene_index(),
391                scene
392                    .root_node_indices()
393                    .iter()
394                    .copied()
395                    .enumerate()
396                    .map(|(ordinal, node)| (node, ordinal as u64))
397                    .collect::<BTreeMap<_, _>>(),
398            )
399        })
400        .collect::<Vec<_>>();
401    let primitives_by_mesh = inventory.mesh_primitives().rows().iter().fold(
402        BTreeMap::<u64, Vec<u64>>::new(),
403        |mut grouped, primitive| {
404            grouped
405                .entry(primitive.source_mesh_index())
406                .or_default()
407                .push(primitive.source_primitive_index());
408            grouped
409        },
410    );
411    let mut work = 0usize;
412    let mut rows = Vec::new();
413    for (source_scene_index, roots) in scene_roots {
414        for attachment in inventory.node_mesh_attachments().rows() {
415            if join_budget_exceeded(&mut work) {
416                return MeshPlan::JoinOverflow;
417            }
418            let Ok(reachable) = reachable_scene_root_indexed(
419                attachment.source_node_index(),
420                &roots,
421                &parents,
422                &mut work,
423            ) else {
424                return MeshPlan::JoinOverflow;
425            };
426            let Some((source_root_ordinal, root_node_index)) = reachable else {
427                continue;
428            };
429            for &source_primitive_index in primitives_by_mesh
430                .get(&attachment.source_mesh_index())
431                .into_iter()
432                .flatten()
433            {
434                if join_budget_exceeded(&mut work) {
435                    return MeshPlan::JoinOverflow;
436                }
437                rows.push(MeshJoinRow {
438                    source_scene_index,
439                    source_root_ordinal,
440                    root_node_index,
441                    source_node_index: attachment.source_node_index(),
442                    source_mesh_index: attachment.source_mesh_index(),
443                    source_primitive_index,
444                });
445            }
446        }
447    }
448    if rows.is_empty() {
449        MeshPlan::CompleteEmpty
450    } else {
451        MeshPlan::Detailed(rows)
452    }
453}
454
455fn join_budget_exceeded(work: &mut usize) -> bool {
456    *work = work.saturating_add(1);
457    *work > PREDICTION_V2_MAX_CANDIDATE_FACETS_PER_RULE
458}
459
460fn evaluate_allocated(
461    source: &LoadedSource,
462    provenance: &PredictionProvenanceV4,
463    successor_provenance: Option<&PredictionProvenanceV5>,
464    plan: &RulePlan,
465    candidate_capacity: usize,
466    summary_required: bool,
467) -> CheckOutput {
468    let measurements = measure_assets(source.document());
469    let dependency_complete = matches!(
470        provenance.dependency_closure().coverage(),
471        DependencyClosureCoverageV1::Complete
472    );
473    let mut candidates = Vec::with_capacity(plan.demand.bounded_count());
474
475    if candidate_capacity != 0 {
476        candidates.push(file_facet(provenance, dependency_complete));
477    }
478    append_scene_facets(
479        &mut candidates,
480        provenance,
481        plan.scenes,
482        dependency_complete,
483        candidate_capacity,
484    );
485    append_mesh_facets(
486        &mut candidates,
487        provenance,
488        &plan.meshes,
489        dependency_complete,
490        candidate_capacity,
491    );
492    append_selected_node_facets(
493        &mut candidates,
494        plan.selectors,
495        SelectedEvaluation {
496            source,
497            provenance,
498            measurements: &measurements,
499            dependency_complete,
500            candidate_capacity,
501        },
502    );
503
504    let mut facets = candidates;
505    if summary_required {
506        facets.push(unavailable_facet(
507            budget_scope(),
508            static_basis(&[BEVY_LOADER_SOURCE]),
509            vec![PredictionUnavailableReasonV2::FacetBudgetExceeded],
510        ));
511    }
512    let evaluated_scopes = facets
513        .iter()
514        .filter(|facet| facet.result().is_some())
515        .map(|facet| facet.scope().clone())
516        .collect();
517    let prediction = EnginePredictionV4::new(provenance.identity().clone(), facets)
518        .expect("allocated unit/scale facets satisfy V4 bounds");
519    let output = CheckOutput::from_coverage(Vec::new(), evaluated_scopes, Vec::new());
520    match successor_provenance {
521        Some(successor) => output.with_engine_prediction_v5(
522            animsmith_core::EnginePredictionV5::new(successor, prediction)
523                .expect("unit-scale V5 prediction binds the same immutable V4 authority"),
524        ),
525        None => output.with_engine_prediction_v4(prediction),
526    }
527}
528
529fn file_facet(
530    _provenance: &PredictionProvenanceV4,
531    dependency_complete: bool,
532) -> EnginePredictionFacetV4 {
533    let scope = EvaluationScope::new(EvaluationScopeCode::custom(FILE_SCOPE));
534    let basis = unit_basis();
535    if !dependency_complete {
536        return unavailable_facet(
537            scope,
538            basis,
539            vec![PredictionUnavailableReasonV2::DependencyClosureIncomplete],
540        );
541    }
542    EnginePredictionFacetV4::available(
543        scope,
544        basis,
545        EngineMachineResultV1::UnitMapping(UnitMappingResultV1::gltf_to_engine_world_length_unit()),
546    )
547    .expect("the frozen Bevy file mapping is valid")
548}
549
550fn append_scene_facets(
551    facets: &mut Vec<EnginePredictionFacetV4>,
552    provenance: &PredictionProvenanceV4,
553    plan: ScenePlan,
554    dependency_complete: bool,
555    candidate_capacity: usize,
556) {
557    if facets.len() >= candidate_capacity {
558        return;
559    }
560    let Some(inventory) = provenance.raw_scene_attachment().inventory() else {
561        facets.push(unavailable_facet(
562            EvaluationScope::new(EvaluationScopeCode::custom(SCENE_INVENTORY_SCOPE)),
563            scene_inventory_basis(None),
564            unavailable_with_dependency(
565                PredictionUnavailableReasonV2::RawSourceIncomplete,
566                dependency_complete,
567            ),
568        ));
569        return;
570    };
571    match plan {
572        ScenePlan::Incomplete => facets.push(unavailable_facet(
573            EvaluationScope::new(EvaluationScopeCode::custom(SCENE_INVENTORY_SCOPE)),
574            scene_inventory_basis(Some(inventory)),
575            unavailable_with_dependency(
576                PredictionUnavailableReasonV2::RawSourceIncomplete,
577                dependency_complete,
578            ),
579        )),
580        ScenePlan::Detailed(expected) => {
581            debug_assert_eq!(expected, inventory.scenes().rows().len());
582            for scene in inventory.scenes().rows() {
583                if facets.len() >= candidate_capacity {
584                    break;
585                }
586                let scope = EvaluationScope::new(EvaluationScopeCode::custom(SCENE_SCOPE))
587                    .subject(format!("source_scene:{}", scene.source_scene_index()));
588                let basis = scene_row_basis(scene.source_scene_index());
589                if dependency_complete {
590                    facets.push(available_transform(
591                        scope,
592                        basis,
593                        TransformScaleSubjectKindV1::LoaderSceneEntity,
594                        ImporterSubjectCreationV1::Created,
595                        TransformScaleDomainV1::Local,
596                        LinearTransformClassification::UnitOrthonormal,
597                    ));
598                } else {
599                    facets.push(unavailable_facet(
600                        scope,
601                        basis,
602                        vec![PredictionUnavailableReasonV2::DependencyClosureIncomplete],
603                    ));
604                }
605            }
606        }
607    }
608}
609
610fn append_mesh_facets(
611    facets: &mut Vec<EnginePredictionFacetV4>,
612    provenance: &PredictionProvenanceV4,
613    plan: &MeshPlan,
614    dependency_complete: bool,
615    candidate_capacity: usize,
616) {
617    if facets.len() >= candidate_capacity {
618        return;
619    }
620    match plan {
621        MeshPlan::Incomplete => {
622            facets.push(unavailable_facet(
623                EvaluationScope::new(EvaluationScopeCode::custom(MESH_INVENTORY_SCOPE)),
624                mesh_inventory_basis(provenance.raw_scene_attachment().inventory()),
625                unavailable_with_dependency(
626                    PredictionUnavailableReasonV2::RawSourceIncomplete,
627                    dependency_complete,
628                ),
629            ));
630        }
631        MeshPlan::JoinOverflow => {
632            facets.push(unavailable_facet(
633                EvaluationScope::new(EvaluationScopeCode::custom(MESH_INVENTORY_SCOPE)),
634                mesh_inventory_basis(provenance.raw_scene_attachment().inventory()),
635                vec![custom_reason("animsmith:mesh_join_work_budget_exceeded")],
636            ));
637        }
638        MeshPlan::CompleteEmpty => {
639            let scope = EvaluationScope::new(EvaluationScopeCode::custom(MESH_INVENTORY_SCOPE));
640            let basis = mesh_inventory_basis(provenance.raw_scene_attachment().inventory());
641            if dependency_complete {
642                facets.push(
643                    EnginePredictionFacetV4::available(
644                        scope,
645                        basis,
646                        EngineMachineResultV1::InventoryCoverage(InventoryCoverageResultV1 {
647                            domain: PredictionInventoryDomainV1::LoaderMeshPrimitiveSubjects,
648                            coverage: PredictionInventoryCoverageStateV1::Complete,
649                            retained_rows: 0,
650                        }),
651                    )
652                    .expect("complete-empty joined mesh subjects form an available result"),
653                );
654            } else {
655                facets.push(unavailable_facet(
656                    scope,
657                    basis,
658                    vec![PredictionUnavailableReasonV2::DependencyClosureIncomplete],
659                ));
660            }
661        }
662        MeshPlan::Detailed(rows) => {
663            for row in rows {
664                if facets.len() >= candidate_capacity {
665                    break;
666                }
667                let scope =
668                    EvaluationScope::new(EvaluationScopeCode::custom(MESH_SCOPE)).subject(format!(
669                        "source_scene:{}:source_node:{}:source_mesh:{}:source_primitive:{}",
670                        row.source_scene_index,
671                        row.source_node_index,
672                        row.source_mesh_index,
673                        row.source_primitive_index,
674                    ));
675                let basis = mesh_row_basis(
676                    row.source_scene_index,
677                    row.source_root_ordinal,
678                    row.root_node_index,
679                    row.source_node_index,
680                    row.source_mesh_index,
681                    row.source_primitive_index,
682                );
683                if dependency_complete {
684                    facets.push(available_transform(
685                        scope,
686                        basis,
687                        TransformScaleSubjectKindV1::LoaderMeshPrimitiveEntity,
688                        if load_meshes(provenance) {
689                            ImporterSubjectCreationV1::Created
690                        } else {
691                            ImporterSubjectCreationV1::SuppressedBySetting
692                        },
693                        TransformScaleDomainV1::Local,
694                        LinearTransformClassification::UnitOrthonormal,
695                    ));
696                } else {
697                    facets.push(unavailable_facet(
698                        scope,
699                        basis,
700                        vec![PredictionUnavailableReasonV2::DependencyClosureIncomplete],
701                    ));
702                }
703            }
704        }
705    }
706}
707
708#[derive(Clone, Copy)]
709struct SelectedEvaluation<'a> {
710    source: &'a LoadedSource,
711    provenance: &'a PredictionProvenanceV4,
712    measurements: &'a AssetMeasurements,
713    dependency_complete: bool,
714    candidate_capacity: usize,
715}
716
717fn append_selected_node_facets(
718    facets: &mut Vec<EnginePredictionFacetV4>,
719    expected_facets: usize,
720    evaluation: SelectedEvaluation<'_>,
721) {
722    let SelectedEvaluation {
723        source,
724        provenance,
725        measurements: _,
726        dependency_complete,
727        candidate_capacity,
728    } = evaluation;
729    let selectors = provenance.rule_inputs().runtime_node_selectors();
730    if selectors.is_empty() {
731        debug_assert_eq!(expected_facets, 0);
732        return;
733    }
734    let inventory = provenance.raw_scene_attachment().inventory();
735    let inventory_complete = inventory.is_some_and(|inventory| {
736        inventory.scenes().coverage() == RawSceneAttachmentCoverageV1::Complete
737            && inventory.source_skeleton().coverage() == RawSceneAttachmentCoverageV1::Complete
738    });
739    let start_len = facets.len();
740    if source.document().assets.source_skeleton.coverage != SourceSkeletonCoverage::Complete
741        || !inventory_complete
742    {
743        for selector in selectors {
744            if facets.len() >= candidate_capacity {
745                break;
746            }
747            facets.push(unavailable_facet(
748                selected_scope(selector),
749                selected_resolution_basis(source, inventory, selector, &[]),
750                unavailable_with_dependency(
751                    PredictionUnavailableReasonV2::RawSourceIncomplete,
752                    dependency_complete,
753                ),
754            ));
755        }
756        return;
757    }
758
759    let inventory = inventory.expect("complete selected-node inventory exists");
760    for selector in selectors {
761        if facets.len() >= candidate_capacity {
762            break;
763        }
764        let mut matches = source
765            .document()
766            .assets
767            .source_skeleton
768            .nodes
769            .iter()
770            .filter(|node| {
771                node.name
772                    .as_deref()
773                    .is_some_and(|name| animsmith_core::config::glob_match(selector, name))
774            });
775        let first = matches.next();
776        let second = matches.next();
777        match (first, second) {
778            (None, _) => facets.push(unavailable_facet(
779                selected_scope(selector),
780                selected_resolution_basis(source, Some(inventory), selector, &[]),
781                unavailable_with_dependency(
782                    PredictionUnavailableReasonV2::SourceSelectorNoMatch,
783                    dependency_complete,
784                ),
785            )),
786            (Some(first), Some(second)) => facets.push(unavailable_facet(
787                selected_scope(selector),
788                selected_resolution_basis(
789                    source,
790                    Some(inventory),
791                    selector,
792                    &[first.source_node_index, second.source_node_index],
793                ),
794                unavailable_with_dependency(
795                    PredictionUnavailableReasonV2::SourceSelectorAmbiguous,
796                    dependency_complete,
797                ),
798            )),
799            (Some(node), None) => append_resolved_selected_node(
800                facets,
801                inventory,
802                selector,
803                node.source_node_index,
804                evaluation,
805            ),
806        }
807    }
808    if candidate_capacity >= start_len.saturating_add(expected_facets) {
809        debug_assert_eq!(facets.len() - start_len, expected_facets);
810    }
811}
812
813fn append_resolved_selected_node(
814    facets: &mut Vec<EnginePredictionFacetV4>,
815    inventory: &RawSceneAttachmentInventoryV1,
816    selector: &str,
817    node_index: usize,
818    evaluation: SelectedEvaluation<'_>,
819) {
820    let SelectedEvaluation {
821        source,
822        measurements,
823        dependency_complete,
824        candidate_capacity,
825        ..
826    } = evaluation;
827    let parents = selected_parent_index(source);
828    let mut reachable_scenes = Vec::new();
829    let mut work = 0usize;
830    for scene in inventory.scenes().rows() {
831        match selected_scene_reachability(
832            node_index as u64,
833            scene.root_node_indices(),
834            &parents,
835            &mut work,
836        ) {
837            SelectedSceneReachability::Reachable(root_ordinal, root_node_index) => {
838                reachable_scenes.push((scene, (root_ordinal, root_node_index)));
839            }
840            SelectedSceneReachability::Unreachable => {}
841            SelectedSceneReachability::Unavailable
842            | SelectedSceneReachability::WorkBudgetExceeded => {
843                facets.push(unavailable_facet(
844                    selected_scope(selector),
845                    selected_node_basis(
846                        source,
847                        Some(inventory),
848                        selector,
849                        node_index,
850                        &[],
851                        None,
852                        None,
853                    ),
854                    unavailable_with_dependency(
855                        custom_reason(SELECTED_REACHABILITY_UNAVAILABLE_REASON),
856                        dependency_complete,
857                    ),
858                ));
859                return;
860            }
861        }
862    }
863    let ancestry = selected_ancestry(source, node_index);
864    let measurement = measurements
865        .skeleton_nodes
866        .iter()
867        .enumerate()
868        .find(|(_, node)| node.node_index == node_index);
869    if reachable_scenes.is_empty() {
870        facets.push(unavailable_facet(
871            selected_scope(selector),
872            selected_node_basis(
873                source,
874                Some(inventory),
875                selector,
876                node_index,
877                &ancestry.rows,
878                None,
879                None,
880            ),
881            unavailable_with_dependency(
882                custom_reason("animsmith:selected_node_unreachable"),
883                dependency_complete,
884            ),
885        ));
886        return;
887    }
888
889    for (scene, (root_ordinal, root_node_index)) in reachable_scenes {
890        if facets.len() >= candidate_capacity {
891            break;
892        }
893        let scene_witness = Some(SelectedSceneWitness {
894            source_scene_index: scene.source_scene_index(),
895            source_root_ordinal: root_ordinal,
896            root_node_index,
897        });
898        let basis = selected_node_basis(
899            source,
900            Some(inventory),
901            selector,
902            node_index,
903            &ancestry.rows,
904            scene_witness,
905            measurement,
906        );
907        let scope = selected_scene_scope(selector, scene.source_scene_index(), node_index);
908        let unavailable_reason = match ancestry.representation {
909            SelectedRepresentation::AllTrs => measurement.and_then(|(_, node)| {
910                (node.rest_world_linear.classification == LinearTransformClassification::NonFinite
911                    || node.rest_world_matrix.is_none())
912                .then_some(PredictionUnavailableReasonV2::MeasurementUnavailable)
913            }),
914            SelectedRepresentation::MatrixAuthored => Some(custom_reason(
915                "animsmith:matrix_authored_selected_node_or_ancestry",
916            )),
917            SelectedRepresentation::Unavailable => Some(custom_reason(
918                "animsmith:selected_node_ancestry_unavailable",
919            )),
920        }
921        .or_else(|| {
922            measurement
923                .is_none()
924                .then_some(PredictionUnavailableReasonV2::MeasurementUnavailable)
925        });
926        if let Some(reason) = unavailable_reason {
927            facets.push(unavailable_facet(
928                scope,
929                basis,
930                unavailable_with_dependency(reason, dependency_complete),
931            ));
932        } else if dependency_complete {
933            let node = measurement
934                .expect("available selected node has a measurement")
935                .1;
936            // Bevy's optional loader-root 180-degree Y rotation is proper
937            // orthonormal pre-multiplication. Its two linear sign flips
938            // preserve the same-load affine classification exactly.
939            facets.push(available_transform(
940                scope,
941                basis,
942                TransformScaleSubjectKindV1::SelectedSourceNode,
943                ImporterSubjectCreationV1::Created,
944                TransformScaleDomainV1::LoaderRootToSubject,
945                node.rest_world_linear.classification,
946            ));
947        } else {
948            facets.push(unavailable_facet(
949                scope,
950                basis,
951                vec![PredictionUnavailableReasonV2::DependencyClosureIncomplete],
952            ));
953        }
954    }
955}
956
957#[derive(Debug, Clone, Copy, PartialEq, Eq)]
958enum SelectedRepresentation {
959    AllTrs,
960    MatrixAuthored,
961    Unavailable,
962}
963
964struct SelectedAncestry {
965    representation: SelectedRepresentation,
966    rows: Vec<usize>,
967}
968
969fn selected_ancestry(source: &LoadedSource, start: usize) -> SelectedAncestry {
970    let nodes = source
971        .document()
972        .assets
973        .source_skeleton
974        .nodes
975        .iter()
976        .map(|node| (node.source_node_index, node))
977        .collect::<BTreeMap<_, _>>();
978    let mut seen = BTreeSet::new();
979    let mut current = start;
980    let mut rows = Vec::new();
981    for _ in 0..SELECTED_ANCESTRY_MAX_NODES {
982        let index = current;
983        if !seen.insert(index) {
984            return SelectedAncestry {
985                representation: SelectedRepresentation::Unavailable,
986                rows,
987            };
988        }
989        let Some(node) = nodes.get(&index) else {
990            return SelectedAncestry {
991                representation: SelectedRepresentation::Unavailable,
992                rows,
993            };
994        };
995        rows.push(index);
996        if matches!(node.local_rest, SourceNodeLocalRest::Matrix(_)) {
997            return SelectedAncestry {
998                representation: SelectedRepresentation::MatrixAuthored,
999                rows,
1000            };
1001        }
1002        let Some(parent) = node.parent_source_node_index else {
1003            return SelectedAncestry {
1004                representation: SelectedRepresentation::AllTrs,
1005                rows,
1006            };
1007        };
1008        current = parent;
1009    }
1010    SelectedAncestry {
1011        representation: SelectedRepresentation::Unavailable,
1012        rows,
1013    }
1014}
1015
1016#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1017enum SelectedSceneReachability {
1018    Reachable(u64, u64),
1019    Unreachable,
1020    Unavailable,
1021    WorkBudgetExceeded,
1022}
1023
1024fn selected_reachability_budget_exceeded(work: &mut usize) -> bool {
1025    *work = work.saturating_add(1);
1026    *work > PREDICTION_V2_MAX_CANDIDATE_FACETS_PER_RULE
1027}
1028
1029fn selected_parent_index(source: &LoadedSource) -> BTreeMap<u64, Option<u64>> {
1030    source
1031        .document()
1032        .assets
1033        .source_skeleton
1034        .nodes
1035        .iter()
1036        .map(|node| {
1037            (
1038                node.source_node_index as u64,
1039                node.parent_source_node_index.map(|parent| parent as u64),
1040            )
1041        })
1042        .collect()
1043}
1044
1045fn selected_scene_reachability(
1046    start: u64,
1047    roots: &[u64],
1048    parents: &BTreeMap<u64, Option<u64>>,
1049    work: &mut usize,
1050) -> SelectedSceneReachability {
1051    if *work >= PREDICTION_V2_MAX_CANDIDATE_FACETS_PER_RULE {
1052        return SelectedSceneReachability::WorkBudgetExceeded;
1053    }
1054    let roots = roots
1055        .iter()
1056        .copied()
1057        .enumerate()
1058        .map(|(ordinal, node)| (node, ordinal as u64))
1059        .collect::<BTreeMap<_, _>>();
1060    let mut seen = BTreeSet::new();
1061    let mut current = Some(start);
1062    for _ in 0..SELECTED_ANCESTRY_MAX_NODES {
1063        let Some(index) = current else {
1064            return SelectedSceneReachability::Unreachable;
1065        };
1066        if selected_reachability_budget_exceeded(work) {
1067            return SelectedSceneReachability::WorkBudgetExceeded;
1068        }
1069        if let Some(ordinal) = roots.get(&index) {
1070            return SelectedSceneReachability::Reachable(*ordinal, index);
1071        }
1072        if !seen.insert(index) {
1073            return SelectedSceneReachability::Unavailable;
1074        }
1075        current = match parents.get(&index) {
1076            Some(parent) => *parent,
1077            None => return SelectedSceneReachability::Unavailable,
1078        };
1079    }
1080    match current {
1081        Some(_) => SelectedSceneReachability::Unavailable,
1082        None => SelectedSceneReachability::Unreachable,
1083    }
1084}
1085
1086fn reachable_scene_root_indexed(
1087    start: u64,
1088    roots: &BTreeMap<u64, u64>,
1089    parents: &BTreeMap<u64, Option<u64>>,
1090    work: &mut usize,
1091) -> Result<Option<(u64, u64)>, ()> {
1092    let mut seen = BTreeSet::new();
1093    let mut current = Some(start);
1094    while let Some(index) = current {
1095        if join_budget_exceeded(work) {
1096            return Err(());
1097        }
1098        if let Some(ordinal) = roots.get(&index) {
1099            return Ok(Some((*ordinal, index)));
1100        }
1101        if !seen.insert(index) {
1102            return Ok(None);
1103        }
1104        current = match parents.get(&index) {
1105            Some(parent) => *parent,
1106            None => return Ok(None),
1107        };
1108    }
1109    Ok(None)
1110}
1111
1112fn available_transform(
1113    scope: EvaluationScope,
1114    basis: EnginePredictionBasisV4,
1115    subject_kind: TransformScaleSubjectKindV1,
1116    creation: ImporterSubjectCreationV1,
1117    domain: TransformScaleDomainV1,
1118    classification: LinearTransformClassification,
1119) -> EnginePredictionFacetV4 {
1120    EnginePredictionFacetV4::available(
1121        scope,
1122        basis,
1123        EngineMachineResultV1::TransformScale(TransformScaleResultV1 {
1124            subject_kind,
1125            creation,
1126            domain,
1127            classification: (creation == ImporterSubjectCreationV1::Created)
1128                .then_some(classification),
1129        }),
1130    )
1131    .expect("the exact Bevy transform result is valid")
1132}
1133
1134fn unavailable_facet(
1135    scope: EvaluationScope,
1136    basis: EnginePredictionBasisV4,
1137    reasons: Vec<PredictionUnavailableReasonV2>,
1138) -> EnginePredictionFacetV4 {
1139    EnginePredictionFacetV4::required_unavailable(scope, basis, reasons)
1140        .expect("typed unit/scale unavailability is valid")
1141}
1142
1143fn unavailable_with_dependency(
1144    reason: PredictionUnavailableReasonV2,
1145    dependency_complete: bool,
1146) -> Vec<PredictionUnavailableReasonV2> {
1147    let mut reasons = vec![reason];
1148    if !dependency_complete {
1149        reasons.push(PredictionUnavailableReasonV2::DependencyClosureIncomplete);
1150    }
1151    reasons
1152}
1153
1154fn unit_basis() -> EnginePredictionBasisV4 {
1155    let mut references = [
1156        "application_world_unit_policy",
1157        "importer_scale_conversion",
1158        "physical_dimensions_preserved",
1159        "source_to_target_unit_mapping",
1160        "target_linear_unit",
1161    ]
1162    .into_iter()
1163    .map(|fact| {
1164        PredictionBasisReferenceV4::v2(PredictionBasisReferenceV2::v1(
1165            PredictionBasisReferenceV1::profile_fact(fact).expect("static fact id is valid"),
1166        ))
1167    })
1168    .collect::<Vec<_>>();
1169    for source in [BEVY_LOADER_SOURCE, BEVY_COORDINATE_SOURCE, GLTF_UNIT_SOURCE] {
1170        references.push(primary_source_reference(source));
1171    }
1172    EnginePredictionBasisV4::new(references).expect("the frozen unit basis is valid")
1173}
1174
1175fn scene_inventory_basis(
1176    inventory: Option<&RawSceneAttachmentInventoryV1>,
1177) -> EnginePredictionBasisV4 {
1178    let mut references = static_references(&[BEVY_LOADER_SOURCE, BEVY_COORDINATE_SOURCE]);
1179    references.push(transform_profile_fact_reference());
1180    references.push(setting_reference(SettingIdV2::ExtensionHandlerEnvironment));
1181    references.push(setting_reference(SettingIdV2::RotateSceneEntity));
1182    if inventory.is_some() {
1183        references.push(raw_inventory_reference(
1184            RawSceneAttachmentBasisReferenceV1::Coverage {
1185                domain: RawSceneAttachmentBasisDomainV1::Scenes,
1186            },
1187        ));
1188    }
1189    EnginePredictionBasisV4::new(references).expect("the frozen scene basis is valid")
1190}
1191
1192fn scene_row_basis(source_scene_index: u64) -> EnginePredictionBasisV4 {
1193    let mut references = static_references(&[BEVY_LOADER_SOURCE, BEVY_COORDINATE_SOURCE]);
1194    references.push(transform_profile_fact_reference());
1195    references.push(setting_reference(SettingIdV2::ExtensionHandlerEnvironment));
1196    references.push(setting_reference(SettingIdV2::RotateSceneEntity));
1197    references.push(raw_inventory_reference(
1198        RawSceneAttachmentBasisReferenceV1::Coverage {
1199            domain: RawSceneAttachmentBasisDomainV1::Scenes,
1200        },
1201    ));
1202    references.push(raw_inventory_reference(
1203        RawSceneAttachmentBasisReferenceV1::SceneRow { source_scene_index },
1204    ));
1205    EnginePredictionBasisV4::new(references).expect("the scene-row basis is valid")
1206}
1207
1208fn mesh_inventory_basis(
1209    inventory: Option<&RawSceneAttachmentInventoryV1>,
1210) -> EnginePredictionBasisV4 {
1211    let mut references = static_references(&[
1212        BEVY_LOADER_SOURCE,
1213        BEVY_COORDINATE_SOURCE,
1214        BEVY_RENDER_ASSET_SOURCE,
1215    ]);
1216    references.push(transform_profile_fact_reference());
1217    references.push(setting_reference(SettingIdV2::ExtensionHandlerEnvironment));
1218    references.push(setting_reference(SettingIdV2::LoadMeshes));
1219    references.push(setting_reference(SettingIdV2::RotateMeshes));
1220    if inventory.is_some() {
1221        for domain in [
1222            RawSceneAttachmentBasisDomainV1::SourceSkeleton,
1223            RawSceneAttachmentBasisDomainV1::Scenes,
1224            RawSceneAttachmentBasisDomainV1::NodeMeshAttachments,
1225            RawSceneAttachmentBasisDomainV1::MeshPrimitives,
1226        ] {
1227            references.push(raw_inventory_reference(
1228                RawSceneAttachmentBasisReferenceV1::Coverage { domain },
1229            ));
1230        }
1231    }
1232    EnginePredictionBasisV4::new(references).expect("the frozen mesh basis is valid")
1233}
1234
1235fn mesh_row_basis(
1236    source_scene_index: u64,
1237    source_root_ordinal: u64,
1238    root_node_index: u64,
1239    source_node_index: u64,
1240    source_mesh_index: u64,
1241    source_primitive_index: u64,
1242) -> EnginePredictionBasisV4 {
1243    let mut references = static_references(&[
1244        BEVY_LOADER_SOURCE,
1245        BEVY_COORDINATE_SOURCE,
1246        BEVY_RENDER_ASSET_SOURCE,
1247    ]);
1248    references.push(transform_profile_fact_reference());
1249    references.push(setting_reference(SettingIdV2::ExtensionHandlerEnvironment));
1250    references.push(setting_reference(SettingIdV2::LoadMeshes));
1251    references.push(setting_reference(SettingIdV2::RotateMeshes));
1252    for domain in [
1253        RawSceneAttachmentBasisDomainV1::SourceSkeleton,
1254        RawSceneAttachmentBasisDomainV1::Scenes,
1255        RawSceneAttachmentBasisDomainV1::NodeMeshAttachments,
1256        RawSceneAttachmentBasisDomainV1::MeshPrimitives,
1257    ] {
1258        references.push(raw_inventory_reference(
1259            RawSceneAttachmentBasisReferenceV1::Coverage { domain },
1260        ));
1261    }
1262    for reference in [
1263        RawSceneAttachmentBasisReferenceV1::SceneRow { source_scene_index },
1264        RawSceneAttachmentBasisReferenceV1::SceneRoot {
1265            source_scene_index,
1266            source_root_ordinal,
1267            source_node_index: root_node_index,
1268        },
1269        RawSceneAttachmentBasisReferenceV1::NodeMeshAttachmentRow {
1270            source_node_index,
1271            source_mesh_index,
1272        },
1273        RawSceneAttachmentBasisReferenceV1::MeshPrimitiveRow {
1274            source_mesh_index,
1275            source_primitive_index,
1276        },
1277    ] {
1278        references.push(raw_inventory_reference(reference));
1279    }
1280    EnginePredictionBasisV4::new(references).expect("the mesh-row basis is valid")
1281}
1282
1283fn raw_inventory_reference(
1284    reference: RawSceneAttachmentBasisReferenceV1,
1285) -> PredictionBasisReferenceV4 {
1286    PredictionBasisReferenceV4::raw_scene_attachment(reference)
1287}
1288
1289fn selected_static_references(selector: &str) -> Vec<PredictionBasisReferenceV4> {
1290    let mut references = static_references(&[BEVY_LOADER_SOURCE, BEVY_COORDINATE_SOURCE]);
1291    references.push(transform_profile_fact_reference());
1292    references.push(setting_reference(SettingIdV2::ExtensionHandlerEnvironment));
1293    references.push(setting_reference(SettingIdV2::RotateSceneEntity));
1294    references.push(PredictionBasisReferenceV4::v2(
1295        PredictionBasisReferenceV2::v1(
1296            PredictionBasisReferenceV1::project_field(
1297                "runtime_nodes.selector",
1298                PredictionScalarV1::text(selector).expect("validated selector text is bounded"),
1299            )
1300            .expect("the runtime-node selector field id is valid"),
1301        ),
1302    ));
1303    references
1304}
1305
1306fn selected_resolution_basis(
1307    source: &LoadedSource,
1308    inventory: Option<&RawSceneAttachmentInventoryV1>,
1309    selector: &str,
1310    matched_node_indices: &[usize],
1311) -> EnginePredictionBasisV4 {
1312    let mut references = selected_static_references(selector);
1313    if inventory.is_some() {
1314        for domain in [
1315            RawSceneAttachmentBasisDomainV1::SourceSkeleton,
1316            RawSceneAttachmentBasisDomainV1::Scenes,
1317        ] {
1318            references.push(raw_inventory_reference(
1319                RawSceneAttachmentBasisReferenceV1::Coverage { domain },
1320            ));
1321        }
1322    }
1323    for &node_index in matched_node_indices {
1324        references.push(raw_source_node_reference(source, node_index, "name"));
1325    }
1326    EnginePredictionBasisV4::new(references).expect("the selected-node resolution basis is valid")
1327}
1328
1329#[derive(Debug, Clone, Copy)]
1330struct SelectedSceneWitness {
1331    source_scene_index: u64,
1332    source_root_ordinal: u64,
1333    root_node_index: u64,
1334}
1335
1336fn selected_node_basis(
1337    source: &LoadedSource,
1338    inventory: Option<&RawSceneAttachmentInventoryV1>,
1339    selector: &str,
1340    node_index: usize,
1341    ancestry_rows: &[usize],
1342    scene: Option<SelectedSceneWitness>,
1343    measurement: Option<(usize, &SkeletonNodeMeasurements)>,
1344) -> EnginePredictionBasisV4 {
1345    let mut references = selected_resolution_basis(source, inventory, selector, &[node_index])
1346        .references()
1347        .to_vec();
1348    for &ancestry_node_index in ancestry_rows {
1349        references.push(raw_source_node_reference(
1350            source,
1351            ancestry_node_index,
1352            "local_rest.kind",
1353        ));
1354        references.push(raw_source_node_reference(
1355            source,
1356            ancestry_node_index,
1357            "parent_source_node_index",
1358        ));
1359    }
1360    if let Some(scene) = scene {
1361        references.push(raw_inventory_reference(
1362            RawSceneAttachmentBasisReferenceV1::SceneRow {
1363                source_scene_index: scene.source_scene_index,
1364            },
1365        ));
1366        references.push(raw_inventory_reference(
1367            RawSceneAttachmentBasisReferenceV1::SceneRoot {
1368                source_scene_index: scene.source_scene_index,
1369                source_root_ordinal: scene.source_root_ordinal,
1370                source_node_index: scene.root_node_index,
1371            },
1372        ));
1373    }
1374    if let Some((measurement_ordinal, node)) = measurement {
1375        references.push(PredictionBasisReferenceV4::v2(
1376            PredictionBasisReferenceV2::v1(PredictionBasisReferenceV1::measurement_v16(
1377                MeasurementPointerV1::new(format!(
1378                    "/measurements/skeleton_nodes/{measurement_ordinal}/rest_world_linear/classification"
1379                ))
1380                .expect("static measurement path shape is valid"),
1381                PredictionScalarV1::token(classification_name(
1382                    node.rest_world_linear.classification,
1383                ))
1384                .expect("classification spelling is a token"),
1385            )),
1386        ));
1387    }
1388    EnginePredictionBasisV4::new(references).expect("the selected-node evidence basis is valid")
1389}
1390
1391fn raw_source_node_reference(
1392    source: &LoadedSource,
1393    node_index: usize,
1394    field: &'static str,
1395) -> PredictionBasisReferenceV4 {
1396    let raw = RawSourceBasisReferenceV1::from_source(
1397        RawSourceDomainV1::SourceNode,
1398        RawSourceKeyV1::SourceSkeleton {
1399            row_kind: SourceSkeletonRowKindV1::SourceNode,
1400            source_index: node_index as u64,
1401        },
1402        RawSourceFieldIdV1::new(field).expect("static raw field is valid"),
1403        source.source_facts(),
1404    )
1405    .expect("resolved selected source node has the cited raw field");
1406    PredictionBasisReferenceV4::v2(PredictionBasisReferenceV2::v1(
1407        PredictionBasisReferenceV1::raw_source(raw),
1408    ))
1409}
1410
1411fn transform_profile_fact_reference() -> PredictionBasisReferenceV4 {
1412    PredictionBasisReferenceV4::v2(PredictionBasisReferenceV2::v1(
1413        PredictionBasisReferenceV1::profile_fact("resulting_transform_scale")
1414            .expect("static profile fact id is valid"),
1415    ))
1416}
1417
1418fn static_basis(sources: &[&str]) -> EnginePredictionBasisV4 {
1419    EnginePredictionBasisV4::new(static_references(sources))
1420        .expect("the frozen static basis is valid")
1421}
1422
1423fn static_references(sources: &[&str]) -> Vec<PredictionBasisReferenceV4> {
1424    sources
1425        .iter()
1426        .map(|source| primary_source_reference(source))
1427        .collect()
1428}
1429
1430fn primary_source_reference(source: &str) -> PredictionBasisReferenceV4 {
1431    PredictionBasisReferenceV4::v2(PredictionBasisReferenceV2::v1(
1432        PredictionBasisReferenceV1::primary_source(source)
1433            .expect("static primary-source id is valid"),
1434    ))
1435}
1436
1437fn setting_reference(setting: SettingIdV2) -> PredictionBasisReferenceV4 {
1438    PredictionBasisReferenceV4::v2(PredictionBasisReferenceV2::v1(
1439        PredictionBasisReferenceV1::resolved_setting(
1440            ResolvedSettingLocationV1::Document,
1441            setting.as_str(),
1442        )
1443        .expect("static setting id is valid"),
1444    ))
1445}
1446
1447fn selected_scope(selector: &str) -> EvaluationScope {
1448    EvaluationScope::new(EvaluationScopeCode::custom(SELECTED_NODE_SCOPE))
1449        .subject(format!("selector:{selector}"))
1450}
1451
1452fn selected_scene_scope(
1453    selector: &str,
1454    source_scene_index: u64,
1455    node_index: usize,
1456) -> EvaluationScope {
1457    EvaluationScope::new(EvaluationScopeCode::custom(SELECTED_NODE_SCOPE)).subject(format!(
1458        "selector:{selector}:source_scene:{source_scene_index}:source_node:{node_index}"
1459    ))
1460}
1461
1462fn budget_scope() -> EvaluationScope {
1463    EvaluationScope::new(EvaluationScopeCode::custom(
1464        "engine-unit-scale:facet-budget",
1465    ))
1466}
1467
1468fn custom_reason(code: &'static str) -> PredictionUnavailableReasonV2 {
1469    PredictionUnavailableReasonV2::custom(code).expect("static reason code is valid")
1470}
1471
1472fn classification_name(classification: LinearTransformClassification) -> &'static str {
1473    match classification {
1474        LinearTransformClassification::UnitOrthonormal => "unit_orthonormal",
1475        LinearTransformClassification::UniformScaled => "uniform_scaled",
1476        LinearTransformClassification::NonUniform => "non_uniform",
1477        LinearTransformClassification::Sheared => "sheared",
1478        LinearTransformClassification::Reflected => "reflected",
1479        LinearTransformClassification::Singular => "singular",
1480        LinearTransformClassification::NonFinite => "non_finite",
1481        _ => "unknown",
1482    }
1483}
1484
1485fn load_meshes(provenance: &PredictionProvenanceV4) -> bool {
1486    matches!(
1487        provenance
1488            .settings()
1489            .document_setting(EngineSettingIdV2::LoadMeshes)
1490            .map(|setting| setting.value()),
1491        Some(EngineSettingValueV2::Token(value)) if value == "nonempty"
1492    )
1493}
1494
1495fn is_bevy_tuple(provenance: &PredictionProvenanceV4) -> bool {
1496    is_bevy_tuple_revision(provenance, BEVY_PROFILE_REVISION)
1497}
1498
1499fn is_bevy_tuple_revision(provenance: &PredictionProvenanceV4, revision: u32) -> bool {
1500    let selection = provenance.profile().selection();
1501    selection.family() == BEVY_FAMILY
1502        && selection.profile_revision() == revision
1503        && selection.engine_version() == BEVY_ENGINE_VERSION
1504        && selection.importer() == BEVY_IMPORTER
1505}
1506
1507fn is_exact_bevy_gltf(source: &LoadedSource, provenance: &PredictionProvenanceV4) -> bool {
1508    is_exact_bevy_gltf_revision(source, provenance, BEVY_PROFILE_REVISION)
1509}
1510
1511fn is_exact_bevy_gltf_revision(
1512    source: &LoadedSource,
1513    provenance: &PredictionProvenanceV4,
1514    revision: u32,
1515) -> bool {
1516    matches!(
1517        source.source_facts().format(),
1518        SourceFormatV1::GltfJson | SourceFormatV1::Glb
1519    ) && source.source_facts().format() == provenance.source_format()
1520        && is_bevy_tuple_revision(provenance, revision)
1521        && matches_frozen_registry_projection_v2(provenance.profile())
1522}
1523
1524fn empty_output() -> CheckOutput {
1525    CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new())
1526}