animsmith-engine 0.14.0

Strict, versioned engine-import profile facts and deterministic settings resolution for AnimSmith
Documentation
use animsmith_core::ImportSettingProjectionKindV1;
use animsmith_core::{
    DependencyClosureBuilderV1, Document, InputIdentity, LoadedSource, RawSourceFactsBuilderV1,
    SourceFactDomainV1, SourceFormatV1, ToolInfo, ToolSource,
};
use animsmith_engine::{
    EngineDeclarationV2, EngineImportAdviceInputV2, EngineImportAdviceProjectionValueV2,
    EngineImportAdviceRefusalReasonV2, EngineImportAdviceStateV2, EngineImportAdviceV2,
    ProfileSelection, SettingValueV2, resolve_static_v2,
};
use serde_json::{Value, json};
use std::collections::BTreeMap;

fn tool() -> ToolInfo {
    ToolInfo::animsmith(ToolSource::new(
        Some("0123456789abcdef0123456789abcdef01234567".into()),
        Some(false),
    ))
}

#[test]
fn supports_profile_pins_only_the_two_v2_import_tuples() {
    assert!(EngineImportAdviceV2::supports_profile(
        animsmith_engine::lookup_profile_v2(&ProfileSelection::new(
            "godot",
            2,
            "4.7",
            "resource-importer-scene",
        ))
        .unwrap()
    ));
    assert!(EngineImportAdviceV2::supports_profile(
        animsmith_engine::lookup_profile_v2(&ProfileSelection::new(
            "unreal",
            2,
            "5.8",
            "fbx-importer",
        ))
        .unwrap()
    ));
    assert!(!EngineImportAdviceV2::supports_profile(
        animsmith_engine::lookup_profile_v2(&ProfileSelection::new(
            "bevy",
            2,
            "0.19.0",
            "gltf-asset-loader",
        ))
        .unwrap()
    ));
}

fn source(format: SourceFormatV1) -> LoadedSource {
    let primary = InputIdentity::from_bytes(b"v2 import-advice source");
    let mut facts = RawSourceFactsBuilderV1::new(format, primary.clone());
    facts.mark_complete(SourceFactDomainV1::Clips);
    facts.mark_complete(SourceFactDomainV1::Constructs);
    facts.mark_complete(SourceFactDomainV1::Resources);
    let closure = DependencyClosureBuilderV1::new(
        primary,
        facts.resource_coverage(),
        facts.resource_rows().len(),
    )
    .finish()
    .unwrap();
    facts
        .finish_with_dependency_closure(Document::default(), closure)
        .unwrap()
}

fn resolved(
    selection: ProfileSelection,
    format: SourceFormatV1,
    settings: BTreeMap<String, SettingValueV2>,
) -> (LoadedSource, animsmith_engine::ResolvedProfileSettingsV2) {
    let source = source(format);
    let declaration = EngineDeclarationV2 {
        selection: Some(selection),
        document_settings: Some(settings),
        ..EngineDeclarationV2::default()
    };
    let resolution = resolve_static_v2(declaration).unwrap().unwrap();
    let resolved = resolution.resolve_input(format).unwrap();
    (source, resolved)
}

#[test]
fn godot_defaults_project_exact_params_and_round_trip_strictly() {
    let (source, resolved) = resolved(
        ProfileSelection::new("godot", 2, "4.7", "resource-importer-scene"),
        SourceFormatV1::GltfJson,
        BTreeMap::new(),
    );
    let report = EngineImportAdviceV2::from_source(tool(), &source, &resolved).unwrap();
    assert_eq!(report.state(), EngineImportAdviceStateV2::Available);
    assert_eq!(report.identity().input_identity().bytes(), 1_066);
    assert_eq!(
        report.identity().input_identity().sha256(),
        "d80b40b7726eea07283148a6fef5f697008334f8b0fa8958ee29911529d5564f"
    );
    assert_eq!(
        serde_json::to_value(report.basis().references()).unwrap(),
        json!([
            { "contract": "v2", "reference": { "contract": "v1", "reference": { "kind": "primary_source", "source_id": "godot-resource-importer-scene-4.7" } } },
            { "contract": "v2", "reference": { "contract": "v1", "reference": { "kind": "profile_fact", "fact_id": "import_setting_projection" } } },
            { "contract": "v2", "reference": { "contract": "v1", "reference": { "kind": "resolved_setting", "location": { "scope": "document" }, "setting_id": "animation_fps" } } },
            { "contract": "v2", "reference": { "contract": "v1", "reference": { "kind": "resolved_setting", "location": { "scope": "document" }, "setting_id": "animation_trimming" } } }
        ])
    );
    let projection = report
        .projection()
        .expect("expected import-setting projection");
    assert_eq!(
        projection.projection_kind,
        ImportSettingProjectionKindV1::GodotParams
    );
    assert_eq!(
        projection
            .fields
            .iter()
            .map(|field| field.key.as_str())
            .collect::<Vec<_>>(),
        ["animation/fps", "animation/trimming"]
    );
    assert!(projection.fields.iter().all(|field| {
        field.value_origin
            == animsmith_core::engine_contract::EngineSettingValueOriginV3::ProfileDefault
    }));
    let wire = serde_json::to_vec(&report).unwrap();
    let readback = EngineImportAdviceInputV2::read_from(wire.as_slice())
        .unwrap()
        .into_report()
        .unwrap();
    assert_eq!(readback.identity(), report.identity());
    assert_eq!(
        readback.prediction_provenance(),
        report.prediction_provenance()
    );
}

#[test]
fn unreal_projection_has_no_hidden_custom_rate_for_default30() {
    let (source, resolved) = resolved(
        ProfileSelection::new("unreal", 2, "5.8", "fbx-importer"),
        SourceFormatV1::Fbx,
        BTreeMap::from([(
            "sample_rate".into(),
            SettingValueV2::SampleRate(animsmith_engine::UnrealSampleRateV2::Default30),
        )]),
    );
    let report = EngineImportAdviceV2::from_source(tool(), &source, &resolved).unwrap();
    let projection = report
        .projection()
        .expect("expected import-setting projection");
    assert_eq!(
        projection
            .fields
            .iter()
            .map(|field| field.key.as_str())
            .collect::<Vec<_>>(),
        ["bUseDefaultSampleRate"]
    );
}

#[test]
fn unreal_source_determined_and_custom_rates_are_native_numeric_fields() {
    for (policy, expected_custom) in [
        (
            animsmith_engine::UnrealSampleRateV2::SourceDetermined,
            Some(0),
        ),
        (
            animsmith_engine::UnrealSampleRateV2::CustomHz(48_000),
            Some(48_000),
        ),
    ] {
        let (source, resolved) = resolved(
            ProfileSelection::new("unreal", 2, "5.8", "fbx-importer"),
            SourceFormatV1::Fbx,
            BTreeMap::from([("sample_rate".into(), SettingValueV2::SampleRate(policy))]),
        );
        let report = EngineImportAdviceV2::from_source(tool(), &source, &resolved).unwrap();
        let projection = report
            .projection()
            .expect("expected import-setting projection");
        assert_eq!(projection.fields.len(), 2);
        assert_eq!(projection.fields[0].key, "CustomSampleRate");
        assert_eq!(projection.fields[1].key, "bUseDefaultSampleRate");
        let wire = serde_json::to_string(&report).unwrap();
        if expected_custom == Some(0) {
            assert!(wire.contains("\"unsigned_integer\":0"));
        }
        assert_eq!(
            projection.fields[0].value,
            EngineImportAdviceProjectionValueV2::UnsignedInteger(expected_custom.unwrap())
        );
    }
}

#[test]
fn incomplete_dependency_closure_is_typed_refusal_with_basis() {
    let primary = InputIdentity::from_bytes(b"unavailable closure");
    let mut facts = RawSourceFactsBuilderV1::new(SourceFormatV1::Fbx, primary.clone());
    facts.mark_complete(SourceFactDomainV1::Clips);
    facts.mark_complete(SourceFactDomainV1::Constructs);
    let closure = DependencyClosureBuilderV1::new(
        primary,
        facts.resource_coverage(),
        facts.resource_rows().len(),
    )
    .finish()
    .unwrap();
    let source = facts
        .finish_with_dependency_closure(Document::default(), closure)
        .unwrap();
    let resolution = resolve_static_v2(EngineDeclarationV2 {
        selection: Some(ProfileSelection::new("unreal", 2, "5.8", "fbx-importer")),
        document_settings: Some(BTreeMap::from([(
            "sample_rate".into(),
            SettingValueV2::SampleRate(animsmith_engine::UnrealSampleRateV2::SourceDetermined),
        )])),
        ..EngineDeclarationV2::default()
    })
    .unwrap()
    .unwrap();
    let resolved = resolution.resolve_input(SourceFormatV1::Fbx).unwrap();
    let report = EngineImportAdviceV2::from_source(tool(), &source, &resolved).unwrap();
    assert_eq!(report.state(), EngineImportAdviceStateV2::Refused);
    assert_eq!(
        report.refusal_reason(),
        Some(EngineImportAdviceRefusalReasonV2::DependencyClosureIncomplete)
    );
    assert!(!report.basis().references().is_empty());
}

#[test]
fn strict_readback_rejects_unknown_envelope_fields() {
    let (source, resolved) = resolved(
        ProfileSelection::new("godot", 2, "4.7", "resource-importer-scene"),
        SourceFormatV1::Glb,
        BTreeMap::new(),
    );
    let report = EngineImportAdviceV2::from_source(tool(), &source, &resolved).unwrap();
    let mut value: Value = serde_json::from_slice(&serde_json::to_vec(&report).unwrap()).unwrap();
    value["unexpected"] = Value::Bool(true);
    let bytes = serde_json::to_vec(&value).unwrap();
    assert!(EngineImportAdviceInputV2::read_from(bytes.as_slice()).is_err());
}

#[test]
fn strict_readback_rejects_projection_value_mutation() {
    let (source, resolved) = resolved(
        ProfileSelection::new("unreal", 2, "5.8", "fbx-importer"),
        SourceFormatV1::Fbx,
        BTreeMap::from([(
            "sample_rate".into(),
            SettingValueV2::SampleRate(animsmith_engine::UnrealSampleRateV2::SourceDetermined),
        )]),
    );
    let report = EngineImportAdviceV2::from_source(tool(), &source, &resolved).unwrap();
    let mut value: Value = serde_json::from_slice(&serde_json::to_vec(&report).unwrap()).unwrap();
    value["projection"]["fields"][0]["value"]["unsigned_integer"] = Value::from(1);
    let bytes = serde_json::to_vec(&value).unwrap();
    let input = EngineImportAdviceInputV2::read_from(bytes.as_slice()).unwrap();
    assert!(input.into_report().is_err());

    let mut lifecycle: Value =
        serde_json::from_slice(&serde_json::to_vec(&report).unwrap()).unwrap();
    lifecycle["refusal_reason"] = Value::from("dependency_closure_incomplete");
    let bytes = serde_json::to_vec(&lifecycle).unwrap();
    let input = EngineImportAdviceInputV2::read_from(bytes.as_slice()).unwrap();
    assert!(input.into_report().is_err());
}

#[test]
fn strict_readback_rejects_embedded_authority_and_basis_mutations() {
    let (source, resolved) = resolved(
        ProfileSelection::new("godot", 2, "4.7", "resource-importer-scene"),
        SourceFormatV1::Glb,
        BTreeMap::new(),
    );
    let report = EngineImportAdviceV2::from_source(tool(), &source, &resolved).unwrap();
    let value: Value = serde_json::from_slice(&serde_json::to_vec(&report).unwrap()).unwrap();

    let mut profile_identity = value.clone();
    profile_identity["prediction_provenance"]["profile"]["identity"]["bytes"] = Value::from(0);
    let input = EngineImportAdviceInputV2::read_from(
        serde_json::to_vec(&profile_identity).unwrap().as_slice(),
    )
    .unwrap();
    assert!(matches!(
        input.into_report(),
        Err(animsmith_engine::EngineImportAdviceError::InvalidV2Provenance(_))
    ));

    let mut fact_identity = value.clone();
    fact_identity["prediction_provenance"]["profile"]["facts"][0]["state"] = json!("unknown");
    let input = EngineImportAdviceInputV2::read_from(
        serde_json::to_vec(&fact_identity).unwrap().as_slice(),
    )
    .unwrap();
    assert!(matches!(
        input.into_report(),
        Err(animsmith_engine::EngineImportAdviceError::InvalidV2Provenance(_))
    ));

    let mut setting_origin = value.clone();
    setting_origin["prediction_provenance"]["settings"]["document_settings"][0]["value_origin"] =
        json!("explicit_config");
    let input = EngineImportAdviceInputV2::read_from(
        serde_json::to_vec(&setting_origin).unwrap().as_slice(),
    )
    .unwrap();
    assert!(matches!(
        input.into_report(),
        Err(animsmith_engine::EngineImportAdviceError::InvalidV2Provenance(_))
    ));

    let mut basis_reference = value;
    basis_reference["basis"]["references"][0]["reference"]["reference"]["source_id"] =
        json!("unknown-source");
    let input = EngineImportAdviceInputV2::read_from(
        serde_json::to_vec(&basis_reference).unwrap().as_slice(),
    )
    .unwrap();
    assert!(matches!(
        input.into_report(),
        Err(animsmith_engine::EngineImportAdviceError::InvalidV2Prediction(_))
    ));
}

#[test]
fn strict_readback_rejects_refusal_when_authority_is_complete() {
    let (source, resolved) = resolved(
        ProfileSelection::new("godot", 2, "4.7", "resource-importer-scene"),
        SourceFormatV1::Glb,
        BTreeMap::new(),
    );
    let report = EngineImportAdviceV2::from_source(tool(), &source, &resolved).unwrap();
    let mut value: Value = serde_json::from_slice(&serde_json::to_vec(&report).unwrap()).unwrap();
    value["state"] = Value::from("refused");
    value
        .as_object_mut()
        .expect("advice envelope must be an object")
        .remove("projection");
    value["refusal_reason"] = Value::from("dependency_closure_incomplete");

    let bytes = serde_json::to_vec(&value).unwrap();
    let input = EngineImportAdviceInputV2::read_from(bytes.as_slice()).unwrap();
    assert!(matches!(
        input.into_report(),
        Err(animsmith_engine::EngineImportAdviceError::InvalidV2Lifecycle)
    ));
}